Repository: syncthing/syncthing
File: lib/fs/walkfs.go (Go)

Code before:
//
// If there was a problem walking to the file or directory named by path, the
// incoming error will describe the problem and the function can decide how
// to handle that error (and Walk will not descend into that directory). If
// an error is returned, processing stops. The sole exception is when the function
// returns the special value SkipDir. If the function returns SkipDir when invoked
// on a directory, Walk skips the directory's contents entirely.
// If the function returns SkipDir when invoked on a non-directory file,
// Walk skips the remaining files in the containing directory.
type WalkFunc func(path string, info FileInfo, err error) error

type walkFilesystem struct {
	Filesystem
}

func NewWalkFilesystem(next Filesystem) Filesystem {
	return &walkFilesystem{next}
}

// walk recursively descends path, calling walkFn.
func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ancestors *ancestorDirList) error {
	l.Debugf("walk: path=%s", path)
	path, err := Canonicalize(path)
	if err != nil {
		return err
	}

	err = walkFn(path, info, nil)
	if err != nil {
		if info.IsDir() && err == SkipDir {
			return nil
		}
		return err
	}

	if !info.IsDir() && path != "." {
		return nil
	}

	if ancestors.AppendUnlessPresent(info) {
		defer ancestors.Remove(info)
	} else {
		l.Warnf("Infinite filesystem recursion detected on path '%s', not walking further down", path)
		return nil
	}

	names, err := f.DirNames(path)
	if err != nil {
		return walkFn(path, info, err)
	}


Review comment (suggestion):
This check won't protect against scanning files several iterations deep into a junction loop, if such a path comes in via events or API. (E.g. if you have `c:\foo\bar -> c:\foo` and we ask for a scan of `c:\foo\bar\bar\bar\bar\somefile`.)

If we don't get fs notifications like that (because Windows is smart and canonicalizes the name before notifying or the fsnotify package does) I don't think it's a big deal. But if we do get notifications like that if you open `c:\foo\bar\bar\bar\bar\somefile` in notepad I think it's a problem.

Code after:

// walk recursively descends path, calling walkFn.
func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ancestors *ancestorDirList) error {
	l.Debugf("walk: path=%s", path)
	path, err := Canonicalize(path)
	if err != nil {
		return err
	}

	err = walkFn(path, info, nil)
	if err != nil {
		if info.IsDir() && err == SkipDir {
			return nil
		}
		return err
	}

	if !info.IsDir() && path != "." {
		return nil
	}

	if !ancestors.Contains(info) {
		ancestors.Push(info)
		defer ancestors.Pop()
	} else {
		l.Warnf("Infinite filesystem recursion detected on path '%s', not walking further down", path)
		return nil
	}

	names, err := f.DirNames(path)
	if err != nil {
		return walkFn(path, info, err)
	}

	for _, name := range names {
		filename := filepath.Join(path, name)
		fileInfo, err := f.Lstat(filename)
		if err != nil {
			if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
				return err
			}
		} else {
			err = f.walk(filename, fileInfo, walkFn, ancestors)
			if err != nil {
				if !fileInfo.IsDir() || err != SkipDir {
					return err
				}
			}
		}
	}
	return nil