mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-17 10:35:11 +00:00
lib/scanner: Support walking a symlink root (ref #4353)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4666 LGTM: AudriusButkevicius, imsodin
This commit is contained in:
parent
885e3f19bd
commit
a505231774
1
AUTHORS
1
AUTHORS
@ -90,6 +90,7 @@ Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
|||||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
||||||
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
||||||
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
||||||
|
Nicholas Rishel (PrototypeNM1) <PrototypeNM1@users.noreply.github.com>
|
||||||
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
|
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
|
||||||
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||||
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
||||||
|
@ -46,7 +46,7 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !info.IsDir() {
|
if !info.IsDir() && path != "." {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1954,7 +1954,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
|
|
||||||
runner.setState(FolderScanning)
|
runner.setState(FolderScanning)
|
||||||
|
|
||||||
fchan, err := scanner.Walk(ctx, scanner.Config{
|
fchan := scanner.Walk(ctx, scanner.Config{
|
||||||
Folder: folderCfg.ID,
|
Folder: folderCfg.ID,
|
||||||
Subs: subDirs,
|
Subs: subDirs,
|
||||||
Matcher: ignores,
|
Matcher: ignores,
|
||||||
@ -1970,14 +1970,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
UseWeakHashes: weakhash.Enabled,
|
UseWeakHashes: weakhash.Enabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
// The error we get here is likely an OS level error, which might not be
|
|
||||||
// as readable as our health check errors. Check if we can get a health
|
|
||||||
// check error first, and use that if it's available.
|
|
||||||
if ferr := runner.CheckHealth(); ferr != nil {
|
|
||||||
err = ferr
|
|
||||||
}
|
|
||||||
runner.setError(err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2082,7 +2075,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
})
|
})
|
||||||
|
|
||||||
if iterError != nil {
|
if iterError != nil {
|
||||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), iterError)
|
||||||
return iterError
|
return iterError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,10 @@ func (i infiniteFS) Lstat(name string) (fs.FileInfo, error) {
|
|||||||
return fakeInfo{name, i.filesize}, nil
|
return fakeInfo{name, i.filesize}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i infiniteFS) Stat(name string) (fs.FileInfo, error) {
|
||||||
|
return fakeInfo{name, i.filesize}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i infiniteFS) DirNames(name string) ([]string, error) {
|
func (i infiniteFS) DirNames(name string) ([]string, error) {
|
||||||
// Returns a list of fake files and directories. Names are such that
|
// Returns a list of fake files and directories. Names are such that
|
||||||
// files appear before directories - this makes it so the scanner will
|
// files appear before directories - this makes it so the scanner will
|
||||||
|
@ -8,7 +8,6 @@ package scanner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -76,7 +75,7 @@ type CurrentFiler interface {
|
|||||||
CurrentFile(name string) (protocol.FileInfo, bool)
|
CurrentFile(name string) (protocol.FileInfo, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Walk(ctx context.Context, cfg Config) (chan protocol.FileInfo, error) {
|
func Walk(ctx context.Context, cfg Config) chan protocol.FileInfo {
|
||||||
w := walker{cfg}
|
w := walker{cfg}
|
||||||
|
|
||||||
if w.CurrentFiler == nil {
|
if w.CurrentFiler == nil {
|
||||||
@ -98,13 +97,9 @@ type walker struct {
|
|||||||
|
|
||||||
// Walk returns the list of files found in the local folder by scanning the
|
// Walk returns the list of files found in the local folder by scanning the
|
||||||
// file system. Files are blockwise hashed.
|
// file system. Files are blockwise hashed.
|
||||||
func (w *walker) walk(ctx context.Context) (chan protocol.FileInfo, error) {
|
func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
||||||
l.Debugln("Walk", w.Subs, w.BlockSize, w.Matcher)
|
l.Debugln("Walk", w.Subs, w.BlockSize, w.Matcher)
|
||||||
|
|
||||||
if err := w.checkDir(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
toHashChan := make(chan protocol.FileInfo)
|
toHashChan := make(chan protocol.FileInfo)
|
||||||
finishedChan := make(chan protocol.FileInfo)
|
finishedChan := make(chan protocol.FileInfo)
|
||||||
|
|
||||||
@ -126,7 +121,7 @@ func (w *walker) walk(ctx context.Context) (chan protocol.FileInfo, error) {
|
|||||||
// and feed inputs directly from the walker.
|
// and feed inputs directly from the walker.
|
||||||
if w.ProgressTickIntervalS < 0 {
|
if w.ProgressTickIntervalS < 0 {
|
||||||
newParallelHasher(ctx, w.Filesystem, w.BlockSize, w.Hashers, finishedChan, toHashChan, nil, nil, w.UseWeakHashes)
|
newParallelHasher(ctx, w.Filesystem, w.BlockSize, w.Hashers, finishedChan, toHashChan, nil, nil, w.UseWeakHashes)
|
||||||
return finishedChan, nil
|
return finishedChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults to every 2 seconds.
|
// Defaults to every 2 seconds.
|
||||||
@ -198,7 +193,7 @@ func (w *walker) walk(ctx context.Context) (chan protocol.FileInfo, error) {
|
|||||||
close(realToHashChan)
|
close(realToHashChan)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return finishedChan, nil
|
return finishedChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protocol.FileInfo) fs.WalkFunc {
|
func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protocol.FileInfo) fs.WalkFunc {
|
||||||
@ -480,21 +475,6 @@ func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string,
|
|||||||
return normPath, false
|
return normPath, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *walker) checkDir() error {
|
|
||||||
info, err := w.Filesystem.Lstat(".")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() {
|
|
||||||
return errors.New(w.Filesystem.URI() + ": not a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debugln("checkDir", w.Filesystem.Type(), w.Filesystem.URI(), info)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func PermsEqual(a, b uint32) bool {
|
func PermsEqual(a, b uint32) bool {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -61,7 +62,7 @@ func TestWalkSub(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fchan, err := Walk(context.TODO(), Config{
|
fchan := Walk(context.TODO(), Config{
|
||||||
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
|
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
|
||||||
Subs: []string{"dir2"},
|
Subs: []string{"dir2"},
|
||||||
BlockSize: 128 * 1024,
|
BlockSize: 128 * 1024,
|
||||||
@ -72,9 +73,6 @@ func TestWalkSub(t *testing.T) {
|
|||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The directory contains two files, where one is ignored from a higher
|
// The directory contains two files, where one is ignored from a higher
|
||||||
// level. We should see only the directory and one of the files.
|
// level. We should see only the directory and one of the files.
|
||||||
@ -98,17 +96,13 @@ func TestWalk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Log(ignores)
|
t.Log(ignores)
|
||||||
|
|
||||||
fchan, err := Walk(context.TODO(), Config{
|
fchan := Walk(context.TODO(), Config{
|
||||||
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
|
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"),
|
||||||
BlockSize: 128 * 1024,
|
BlockSize: 128 * 1024,
|
||||||
Matcher: ignores,
|
Matcher: ignores,
|
||||||
Hashers: 2,
|
Hashers: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp []protocol.FileInfo
|
var tmp []protocol.FileInfo
|
||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
tmp = append(tmp, f)
|
tmp = append(tmp, f)
|
||||||
@ -121,27 +115,6 @@ func TestWalk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkError(t *testing.T) {
|
|
||||||
_, err := Walk(context.TODO(), Config{
|
|
||||||
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata-missing"),
|
|
||||||
BlockSize: 128 * 1024,
|
|
||||||
Hashers: 2,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Error("no error from missing directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = Walk(context.TODO(), Config{
|
|
||||||
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata/bar"),
|
|
||||||
BlockSize: 128 * 1024,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Error("no error from non-directory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerify(t *testing.T) {
|
func TestVerify(t *testing.T) {
|
||||||
blocksize := 16
|
blocksize := 16
|
||||||
// data should be an even multiple of blocksize long
|
// data should be an even multiple of blocksize long
|
||||||
@ -292,40 +265,26 @@ func TestWalkSymlinkUnix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a folder with a symlink in it
|
// Create a folder with a symlink in it
|
||||||
|
|
||||||
os.RemoveAll("_symlinks")
|
os.RemoveAll("_symlinks")
|
||||||
defer os.RemoveAll("_symlinks")
|
|
||||||
|
|
||||||
os.Mkdir("_symlinks", 0755)
|
os.Mkdir("_symlinks", 0755)
|
||||||
os.Symlink("destination", "_symlinks/link")
|
defer os.RemoveAll("_symlinks")
|
||||||
|
os.Symlink("../testdata", "_symlinks/link")
|
||||||
|
|
||||||
|
for _, path := range []string{".", "link"} {
|
||||||
// Scan it
|
// Scan it
|
||||||
|
files, _ := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"), path)
|
||||||
fchan, err := Walk(context.TODO(), Config{
|
|
||||||
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"),
|
|
||||||
BlockSize: 128 * 1024,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var files []protocol.FileInfo
|
|
||||||
for f := range fchan {
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that we got one symlink and with the correct attributes
|
// Verify that we got one symlink and with the correct attributes
|
||||||
|
|
||||||
if len(files) != 1 {
|
if len(files) != 1 {
|
||||||
t.Errorf("expected 1 symlink, not %d", len(files))
|
t.Errorf("expected 1 symlink, not %d", len(files))
|
||||||
}
|
}
|
||||||
if len(files[0].Blocks) != 0 {
|
if len(files[0].Blocks) != 0 {
|
||||||
t.Errorf("expected zero blocks for symlink, not %d", len(files[0].Blocks))
|
t.Errorf("expected zero blocks for symlink, not %d", len(files[0].Blocks))
|
||||||
}
|
}
|
||||||
if files[0].SymlinkTarget != "destination" {
|
if files[0].SymlinkTarget != "../testdata" {
|
||||||
t.Errorf("expected symlink to have target destination, not %q", files[0].SymlinkTarget)
|
t.Errorf("expected symlink to have target destination, not %q", files[0].SymlinkTarget)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkSymlinkWindows(t *testing.T) {
|
func TestWalkSymlinkWindows(t *testing.T) {
|
||||||
@ -334,41 +293,57 @@ func TestWalkSymlinkWindows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a folder with a symlink in it
|
// Create a folder with a symlink in it
|
||||||
|
|
||||||
os.RemoveAll("_symlinks")
|
os.RemoveAll("_symlinks")
|
||||||
defer os.RemoveAll("_symlinks")
|
|
||||||
|
|
||||||
os.Mkdir("_symlinks", 0755)
|
os.Mkdir("_symlinks", 0755)
|
||||||
if err := osutil.DebugSymlinkForTestsOnly("destination", "_symlinks/link"); err != nil {
|
defer os.RemoveAll("_symlinks")
|
||||||
|
if err := osutil.DebugSymlinkForTestsOnly("../testdata", "_symlinks/link"); err != nil {
|
||||||
// Probably we require permissions we don't have.
|
// Probably we require permissions we don't have.
|
||||||
t.Skip(err)
|
t.Skip(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, path := range []string{".", "link"} {
|
||||||
// Scan it
|
// Scan it
|
||||||
|
files, _ := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"), path)
|
||||||
|
|
||||||
fchan, err := Walk(context.TODO(), Config{
|
// Verify that we got zero symlinks
|
||||||
Filesystem: fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"),
|
if len(files) != 0 {
|
||||||
BlockSize: 128 * 1024,
|
t.Errorf("expected zero symlinks, not %d", len(files))
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkRootSymlink(t *testing.T) {
|
||||||
|
// Create a folder with a symlink in it
|
||||||
|
tmp, err := ioutil.TempDir("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
var files []protocol.FileInfo
|
link := tmp + "/link"
|
||||||
for f := range fchan {
|
dest, _ := filepath.Abs("testdata/dir1")
|
||||||
files = append(files, f)
|
if err := osutil.DebugSymlinkForTestsOnly(dest, link); err != nil {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Probably we require permissions we don't have.
|
||||||
|
t.Skip("Need admin permissions or developer mode to run symlink test on Windows: " + err.Error())
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that we got zero symlinks
|
// Scan it
|
||||||
|
files, err := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, link), ".")
|
||||||
if len(files) != 0 {
|
if err != nil {
|
||||||
t.Errorf("expected zero symlinks, not %d", len(files))
|
t.Fatal("Expected no error when root folder path is provided via a symlink: " + err.Error())
|
||||||
|
}
|
||||||
|
// Verify that we got two files
|
||||||
|
if len(files) != 2 {
|
||||||
|
t.Errorf("expected two files, not %d", len(files))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkDir(fs fs.Filesystem, dir string) ([]protocol.FileInfo, error) {
|
func walkDir(fs fs.Filesystem, dir string) ([]protocol.FileInfo, error) {
|
||||||
fchan, err := Walk(context.TODO(), Config{
|
fchan := Walk(context.TODO(), Config{
|
||||||
Filesystem: fs,
|
Filesystem: fs,
|
||||||
Subs: []string{dir},
|
Subs: []string{dir},
|
||||||
BlockSize: 128 * 1024,
|
BlockSize: 128 * 1024,
|
||||||
@ -376,10 +351,6 @@ func walkDir(fs fs.Filesystem, dir string) ([]protocol.FileInfo, error) {
|
|||||||
Hashers: 2,
|
Hashers: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp []protocol.FileInfo
|
var tmp []protocol.FileInfo
|
||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
tmp = append(tmp, f)
|
tmp = append(tmp, f)
|
||||||
@ -478,17 +449,13 @@ func TestStopWalk(t *testing.T) {
|
|||||||
|
|
||||||
const numHashers = 4
|
const numHashers = 4
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
fchan, err := Walk(ctx, Config{
|
fchan := Walk(ctx, Config{
|
||||||
Filesystem: fs,
|
Filesystem: fs,
|
||||||
BlockSize: 128 * 1024,
|
BlockSize: 128 * 1024,
|
||||||
Hashers: numHashers,
|
Hashers: numHashers,
|
||||||
ProgressTickIntervalS: -1, // Don't attempt to build the full list of files before starting to scan...
|
ProgressTickIntervalS: -1, // Don't attempt to build the full list of files before starting to scan...
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive a few entries to make sure the walker is up and running,
|
// Receive a few entries to make sure the walker is up and running,
|
||||||
// scanning both files and dirs. Do some quick sanity tests on the
|
// scanning both files and dirs. Do some quick sanity tests on the
|
||||||
// returned file entries to make sure we are not just reading crap from
|
// returned file entries to make sure we are not just reading crap from
|
||||||
|
Loading…
Reference in New Issue
Block a user