in iterator/fs.go [127:244]
func (obj *Fs) Recurse(ctx context.Context, scan interfaces.ScanFunc) ([]interfaces.Iterator, error) {
mu := &sync.Mutex{} // guards any concurrently modified state
iterators := []interfaces.Iterator{}
obj.Logf("running %s", obj.String())
if !obj.Path.IsAbs() {
return nil, fmt.Errorf("path is not absolute")
}
// it's a single file, not a directory
if !obj.Path.IsDir() {
// XXX: symlink detection?
fileInfo, err := os.Stat(obj.Path.Path()) // XXX: stat or Lstat?
if err != nil {
return nil, errwrap.Wrapf(err, "could not stat during single file scan")
}
if fileInfo.IsDir() {
return nil, fmt.Errorf("input path contained no trailing slash but is a dir")
}
uid := FileScheme + obj.Path.String() // the (ugly) default
if obj.GenUID != nil {
var err error
uid, err = obj.GenUID(obj.Path)
if err != nil {
// probable programming error
return nil, errwrap.Wrapf(err, "the GetUID func failed")
}
}
info := &interfaces.Info{
FileInfo: fileInfo,
UID: uid,
}
return nil, errwrap.Wrapf(scan(ctx, obj.Path, info), "single file scan func failed")
}
// TODO: Replace this with a parallel walk for performance
// TODO: Maybe add a separate flag/switch for it in the options?
// TODO: Make sure result aggregation and skipdir support still works!
// TODO: Replace this with a walk that accepts safepath types instead.
err := filepath.Walk(obj.Path.Path(), func(path string, fileInfo fs.FileInfo, err error) error {
if err != nil {
// prevent panic by handling failure accessing a path
return errwrap.Wrapf(err, "fail inside walk with: %s", path)
}
safePath, err := safepath.ParseIntoPath(path, fileInfo.IsDir())
if err != nil {
return err
}
// Mechanism to end a particularly long walk early if needed...
// In an effort to short-circuit things if needed, we run a
// check ourselves and break out early if we see that we have
// cancelled early.
select {
case <-ctx.Done():
return errwrap.Wrapf(ctx.Err(), "ended walk early")
default:
}
// skip symlinks
if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
return nil
}
// Check for a .gitmodules file.
gitIterators, err := obj.GitSubmodulesHelper(ctx, safePath)
if err != nil {
return err
}
if gitIterators != nil && len(gitIterators) > 0 {
mu.Lock()
for _, iterator := range gitIterators {
iterators = append(iterators, iterator)
}
mu.Unlock()
}
// Skip iterating over certain paths.
if skip, err := SkipPath(safePath, fileInfo); skip || err != nil {
if obj.Debug && (skip || err == interfaces.SkipDir) {
obj.Logf("skipping: %s", safePath.String())
}
return err // nil to skip, interfaces.SkipDir, or error
}
if obj.Debug {
obj.Logf("visited file or dir: %q", path)
}
uid := FileScheme + safePath.String() // the (ugly) default
if obj.GenUID != nil {
var err error
uid, err = obj.GenUID(safePath)
if err != nil {
// probable programming error
return errwrap.Wrapf(err, "the GetUID func failed")
}
}
info := &interfaces.Info{
FileInfo: fileInfo,
UID: uid,
}
// We want to ignore the ErrUnknownLicense results, and error if
// we hit any actual errors that we should bubble upwards.
if err := scan(ctx, safePath, info); err != nil && !errors.Is(err, interfaces.ErrUnknownLicense) {
// XXX: ShutdownOnError?
return errwrap.Wrapf(err, "scan func failed")
}
return nil
})
//if obj.Debug { obj.Logf("walk done!") } // debug
return iterators, errwrap.Wrapf(err, "walk failed")
}