1
0
Fork 0
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:
frm 2017-01-28 16:28:29 +01:00
parent b2d00b2a86
commit f661efa7fa
4 changed files with 119 additions and 1 deletions

View file

@ -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) {

View 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)
}
}

View file

@ -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)

View file

@ -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.