diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go
index 2e8b3483e..587b8c0e5 100644
--- a/cmd/syncthing/main.go
+++ b/cmd/syncthing/main.go
@@ -575,17 +575,11 @@ func setupGUI(cfg *config.ConfigWrapper, m *model.Model) {
}
func sanityCheckFolders(cfg *config.ConfigWrapper, m *model.Model) {
- var err error
-
nextFolder:
for id, folder := range cfg.Folders() {
if folder.Invalid != "" {
continue
}
- folder.Path, err = osutil.ExpandTilde(folder.Path)
- if err != nil {
- l.Fatalln("home:", err)
- }
m.AddFolder(folder)
fi, err := os.Stat(folder.Path)
@@ -598,11 +592,25 @@ nextFolder:
l.Warnf("Stopping folder %q - path does not exist, but has files in index", folder.ID)
cfg.InvalidateFolder(id, "folder path missing")
continue nextFolder
+ } else if !folder.HasMarker() {
+ l.Warnf("Stopping folder %q - path exists, but folder marker missing, check for mount issues", folder.ID)
+ cfg.InvalidateFolder(id, "folder marker missing")
+ continue nextFolder
}
} else if os.IsNotExist(err) {
// If we don't have any files in the index, and the directory
// doesn't exist, try creating it.
err = os.MkdirAll(folder.Path, 0700)
+ if err != nil {
+ l.Warnf("Stopping folder %q - %v", err)
+ cfg.InvalidateFolder(id, err.Error())
+ continue nextFolder
+ }
+ err = folder.CreateMarker()
+ } else if !folder.HasMarker() {
+ // If we don't have any files in the index, and the path does exist
+ // but the marker is not there, create it.
+ err = folder.CreateMarker()
}
if err != nil {
diff --git a/cmd/syncthing/main_test.go b/cmd/syncthing/main_test.go
index a36f8f723..b3df6da03 100644
--- a/cmd/syncthing/main_test.go
+++ b/cmd/syncthing/main_test.go
@@ -13,6 +13,107 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see .
-package main_test
+package main
-// Empty test file to generate 0% coverage rather than no coverage
+import (
+ "os"
+ "testing"
+
+ "github.com/syncthing/syncthing/internal/config"
+ "github.com/syncthing/syncthing/internal/files"
+ "github.com/syncthing/syncthing/internal/model"
+ "github.com/syncthing/syncthing/internal/protocol"
+
+ "github.com/syndtr/goleveldb/leveldb"
+ "github.com/syndtr/goleveldb/leveldb/storage"
+)
+
+func TestSanityCheck(t *testing.T) {
+ fcfg := config.FolderConfiguration{
+ ID: "folder",
+ Path: "testdata/testfolder",
+ }
+ cfg := config.Wrap("/tmp/test", config.Configuration{
+ Folders: []config.FolderConfiguration{fcfg},
+ })
+
+ for _, file := range []string{".stfolder", "testfolder", "testfolder/.stfolder"} {
+ _, err := os.Stat("testdata/" + file)
+ if err == nil {
+ t.Error("Found unexpected file")
+ }
+ }
+
+ db, _ := leveldb.Open(storage.NewMemStorage(), nil)
+
+ // Case 1 - new folder, directory and marker created
+
+ m := model.NewModel(cfg, "device", "syncthing", "dev", db)
+ sanityCheckFolders(cfg, m)
+
+ if cfg.Folders()["folder"].Invalid != "" {
+ t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
+ }
+
+ s, err := os.Stat("testdata/testfolder")
+ if err != nil || !s.IsDir() {
+ t.Error(err)
+ }
+
+ _, err = os.Stat("testdata/testfolder/.stfolder")
+ if err != nil {
+ t.Error(err)
+ }
+
+ os.Remove("testdata/testfolder/.stfolder")
+ os.Remove("testdata/testfolder/")
+
+ // Case 2 - new folder, marker created
+
+ fcfg.Path = "testdata/"
+ cfg = config.Wrap("/tmp/test", config.Configuration{
+ Folders: []config.FolderConfiguration{fcfg},
+ })
+
+ m = model.NewModel(cfg, "device", "syncthing", "dev", db)
+ sanityCheckFolders(cfg, m)
+
+ if cfg.Folders()["folder"].Invalid != "" {
+ t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
+ }
+
+ _, err = os.Stat("testdata/.stfolder")
+ if err != nil {
+ t.Error(err)
+ }
+
+ os.Remove("testdata/.stfolder")
+
+ // Case 3 - marker missing
+
+ set := files.NewSet("folder", db)
+ set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
+ {Name: "dummyfile"},
+ })
+
+ m = model.NewModel(cfg, "device", "syncthing", "dev", db)
+ sanityCheckFolders(cfg, m)
+
+ if cfg.Folders()["folder"].Invalid != "folder marker missing" {
+ t.Error("Incorrect error")
+ }
+
+ // Case 4 - path missing
+
+ fcfg.Path = "testdata/testfolder"
+ cfg = config.Wrap("/tmp/test", config.Configuration{
+ Folders: []config.FolderConfiguration{fcfg},
+ })
+
+ m = model.NewModel(cfg, "device", "syncthing", "dev", db)
+ sanityCheckFolders(cfg, m)
+
+ if cfg.Folders()["folder"].Invalid != "folder path missing" {
+ t.Error("Incorrect error")
+ }
+}
diff --git a/internal/config/config.go b/internal/config/config.go
index 58ccc9f3c..8d90d9200 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -21,18 +21,20 @@ import (
"fmt"
"io"
"os"
+ "path/filepath"
"reflect"
"sort"
"strconv"
"code.google.com/p/go.crypto/bcrypt"
"github.com/syncthing/syncthing/internal/logger"
+ "github.com/syncthing/syncthing/internal/osutil"
"github.com/syncthing/syncthing/internal/protocol"
)
var l = logger.DefaultLogger
-const CurrentVersion = 5
+const CurrentVersion = 6
type Configuration struct {
Version int `xml:"version,attr"`
@@ -64,6 +66,28 @@ type FolderConfiguration struct {
Deprecated_Nodes []FolderDeviceConfiguration `xml:"node" json:"-"`
}
+func (f *FolderConfiguration) CreateMarker() error {
+ if !f.HasMarker() {
+ marker := filepath.Join(f.Path, ".stfolder")
+ fd, err := os.Create(marker)
+ if err != nil {
+ return err
+ }
+ fd.Close()
+ osutil.HideFile(marker)
+ }
+
+ return nil
+}
+
+func (f *FolderConfiguration) HasMarker() bool {
+ _, err := os.Stat(filepath.Join(f.Path, ".stfolder"))
+ if err != nil {
+ return false
+ }
+ return true
+}
+
func (r *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
if r.deviceIDs == nil {
for _, n := range r.Devices {
@@ -272,6 +296,11 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
convertV4V5(cfg)
}
+ // Upgrade to v6 configuration if appropriate
+ if cfg.Version == 5 {
+ convertV5V6(cfg)
+ }
+
// Hash old cleartext passwords
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
hash, err := bcrypt.GenerateFromPassword([]byte(cfg.GUI.Password), 0)
@@ -344,6 +373,20 @@ func ChangeRequiresRestart(from, to Configuration) bool {
return false
}
+func convertV5V6(cfg *Configuration) {
+ // Added ".stfolder" file at folder roots to identify mount issues
+ // Doesn't affect the config itself, but uses config migrations to identify
+ // the migration point.
+ for _, folder := range Wrap("", *cfg).Folders() {
+ err := folder.CreateMarker()
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ cfg.Version = 6
+}
+
func convertV4V5(cfg *Configuration) {
// Renamed a bunch of fields in the structs.
if cfg.Deprecated_Nodes == nil {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 9c4102539..216b05f17 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -16,6 +16,7 @@
package config
import (
+ "fmt"
"os"
"reflect"
"testing"
@@ -60,17 +61,26 @@ func TestDefaultValues(t *testing.T) {
}
func TestDeviceConfig(t *testing.T) {
- for i, ver := range []string{"v1", "v2", "v3", "v4", "v5"} {
- wr, err := Load("testdata/"+ver+".xml", device1)
+ for i := 1; i <= CurrentVersion; i++ {
+ os.Remove("testdata/.stfolder")
+ wr, err := Load(fmt.Sprintf("testdata/v%d.xml", i), device1)
if err != nil {
t.Fatal(err)
}
+
+ _, err = os.Stat("testdata/.stfolder")
+ if i < 6 && err != nil {
+ t.Fatal(err)
+ } else if i >= 6 && err == nil {
+ t.Fatal("Unexpected file")
+ }
+
cfg := wr.cfg
expectedFolders := []FolderConfiguration{
{
ID: "test",
- Path: "~/Sync",
+ Path: "testdata/",
Devices: []FolderDeviceConfiguration{{DeviceID: device1}, {DeviceID: device4}},
ReadOnly: true,
RescanIntervalS: 600,
@@ -92,8 +102,8 @@ func TestDeviceConfig(t *testing.T) {
}
expectedDeviceIDs := []protocol.DeviceID{device1, device4}
- if cfg.Version != 5 {
- t.Errorf("%d: Incorrect version %d != 5", i, cfg.Version)
+ if cfg.Version != CurrentVersion {
+ t.Errorf("%d: Incorrect version %d != %d", i, cfg.Version, CurrentVersion)
}
if !reflect.DeepEqual(cfg.Folders, expectedFolders) {
t.Errorf("%d: Incorrect Folders\n A: %#v\n E: %#v", i, cfg.Folders, expectedFolders)
@@ -296,7 +306,7 @@ func TestPrepare(t *testing.T) {
}
func TestRequiresRestart(t *testing.T) {
- wr, err := Load("testdata/v5.xml", device1)
+ wr, err := Load("testdata/v6.xml", device1)
if err != nil {
t.Fatal(err)
}
diff --git a/internal/config/testdata/.stfolder b/internal/config/testdata/.stfolder
new file mode 100644
index 000000000..e69de29bb
diff --git a/internal/config/testdata/v1.xml b/internal/config/testdata/v1.xml
index 2365effbb..a75154530 100755
--- a/internal/config/testdata/v1.xml
+++ b/internal/config/testdata/v1.xml
@@ -1,5 +1,5 @@
-
+
a
diff --git a/internal/config/testdata/v2.xml b/internal/config/testdata/v2.xml
index bfddd25ea..ef0fd7cfb 100755
--- a/internal/config/testdata/v2.xml
+++ b/internal/config/testdata/v2.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/internal/config/testdata/v3.xml b/internal/config/testdata/v3.xml
index 0f527fae6..5bd748500 100755
--- a/internal/config/testdata/v3.xml
+++ b/internal/config/testdata/v3.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/internal/config/testdata/v4.xml b/internal/config/testdata/v4.xml
index 73d6649d8..c0a8e0010 100755
--- a/internal/config/testdata/v4.xml
+++ b/internal/config/testdata/v4.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/internal/config/testdata/v5.xml b/internal/config/testdata/v5.xml
index c09723aad..7daa88e14 100755
--- a/internal/config/testdata/v5.xml
+++ b/internal/config/testdata/v5.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/internal/config/testdata/v6.xml b/internal/config/testdata/v6.xml
new file mode 100644
index 000000000..ae73c0bc5
--- /dev/null
+++ b/internal/config/testdata/v6.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ a
+
+
+ b
+
+
diff --git a/internal/config/testdata/versioningconfig.xml b/internal/config/testdata/versioningconfig.xml
index c98be30b2..137b84be6 100755
--- a/internal/config/testdata/versioningconfig.xml
+++ b/internal/config/testdata/versioningconfig.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/internal/config/wrapper.go b/internal/config/wrapper.go
index e83aac4e3..b5070789b 100644
--- a/internal/config/wrapper.go
+++ b/internal/config/wrapper.go
@@ -167,6 +167,12 @@ func (w *ConfigWrapper) Folders() map[string]FolderConfiguration {
if w.folderMap == nil {
w.folderMap = make(map[string]FolderConfiguration, len(w.cfg.Folders))
for _, fld := range w.cfg.Folders {
+ path, err := osutil.ExpandTilde(fld.Path)
+ if err != nil {
+ l.Warnln("home:", err)
+ continue
+ }
+ fld.Path = path
w.folderMap[fld.ID] = fld
}
}
diff --git a/internal/scanner/walk.go b/internal/scanner/walk.go
index 5208a13bc..cd7657aff 100644
--- a/internal/scanner/walk.go
+++ b/internal/scanner/walk.go
@@ -113,7 +113,7 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
return nil
}
- if sn := filepath.Base(rn); sn == ".stignore" || sn == ".stversions" || w.Ignores.Match(rn) {
+ if sn := filepath.Base(rn); sn == ".stignore" || sn == ".stversions" || sn == ".stfolder" || w.Ignores.Match(rn) {
// An ignored file
if debug {
l.Debugln("ignored:", rn)