From ebbd4e26d7027c92498184083d816a28684178d7 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Jun 2024 23:02:46 +0200 Subject: [PATCH] restorer: allow directory to replace existing file --- internal/restorer/restorer.go | 26 +++++++++++++++++++------- internal/restorer/restorer_test.go | 2 ++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/restorer/restorer.go b/internal/restorer/restorer.go index 313174fc3..19555afb8 100644 --- a/internal/restorer/restorer.go +++ b/internal/restorer/restorer.go @@ -259,6 +259,23 @@ func (res *Restorer) restoreHardlinkAt(node *restic.Node, target, 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. // Before an item is created, res.Filter is called. 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 { debug.Log("first pass, enterDir: mkdir %q, leaveDir should restore metadata", location) res.opts.Progress.AddFile(0) - // create dir with default permissions - // #leaveDir restores dir metadata after visiting all children - return fs.MkdirAll(target, 0700) + return res.ensureDir(target) }, visitNode: func(node *restic.Node, target, location string) error { debug.Log("first pass, visitNode: mkdir %q, leaveDir on second pass should restore metadata", location) - // create parent dir with default permissions - // second pass #leaveDir restores dir metadata after visiting/restoring all children - err := fs.MkdirAll(filepath.Dir(target), 0700) - if err != nil { + if err := res.ensureDir(filepath.Dir(target)); err != nil { return err } diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 5c23a88e4..3becf7c7a 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -1040,6 +1040,7 @@ func TestRestorerOverwriteSpecial(t *testing.T) { "link": Symlink{Target: "foo", 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}, + "newdir": File{Data: "content: dir\n", ModTime: baseTime}, }, } 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)}, "file": Symlink{Target: "foo2", ModTime: baseTime}, "hardlink": File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)}, + "newdir": Dir{ModTime: baseTime}, }, }