From 168fc09d5f25b34d72a25de5aaafb704e97035d4 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 29 Jun 2024 19:54:12 +0200 Subject: [PATCH] restore: use case insensitive file name comparison on windows --- internal/restorer/restorer.go | 4 +-- internal/restorer/restorer_unix.go | 10 +++++++ internal/restorer/restorer_windows.go | 13 +++++++++ internal/restorer/restorer_windows_test.go | 34 ++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 internal/restorer/restorer_unix.go create mode 100644 internal/restorer/restorer_windows.go diff --git a/internal/restorer/restorer.go b/internal/restorer/restorer.go index 52d34c5ed..6e81812c2 100644 --- a/internal/restorer/restorer.go +++ b/internal/restorer/restorer.go @@ -480,11 +480,11 @@ func (res *Restorer) removeUnexpectedFiles(target, location string, expectedFile keep := map[string]struct{}{} for _, name := range expectedFilenames { - keep[name] = struct{}{} + keep[toComparableFilename(name)] = struct{}{} } for _, entry := range entries { - if _, ok := keep[entry]; ok { + if _, ok := keep[toComparableFilename(entry)]; ok { continue } diff --git a/internal/restorer/restorer_unix.go b/internal/restorer/restorer_unix.go new file mode 100644 index 000000000..7316f7b5d --- /dev/null +++ b/internal/restorer/restorer_unix.go @@ -0,0 +1,10 @@ +//go:build !windows +// +build !windows + +package restorer + +// toComparableFilename returns a filename suitable for equality checks. On Windows, it returns the +// uppercase version of the string. On all other systems, it returns the unmodified filename. +func toComparableFilename(path string) string { + return path +} diff --git a/internal/restorer/restorer_windows.go b/internal/restorer/restorer_windows.go new file mode 100644 index 000000000..72337d8ae --- /dev/null +++ b/internal/restorer/restorer_windows.go @@ -0,0 +1,13 @@ +//go:build windows +// +build windows + +package restorer + +import "strings" + +// toComparableFilename returns a filename suitable for equality checks. On Windows, it returns the +// uppercase version of the string. On all other systems, it returns the unmodified filename. +func toComparableFilename(path string) string { + // apparently NTFS internally uppercases filenames for comparision + return strings.ToUpper(path) +} diff --git a/internal/restorer/restorer_windows_test.go b/internal/restorer/restorer_windows_test.go index 61d075061..3f6c8472b 100644 --- a/internal/restorer/restorer_windows_test.go +++ b/internal/restorer/restorer_windows_test.go @@ -9,6 +9,7 @@ import ( "math" "os" "path" + "path/filepath" "syscall" "testing" "time" @@ -539,3 +540,36 @@ func TestDirAttributeCombinationsOverwrite(t *testing.T) { } } } + +func TestRestoreDeleteCaseInsensitive(t *testing.T) { + repo := repository.TestRepository(t) + tempdir := rtest.TempDir(t) + + sn, _ := saveSnapshot(t, repo, Snapshot{ + Nodes: map[string]Node{ + "anotherfile": File{Data: "content: file\n"}, + }, + }, noopGetGenericAttributes) + + // should delete files that no longer exist in the snapshot + deleteSn, _ := saveSnapshot(t, repo, Snapshot{ + Nodes: map[string]Node{ + "AnotherfilE": File{Data: "content: file\n"}, + }, + }, noopGetGenericAttributes) + + res := NewRestorer(repo, sn, Options{}) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := res.RestoreTo(ctx, tempdir) + rtest.OK(t, err) + + res = NewRestorer(repo, deleteSn, Options{Delete: true}) + err = res.RestoreTo(ctx, tempdir) + rtest.OK(t, err) + + // anotherfile must still exist + _, err = os.Stat(filepath.Join(tempdir, "anotherfile")) + rtest.OK(t, err) +}