-
Notifications
You must be signed in to change notification settings - Fork 10
/
walkdir_iterate.go
156 lines (143 loc) · 4.73 KB
/
walkdir_iterate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
// Derived from Go's src/io/fs/walk.go
//
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import (
"errors"
"io"
"io/fs"
"path"
)
// walkDirUnsorted recursively descends path, calling walkDirFn. From caller side this function
// should be equivalent to fs.walkDir, except that the files are not ordered, but walked in the
// order returned by the file system. This function does not store the full directory in memory,
// which should reduce the memory footprint from O(n) to O(1).
// This might lead to inconsistencies in the seen files, as another process might delete a file
// during the walk, which is then not seen by the walk. That problem existed also with fs.WalkDir,
// which would return files which do not exist anymore. More details for unix:
// https://man7.org/linux/man-pages/man2/getdents.2.html
func walkDirUnsorted(fsys fs.FS, name string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
// This is the main call to walkDirFn for files and directories, without errors.
if err := walkDirFn(name, d, nil); err != nil || !d.IsDir() {
if err == fs.SkipDir && d.IsDir() {
// Successfully skipped directory.
err = nil
}
return err
}
dirs, err := readDir(fsys, name)
if err != nil {
// Second call, to report ReadDir error.
// Same error handling as in fs.WalkDir: If an error occurred, the walkDirFn is called again,
// which can decide to continue (nil), SkipDir or skip all by other errors (e.g. SkipAll).
err = walkDirFn(name, d, err)
if err != nil {
if err == fs.SkipDir && d.IsDir() {
err = nil
}
return err
}
// End iteration after an error
return nil
}
// Error can be ignored, as no write is happening.
defer dirs.close()
for {
d1, err := dirs.next()
if err != nil {
if err == io.EOF {
break
}
// Second call, to report ReadDir error.
// Same error handling as in fs.WalkDir: If an error occurred, the walkDirFn is called again,
// which can decide to continue (nil), SkipDir or skip all by other errors (e.g. SkipAll).
err = walkDirFn(name, d, err)
if err != nil {
if err == fs.SkipDir && d.IsDir() {
err = nil
}
return err
}
// End iteration after an error
return nil
}
name1 := path.Join(name, d1.Name())
if err := walkDirUnsorted(fsys, name1, d1, walkDirFn); err != nil {
if err == fs.SkipDir {
break
}
return err
}
}
return nil
}
// WalkDirUnsorted walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root.
//
// All errors that arise visiting files and directories are filtered by fn:
// see the [fs.WalkDirFunc] documentation for details.
//
// WalkDirUnsorted does not follow symbolic links found in directories,
// but if root itself is a symbolic link, its target will be walked.
func WalkDirUnsorted(fsys fs.FS, root string, fn fs.WalkDirFunc) error {
info, err := fs.Stat(fsys, root)
if err != nil {
err = fn(root, nil, err)
} else {
err = walkDirUnsorted(fsys, root, fs.FileInfoToDirEntry(info), fn)
}
if err == fs.SkipDir || err == fs.SkipAll {
return nil
}
return err
}
// readDir reads the named directory
// and returns a list of directory entries sorted by filename.
//
// If fs implements [ReadDirFS], ReadDir calls fs.ReadDir.
// Otherwise ReadDir calls fs.Open and uses ReadDir and Close
// on the returned file.
func readDir(fsys fs.FS, name string) (*dirIterator, error) {
file, err := fsys.Open(name)
if err != nil {
return nil, err
}
dir, ok := file.(fs.ReadDirFile)
if !ok {
file.Close()
return nil, &fs.PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")}
}
return &dirIterator{dir}, nil
}
type dirIterator struct {
// dir is used to iterate directory entries
dir fs.ReadDirFile
}
// next returns the next fs.DirEntry from the directory. If error is nil, there will be a
// fs.DirEntry returned.
func (i *dirIterator) next() (fs.DirEntry, error) {
list, err := i.dir.ReadDir(1)
if err != nil {
return nil, err
}
return list[0], nil
}
// close closes the directory file.
func (i *dirIterator) close() error {
return i.dir.Close()
}