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
5package fs
6
7import (
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.
15var 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.
20var 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.
69type WalkDirFunc func(path string, d DirEntry, err error) error
70
71// walkDir recursively descends path, calling walkDirFn.
72func 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.
117func 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