Compare commits

...
8 changed files with 95 additions and 55 deletions
+9 -16
View File
@@ -32,29 +32,22 @@ func (e NotADirectoryError) Error() string {
return fmt.Sprintf("not a directory: %s", e.path)
}
// TraversesSymlink returns an error if base and any path component of name up to and
// including filepath.Join(base, name) traverses a symlink.
// Base and name must both be clean and name must be relative to base.
// TraversesSymlink returns an error if any path component of name (including name
// itself) traverses a symlink.
func TraversesSymlink(filesystem fs.Filesystem, name string) error {
base := "."
path := base
info, err := filesystem.Lstat(path)
var err error
name, err = fs.Canonicalize(name)
if err != nil {
return err
}
if !info.IsDir() {
return &NotADirectoryError{
path: base,
}
}
if name == "." {
// The result of calling TraversesSymlink("some/where", filepath.Dir("foo"))
// The result of calling TraversesSymlink(filesystem, filepath.Dir("foo"))
return nil
}
parts := strings.Split(name, string(fs.PathSeparator))
for _, part := range parts {
var path string
for _, part := range strings.Split(name, string(fs.PathSeparator)) {
path = filepath.Join(path, part)
info, err := filesystem.Lstat(path)
if err != nil {
@@ -65,12 +58,12 @@ func TraversesSymlink(filesystem fs.Filesystem, name string) error {
}
if info.IsSymlink() {
return &TraversesSymlinkError{
path: strings.TrimPrefix(path, base),
path: path,
}
}
if !info.IsDir() {
return &NotADirectoryError{
path: strings.TrimPrefix(path, base),
path: path,
}
}
}
+46 -6
View File
@@ -4,12 +4,13 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build !windows
package osutil_test
import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/syncthing/syncthing/lib/fs"
@@ -17,12 +18,20 @@ import (
)
func TestTraversesSymlink(t *testing.T) {
os.RemoveAll("testdata")
defer os.RemoveAll("testdata")
tmpDir, err := ioutil.TempDir(".", ".test-TraversesSymlink-")
if err != nil {
panic("Failed to create temporary testing dir")
}
defer os.RemoveAll(tmpDir)
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata")
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
fs.MkdirAll("a/b/c", 0755)
fs.CreateSymlink("b", "a/l")
if err = osutil.DebugSymlinkForTestsOnly(filepath.Join(fs.URI(), "a", "b"), filepath.Join(fs.URI(), "a", "l")); err != nil {
if runtime.GOOS == "windows" {
t.Skip("Symlinks aren't working")
}
t.Fatal(err)
}
// a/l -> b, so a/l/c should resolve by normal stat
info, err := fs.Lstat("a/l/c")
@@ -61,6 +70,37 @@ func TestTraversesSymlink(t *testing.T) {
}
}
func TestIssue4875(t *testing.T) {
tmpDir, err := ioutil.TempDir("", ".test-Issue4875-")
if err != nil {
panic("Failed to create temporary testing dir")
}
defer os.RemoveAll(tmpDir)
testFs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
testFs.MkdirAll("a/b/c", 0755)
if err = osutil.DebugSymlinkForTestsOnly(filepath.Join(testFs.URI(), "a", "b"), filepath.Join(testFs.URI(), "a", "l")); err != nil {
if runtime.GOOS == "windows" {
t.Skip("Symlinks aren't working")
}
t.Fatal(err)
}
// a/l -> b, so a/l/c should resolve by normal stat
info, err := testFs.Lstat("a/l/c")
if err != nil {
t.Fatal("unexpected error", err)
}
if !info.IsDir() {
t.Fatal("error in setup, a/l/c should be a directory")
}
testFs = fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Join(tmpDir, "a/l"))
if err := osutil.TraversesSymlink(testFs, "."); err != nil {
t.Error(`TraversesSymlink on filesystem with symlink at root returned error for ".":`, err)
}
}
var traversesSymlinkResult error
func BenchmarkTraversesSymlink(b *testing.B) {
+24 -10
View File
@@ -6,7 +6,6 @@ package notify
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
)
@@ -60,7 +59,7 @@ func (nd node) Add(name string) node {
return nd.addchild(name, name[i:])
}
func (nd node) AddDir(fn walkFunc) error {
func (nd node) AddDir(fn walkFunc, doNotWatch DoNotWatchFn) error {
stack := []node{nd}
Traverse:
for n := len(stack); n != 0; n = len(stack) {
@@ -78,13 +77,25 @@ Traverse:
}
// TODO(rjeczalik): tolerate open failures - add failed names to
// AddDirError and notify users which names are not added to the tree.
fi, err := ioutil.ReadDir(nd.Name)
f, err := os.Open(nd.Name)
if err != nil {
return err
}
for _, fi := range fi {
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
return err
}
for _, name := range names {
name = filepath.Join(nd.Name, name)
if doNotWatch != nil && doNotWatch(name) {
continue
}
fi, err := os.Lstat(name)
if err != nil {
return err
}
if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir {
name := filepath.Join(nd.Name, fi.Name())
stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:]))
}
}
@@ -141,7 +152,7 @@ func (nd node) Del(name string) error {
return nil
}
func (nd node) Walk(fn walkFunc) error {
func (nd node) Walk(fn walkFunc, doNotWatch DoNotWatchFn) error {
stack := []node{nd}
Traverse:
for n := len(stack); n != 0; n = len(stack) {
@@ -160,6 +171,9 @@ Traverse:
// never has a parent node.
continue
}
if doNotWatch != nil && doNotWatch(nd.Name) {
continue
}
stack = append(stack, nd)
}
}
@@ -233,8 +247,8 @@ func (r root) Add(name string) node {
return r.addroot(name).Add(name)
}
func (r root) AddDir(dir string, fn walkFunc) error {
return r.Add(dir).AddDir(fn)
func (r root) AddDir(dir string, fn walkFunc, doNotWatch DoNotWatchFn) error {
return r.Add(dir).AddDir(fn, doNotWatch)
}
func (r root) Del(name string) error {
@@ -258,12 +272,12 @@ func (r root) Get(name string) (node, error) {
return nd, nil
}
func (r root) Walk(name string, fn walkFunc) error {
func (r root) Walk(name string, fn walkFunc, doNotWatch DoNotWatchFn) error {
nd, err := r.Get(name)
if err != nil {
return err
}
return nd.Walk(fn)
return nd.Walk(fn, doNotWatch)
}
func (r root) WalkPath(name string, fn walkPathFunc) error {
+3 -1
View File
@@ -23,6 +23,8 @@ import "fmt"
var defaultTree tree // lazy init
type DoNotWatchFn func(string) bool
func lazyInitDefaultTree() (err error) {
if defaultTree != nil {
// already initialized
@@ -96,7 +98,7 @@ func Watch(path string, c chan<- EventInfo, events ...Event) error {
// doNotWatch. Given a path as argument doNotWatch should return true if the
// file or directory should not be watched.
func WatchWithFilter(path string, c chan<- EventInfo,
doNotWatch func(string) bool, events ...Event) error {
doNotWatch DoNotWatchFn, events ...Event) error {
if err := lazyInitDefaultTree(); err != nil {
return err
}
+1 -1
View File
@@ -7,7 +7,7 @@ package notify
const buffer = 128
type tree interface {
Watch(string, chan<- EventInfo, func(string) bool, ...Event) error
Watch(string, chan<- EventInfo, DoNotWatchFn, ...Event) error
Stop(chan<- EventInfo)
Close() error
}
+7 -16
View File
@@ -93,7 +93,7 @@ func (t *nonrecursiveTree) internal(rec <-chan EventInfo) {
t.rw.Unlock()
continue
}
err := nd.Add(ei.Path()).AddDir(t.recFunc(eset, nil))
err := nd.Add(ei.Path()).AddDir(t.recFunc(eset), nil)
t.rw.Unlock()
if err != nil {
dbgprintf("internal(%p) error: %v", rec, err)
@@ -146,7 +146,7 @@ func (t *nonrecursiveTree) watchDel(nd node, c chan<- EventInfo, e Event) eventD
// Watch TODO(rjeczalik)
func (t *nonrecursiveTree) Watch(path string, c chan<- EventInfo,
doNotWatch func(string) bool, events ...Event) error {
doNotWatch DoNotWatchFn, events ...Event) error {
if c == nil {
panic("notify: Watch using nil channel")
}
@@ -188,8 +188,8 @@ func (t *nonrecursiveTree) watch(nd node, c chan<- EventInfo, e Event) (err erro
return nil
}
func (t *nonrecursiveTree) recFunc(e Event, doNotWatch func(string) bool) walkFunc {
addWatch := func(nd node) (err error) {
func (t *nonrecursiveTree) recFunc(e Event) walkFunc {
return func(nd node) (err error) {
switch diff := nd.Watch.Add(t.rec, e|omit|Create); {
case diff == none:
case diff[1] == 0:
@@ -202,20 +202,11 @@ func (t *nonrecursiveTree) recFunc(e Event, doNotWatch func(string) bool) walkFu
}
return
}
if doNotWatch != nil {
return func(nd node) (err error) {
if doNotWatch(nd.Name) {
return errSkip
}
return addWatch(nd)
}
}
return addWatch
}
func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event,
doNotWatch func(string) bool) error {
var traverse func(walkFunc) error
doNotWatch DoNotWatchFn) error {
var traverse func(walkFunc, DoNotWatchFn) error
// Non-recursive tree listens on Create event for every recursive
// watchpoint in order to automagically set a watch for every
// created directory.
@@ -236,7 +227,7 @@ func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event,
}
// TODO(rjeczalik): account every path that failed to be (re)watched
// and retry.
if err := traverse(t.recFunc(e, doNotWatch)); err != nil {
if err := traverse(t.recFunc(e), doNotWatch); err != nil {
return err
}
t.watchAdd(nd, c, e)
+4 -4
View File
@@ -154,7 +154,7 @@ func (t *recursiveTree) dispatch() {
// Watch TODO(rjeczalik)
func (t *recursiveTree) Watch(path string, c chan<- EventInfo,
doNotWatch func(string) bool, events ...Event) error {
_ DoNotWatchFn, events ...Event) error {
if c == nil {
panic("notify: Watch using nil channel")
}
@@ -233,7 +233,7 @@ func (t *recursiveTree) Watch(path string, c chan<- EventInfo,
children = append(children, nd)
return errSkip
}
switch must(cur.Walk(fn)); len(children) {
switch must(cur.Walk(fn, nil)); len(children) {
case 0:
// no child watches, cur holds a new watch
case 1:
@@ -332,14 +332,14 @@ func (t *recursiveTree) Stop(c chan<- EventInfo) {
watchDel(nd, c, all)
return nil
}
err = nonil(err, e, nd.Walk(fn))
err = nonil(err, e, nd.Walk(fn, nil))
// TODO(rjeczalik): if e != nil store dummy chan in nd.Watch just to
// retry un/rewatching next time and/or let the user handle the failure
// vie Error event?
return errSkip
}
t.rw.Lock()
e := t.root.Walk("", fn) // TODO(rjeczalik): use max root per c
e := t.root.Walk("", fn, nil) // TODO(rjeczalik): use max root per c
t.rw.Unlock()
if e != nil {
err = nonil(err, e)
+1 -1
View File
@@ -451,7 +451,7 @@
"importpath": "github.com/syncthing/notify",
"repository": "https://github.com/syncthing/notify",
"vcs": "git",
"revision": "e6390324ae88de3571a6b29ed1a20aa631b533d9",
"revision": "b9ceffc925039c77cd9e0d38f248279ccc4399e2",
"branch": "master",
"notests": true
},