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