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 []byte, d DirEntry, err error) error
70 71 // walkDir recursively descends path, calling walkDirFn.
72 func walkDir(fsys FS, name []byte, 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 []byte, 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