@@ -8,6 +8,8 @@ package scanner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -61,7 +63,13 @@ type CurrentFiler interface {
|
||||
CurrentFile(name string) (protocol.FileInfo, bool)
|
||||
}
|
||||
|
||||
func Walk(ctx context.Context, cfg Config) chan protocol.FileInfo {
|
||||
type ScanResult struct {
|
||||
File protocol.FileInfo
|
||||
Err error
|
||||
Path string // to be set in case Err != nil and File == nil
|
||||
}
|
||||
|
||||
func Walk(ctx context.Context, cfg Config) chan ScanResult {
|
||||
w := walker{cfg}
|
||||
|
||||
if w.CurrentFiler == nil {
|
||||
@@ -77,17 +85,23 @@ func Walk(ctx context.Context, cfg Config) chan protocol.FileInfo {
|
||||
return w.walk(ctx)
|
||||
}
|
||||
|
||||
var (
|
||||
errUTF8Invalid = errors.New("item is not in UTF8 encoding")
|
||||
errUTF8Normalization = errors.New("item is not in the correct UTF8 normalization form")
|
||||
errUTF8Conflict = errors.New("item has UTF8 encoding conflict with another item")
|
||||
)
|
||||
|
||||
type walker struct {
|
||||
Config
|
||||
}
|
||||
|
||||
// Walk returns the list of files found in the local folder by scanning the
|
||||
// file system. Files are blockwise hashed.
|
||||
func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
||||
func (w *walker) walk(ctx context.Context) chan ScanResult {
|
||||
l.Debugln("Walk", w.Subs, w.Matcher)
|
||||
|
||||
toHashChan := make(chan protocol.FileInfo)
|
||||
finishedChan := make(chan protocol.FileInfo)
|
||||
finishedChan := make(chan ScanResult)
|
||||
|
||||
// A routine which walks the filesystem tree, and sends files which have
|
||||
// been modified to the counter routine.
|
||||
@@ -182,7 +196,7 @@ func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
|
||||
return finishedChan
|
||||
}
|
||||
|
||||
func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protocol.FileInfo) fs.WalkFunc {
|
||||
func (w *walker) walkAndHashFiles(ctx context.Context, toHashChan chan<- protocol.FileInfo, finishedChan chan<- ScanResult) fs.WalkFunc {
|
||||
now := time.Now()
|
||||
ignoredParent := ""
|
||||
|
||||
@@ -202,7 +216,7 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l.Debugln("error:", path, info, err)
|
||||
w.handleError(ctx, "scan", path, err, finishedChan)
|
||||
return skip
|
||||
}
|
||||
|
||||
@@ -225,7 +239,7 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
}
|
||||
|
||||
if !utf8.ValidString(path) {
|
||||
l.Warnf("File name %q is not in UTF8 encoding; skipping.", path)
|
||||
w.handleError(ctx, "scan", path, errUTF8Invalid, finishedChan)
|
||||
return skip
|
||||
}
|
||||
|
||||
@@ -244,7 +258,7 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
|
||||
if ignoredParent == "" {
|
||||
// parent isn't ignored, nothing special
|
||||
return w.handleItem(ctx, path, fchan, dchan, skip)
|
||||
return w.handleItem(ctx, path, toHashChan, finishedChan, skip)
|
||||
}
|
||||
|
||||
// Part of current path below the ignored (potential) parent
|
||||
@@ -253,17 +267,17 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
// ignored path isn't actually a parent of the current path
|
||||
if rel == path {
|
||||
ignoredParent = ""
|
||||
return w.handleItem(ctx, path, fchan, dchan, skip)
|
||||
return w.handleItem(ctx, path, toHashChan, finishedChan, skip)
|
||||
}
|
||||
|
||||
// The previously ignored parent directories of the current, not
|
||||
// ignored path need to be handled as well.
|
||||
if err = w.handleItem(ctx, ignoredParent, fchan, dchan, skip); err != nil {
|
||||
if err = w.handleItem(ctx, ignoredParent, toHashChan, finishedChan, skip); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range strings.Split(rel, string(fs.PathSeparator)) {
|
||||
ignoredParent = filepath.Join(ignoredParent, name)
|
||||
if err = w.handleItem(ctx, ignoredParent, fchan, dchan, skip); err != nil {
|
||||
if err = w.handleItem(ctx, ignoredParent, toHashChan, finishedChan, skip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -273,21 +287,23 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
}
|
||||
}
|
||||
|
||||
func (w *walker) handleItem(ctx context.Context, path string, fchan, dchan chan protocol.FileInfo, skip error) error {
|
||||
func (w *walker) handleItem(ctx context.Context, path string, toHashChan chan<- protocol.FileInfo, finishedChan chan<- ScanResult, skip error) error {
|
||||
info, err := w.Filesystem.Lstat(path)
|
||||
// An error here would be weird as we've already gotten to this point, but act on it nonetheless
|
||||
if err != nil {
|
||||
w.handleError(ctx, "scan", path, err, finishedChan)
|
||||
return skip
|
||||
}
|
||||
|
||||
path, shouldSkip := w.normalizePath(path, info)
|
||||
if shouldSkip {
|
||||
path, err = w.normalizePath(path, info)
|
||||
if err != nil {
|
||||
w.handleError(ctx, "normalizing path", path, err, finishedChan)
|
||||
return skip
|
||||
}
|
||||
|
||||
switch {
|
||||
case info.IsSymlink():
|
||||
if err := w.walkSymlink(ctx, path, dchan); err != nil {
|
||||
if err := w.walkSymlink(ctx, path, finishedChan); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
@@ -297,16 +313,16 @@ func (w *walker) handleItem(ctx context.Context, path string, fchan, dchan chan
|
||||
return nil
|
||||
|
||||
case info.IsDir():
|
||||
err = w.walkDir(ctx, path, info, dchan)
|
||||
err = w.walkDir(ctx, path, info, finishedChan)
|
||||
|
||||
case info.IsRegular():
|
||||
err = w.walkRegular(ctx, path, info, fchan)
|
||||
err = w.walkRegular(ctx, path, info, toHashChan)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, fchan chan protocol.FileInfo) error {
|
||||
func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, toHashChan chan<- protocol.FileInfo) error {
|
||||
curFile, hasCurFile := w.CurrentFiler.CurrentFile(relPath)
|
||||
|
||||
blockSize := protocol.MinBlockSize
|
||||
@@ -352,7 +368,7 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
||||
l.Debugln("to hash:", relPath, f)
|
||||
|
||||
select {
|
||||
case fchan <- f:
|
||||
case toHashChan <- f:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -360,7 +376,7 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, dchan chan protocol.FileInfo) error {
|
||||
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, finishedChan chan<- ScanResult) error {
|
||||
curFile, hasCurFile := w.CurrentFiler.CurrentFile(relPath)
|
||||
|
||||
f, _ := CreateFileInfo(info, relPath, nil)
|
||||
@@ -384,7 +400,7 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
|
||||
l.Debugln("dir:", relPath, f)
|
||||
|
||||
select {
|
||||
case dchan <- f:
|
||||
case finishedChan <- ScanResult{File: f}:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -394,7 +410,7 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
|
||||
|
||||
// walkSymlink returns nil or an error, if the error is of the nature that
|
||||
// it should stop the entire walk.
|
||||
func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan protocol.FileInfo) error {
|
||||
func (w *walker) walkSymlink(ctx context.Context, relPath string, finishedChan chan<- ScanResult) error {
|
||||
// Symlinks are not supported on Windows. We ignore instead of returning
|
||||
// an error.
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -408,7 +424,7 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan pro
|
||||
|
||||
target, err := w.Filesystem.ReadSymlink(relPath)
|
||||
if err != nil {
|
||||
l.Debugln("readlink error:", relPath, err)
|
||||
w.handleError(ctx, "reading link:", relPath, err, finishedChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -439,7 +455,7 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan pro
|
||||
l.Debugln("symlink changedb:", relPath, f)
|
||||
|
||||
select {
|
||||
case dchan <- f:
|
||||
case finishedChan <- ScanResult{File: f}:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -449,7 +465,7 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan pro
|
||||
|
||||
// normalizePath returns the normalized relative path (possibly after fixing
|
||||
// it on disk), or skip is true.
|
||||
func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string, skip bool) {
|
||||
func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string, err error) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Mac OS X file names should always be NFD normalized.
|
||||
normPath = norm.NFD.String(path)
|
||||
@@ -462,14 +478,13 @@ func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string,
|
||||
|
||||
if path == normPath {
|
||||
// The file name is already normalized: nothing to do
|
||||
return path, false
|
||||
return path, nil
|
||||
}
|
||||
|
||||
if !w.AutoNormalize {
|
||||
// We're not authorized to do anything about it, so complain and skip.
|
||||
|
||||
l.Warnf("File name %q is not in the correct UTF8 normalization form; skipping.", path)
|
||||
return "", true
|
||||
return "", errUTF8Normalization
|
||||
}
|
||||
|
||||
// We will attempt to normalize it.
|
||||
@@ -477,11 +492,12 @@ func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string,
|
||||
if fs.IsNotExist(err) {
|
||||
// Nothing exists with the normalized filename. Good.
|
||||
if err = w.Filesystem.Rename(path, normPath); err != nil {
|
||||
l.Infof(`Error normalizing UTF8 encoding of file "%s": %v`, path, err)
|
||||
return "", true
|
||||
return "", err
|
||||
}
|
||||
l.Infof(`Normalized UTF8 encoding of file name "%s".`, path)
|
||||
} else if w.Filesystem.SameFile(info, normInfo) {
|
||||
return normPath, nil
|
||||
}
|
||||
if w.Filesystem.SameFile(info, normInfo) {
|
||||
// With some filesystems (ZFS), if there is an un-normalized path and you ask whether the normalized
|
||||
// version exists, it responds with true. Therefore we need to check fs.SameFile as well.
|
||||
// In this case, a call to Rename won't do anything, so we have to rename via a temp file.
|
||||
@@ -491,23 +507,19 @@ func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string,
|
||||
|
||||
tempPath := fs.TempNameWithPrefix(normPath, "")
|
||||
if err = w.Filesystem.Rename(path, tempPath); err != nil {
|
||||
l.Infof(`Error during normalizing UTF8 encoding of file "%s" (renamed to "%s"): %v`, path, tempPath, err)
|
||||
return "", true
|
||||
return "", err
|
||||
}
|
||||
if err = w.Filesystem.Rename(tempPath, normPath); err != nil {
|
||||
// I don't ever expect this to happen, but if it does, we should probably tell our caller that the normalized
|
||||
// path is the temp path: that way at least the user's data still gets synced.
|
||||
l.Warnf(`Error renaming "%s" to "%s" while normalizating UTF8 encoding: %v. You will want to rename this file back manually`, tempPath, normPath, err)
|
||||
return tempPath, false
|
||||
return tempPath, nil
|
||||
}
|
||||
} else {
|
||||
// There is something already in the way at the normalized
|
||||
// file name.
|
||||
l.Infof(`File "%s" path has UTF8 encoding conflict with another file; ignoring.`, path)
|
||||
return "", true
|
||||
return normPath, nil
|
||||
}
|
||||
|
||||
return normPath, false
|
||||
// There is something already in the way at the normalized
|
||||
// file name.
|
||||
return "", errUTF8Conflict
|
||||
}
|
||||
|
||||
// updateFileInfo updates walker specific members of protocol.FileInfo that do not depend on type
|
||||
@@ -522,6 +534,16 @@ func (w *walker) updateFileInfo(file, curFile protocol.FileInfo) protocol.FileIn
|
||||
file.LocalFlags = w.LocalFlags
|
||||
return file
|
||||
}
|
||||
func (w *walker) handleError(ctx context.Context, context, path string, err error, finishedChan chan<- ScanResult) {
|
||||
l.Infof("Scanner (folder %s, file %q): %s: %v", w.Folder, path, context, err)
|
||||
select {
|
||||
case finishedChan <- ScanResult{
|
||||
Err: fmt.Errorf("%s: %s", context, err.Error()),
|
||||
Path: path,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// A byteCounter gets bytes added to it via Update() and then provides the
|
||||
// Total() and one minute moving average Rate() in bytes per second.
|
||||
|
||||
Reference in New Issue
Block a user