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