lib/fs: Fix watcher panic due to casing on windows (fixes #4877) (#4878)

This commit is contained in:
Simon Frei 2018-04-16 20:07:00 +02:00 committed by Jakob Borg
parent 4072ae4d05
commit 17e3608865
3 changed files with 48 additions and 5 deletions

View File

@ -88,8 +88,20 @@ func adjustRoot(root string) string {
// directory, this returns an error, to prevent accessing files that are not in the // directory, this returns an error, to prevent accessing files that are not in the
// shared directory. // shared directory.
func (f *BasicFilesystem) rooted(rel string) (string, error) { func (f *BasicFilesystem) rooted(rel string) (string, error) {
return rooted(rel, f.root)
}
// rootedSymlinkEvaluated does the same as rooted, but the returned path will not
// contain any symlinks. package. If the relative path somehow causes the final
// path to escape the root directory, this returns an error, to prevent accessing
// files that are not in the shared directory.
func (f *BasicFilesystem) rootedSymlinkEvaluated(rel string) (string, error) {
return rooted(rel, f.rootSymlinkEvaluated)
}
func rooted(rel, root string) (string, error) {
// The root must not be empty. // The root must not be empty.
if f.root == "" { if root == "" {
return "", ErrInvalidFilename return "", ErrInvalidFilename
} }
@ -97,7 +109,7 @@ func (f *BasicFilesystem) rooted(rel string) (string, error) {
// The expected prefix for the resulting path is the root, with a path // The expected prefix for the resulting path is the root, with a path
// separator at the end. // separator at the end.
expectedPrefix := filepath.FromSlash(f.root) expectedPrefix := filepath.FromSlash(root)
if !strings.HasSuffix(expectedPrefix, pathSep) { if !strings.HasSuffix(expectedPrefix, pathSep) {
expectedPrefix += pathSep expectedPrefix += pathSep
} }
@ -111,7 +123,7 @@ func (f *BasicFilesystem) rooted(rel string) (string, error) {
// The supposedly correct path is the one filepath.Join will return, as // The supposedly correct path is the one filepath.Join will return, as
// it does cleaning and so on. Check that one first to make sure no // it does cleaning and so on. Check that one first to make sure no
// obvious escape attempts have been made. // obvious escape attempts have been made.
joined := filepath.Join(f.root, rel) joined := filepath.Join(root, rel)
if rel == "." && !strings.HasSuffix(joined, pathSep) { if rel == "." && !strings.HasSuffix(joined, pathSep) {
joined += pathSep joined += pathSep
} }

View File

@ -11,6 +11,7 @@ package fs
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"path/filepath" "path/filepath"
"strings" "strings"
@ -23,7 +24,7 @@ import (
var backendBuffer = 500 var backendBuffer = 500
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) { func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
absName, err := f.rooted(name) absName, err := f.rootedSymlinkEvaluated(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -110,7 +111,7 @@ func (f *BasicFilesystem) unrootedChecked(absPath string) string {
return "." return "."
} }
if !strings.HasPrefix(absPath, f.rootSymlinkEvaluated) { if !strings.HasPrefix(absPath, f.rootSymlinkEvaluated) {
panic("bug: Notify backend is processing a change outside of the watched path: " + absPath) panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: root==%v, rootSymEval==%v, path==%v", f.root, f.rootSymlinkEvaluated, absPath))
} }
return f.unrootedSymlinkEvaluated(absPath) return f.unrootedSymlinkEvaluated(absPath)
} }

View File

@ -16,6 +16,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"syscall" "syscall"
"testing" "testing"
"time" "time"
@ -256,6 +257,35 @@ func TestUnrootedChecked(t *testing.T) {
unrooted = fs.unrootedChecked("/random/other/path") unrooted = fs.unrootedChecked("/random/other/path")
} }
func TestWatchIssue4877(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("Windows specific test")
}
name := "Issue4877"
file := "file"
testCase := func() {
createTestFile(name, file)
}
expectedEvents := []Event{
{file, NonRemove},
}
allowedEvents := []Event{
{name, NonRemove},
}
origTestFs := testFs
testFs = NewFilesystem(FilesystemTypeBasic, strings.ToLower(testDirAbs[:1])+strings.ToUpper(testDirAbs[1:]))
defer func() {
testFs = origTestFs
}()
testScenario(t, name, testCase, expectedEvents, allowedEvents, "")
}
// path relative to folder root, also creates parent dirs if necessary // path relative to folder root, also creates parent dirs if necessary
func createTestFile(name string, file string) string { func createTestFile(name string, file string) string {
joined := filepath.Join(name, file) joined := filepath.Join(name, file)