1use crate::report::FileVersion;
2use crate::walk_platform::DirectoryIdentity;
3use std::ffi::OsStr;
4use std::fmt;
5use std::io;
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ErrorPolicy {
10 Continue,
11 Abort,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum RootSymlinkPolicy {
17 Follow,
19 Reject,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct WalkOptions {
25 pub min_depth: usize,
26 pub max_depth: Option<usize>,
27 pub max_open: usize,
28 pub same_file_system: bool,
29 pub follow_links: bool,
30 pub collect_metadata: bool,
31 pub error_policy: ErrorPolicy,
32 pub root_symlink_policy: RootSymlinkPolicy,
33}
34
35impl Default for WalkOptions {
36 fn default() -> Self {
37 Self {
38 min_depth: 0,
39 max_depth: None,
40 max_open: Self::DEFAULT_MAX_OPEN,
41 same_file_system: false,
42 follow_links: false,
43 collect_metadata: false,
44 error_policy: ErrorPolicy::Continue,
45 root_symlink_policy: RootSymlinkPolicy::Follow,
46 }
47 }
48}
49
50impl WalkOptions {
51 pub const DEFAULT_MAX_OPEN: usize = 64;
53
54 #[must_use]
55 pub const fn with_min_depth(mut self, min_depth: usize) -> Self {
56 self.min_depth = min_depth;
57 self
58 }
59
60 #[must_use]
61 pub const fn with_max_depth(mut self, max_depth: Option<usize>) -> Self {
62 self.max_depth = max_depth;
63 self
64 }
65
66 #[must_use]
67 pub const fn with_max_open(mut self, max_open: usize) -> Self {
68 self.max_open = if max_open == 0 { 1 } else { max_open };
69 self
70 }
71
72 #[must_use]
73 pub const fn with_same_file_system(mut self, enabled: bool) -> Self {
74 self.same_file_system = enabled;
75 self
76 }
77
78 #[must_use]
79 pub const fn with_follow_links(mut self, enabled: bool) -> Self {
80 self.follow_links = enabled;
81 self
82 }
83
84 #[must_use]
85 pub const fn with_metadata(mut self, enabled: bool) -> Self {
86 self.collect_metadata = enabled;
87 self
88 }
89
90 #[must_use]
91 pub const fn with_error_policy(mut self, policy: ErrorPolicy) -> Self {
92 self.error_policy = policy;
93 self
94 }
95
96 #[must_use]
97 pub const fn with_root_symlink_policy(mut self, policy: RootSymlinkPolicy) -> Self {
98 self.root_symlink_policy = policy;
99 self
100 }
101
102 pub(crate) fn normalized(mut self) -> Self {
103 self.max_open = self.max_open.max(1);
104 if let Some(max_depth) = self.max_depth
105 && self.min_depth > max_depth
106 {
107 self.min_depth = max_depth;
108 }
109 self
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum WalkOperation {
115 Canonicalize,
116 ReadDirectory,
117 ReadEntry,
118 ReadMetadata,
119 ScheduleWorker,
120}
121
122#[derive(Debug)]
123pub struct WalkError {
124 pub(crate) path: PathBuf,
125 pub(crate) depth: usize,
126 pub(crate) operation: WalkOperation,
127 pub(crate) source: io::Error,
128}
129
130impl WalkError {
131 pub(crate) fn new(
132 path: impl Into<PathBuf>,
133 depth: usize,
134 operation: WalkOperation,
135 source: io::Error,
136 ) -> Self {
137 Self {
138 path: path.into(),
139 depth,
140 operation,
141 source,
142 }
143 }
144
145 #[must_use]
146 pub fn path(&self) -> &Path {
147 &self.path
148 }
149
150 #[must_use]
151 pub const fn depth(&self) -> usize {
152 self.depth
153 }
154
155 #[must_use]
156 pub const fn operation(&self) -> WalkOperation {
157 self.operation
158 }
159
160 #[must_use]
161 pub const fn io_error(&self) -> &io::Error {
162 &self.source
163 }
164
165 pub(crate) fn into_parts(self) -> (PathBuf, io::Error) {
166 (self.path, self.source)
167 }
168
169 pub(crate) fn rebase_depth(&mut self, depth_offset: usize) {
170 self.depth += depth_offset;
171 }
172}
173
174impl fmt::Display for WalkError {
175 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176 write!(
177 formatter,
178 "{} at depth {} for {}: {}",
179 operation_name(self.operation),
180 self.depth,
181 self.path.display(),
182 self.source
183 )
184 }
185}
186
187impl std::error::Error for WalkError {
188 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
189 Some(&self.source)
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum WalkSkipReason {
195 MaxDepth,
196 FileSystemBoundary,
197 PathEscape,
198 SymlinkLoop,
199}
200
201#[derive(Debug, Clone)]
202pub struct WalkEntry {
203 pub(crate) root_components: usize,
204 pub(crate) path: PathBuf,
205 pub(crate) depth: usize,
206 pub(crate) is_file: bool,
207 pub(crate) is_directory: bool,
208 pub(crate) is_symlink: bool,
209 pub(crate) bytes: Option<u64>,
210 pub(crate) version: Option<FileVersion>,
211 pub(crate) hidden: Option<bool>,
212 pub(crate) directory_identity: Option<DirectoryIdentity>,
213 pub(crate) skip_reason: Option<WalkSkipReason>,
214}
215
216impl WalkEntry {
217 #[must_use]
218 pub fn path(&self) -> &Path {
219 &self.path
220 }
221
222 #[must_use]
224 pub fn into_path(self) -> PathBuf {
225 self.path
226 }
227
228 #[must_use]
229 pub fn relative_path(&self) -> &Path {
230 let mut components = self.path.components();
231 for _ in 0..self.root_components {
232 if components.next().is_none() {
233 return self.path.as_path();
234 }
235 }
236 components.as_path()
237 }
238
239 #[must_use]
240 pub fn file_name(&self) -> &OsStr {
241 self.path
242 .file_name()
243 .unwrap_or_else(|| self.path.as_os_str())
244 }
245
246 #[must_use]
247 pub const fn depth(&self) -> usize {
248 self.depth
249 }
250
251 #[must_use]
252 pub const fn is_file(&self) -> bool {
253 self.is_file
254 }
255
256 #[must_use]
257 pub const fn is_dir(&self) -> bool {
258 self.is_directory
259 }
260
261 #[must_use]
262 pub const fn is_symlink(&self) -> bool {
263 self.is_symlink
264 }
265
266 #[must_use]
267 pub const fn bytes(&self) -> Option<u64> {
268 self.bytes
269 }
270
271 #[must_use]
273 pub const fn version(&self) -> Option<FileVersion> {
274 self.version
275 }
276
277 pub(crate) const fn hidden(&self) -> Option<bool> {
278 self.hidden
279 }
280
281 #[must_use]
282 pub const fn skip_reason(&self) -> Option<WalkSkipReason> {
283 self.skip_reason
284 }
285
286 pub(crate) fn rebase(&mut self, root: &Path, depth_offset: usize) {
287 self.root_components = root.components().count();
288 self.depth += depth_offset;
289 }
290
291 pub(crate) fn clear_depth_skip(&mut self) {
292 if self.skip_reason == Some(WalkSkipReason::MaxDepth) {
293 self.skip_reason = None;
294 }
295 }
296
297 pub(crate) const fn directory_identity(&self) -> Option<DirectoryIdentity> {
298 self.directory_identity
299 }
300}
301
302const fn operation_name(operation: WalkOperation) -> &'static str {
303 match operation {
304 WalkOperation::Canonicalize => "canonicalize",
305 WalkOperation::ReadDirectory => "read directory",
306 WalkOperation::ReadEntry => "read entry",
307 WalkOperation::ReadMetadata => "read metadata",
308 WalkOperation::ScheduleWorker => "schedule worker",
309 }
310}