From c2a5e180b8bac357fa7331adaa0bf5f64c04a442 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Sun, 18 Oct 2015 20:13:58 -0400 Subject: [PATCH] Emit warning when sync could overwrite configuration Overwriting configuration files is likely to happen if a user syncs their home directories across computers. In this case, the biggest risk is that all nodes will end up with the same certificate and thus Device ID. When the model prepares a folder for syncing, it checks to see if the configuration files this instance is using are getting synced. If the are getting synced, and they aren't getting ignored, a warning is emitted. The model is used so that when a new folder is added dynamically, a warning is also emitted. This will not prevent a user from shooting themselves in the foot, and will not cover all cases (e.g. symlinks). It should provide _something_ for many users in this situation to go on, though. --- cmd/syncthing/main.go | 6 +++++- cmd/syncthing/main_test.go | 8 ++++---- lib/model/model.go | 34 +++++++++++++++++++++++++++++++++- lib/model/model_test.go | 28 ++++++++++++++-------------- lib/model/rwfolder_test.go | 14 +++++++------- 5 files changed, 63 insertions(+), 27 deletions(-) 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)