From 95a65bf0d0d7c54be83f2d894cda54f10508a1b2 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 26 Nov 2017 07:51:22 +0000 Subject: [PATCH] lib/config: Support symlinked root (fixes #4542, fixes #4353) GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4545 LGTM: imsodin, calmh --- lib/config/config_test.go | 58 +++++++++++++++++++++++++++++++ lib/config/folderconfiguration.go | 23 ++++++++++-- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/lib/config/config_test.go b/lib/config/config_test.go index ba03db5ab..ebe77db04 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -10,6 +10,7 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" "os" "path/filepath" "reflect" @@ -20,6 +21,7 @@ import ( "github.com/d4l3k/messagediff" "github.com/syncthing/syncthing/lib/fs" + "github.com/syncthing/syncthing/lib/osutil" "github.com/syncthing/syncthing/lib/protocol" ) @@ -430,6 +432,62 @@ func TestFolderPath(t *testing.T) { } } +func TestFolderCheckPath(t *testing.T) { + n, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + + err = os.MkdirAll(filepath.Join(n, "dir", ".stfolder"), os.FileMode(0777)) + if err != nil { + t.Fatal(err) + } + + testcases := []struct { + path string + err error + }{ + { + path: "", + err: errMarkerMissing, + }, + { + path: "does not exist", + err: errPathMissing, + }, + { + path: "dir", + err: nil, + }, + } + + err = osutil.DebugSymlinkForTestsOnly(filepath.Join(n, "dir"), filepath.Join(n, "link")) + if err == nil { + t.Log("running with symlink check") + testcases = append(testcases, struct { + path string + err error + }{ + path: "link", + err: nil, + }) + } else if runtime.GOOS != "windows" { + t.Log("running without symlink check") + t.Fatal(err) + } + + for _, testcase := range testcases { + cfg := FolderConfiguration{ + Path: filepath.Join(n, testcase.path), + MarkerName: DefaultMarkerName, + } + + if err := cfg.CheckPath(); testcase.err != err { + t.Errorf("unexpected error in case %s: %s != %s", testcase.path, err, testcase.err) + } + } +} + func TestNewSaveLoad(t *testing.T) { path := "testdata/temp.xml" os.Remove(path) diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index 13401b57d..fa488182d 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -16,8 +16,9 @@ import ( ) var ( - errPathMissing = errors.New("folder path missing") - errMarkerMissing = errors.New("folder marker missing") + errPathNotDirectory = errors.New("folder path not a directory") + errPathMissing = errors.New("folder path missing") + errMarkerMissing = errors.New("folder marker missing") ) const DefaultMarkerName = ".stfolder" @@ -124,12 +125,28 @@ func (f *FolderConfiguration) CreateMarker() error { // CheckPath returns nil if the folder root exists and contains the marker file func (f *FolderConfiguration) CheckPath() error { fi, err := f.Filesystem().Stat(".") - if err != nil || !fi.IsDir() { + if err != nil { + if !fs.IsNotExist(err) { + return err + } return errPathMissing } + // Users might have the root directory as a symlink or reparse point. + // Furthermore, OneDrive bullcrap uses a magic reparse point to the cloudz... + // Yet it's impossible for this to happen, as filesystem adds a trailing + // path separator to the root, so even if you point the filesystem at a file + // Stat ends up calling stat on C:\dir\file\ which, fails with "is not a directory" + // in the error check above, and we don't even get to here. + if !fi.IsDir() && !fi.IsSymlink() { + return errPathNotDirectory + } + _, err = f.Filesystem().Stat(f.MarkerName) if err != nil { + if !fs.IsNotExist(err) { + return err + } return errMarkerMissing }