Source file src/io/fs/walk.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fs
     6  
     7  import (
     8  	"errors"
     9  	"path"
    10  )
    11  
    12  // SkipDir is used as a return value from [WalkDirFunc] to indicate that
    13  // the directory named in the call is to be skipped. It is not returned
    14  // as an error by any function.
    15  var SkipDir = errors.New("skip this directory")
    16  
    17  // SkipAll is used as a return value from [WalkDirFunc] to indicate that
    18  // all remaining files and directories are to be skipped. It is not returned
    19  // as an error by any function.
    20  var SkipAll = errors.New("skip everything and stop the walk")
    21  
    22  // WalkDirFunc is the type of the function called by [WalkDir] to visit
    23  // each file or directory.
    24  //
    25  // The path argument contains the argument to [WalkDir] as a prefix.
    26  // That is, if WalkDir is called with root argument "dir" and finds a file
    27  // named "a" in that directory, the walk function will be called with
    28  // argument "dir/a".
    29  //
    30  // The d argument is the [DirEntry] for the named path.
    31  //
    32  // The error result returned by the function controls how [WalkDir]
    33  // continues. If the function returns the special value [SkipDir], WalkDir
    34  // skips the current directory (path if d.IsDir() is true, otherwise
    35  // path's parent directory). If the function returns the special value
    36  // [SkipAll], WalkDir skips all remaining files and directories. Otherwise,
    37  // if the function returns a non-nil error, WalkDir stops entirely and
    38  // returns that error.
    39  //
    40  // The err argument reports an error related to path, signaling that
    41  // [WalkDir] will not walk into that directory. The function can decide how
    42  // to handle that error; as described earlier, returning the error will
    43  // cause WalkDir to stop walking the entire tree.
    44  //
    45  // [WalkDir] calls the function with a non-nil err argument in two cases.
    46  //
    47  // First, if the initial [Stat] on the root directory fails, WalkDir
    48  // calls the function with path set to root, d set to nil, and err set to
    49  // the error from [fs.Stat].
    50  //
    51  // Second, if a directory's ReadDir method (see [ReadDirFile]) fails, WalkDir calls the
    52  // function with path set to the directory's path, d set to an
    53  // [DirEntry] describing the directory, and err set to the error from
    54  // ReadDir. In this second case, the function is called twice with the
    55  // path of the directory: the first call is before the directory read is
    56  // attempted and has err set to nil, giving the function a chance to
    57  // return [SkipDir] or [SkipAll] and avoid the ReadDir entirely. The second call
    58  // is after a failed ReadDir and reports the error from ReadDir.
    59  // (If ReadDir succeeds, there is no second call.)
    60  //
    61  // The differences between WalkDirFunc compared to [path/filepath.WalkFunc] are:
    62  //
    63  //   - The second argument has type [DirEntry] instead of [FileInfo].
    64  //   - The function is called before reading a directory, to allow [SkipDir]
    65  //     or [SkipAll] to bypass the directory read entirely or skip all remaining
    66  //     files and directories respectively.
    67  //   - If a directory read fails, the function is called a second time
    68  //     for that directory to report the error.
    69  type WalkDirFunc func(path string, d DirEntry, err error) error
    70  
    71  // walkDir recursively descends path, calling walkDirFn.
    72  func walkDir(fsys FS, name string, d DirEntry, walkDirFn WalkDirFunc) error {
    73  	if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() {
    74  		if err == SkipDir && d.IsDir() {
    75  			// Successfully skipped directory.
    76  			err = nil
    77  		}
    78  		return err
    79  	}
    80  
    81  	dirs, err := ReadDir(fsys, name)
    82  	if err != nil {
    83  		// Second call, to report ReadDir error.
    84  		err = walkDirFn(name, d, err)
    85  		if err != nil {
    86  			if err == SkipDir && d.IsDir() {
    87  				err = nil
    88  			}
    89  			return err
    90  		}
    91  	}
    92  
    93  	for _, d1 := range dirs {
    94  		name1 := path.Join(name, d1.Name())
    95  		if err := walkDir(fsys, name1, d1, walkDirFn); err != nil {
    96  			if err == SkipDir {
    97  				break
    98  			}
    99  			return err
   100  		}
   101  	}
   102  	return nil
   103  }
   104  
   105  // WalkDir walks the file tree rooted at root, calling fn for each file or
   106  // directory in the tree, including root.
   107  //
   108  // All errors that arise visiting files and directories are filtered by fn:
   109  // see the [fs.WalkDirFunc] documentation for details.
   110  //
   111  // The files are walked in lexical order, which makes the output deterministic
   112  // but requires WalkDir to read an entire directory into memory before proceeding
   113  // to walk that directory.
   114  //
   115  // WalkDir does not follow symbolic links found in directories,
   116  // but if root itself is a symbolic link, its target will be walked.
   117  func WalkDir(fsys FS, root string, fn WalkDirFunc) error {
   118  	info, err := Stat(fsys, root)
   119  	if err != nil {
   120  		err = fn(root, nil, err)
   121  	} else {
   122  		err = walkDir(fsys, root, FileInfoToDirEntry(info), fn)
   123  	}
   124  	if err == SkipDir || err == SkipAll {
   125  		return nil
   126  	}
   127  	return err
   128  }
   129  

View as plain text