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) directory_identity: Option<DirectoryIdentity>,
212 pub(crate) skip_reason: Option<WalkSkipReason>,
213}
214
215impl WalkEntry {
216 #[must_use]
217 pub fn path(&self) -> &Path {
218 &self.path
219 }
220
221 #[must_use]
223 pub fn into_path(self) -> PathBuf {
224 self.path
225 }
226
227 #[must_use]
228 pub fn relative_path(&self) -> &Path {
229 let mut components = self.path.components();
230 for _ in 0..self.root_components {
231 if components.next().is_none() {
232 return self.path.as_path();
233 }
234 }
235 components.as_path()
236 }
237
238 #[must_use]
239 pub fn file_name(&self) -> &OsStr {
240 self.path
241 .file_name()
242 .unwrap_or_else(|| self.path.as_os_str())
243 }
244
245 #[must_use]
246 pub const fn depth(&self) -> usize {
247 self.depth
248 }
249
250 #[must_use]
251 pub const fn is_file(&self) -> bool {
252 self.is_file
253 }
254
255 #[must_use]
256 pub const fn is_dir(&self) -> bool {
257 self.is_directory
258 }
259
260 #[must_use]
261 pub const fn is_symlink(&self) -> bool {
262 self.is_symlink
263 }
264
265 #[must_use]
266 pub const fn bytes(&self) -> Option<u64> {
267 self.bytes
268 }
269
270 #[must_use]
272 pub const fn version(&self) -> Option<FileVersion> {
273 self.version
274 }
275
276 #[must_use]
277 pub const fn skip_reason(&self) -> Option<WalkSkipReason> {
278 self.skip_reason
279 }
280
281 pub(crate) fn rebase(&mut self, root: &Path, depth_offset: usize) {
282 self.root_components = root.components().count();
283 self.depth += depth_offset;
284 }
285
286 pub(crate) fn clear_depth_skip(&mut self) {
287 if self.skip_reason == Some(WalkSkipReason::MaxDepth) {
288 self.skip_reason = None;
289 }
290 }
291
292 pub(crate) const fn directory_identity(&self) -> Option<DirectoryIdentity> {
293 self.directory_identity
294 }
295}
296
297const fn operation_name(operation: WalkOperation) -> &'static str {
298 match operation {
299 WalkOperation::Canonicalize => "canonicalize",
300 WalkOperation::ReadDirectory => "read directory",
301 WalkOperation::ReadEntry => "read entry",
302 WalkOperation::ReadMetadata => "read metadata",
303 WalkOperation::ScheduleWorker => "schedule worker",
304 }
305}