瀏覽代碼

lib/fs: Remove \\?\ for drive letters when watching (fixes #5578) (#5701)

Simon Frei 6 年之前
父節點
當前提交
2558b021e5
共有 2 個文件被更改,包括 76 次插入11 次删除
  1. 28 10
      lib/fs/basicfs_watch.go
  2. 48 1
      lib/fs/basicfs_watch_test.go

+ 28 - 10
lib/fs/basicfs_watch.go

@@ -12,6 +12,7 @@ import (
 	"context"
 	"errors"
 	"path/filepath"
+	"runtime"
 
 	"github.com/syncthing/notify"
 )
@@ -22,12 +23,7 @@ import (
 var backendBuffer = 500
 
 func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
-	evalRoot, err := evalSymlinks(f.root)
-	if err != nil {
-		return nil, err
-	}
-
-	absName, err := rooted(name, evalRoot)
+	watchPath, root, err := f.watchPaths(name)
 	if err != nil {
 		return nil, err
 	}
@@ -42,11 +38,11 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
 
 	if ignore.SkipIgnoredDirs() {
 		absShouldIgnore := func(absPath string) bool {
-			return ignore.ShouldIgnore(f.unrootedChecked(absPath, evalRoot))
+			return ignore.ShouldIgnore(f.unrootedChecked(absPath, root))
 		}
-		err = notify.WatchWithFilter(filepath.Join(absName, "..."), backendChan, absShouldIgnore, eventMask)
+		err = notify.WatchWithFilter(watchPath, backendChan, absShouldIgnore, eventMask)
 	} else {
-		err = notify.Watch(filepath.Join(absName, "..."), backendChan, eventMask)
+		err = notify.Watch(watchPath, backendChan, eventMask)
 	}
 	if err != nil {
 		notify.Stop(backendChan)
@@ -56,11 +52,33 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
 		return nil, err
 	}
 
-	go f.watchLoop(name, evalRoot, backendChan, outChan, ignore, ctx)
+	go f.watchLoop(name, root, backendChan, outChan, ignore, ctx)
 
 	return outChan, nil
 }
 
+// watchPaths adjust the folder root for use with the notify backend and the
+// corresponding absolute path to be passed to notify to watch name.
+func (f *BasicFilesystem) watchPaths(name string) (string, string, error) {
+	root, err := evalSymlinks(f.root)
+	if err != nil {
+		return "", "", err
+	}
+
+	// Remove `\\?\` prefix if the path is just a drive letter as a dirty
+	// fix for https://github.com/syncthing/syncthing/issues/5578
+	if runtime.GOOS == "windows" && filepath.Clean(name) == "." && len(root) <= 7 && len(root) > 4 && root[:4] == `\\?\` {
+		root = root[4:]
+	}
+
+	absName, err := rooted(name, root)
+	if err != nil {
+		return "", "", err
+	}
+
+	return filepath.Join(absName, "..."), root, nil
+}
+
 func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan notify.EventInfo, outChan chan<- Event, ignore Matcher, ctx context.Context) {
 	for {
 		// Detect channel overflow

+ 48 - 1
lib/fs/basicfs_watch_test.go

@@ -149,6 +149,53 @@ func TestWatchRename(t *testing.T) {
 	testScenario(t, name, testCase, expectedEvents, allowedEvents, fakeMatcher{})
 }
 
+// TestWatchWinRoot checks that a watch at a drive letter does not panic due to
+// out of root event on every event.
+// https://github.com/syncthing/syncthing/issues/5695
+func TestWatchWinRoot(t *testing.T) {
+	if runtime.GOOS != "windows" {
+		t.Skip("Windows specific test")
+	}
+
+	outChan := make(chan Event)
+	backendChan := make(chan notify.EventInfo, backendBuffer)
+
+	ctx, cancel := context.WithCancel(context.Background())
+
+	// testFs is Filesystem, but we need BasicFilesystem here
+	root := `D:\`
+	fs := newBasicFilesystem(root)
+	watch, root, err := fs.watchPaths(".")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				t.Error(r)
+			}
+			cancel()
+		}()
+		fs.watchLoop(".", root, backendChan, outChan, fakeMatcher{}, ctx)
+	}()
+
+	// filepath.Dir as watch has a /... suffix
+	name := "foo"
+	backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(watch), name))
+
+	select {
+	case <-time.After(10 * time.Second):
+		cancel()
+		t.Errorf("Timed out before receiving event")
+	case ev := <-outChan:
+		if ev.Name != name {
+			t.Errorf("Unexpected event %v, expected %v", ev.Name, name)
+		}
+	case <-ctx.Done():
+	}
+}
+
 // TestWatchOutside checks that no changes from outside the folder make it in
 func TestWatchOutside(t *testing.T) {
 	outChan := make(chan Event)
@@ -391,7 +438,7 @@ func testScenario(t *testing.T, name string, testCase func(), expectedEvents, al
 	testCase()
 
 	select {
-	case <-time.After(time.Minute):
+	case <-time.After(10 * time.Second):
 		t.Errorf("Timed out before receiving all expected events")
 
 	case <-ctx.Done():