From e9e73e6c3c53bd29ddfd27116bc21f1fa986f273 Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Sun, 25 May 2025 12:43:51 -0700 Subject: [PATCH] fix(snapshots): fixed snapshotting of \\server\share (#4603) * fix(snapshots): fixed snapshotting of \\server\share * fixed linter --- fs/localfs/local_fs.go | 14 ++++++----- fs/localfs/local_fs_os.go | 41 ++++++++++++++++++------------- fs/localfs/local_fs_test.go | 49 +++++++++++++++++++++---------------- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/fs/localfs/local_fs.go b/fs/localfs/local_fs.go index b194a6c0d..6828f47e4 100644 --- a/fs/localfs/local_fs.go +++ b/fs/localfs/local_fs.go @@ -100,7 +100,9 @@ func (f *fileWithMetadata) Entry() (fs.Entry, error) { return nil, errors.Wrap(err, "unable to stat() local file") } - return newFilesystemFile(newEntry(fi, dirPrefix(f.Name()))), nil + basename, prefix := splitDirPrefix(f.Name()) + + return newFilesystemFile(newEntry(basename, fi, prefix)), nil } func (fsf *filesystemFile) Open(_ context.Context) (fs.Reader, error) { @@ -130,16 +132,16 @@ func (e *filesystemErrorEntry) ErrorInfo() error { return e.err } -// dirPrefix returns the directory prefix for a given path - the initial part of the path up to and including the final slash (or backslash on Windows). -// this is similar to filepath.Dir() except dirPrefix("\\foo\bar") == "\\foo\", which is unsupported in filepath. -func dirPrefix(s string) string { +// splitDirPrefix returns the directory prefix for a given path - the initial part of the path up to and including the final slash (or backslash on Windows). +// this is similar to filepath.Dir() and filepath.Base() except splitDirPrefix("\\foo\bar") == "\\foo\", which is unsupported in filepath. +func splitDirPrefix(s string) (basename, prefix string) { for i := len(s) - 1; i >= 0; i-- { if s[i] == filepath.Separator || s[i] == '/' { - return s[0 : i+1] + return s[i+1:], s[0 : i+1] } } - return "" + return s, "" } // Directory returns fs.Directory for the specified path. diff --git a/fs/localfs/local_fs_os.go b/fs/localfs/local_fs_os.go index d9850590d..9d57bd257 100644 --- a/fs/localfs/local_fs_os.go +++ b/fs/localfs/local_fs_os.go @@ -88,11 +88,13 @@ func (fsd *filesystemDirectory) Child(_ context.Context, name string) (fs.Entry, return nil, errors.Wrap(err, "unable to get child") } - return entryFromDirEntry(st, fullPath+string(filepath.Separator)), nil + return entryFromDirEntry(name, st, fullPath+string(filepath.Separator)), nil } func toDirEntryOrNil(dirEntry os.DirEntry, prefix string) (fs.Entry, error) { - fi, err := os.Lstat(prefix + dirEntry.Name()) + n := dirEntry.Name() + + fi, err := os.Lstat(prefix + n) if err != nil { if os.IsNotExist(err) { return nil, nil @@ -101,7 +103,11 @@ func toDirEntryOrNil(dirEntry os.DirEntry, prefix string) (fs.Entry, error) { return nil, errors.Wrap(err, "error reading directory") } - return entryFromDirEntry(fi, prefix), nil + return entryFromDirEntry(n, fi, prefix), nil +} + +func isWindows() bool { + return runtime.GOOS == "windows" } // NewEntry returns fs.Entry for the specified path, the result will be one of supported entry types: fs.File, fs.Directory, fs.Symlink @@ -115,8 +121,7 @@ func NewEntry(path string) (fs.Entry, error) { // cause os.Lstat to fail with "Incorrect function" error unless they // end with a separator. Retry the operation with the separator added. var e syscall.Errno - //nolint:goconst - if runtime.GOOS == "windows" && + if isWindows() && !strings.HasSuffix(path, string(filepath.Separator)) && errors.As(err, &e) && e == 1 { fi, err = os.Lstat(path + string(filepath.Separator)) @@ -128,42 +133,44 @@ func NewEntry(path string) (fs.Entry, error) { } if path == "/" { - return entryFromDirEntry(fi, ""), nil + return entryFromDirEntry("/", fi, ""), nil } - return entryFromDirEntry(fi, dirPrefix(path)), nil + basename, prefix := splitDirPrefix(path) + + return entryFromDirEntry(basename, fi, prefix), nil } -func entryFromDirEntry(fi os.FileInfo, prefix string) fs.Entry { - isplaceholder := strings.HasSuffix(fi.Name(), ShallowEntrySuffix) +func entryFromDirEntry(basename string, fi os.FileInfo, prefix string) fs.Entry { + isplaceholder := strings.HasSuffix(basename, ShallowEntrySuffix) maskedmode := fi.Mode() & os.ModeType switch { case maskedmode == os.ModeDir && !isplaceholder: - return newFilesystemDirectory(newEntry(fi, prefix)) + return newFilesystemDirectory(newEntry(basename, fi, prefix)) case maskedmode == os.ModeDir && isplaceholder: - return newShallowFilesystemDirectory(newEntry(fi, prefix)) + return newShallowFilesystemDirectory(newEntry(basename, fi, prefix)) case maskedmode == os.ModeSymlink && !isplaceholder: - return newFilesystemSymlink(newEntry(fi, prefix)) + return newFilesystemSymlink(newEntry(basename, fi, prefix)) case maskedmode == 0 && !isplaceholder: - return newFilesystemFile(newEntry(fi, prefix)) + return newFilesystemFile(newEntry(basename, fi, prefix)) case maskedmode == 0 && isplaceholder: - return newShallowFilesystemFile(newEntry(fi, prefix)) + return newShallowFilesystemFile(newEntry(basename, fi, prefix)) default: - return newFilesystemErrorEntry(newEntry(fi, prefix), fs.ErrUnknown) + return newFilesystemErrorEntry(newEntry(basename, fi, prefix), fs.ErrUnknown) } } var _ os.FileInfo = (*filesystemEntry)(nil) -func newEntry(fi os.FileInfo, prefix string) filesystemEntry { +func newEntry(basename string, fi os.FileInfo, prefix string) filesystemEntry { return filesystemEntry{ - TrimShallowSuffix(fi.Name()), + TrimShallowSuffix(basename), fi.Size(), fi.ModTime().UnixNano(), fi.Mode(), diff --git a/fs/localfs/local_fs_test.go b/fs/localfs/local_fs_test.go index 900f5e26c..da5c93339 100644 --- a/fs/localfs/local_fs_test.go +++ b/fs/localfs/local_fs_test.go @@ -5,7 +5,6 @@ "fmt" "os" "path/filepath" - "runtime" "testing" "github.com/pkg/errors" @@ -250,7 +249,7 @@ func verifyChild(t *testing.T, dir fs.Directory) { } func TestLocalFilesystemPath(t *testing.T) { - if runtime.GOOS == "windows" { + if isWindows() { t.Skip() } @@ -273,29 +272,37 @@ func TestLocalFilesystemPath(t *testing.T) { } } -func TestDirPrefix(t *testing.T) { - cases := map[string]string{ - "foo": "", - "/": "/", - "/tmp": "/", - "/tmp/": "/tmp/", - "/tmp/foo": "/tmp/", +func TestSplitDirPrefix(t *testing.T) { + type pair struct { + prefix string + basename string } - if runtime.GOOS == "windows" { - cases["c:/"] = "c:/" - cases["c:\\"] = "c:\\" - cases["c:/temp"] = "c:/" - cases["c:\\temp"] = "c:\\" - cases["c:/temp/orary"] = "c:/temp/" - cases["c:\\temp\\orary"] = "c:\\temp\\" - cases["c:/temp\\orary"] = "c:/temp\\" - cases["c:\\temp/orary"] = "c:\\temp/" - cases["\\\\server\\path"] = "\\\\server\\" - cases["\\\\server\\path\\subdir"] = "\\\\server\\path\\" + cases := map[string]pair{ + "foo": {"", "foo"}, + "/": {"/", ""}, + "/tmp": {"/", "tmp"}, + "/tmp/": {"/tmp/", ""}, + "/tmp/foo": {"/tmp/", "foo"}, + } + + if isWindows() { + cases["c:/"] = pair{"c:/", ""} + cases["c:\\"] = pair{"c:\\", ""} + cases["c:/temp"] = pair{"c:/", "temp"} + cases["c:\\temp"] = pair{"c:\\", "temp"} + cases["c:/temp/orary"] = pair{"c:/temp/", "orary"} + cases["c:\\temp\\orary"] = pair{"c:\\temp\\", "orary"} + cases["c:/temp\\orary"] = pair{"c:/temp\\", "orary"} + cases["c:\\temp/orary"] = pair{"c:\\temp/", "orary"} + cases["\\\\server\\path"] = pair{"\\\\server\\", "path"} + cases["\\\\server\\path\\"] = pair{"\\\\server\\path\\", ""} + cases["\\\\server\\path\\subdir"] = pair{"\\\\server\\path\\", "subdir"} } for input, want := range cases { - require.Equal(t, want, dirPrefix(input), input) + basename, prefix := splitDirPrefix(input) + require.Equal(t, want.basename, basename, input) + require.Equal(t, want.prefix, prefix, input) } }