1use super::{
2 Arc, DirectoryEntries, DirectoryFrame, DirectoryIdentity, EntryFilter, EntrySorter,
3 FileSystemId, HashSet, Path, PathBuf, PendingDirectory, RootSymlinkPolicy, VecDeque, WalkError,
4 WalkOperation, WalkOptions, Walker, directory_info, fs, io,
5};
6
7impl Walker {
8 pub fn new(root: impl AsRef<Path>) -> Result<Self, WalkError> {
14 Self::with_options(root, WalkOptions::default())
15 }
16
17 pub fn with_options(root: impl AsRef<Path>, options: WalkOptions) -> Result<Self, WalkError> {
23 Self::with_behavior(root, options, None, None, None, false)
24 }
25
26 pub(crate) fn with_behavior(
27 root: impl AsRef<Path>,
28 options: WalkOptions,
29 sorter: Option<EntrySorter>,
30 filter: Option<EntryFilter>,
31 skip_stdout: Option<crate::report::FileIdentity>,
32 contents_first: bool,
33 ) -> Result<Self, WalkError> {
34 let requested = root.as_ref();
35 let options = options.normalized();
36 if options.root_symlink_policy == RootSymlinkPolicy::Reject {
37 let metadata = fs::symlink_metadata(requested).map_err(|source| {
38 WalkError::new(requested, 0, WalkOperation::ReadMetadata, source)
39 })?;
40 if metadata.file_type().is_symlink() {
41 return Err(WalkError::new(
42 requested,
43 0,
44 WalkOperation::ReadMetadata,
45 io::Error::new(
46 io::ErrorKind::InvalidInput,
47 "root symlink rejected by policy",
48 ),
49 ));
50 }
51 }
52 let canonical = if options.follow_links || options.same_file_system {
53 requested.canonicalize().map_err(|source| {
54 WalkError::new(requested, 0, WalkOperation::Canonicalize, source)
55 })?
56 } else if requested.is_absolute() {
57 requested.to_path_buf()
58 } else {
59 std::env::current_dir()
60 .map_err(|source| {
61 WalkError::new(requested, 0, WalkOperation::Canonicalize, source)
62 })?
63 .join(requested)
64 };
65 let metadata = fs::metadata(&canonical)
66 .map_err(|source| WalkError::new(&canonical, 0, WalkOperation::ReadMetadata, source))?;
67 let root_directory_info =
68 if metadata.is_dir() && (options.follow_links || options.same_file_system) {
69 Some(directory_info(&canonical, &metadata).map_err(|source| {
70 WalkError::new(&canonical, 0, WalkOperation::ReadMetadata, source)
71 })?)
72 } else {
73 None
74 };
75 let root_file_system = if options.same_file_system {
76 root_directory_info.map(|info| info.file_system)
77 } else {
78 None
79 };
80 let (root_bytes, root_version) = if options.collect_metadata && metadata.is_file() {
81 (
82 Some(metadata.len()),
83 Some(crate::file_version::from_metadata(&metadata)),
84 )
85 } else {
86 (None, None)
87 };
88 let plain_entries = !options.follow_links
89 && !options.same_file_system
90 && !options.collect_metadata
91 && options.max_depth.is_none()
92 && options.min_depth == 0
93 && filter.is_none()
94 && skip_stdout.is_none()
95 && !contents_first;
96 let root = Arc::new(canonical.clone());
97 Ok(Self {
98 root: Arc::clone(&root),
99 root_components: canonical.components().count(),
100 root_file_type: Some(metadata.file_type()),
101 root_bytes,
102 root_version,
103 root_file_system,
104 root_directory_info,
105 options,
106 frames: Vec::new(),
107 open_handles: 0,
108 yield_root: true,
109 pending_directory: None,
110 skip_pending_directory: false,
111 active_directories: HashSet::new(),
112 finished: false,
113 sorter,
114 filter,
115 skip_stdout,
116 contents_first,
117 deferred_entry: None,
118 plain_entries,
119 })
120 }
121
122 pub(crate) fn from_known_directory(
123 root: &Arc<PathBuf>,
124 directory: PathBuf,
125 depth: usize,
126 options: WalkOptions,
127 root_file_system: Option<FileSystemId>,
128 ) -> Self {
129 Self::from_known_directory_with_ancestry(
130 root,
131 directory,
132 depth,
133 options,
134 root_file_system,
135 None,
136 HashSet::new(),
137 )
138 }
139
140 pub(crate) fn from_known_directory_with_ancestry(
141 root: &Arc<PathBuf>,
142 directory: PathBuf,
143 depth: usize,
144 options: WalkOptions,
145 root_file_system: Option<FileSystemId>,
146 directory_identity: Option<DirectoryIdentity>,
147 active_directories: HashSet<DirectoryIdentity>,
148 ) -> Self {
149 let options = options.normalized();
150 let root_components = root.components().count();
151 let plain_entries = !options.follow_links
152 && !options.same_file_system
153 && !options.collect_metadata
154 && options.max_depth.is_none()
155 && options.min_depth == 0;
156 Self {
157 root: Arc::clone(root),
158 root_components,
159 root_file_type: None,
160 root_bytes: None,
161 root_version: None,
162 root_file_system,
163 root_directory_info: None,
164 options,
165 frames: Vec::new(),
166 open_handles: 0,
167 yield_root: false,
168 pending_directory: Some(PendingDirectory {
169 path: directory,
170 depth,
171 identity: directory_identity,
172 post_entry: None,
173 }),
174 skip_pending_directory: false,
175 active_directories,
176 finished: false,
177 sorter: None,
178 filter: None,
179 skip_stdout: None,
180 contents_first: false,
181 deferred_entry: None,
182 plain_entries,
183 }
184 }
185
186 #[must_use]
187 pub fn root(&self) -> &Path {
188 self.root.as_path()
189 }
190
191 #[must_use]
192 pub const fn options(&self) -> &WalkOptions {
193 &self.options
194 }
195
196 pub fn skip_current_dir(&mut self) {
198 if self.pending_directory.is_some() {
199 self.skip_pending_directory = true;
200 }
201 }
202
203 pub(crate) fn schedule_pending_directory(&mut self) -> Option<WalkError> {
204 let pending = self.pending_directory.take()?;
205 if self.skip_pending_directory {
206 self.skip_pending_directory = false;
207 return None;
208 }
209 if self.open_handles >= self.options.max_open {
210 self.buffer_oldest_open_directory();
211 }
212 match fs::read_dir(&pending.path) {
213 Ok(entries) => {
214 if let Some(identity) = pending.identity {
215 self.active_directories.insert(identity);
216 }
217 let (entries, opened) = match self.sorter.as_ref() {
218 None => (DirectoryEntries::Open(entries), true),
219 Some(sorter) => {
220 let mut entries = entries.collect::<Vec<_>>();
221 entries.sort_by(|left, right| match (left, right) {
222 (Ok(left), Ok(right)) => sorter(left, right),
223 (Err(_), Ok(_)) => std::cmp::Ordering::Less,
224 (Ok(_), Err(_)) => std::cmp::Ordering::Greater,
225 (Err(_), Err(_)) => std::cmp::Ordering::Equal,
226 });
227 (DirectoryEntries::Buffered(entries.into()), false)
228 }
229 };
230 self.frames.push(DirectoryFrame {
231 path: pending.path,
232 depth: pending.depth,
233 entries,
234 identity: pending.identity,
235 post_entry: pending.post_entry,
236 });
237 self.open_handles += usize::from(opened);
238 None
239 }
240 Err(source) => {
241 self.deferred_entry = pending.post_entry;
242 Some(WalkError::new(
243 pending.path,
244 pending.depth,
245 WalkOperation::ReadDirectory,
246 source,
247 ))
248 }
249 }
250 }
251
252 fn buffer_oldest_open_directory(&mut self) {
253 let Some(index) = self.frames.iter().position(|frame| frame.entries.is_open()) else {
254 return;
255 };
256 let placeholder = DirectoryEntries::Buffered(VecDeque::new());
257 let entries = std::mem::replace(&mut self.frames[index].entries, placeholder);
258 if let DirectoryEntries::Open(entries) = entries {
259 self.frames[index].entries = DirectoryEntries::Buffered(entries.collect());
260 self.open_handles -= 1;
261 }
262 }
263}