mirror of
https://github.com/restic/restic.git
synced 2025-03-16 00:00:05 +01:00
Support for hardlinks with the restore command
This commit is contained in:
parent
b2d00b2a86
commit
f661efa7fa
4 changed files with 119 additions and 1 deletions
|
@ -102,6 +102,12 @@ func Symlink(oldname, newname string) error {
|
|||
return os.Symlink(fixpath(oldname), fixpath(newname))
|
||||
}
|
||||
|
||||
// Hardlink creates newname as a hard link to oldname.
|
||||
// If there is an error, it will be of type *LinkError.
|
||||
func Hardlink(oldname, newname string) error {
|
||||
return os.Link(fixpath(oldname), fixpath(newname))
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo structure describing the named file.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func Stat(name string) (os.FileInfo, error) {
|
||||
|
|
91
src/restic/hardlinks_index.go
Normal file
91
src/restic/hardlinks_index.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package restic
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type HardlinkKey struct {
|
||||
Inode, Device uint64
|
||||
}
|
||||
|
||||
type HardlinkData struct {
|
||||
Links uint64
|
||||
Name string
|
||||
}
|
||||
|
||||
var (
|
||||
hardLinkIndex = make(map[HardlinkKey]*HardlinkData)
|
||||
hardLinkIndexMutex = sync.RWMutex{}
|
||||
)
|
||||
|
||||
func ExistsLink(inode uint64, device uint64) bool {
|
||||
hardLinkIndexMutex.RLock()
|
||||
defer hardLinkIndexMutex.RUnlock()
|
||||
_, ok := hardLinkIndex[HardlinkKey{inode, device}]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func AddLink(inode uint64, device uint64, links uint64, name string) {
|
||||
hardLinkIndexMutex.RLock()
|
||||
_, ok := hardLinkIndex[HardlinkKey{inode, device}]
|
||||
hardLinkIndexMutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
hardLinkIndexMutex.Lock()
|
||||
hardLinkIndex[HardlinkKey{inode,device}] = &HardlinkData{links, name};
|
||||
hardLinkIndexMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func GetLink(inode uint64, device uint64) *HardlinkData {
|
||||
hardLinkIndexMutex.RLock()
|
||||
defer hardLinkIndexMutex.RUnlock()
|
||||
return hardLinkIndex[HardlinkKey{inode, device}]
|
||||
}
|
||||
|
||||
func RemoveLink(inode uint64, device uint64) {
|
||||
hardLinkIndexMutex.Lock()
|
||||
defer hardLinkIndexMutex.Unlock()
|
||||
delete(hardLinkIndex, HardlinkKey{inode, device})
|
||||
}
|
||||
|
||||
func DecrementLink(inode uint64, device uint64) {
|
||||
hardLinkIndexMutex.RLock()
|
||||
_, ok := hardLinkIndex[HardlinkKey{inode, device}]
|
||||
hardLinkIndexMutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
hardLinkIndexMutex.RLock()
|
||||
if hardLinkIndex[HardlinkKey{inode, device}].Links > 0 {
|
||||
hardLinkIndexMutex.RUnlock()
|
||||
hardLinkIndexMutex.Lock()
|
||||
hardLinkIndex[HardlinkKey{inode, device}].Links--
|
||||
hardLinkIndexMutex.Unlock()
|
||||
} else {
|
||||
hardLinkIndexMutex.RUnlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* return the number of links for a given inode-device combination */
|
||||
func CountLink(inode uint64, device uint64) uint64 {
|
||||
hardLinkIndexMutex.RLock()
|
||||
defer hardLinkIndexMutex.RUnlock()
|
||||
|
||||
_, ok := hardLinkIndex[HardlinkKey{inode, device}]
|
||||
if ok {
|
||||
return hardLinkIndex[HardlinkKey{inode, device}].Links
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func PrintLinkSummary() {
|
||||
hardLinkIndexMutex.RLock()
|
||||
defer hardLinkIndexMutex.RUnlock()
|
||||
|
||||
for _, value := range hardLinkIndex {
|
||||
fmt.Printf("File %v has unrestored links; probably you did not backup the complete volume\n", value.Name)
|
||||
}
|
||||
}
|
|
@ -192,6 +192,24 @@ func (node Node) createDirAt(path string) error {
|
|||
}
|
||||
|
||||
func (node Node) createFileAt(path string, repo Repository) error {
|
||||
|
||||
if node.Links > 1 {
|
||||
if !ExistsLink(node.Inode, node.Device) {
|
||||
AddLink(node.Inode, node.Device, node.Links, path)
|
||||
DecrementLink(node.Inode, node.Device)
|
||||
} else {
|
||||
err :=fs.Hardlink(GetLink(node.Inode, node.Device).Name, path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CreateHardlink")
|
||||
}
|
||||
DecrementLink(node.Inode, node.Device)
|
||||
if CountLink(node.Inode, node.Device) == 0 {
|
||||
RemoveLink(node.Inode, node.Device)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
f, err := fs.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
defer f.Close()
|
||||
|
||||
|
@ -482,6 +500,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||
case "file":
|
||||
node.Size = uint64(stat.size())
|
||||
node.Links = uint64(stat.nlink())
|
||||
node.Device = uint64(stat.dev())
|
||||
case "dir":
|
||||
case "symlink":
|
||||
node.LinkTarget, err = fs.Readlink(path)
|
||||
|
|
|
@ -119,7 +119,9 @@ func (res *Restorer) restoreNodeTo(node *Node, dir string, dst string) error {
|
|||
// RestoreTo creates the directories and files in the snapshot below dir.
|
||||
// Before an item is created, res.Filter is called.
|
||||
func (res *Restorer) RestoreTo(dir string) error {
|
||||
return res.restoreTo(dir, "", *res.sn.Tree)
|
||||
err := res.restoreTo(dir, "", *res.sn.Tree)
|
||||
PrintLinkSummary();
|
||||
return err
|
||||
}
|
||||
|
||||
// Snapshot returns the snapshot this restorer is configured to use.
|
||||
|
|
Loading…
Add table
Reference in a new issue