mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-08 22:31:04 +00:00
lib/fs: Check events against both the user and eval root (#6013)
This commit is contained in:
parent
7127c13f18
commit
35b699dc77
@ -345,6 +345,6 @@ func (e *ErrWatchEventOutsideRoot) Error() string {
|
|||||||
return e.msg
|
return e.msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath, root string) *ErrWatchEventOutsideRoot {
|
func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath string, roots []string) *ErrWatchEventOutsideRoot {
|
||||||
return &ErrWatchEventOutsideRoot{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, root==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, root, absPath)}
|
return &ErrWatchEventOutsideRoot{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, roots, absPath)}
|
||||||
}
|
}
|
||||||
|
@ -60,14 +60,16 @@ func (f *BasicFilesystem) Roots() ([]string, error) {
|
|||||||
// unrooted) or an error if the given path is not a subpath and handles the
|
// unrooted) or an error if the given path is not a subpath and handles the
|
||||||
// special case when the given path is the folder root without a trailing
|
// special case when the given path is the folder root without a trailing
|
||||||
// pathseparator.
|
// pathseparator.
|
||||||
func (f *BasicFilesystem) unrootedChecked(absPath, root string) (string, *ErrWatchEventOutsideRoot) {
|
func (f *BasicFilesystem) unrootedChecked(absPath string, roots []string) (string, *ErrWatchEventOutsideRoot) {
|
||||||
|
for _, root := range roots {
|
||||||
if absPath+string(PathSeparator) == root {
|
if absPath+string(PathSeparator) == root {
|
||||||
return ".", nil
|
return ".", nil
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(absPath, root) {
|
if strings.HasPrefix(absPath, root) {
|
||||||
return "", f.newErrWatchEventOutsideRoot(absPath, root)
|
|
||||||
}
|
|
||||||
return rel(absPath, root), nil
|
return rel(absPath, root), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", f.newErrWatchEventOutsideRoot(absPath, roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rel(path, prefix string) string {
|
func rel(path, prefix string) string {
|
||||||
@ -78,16 +80,16 @@ var evalSymlinks = filepath.EvalSymlinks
|
|||||||
|
|
||||||
// watchPaths adjust the folder root for use with the notify backend and the
|
// watchPaths adjust the folder root for use with the notify backend and the
|
||||||
// corresponding absolute path to be passed to notify to watch name.
|
// corresponding absolute path to be passed to notify to watch name.
|
||||||
func (f *BasicFilesystem) watchPaths(name string) (string, string, error) {
|
func (f *BasicFilesystem) watchPaths(name string) (string, []string, error) {
|
||||||
root, err := evalSymlinks(f.root)
|
root, err := evalSymlinks(f.root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
absName, err := rooted(name, root)
|
absName, err := rooted(name, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(absName, "..."), root, nil
|
return filepath.Join(absName, "..."), []string{root}, nil
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
var backendBuffer = 500
|
var backendBuffer = 500
|
||||||
|
|
||||||
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||||
watchPath, root, err := f.watchPaths(name)
|
watchPath, roots, err := f.watchPaths(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
|
|||||||
|
|
||||||
if ignore.SkipIgnoredDirs() {
|
if ignore.SkipIgnoredDirs() {
|
||||||
absShouldIgnore := func(absPath string) bool {
|
absShouldIgnore := func(absPath string) bool {
|
||||||
rel, err := f.unrootedChecked(absPath, root)
|
rel, err := f.unrootedChecked(absPath, roots)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -55,12 +55,12 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
|
|||||||
}
|
}
|
||||||
|
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
go f.watchLoop(name, root, backendChan, outChan, errChan, ignore, ctx)
|
go f.watchLoop(name, roots, backendChan, outChan, errChan, ignore, ctx)
|
||||||
|
|
||||||
return outChan, errChan, nil
|
return outChan, errChan, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan notify.EventInfo, outChan chan<- Event, errChan chan<- error, ignore Matcher, ctx context.Context) {
|
func (f *BasicFilesystem) watchLoop(name string, roots []string, backendChan chan notify.EventInfo, outChan chan<- Event, errChan chan<- error, ignore Matcher, ctx context.Context) {
|
||||||
for {
|
for {
|
||||||
// Detect channel overflow
|
// Detect channel overflow
|
||||||
if len(backendChan) == backendBuffer {
|
if len(backendChan) == backendBuffer {
|
||||||
@ -79,7 +79,7 @@ func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan noti
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case ev := <-backendChan:
|
case ev := <-backendChan:
|
||||||
relPath, err := f.unrootedChecked(ev.Path(), evalRoot)
|
relPath, err := f.unrootedChecked(ev.Path(), roots)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case errChan <- err:
|
case errChan <- err:
|
||||||
|
@ -166,7 +166,7 @@ func TestWatchWinRoot(t *testing.T) {
|
|||||||
// testFs is Filesystem, but we need BasicFilesystem here
|
// testFs is Filesystem, but we need BasicFilesystem here
|
||||||
root := `D:\`
|
root := `D:\`
|
||||||
fs := newBasicFilesystem(root)
|
fs := newBasicFilesystem(root)
|
||||||
watch, root, err := fs.watchPaths(".")
|
watch, roots, err := fs.watchPaths(".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ func TestWatchWinRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
fs.watchLoop(".", root, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
fs.watchLoop(".", roots, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// filepath.Dir as watch has a /... suffix
|
// filepath.Dir as watch has a /... suffix
|
||||||
@ -210,7 +210,7 @@ func TestWatchOutside(t *testing.T) {
|
|||||||
// testFs is Filesystem, but we need BasicFilesystem here
|
// testFs is Filesystem, but we need BasicFilesystem here
|
||||||
fs := newBasicFilesystem(testDirAbs)
|
fs := newBasicFilesystem(testDirAbs)
|
||||||
|
|
||||||
go fs.watchLoop(".", testDirAbs, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
go fs.watchLoop(".", []string{testDirAbs}, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
||||||
|
|
||||||
backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
|
backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ func TestWatchSubpath(t *testing.T) {
|
|||||||
fs := newBasicFilesystem(testDirAbs)
|
fs := newBasicFilesystem(testDirAbs)
|
||||||
|
|
||||||
abs, _ := fs.rooted("sub")
|
abs, _ := fs.rooted("sub")
|
||||||
go fs.watchLoop("sub", testDirAbs, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
go fs.watchLoop("sub", []string{testDirAbs}, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
||||||
|
|
||||||
backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
|
backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
|
||||||
|
|
||||||
@ -347,7 +347,7 @@ func TestWatchSymlinkedRoot(t *testing.T) {
|
|||||||
|
|
||||||
func TestUnrootedChecked(t *testing.T) {
|
func TestUnrootedChecked(t *testing.T) {
|
||||||
fs := newBasicFilesystem(testDirAbs)
|
fs := newBasicFilesystem(testDirAbs)
|
||||||
if unrooted, err := fs.unrootedChecked("/random/other/path", testDirAbs); err == nil {
|
if unrooted, err := fs.unrootedChecked("/random/other/path", []string{testDirAbs}); err == nil {
|
||||||
t.Error("unrootedChecked did not return an error on outside path, but returned", unrooted)
|
t.Error("unrootedChecked did not return an error on outside path, but returned", unrooted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,17 +156,19 @@ func (f *BasicFilesystem) Roots() ([]string, error) {
|
|||||||
// unrooted) or an error if the given path is not a subpath and handles the
|
// unrooted) or an error if the given path is not a subpath and handles the
|
||||||
// special case when the given path is the folder root without a trailing
|
// special case when the given path is the folder root without a trailing
|
||||||
// pathseparator.
|
// pathseparator.
|
||||||
func (f *BasicFilesystem) unrootedChecked(absPath, root string) (string, error) {
|
func (f *BasicFilesystem) unrootedChecked(absPath string, roots []string) (string, error) {
|
||||||
absPath = f.resolveWin83(absPath)
|
absPath = f.resolveWin83(absPath)
|
||||||
lowerAbsPath := UnicodeLowercase(absPath)
|
lowerAbsPath := UnicodeLowercase(absPath)
|
||||||
|
for _, root := range roots {
|
||||||
lowerRoot := UnicodeLowercase(root)
|
lowerRoot := UnicodeLowercase(root)
|
||||||
if lowerAbsPath+string(PathSeparator) == lowerRoot {
|
if lowerAbsPath+string(PathSeparator) == lowerRoot {
|
||||||
return ".", nil
|
return ".", nil
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(lowerAbsPath, lowerRoot) {
|
if strings.HasPrefix(lowerAbsPath, lowerRoot) {
|
||||||
return "", f.newErrWatchEventOutsideRoot(lowerAbsPath, lowerRoot)
|
|
||||||
}
|
|
||||||
return rel(absPath, root), nil
|
return rel(absPath, root), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", f.newErrWatchEventOutsideRoot(lowerAbsPath, roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rel(path, prefix string) string {
|
func rel(path, prefix string) string {
|
||||||
@ -294,10 +296,10 @@ func evalSymlinks(in string) (string, error) {
|
|||||||
|
|
||||||
// watchPaths adjust the folder root for use with the notify backend and the
|
// watchPaths adjust the folder root for use with the notify backend and the
|
||||||
// corresponding absolute path to be passed to notify to watch name.
|
// corresponding absolute path to be passed to notify to watch name.
|
||||||
func (f *BasicFilesystem) watchPaths(name string) (string, string, error) {
|
func (f *BasicFilesystem) watchPaths(name string) (string, []string, error) {
|
||||||
root, err := evalSymlinks(f.root)
|
root, err := evalSymlinks(f.root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove `\\?\` prefix if the path is just a drive letter as a dirty
|
// Remove `\\?\` prefix if the path is just a drive letter as a dirty
|
||||||
@ -308,11 +310,17 @@ func (f *BasicFilesystem) watchPaths(name string) (string, string, error) {
|
|||||||
|
|
||||||
absName, err := rooted(name, root)
|
absName, err := rooted(name, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root = f.resolveWin83(root)
|
roots := []string{f.resolveWin83(root)}
|
||||||
absName = f.resolveWin83(absName)
|
absName = f.resolveWin83(absName)
|
||||||
|
|
||||||
return filepath.Join(absName, "..."), root, nil
|
// Events returned from fs watching are all over the place, so allow
|
||||||
|
// both the user's input and the result of "canonicalizing" the path.
|
||||||
|
if roots[0] != f.root {
|
||||||
|
roots = append(roots, f.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(absName, "..."), roots, nil
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ func TestRelUnrootedCheckedWindows(t *testing.T) {
|
|||||||
// on these test cases.
|
// on these test cases.
|
||||||
for _, root := range []string{tc.root, strings.ToLower(tc.root), strings.ToUpper(tc.root)} {
|
for _, root := range []string{tc.root, strings.ToLower(tc.root), strings.ToUpper(tc.root)} {
|
||||||
fs := BasicFilesystem{root: root}
|
fs := BasicFilesystem{root: root}
|
||||||
if res, err := fs.unrootedChecked(tc.abs, tc.root); err != nil {
|
if res, err := fs.unrootedChecked(tc.abs, []string{tc.root}); err != nil {
|
||||||
t.Errorf(`Unexpected error from unrootedChecked("%v", "%v"): %v (fs.root: %v)`, tc.abs, tc.root, err, root)
|
t.Errorf(`Unexpected error from unrootedChecked("%v", "%v"): %v (fs.root: %v)`, tc.abs, tc.root, err, root)
|
||||||
} else if res != tc.expectedRel {
|
} else if res != tc.expectedRel {
|
||||||
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v" (fs.root: %v)`, tc.abs, tc.root, res, tc.expectedRel, root)
|
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v" (fs.root: %v)`, tc.abs, tc.root, res, tc.expectedRel, root)
|
||||||
@ -140,6 +140,21 @@ func TestRelUnrootedCheckedWindows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMultipleRoot checks that fs.unrootedChecked returns the correct path
|
||||||
|
// when given more than one possible root path.
|
||||||
|
func TestMultipleRoot(t *testing.T) {
|
||||||
|
root := `c:\foO`
|
||||||
|
roots := []string{root, `d:\`}
|
||||||
|
rel := `bar`
|
||||||
|
path := filepath.Join(root, rel)
|
||||||
|
fs := BasicFilesystem{root: root}
|
||||||
|
if res, err := fs.unrootedChecked(path, roots); err != nil {
|
||||||
|
t.Errorf(`Unexpected error from unrootedChecked("%v", "%v"): %v (fs.root: %v)`, path, roots, err, root)
|
||||||
|
} else if res != rel {
|
||||||
|
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v" (fs.root: %v)`, path, roots, res, rel, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetFinalPath(t *testing.T) {
|
func TestGetFinalPath(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
input string
|
input string
|
||||||
|
Loading…
Reference in New Issue
Block a user