diff --git a/lib/model/model.go b/lib/model/model.go index 84e99302a..59871b296 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1370,7 +1370,6 @@ nextSub: // TODO: We should limit the Have scanning to start at sub seenPrefix := false var iterError error - css := osutil.NewCachedCaseSensitiveStat(folderCfg.Path()) fs.WithHaveTruncated(protocol.LocalDeviceID, func(fi db.FileIntf) bool { f := fi.(db.FileInfoTruncated) hasPrefix := len(subs) == 0 @@ -1414,7 +1413,7 @@ nextSub: Version: f.Version, // The file is still the same, so don't bump version } batch = append(batch, nf) - } else if _, err := css.Lstat(filepath.Join(folderCfg.Path(), f.Name)); err != nil { + } else if _, err := osutil.Lstat(filepath.Join(folderCfg.Path(), f.Name)); err != nil { // File has been deleted. // We don't specifically verify that the error is diff --git a/lib/osutil/osutil.go b/lib/osutil/osutil.go index a77ed5cf2..f4e15885a 100644 --- a/lib/osutil/osutil.go +++ b/lib/osutil/osutil.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "runtime" - "sort" "strings" "time" @@ -242,90 +241,3 @@ func SetTCPOptions(conn *net.TCPConn) error { } return nil } - -// The CachedCaseSensitiveStat provides an Lstat() method similar to -// os.Lstat(), but that is always case sensitive regardless of underlying file -// system semantics. The "Cached" part refers to the fact that it lists the -// contents of a directory the first time it's needed and then retains this -// information for the duration. It's expected that instances of this type are -// fairly short lived. -// -// There's some song and dance to check directories that are parents to the -// checked path as well, that is we want to catch the situation that someone -// calls Lstat("foo/BAR/baz") when the actual path is "foo/bar/baz" and return -// NotExist appropriately. But we don't want to do this check too high up, as -// the user may have told us the folder path is ~/Sync while it is actually -// ~/sync and this *should* work properly... Sigh. Hence the "base" parameter. -type CachedCaseSensitiveStat struct { - base string // base directory, we should not check stuff above this - results map[string][]os.FileInfo // directory path => list of children -} - -func NewCachedCaseSensitiveStat(base string) *CachedCaseSensitiveStat { - return &CachedCaseSensitiveStat{ - base: strings.ToLower(base), - results: make(map[string][]os.FileInfo), - } -} - -func (c *CachedCaseSensitiveStat) Lstat(name string) (os.FileInfo, error) { - dir := filepath.Dir(name) - base := filepath.Base(name) - - if !strings.HasPrefix(strings.ToLower(dir), c.base) { - // We only validate things within the base directory, which we need to - // compare case insensitively against. - return nil, os.ErrInvalid - } - - // If we don't already have a list of directory entries for this - // directory, try to list it. Return error if this fails. - l, ok := c.results[dir] - if !ok { - if len(dir) > len(c.base) { - // We are checking in a subdirectory of base. Must make sure *it* - // exists with the specified casing, up to the base directory. - if _, err := c.Lstat(dir); err != nil { - return nil, err - } - } - - fd, err := os.Open(dir) - if err != nil { - return nil, err - } - defer fd.Close() - - l, err = fd.Readdir(-1) - if err != nil { - return nil, err - } - - sort.Sort(fileInfoList(l)) - c.results[dir] = l - } - - // Get the index of the first entry with name >= base using binary search. - idx := sort.Search(len(l), func(i int) bool { - return l[i].Name() >= base - }) - - if idx >= len(l) || l[idx].Name() != base { - // The search didn't find any such entry - return nil, os.ErrNotExist - } - - return l[idx], nil -} - -type fileInfoList []os.FileInfo - -func (l fileInfoList) Len() int { - return len(l) -} -func (l fileInfoList) Swap(a, b int) { - l[a], l[b] = l[b], l[a] -} -func (l fileInfoList) Less(a, b int) bool { - return l[a].Name() < l[b].Name() -} diff --git a/lib/osutil/osutil_test.go b/lib/osutil/osutil_test.go index 697bf6e86..d4d5c15bc 100644 --- a/lib/osutil/osutil_test.go +++ b/lib/osutil/osutil_test.go @@ -7,11 +7,8 @@ package osutil_test import ( - "io/ioutil" "os" - "path/filepath" "runtime" - "strings" "testing" "github.com/syncthing/syncthing/lib/osutil" @@ -182,78 +179,3 @@ func TestDiskUsage(t *testing.T) { t.Error("Disk is full?", free) } } - -func TestCaseSensitiveStat(t *testing.T) { - switch runtime.GOOS { - case "windows", "darwin": - break // We can test! - default: - t.Skip("Cannot test on this platform") - return - } - - dir, err := ioutil.TempDir("", "TestCaseSensitiveStat") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - if err := ioutil.WriteFile(filepath.Join(dir, "File"), []byte("data"), 0644); err != nil { - t.Fatal(err) - } - - if _, err := os.Lstat(filepath.Join(dir, "File")); err != nil { - // Standard Lstat should report the file exists - t.Fatal("Unexpected error:", err) - } - if _, err := os.Lstat(filepath.Join(dir, "fILE")); err != nil { - // ... also with the incorrect case spelling - t.Fatal("Unexpected error:", err) - } - - // Create the case sensitive stat:er. We stress it a little by giving it a - // base path with an intentionally incorrect casing. - - css := osutil.NewCachedCaseSensitiveStat(strings.ToUpper(dir)) - - if _, err := css.Lstat(filepath.Join(dir, "File")); err != nil { - // Our Lstat should report the file exists - t.Fatal("Unexpected error:", err) - } - if _, err := css.Lstat(filepath.Join(dir, "fILE")); err == nil || !os.IsNotExist(err) { - // ... but with the incorrect case we should get ErrNotExist - t.Fatal("Unexpected non-IsNotExist error:", err) - } - - // Now do the same tests for a file in a case-sensitive directory. - - if err := os.Mkdir(filepath.Join(dir, "Dir"), 0755); err != nil { - t.Fatal(err) - } - if err := ioutil.WriteFile(filepath.Join(dir, "Dir/File"), []byte("data"), 0644); err != nil { - t.Fatal(err) - } - - if _, err := os.Lstat(filepath.Join(dir, "Dir/File")); err != nil { - // Standard Lstat should report the file exists - t.Fatal("Unexpected error:", err) - } - if _, err := os.Lstat(filepath.Join(dir, "dIR/File")); err != nil { - // ... also with the incorrect case spelling - t.Fatal("Unexpected error:", err) - } - - // Recreate the case sensitive stat:er. We stress it a little by giving it a - // base path with an intentionally incorrect casing. - - css = osutil.NewCachedCaseSensitiveStat(strings.ToLower(dir)) - - if _, err := css.Lstat(filepath.Join(dir, "Dir/File")); err != nil { - // Our Lstat should report the file exists - t.Fatal("Unexpected error:", err) - } - if _, err := css.Lstat(filepath.Join(dir, "dIR/File")); err == nil || !os.IsNotExist(err) { - // ... but with the incorrect case we should get ErrNotExist - t.Fatal("Unexpected non-IsNotExist error:", err) - } -}