diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 5ffe5409..21c2d9a8 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -635,6 +635,10 @@ func syncthingMain() { l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?") } + protectedFiles := []string{ + locations[locDatabase], locations[locConfigFile], locations[locCertFile], locations[locKeyFile], + } + // Remove database entries for folders that no longer exist in the config folders := cfg.Folders() for _, folder := range db.ListFolders(ldb) { @@ -644,7 +648,7 @@ func syncthingMain() { } } - m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb) + m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb, protectedFiles) cfg.Subscribe(m) if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 { diff --git a/cmd/syncthing/main_test.go b/cmd/syncthing/main_test.go index 7730b7df..fd11f109 100644 --- a/cmd/syncthing/main_test.go +++ b/cmd/syncthing/main_test.go @@ -42,7 +42,7 @@ func TestFolderErrors(t *testing.T) { // Case 1 - new folder, directory and marker created - m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb) + m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) if err := m.CheckFolderHealth("folder"); err != nil { @@ -73,7 +73,7 @@ func TestFolderErrors(t *testing.T) { Folders: []config.FolderConfiguration{fcfg}, }) - m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb) + m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) if err := m.CheckFolderHealth("folder"); err != nil { @@ -96,7 +96,7 @@ func TestFolderErrors(t *testing.T) { {Name: "dummyfile"}, }) - m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb) + m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" { @@ -127,7 +127,7 @@ func TestFolderErrors(t *testing.T) { Folders: []config.FolderConfiguration{fcfg}, }) - m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb) + m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder path missing" { diff --git a/lib/model/model.go b/lib/model/model.go index 37bf3411..b6b12c17 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -70,6 +70,7 @@ type Model struct { id protocol.DeviceID shortID uint64 cacheIgnoredFiles bool + protectedFiles []string deviceName string clientName string @@ -98,7 +99,7 @@ var ( // NewModel creates and starts a new model. The model starts in read-only mode, // where it sends index information to connected peers and responds to requests // for file data without altering the local folder in any way. -func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, clientVersion string, ldb *leveldb.DB) *Model { +func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, clientVersion string, ldb *leveldb.DB, protectedFiles []string) *Model { m := &Model{ Supervisor: suture.New("model", suture.Spec{ Log: func(line string) { @@ -112,6 +113,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName, id: id, shortID: id.Short(), cacheIgnoredFiles: cfg.Options().CacheIgnoredFiles, + protectedFiles: protectedFiles, deviceName: deviceName, clientName: clientName, clientVersion: clientVersion, @@ -180,11 +182,41 @@ func (m *Model) StartFolderRW(folder string) { p.versioner = versioner } + m.warnAboutOverwritingProtectedFiles(folder) + m.Add(p) l.Okln("Ready to synchronize", folder, "(read-write)") } +func (m *Model) warnAboutOverwritingProtectedFiles(folder string) { + if m.folderCfgs[folder].ReadOnly { + return + } + + folderLocation := m.folderCfgs[folder].Path() + ignores := m.folderIgnores[folder] + + var filesAtRisk []string + for _, protectedFilePath := range m.protectedFiles { + // check if file is synced in this folder + if !strings.HasPrefix(protectedFilePath, folderLocation) { + continue + } + + // check if file is ignored + if ignores.Match(protectedFilePath) { + continue + } + + filesAtRisk = append(filesAtRisk, protectedFilePath) + } + + if len(filesAtRisk) > 0 { + l.Warnln("Some protected files may be overwritten and cause issues. See http://docs.syncthing.net/users/config.html#syncing-configuration-files for more information. The at risk files are:", strings.Join(filesAtRisk, ", ")) + } +} + // StartFolderRO starts read only processing on the current model. When in // read only mode the model will announce files to the cluster but not pull in // any external changes. diff --git a/lib/model/model_test.go b/lib/model/model_test.go index decfab16..fa35adce 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -92,7 +92,7 @@ func init() { func TestRequest(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) // device1 shares default, but device2 doesn't m.AddFolder(defaultFolderConfig) @@ -168,7 +168,7 @@ func BenchmarkIndex_100(b *testing.B) { func benchmarkIndex(b *testing.B, nfiles int) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.StartFolderRO("default") m.ServeBackground() @@ -197,7 +197,7 @@ func BenchmarkIndexUpdate_10000_1(b *testing.B) { func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.StartFolderRO("default") m.ServeBackground() @@ -262,7 +262,7 @@ func (FakeConnection) Statistics() protocol.Statistics { func BenchmarkRequest(b *testing.B) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.ServeBackground() m.ScanFolder("default") @@ -318,7 +318,7 @@ func TestDeviceRename(t *testing.T) { cfg := config.Wrap("tmpconfig.xml", rawCfg) db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) fc := FakeConnection{ id: device1, @@ -393,7 +393,7 @@ func TestClusterConfig(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(cfg.Folders[0]) m.AddFolder(cfg.Folders[1]) m.ServeBackground() @@ -464,7 +464,7 @@ func TestIgnores(t *testing.T) { ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644) db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.StartFolderRO("default") m.ServeBackground() @@ -539,7 +539,7 @@ func TestIgnores(t *testing.T) { func TestRefuseUnknownBits(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.ServeBackground() @@ -598,7 +598,7 @@ func TestROScanRecovery(t *testing.T) { os.RemoveAll(fcfg.RawPath) - m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb) + m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) m.StartFolderRO("default") m.ServeBackground() @@ -682,7 +682,7 @@ func TestRWScanRecovery(t *testing.T) { os.RemoveAll(fcfg.RawPath) - m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb) + m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil) m.AddFolder(fcfg) m.StartFolderRW("default") m.ServeBackground() @@ -745,7 +745,7 @@ func TestRWScanRecovery(t *testing.T) { func TestGlobalDirectoryTree(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.ServeBackground() @@ -995,7 +995,7 @@ func TestGlobalDirectoryTree(t *testing.T) { func TestGlobalDirectorySelfFixing(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.ServeBackground() @@ -1169,7 +1169,7 @@ func BenchmarkTree_100_10(b *testing.B) { func benchmarkTree(b *testing.B, n1, n2 int) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) m.ServeBackground() @@ -1187,7 +1187,7 @@ func benchmarkTree(b *testing.B, n1, n2 int) { func TestIgnoreDelete(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) // This folder should ignore external deletes cfg := defaultFolderConfig diff --git a/lib/model/rwfolder_test.go b/lib/model/rwfolder_test.go index 7fe0385c..a09c674f 100644 --- a/lib/model/rwfolder_test.go +++ b/lib/model/rwfolder_test.go @@ -70,7 +70,7 @@ func TestHandleFile(t *testing.T) { requiredFile.Blocks = blocks[1:] db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) // Update index m.updateLocals("default", []protocol.FileInfo{existingFile}) @@ -126,7 +126,7 @@ func TestHandleFileWithTemp(t *testing.T) { requiredFile.Blocks = blocks[1:] db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) // Update index m.updateLocals("default", []protocol.FileInfo{existingFile}) @@ -188,7 +188,7 @@ func TestCopierFinder(t *testing.T) { requiredFile.Name = "file2" db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) // Update index m.updateLocals("default", []protocol.FileInfo{existingFile}) @@ -265,7 +265,7 @@ func TestCopierCleanup(t *testing.T) { } db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) // Create a file @@ -314,7 +314,7 @@ func TestCopierCleanup(t *testing.T) { // if it fails to find the block. func TestLastResortPulling(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) // Add a file to index (with the incorrect block representation, as content @@ -389,7 +389,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) { db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) emitter := NewProgressEmitter(defaultConfig) @@ -481,7 +481,7 @@ func TestDeregisterOnFailInPull(t *testing.T) { defer os.Remove("testdata/" + defTempNamer.TempName("filex")) db, _ := leveldb.Open(storage.NewMemStorage(), nil) - m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db) + m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil) m.AddFolder(defaultFolderConfig) emitter := NewProgressEmitter(defaultConfig)