restorer: allow directory to replace existing file

This commit is contained in:
Michael Eischer 2024-06-07 23:02:46 +02:00
parent ac729db3ce
commit ebbd4e26d7
2 changed files with 21 additions and 7 deletions

View file

@ -259,6 +259,23 @@ func (res *Restorer) restoreHardlinkAt(node *restic.Node, target, path, location
return res.restoreNodeMetadataTo(node, path, location) return res.restoreNodeMetadataTo(node, path, location)
} }
func (res *Restorer) ensureDir(target string) error {
fi, err := fs.Lstat(target)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to check for directory: %w", err)
}
if err == nil && !fi.IsDir() {
// try to cleanup unexpected file
if err := fs.Remove(target); err != nil {
return fmt.Errorf("failed to remove stale item: %w", err)
}
}
// create parent dir with default permissions
// second pass #leaveDir restores dir metadata after visiting/restoring all children
return fs.MkdirAll(target, 0700)
}
// RestoreTo creates the directories and files in the snapshot below dst. // RestoreTo creates the directories and files in the snapshot below dst.
// Before an item is created, res.Filter is called. // Before an item is created, res.Filter is called.
func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { func (res *Restorer) RestoreTo(ctx context.Context, dst string) error {
@ -284,17 +301,12 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) error {
enterDir: func(_ *restic.Node, target, location string) error { enterDir: func(_ *restic.Node, target, location string) error {
debug.Log("first pass, enterDir: mkdir %q, leaveDir should restore metadata", location) debug.Log("first pass, enterDir: mkdir %q, leaveDir should restore metadata", location)
res.opts.Progress.AddFile(0) res.opts.Progress.AddFile(0)
// create dir with default permissions return res.ensureDir(target)
// #leaveDir restores dir metadata after visiting all children
return fs.MkdirAll(target, 0700)
}, },
visitNode: func(node *restic.Node, target, location string) error { visitNode: func(node *restic.Node, target, location string) error {
debug.Log("first pass, visitNode: mkdir %q, leaveDir on second pass should restore metadata", location) debug.Log("first pass, visitNode: mkdir %q, leaveDir on second pass should restore metadata", location)
// create parent dir with default permissions if err := res.ensureDir(filepath.Dir(target)); err != nil {
// second pass #leaveDir restores dir metadata after visiting/restoring all children
err := fs.MkdirAll(filepath.Dir(target), 0700)
if err != nil {
return err return err
} }

View file

@ -1040,6 +1040,7 @@ func TestRestorerOverwriteSpecial(t *testing.T) {
"link": Symlink{Target: "foo", ModTime: baseTime}, "link": Symlink{Target: "foo", ModTime: baseTime},
"file": File{Data: "content: file\n", Inode: 42, Links: 2, ModTime: baseTime}, "file": File{Data: "content: file\n", Inode: 42, Links: 2, ModTime: baseTime},
"hardlink": File{Data: "content: file\n", Inode: 42, Links: 2, ModTime: baseTime}, "hardlink": File{Data: "content: file\n", Inode: 42, Links: 2, ModTime: baseTime},
"newdir": File{Data: "content: dir\n", ModTime: baseTime},
}, },
} }
overwriteSnapshot := Snapshot{ overwriteSnapshot := Snapshot{
@ -1048,6 +1049,7 @@ func TestRestorerOverwriteSpecial(t *testing.T) {
"link": File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)}, "link": File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)},
"file": Symlink{Target: "foo2", ModTime: baseTime}, "file": Symlink{Target: "foo2", ModTime: baseTime},
"hardlink": File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)}, "hardlink": File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)},
"newdir": Dir{ModTime: baseTime},
}, },
} }