|
@@ -7,8 +7,10 @@
|
|
|
package fs
|
|
|
|
|
|
import (
|
|
|
+ "fmt"
|
|
|
"path/filepath"
|
|
|
"testing"
|
|
|
+ "time"
|
|
|
|
|
|
"github.com/syncthing/syncthing/lib/build"
|
|
|
)
|
|
@@ -171,3 +173,64 @@ func TestIsParent(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// Reproduces issue 9677:
|
|
|
+// The combination of caching the entire case FS and moving the case FS to be
|
|
|
+// the outmost layer of the FS lead to the mtime FS disappearing. This is
|
|
|
+// because in many places we intentionally create the filesystem without access
|
|
|
+// to the DB and thus without the mtime FS layer. With the case FS layer
|
|
|
+// outside, all the inner layers are also cached - notable without an mtime FS
|
|
|
+// layer. Later when we do try to create an FS with DB/mtime FS, we still get
|
|
|
+// the cached FS without mtime FS.
|
|
|
+func TestRepro9677MissingMtimeFS(t *testing.T) {
|
|
|
+ mtimeDB := make(mapStore)
|
|
|
+ name := "Testfile"
|
|
|
+ nameLower := UnicodeLowercaseNormalized(name)
|
|
|
+ testTime := time.Unix(1723491493, 123456789)
|
|
|
+
|
|
|
+ // Create a file with an mtime FS entry
|
|
|
+ firstFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB))
|
|
|
+
|
|
|
+ // Create a file, set its mtime and check that we get the expected mtime when stat-ing.
|
|
|
+ file, err := firstFS.Create(name)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ file.Close()
|
|
|
+ err = firstFS.Chtimes(name, testTime, testTime)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ checkMtime := func(fs Filesystem) {
|
|
|
+ t.Helper()
|
|
|
+ info, err := fs.Lstat(name)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if !info.ModTime().Equal(testTime) {
|
|
|
+ t.Errorf("Expected mtime %v for %v, got %v", testTime, name, info.ModTime())
|
|
|
+ }
|
|
|
+ info, err = fs.Lstat(nameLower)
|
|
|
+ if !IsErrCaseConflict(err) {
|
|
|
+ t.Errorf("Expected case-conflict error, got %v", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ checkMtime(firstFS)
|
|
|
+
|
|
|
+ // Now syncthing gets upgraded (or even just restarted), which resets the
|
|
|
+ // case FS registry as it lives in memory.
|
|
|
+ globalCaseFilesystemRegistry = caseFilesystemRegistry{fss: make(map[fskey]*caseFilesystem)}
|
|
|
+
|
|
|
+ // This time we first create some filesystem without a database and thus no
|
|
|
+ // mtime-FS, which is used in various places outside of the folder code. We
|
|
|
+ // aren't actually going to do anything, this just adds an entry to the
|
|
|
+ // caseFS cache. And that's the crucial bit: In the broken case this test is
|
|
|
+ // reproducing, it will add the FS without mtime-FS, so all future FSes will
|
|
|
+ // be without mtime, even if requested:
|
|
|
+ NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{})
|
|
|
+
|
|
|
+ newFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB))
|
|
|
+ checkMtime(newFS)
|
|
|
+}
|