Skip to main content

weavatrix_scan/walk_types/
mod.rs

1use crate::report::FileVersion;
2use std::ffi::OsStr;
3use std::fmt;
4use std::io;
5use std::path::{Path, PathBuf};
6
7mod entry;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub(crate) struct FileSystemId(pub(crate) u64);
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub(crate) struct DirectoryIdentity {
14    pub(crate) file_system: FileSystemId,
15    pub(crate) file: u64,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ErrorPolicy {
20    Continue,
21    Abort,
22}
23
24/// Controls whether the explicitly supplied root itself may be a symlink.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum RootSymlinkPolicy {
27    /// Resolve or traverse the requested root for backward compatibility.
28    Follow,
29    /// Reject a symlink at the final root path component.
30    Reject,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct WalkOptions {
35    pub min_depth: usize,
36    pub max_depth: Option<usize>,
37    pub max_open: usize,
38    pub same_file_system: bool,
39    pub follow_links: bool,
40    pub collect_metadata: bool,
41    pub error_policy: ErrorPolicy,
42    pub root_symlink_policy: RootSymlinkPolicy,
43}
44
45impl Default for WalkOptions {
46    fn default() -> Self {
47        Self {
48            min_depth: 0,
49            max_depth: None,
50            max_open: Self::DEFAULT_MAX_OPEN,
51            same_file_system: false,
52            follow_links: false,
53            collect_metadata: false,
54            error_policy: ErrorPolicy::Continue,
55            root_symlink_policy: RootSymlinkPolicy::Follow,
56        }
57    }
58}
59
60impl WalkOptions {
61    /// Default hard upper bound for simultaneously open directory handles.
62    pub const DEFAULT_MAX_OPEN: usize = 64;
63
64    #[must_use]
65    pub const fn with_min_depth(mut self, min_depth: usize) -> Self {
66        self.min_depth = min_depth;
67        self
68    }
69
70    #[must_use]
71    pub const fn with_max_depth(mut self, max_depth: Option<usize>) -> Self {
72        self.max_depth = max_depth;
73        self
74    }
75
76    #[must_use]
77    pub const fn with_max_open(mut self, max_open: usize) -> Self {
78        self.max_open = if max_open == 0 { 1 } else { max_open };
79        self
80    }
81
82    #[must_use]
83    pub const fn with_same_file_system(mut self, enabled: bool) -> Self {
84        self.same_file_system = enabled;
85        self
86    }
87
88    #[must_use]
89    pub const fn with_follow_links(mut self, enabled: bool) -> Self {
90        self.follow_links = enabled;
91        self
92    }
93
94    #[must_use]
95    pub const fn with_metadata(mut self, enabled: bool) -> Self {
96        self.collect_metadata = enabled;
97        self
98    }
99
100    #[must_use]
101    pub const fn with_error_policy(mut self, policy: ErrorPolicy) -> Self {
102        self.error_policy = policy;
103        self
104    }
105
106    #[must_use]
107    pub const fn with_root_symlink_policy(mut self, policy: RootSymlinkPolicy) -> Self {
108        self.root_symlink_policy = policy;
109        self
110    }
111
112    pub(crate) fn normalized(mut self) -> Self {
113        self.max_open = self.max_open.max(1);
114        if let Some(max_depth) = self.max_depth
115            && self.min_depth > max_depth
116        {
117            self.min_depth = max_depth;
118        }
119        self
120    }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum WalkOperation {
125    Canonicalize,
126    ReadDirectory,
127    ReadEntry,
128    ReadMetadata,
129    ScheduleWorker,
130}
131
132#[derive(Debug)]
133pub struct WalkError {
134    pub(crate) path: PathBuf,
135    pub(crate) depth: usize,
136    pub(crate) operation: WalkOperation,
137    pub(crate) source: io::Error,
138}
139
140impl WalkError {
141    pub(crate) fn new(
142        path: impl Into<PathBuf>,
143        depth: usize,
144        operation: WalkOperation,
145        source: io::Error,
146    ) -> Self {
147        Self {
148            path: path.into(),
149            depth,
150            operation,
151            source,
152        }
153    }
154
155    #[must_use]
156    pub fn path(&self) -> &Path {
157        &self.path
158    }
159
160    #[must_use]
161    pub const fn depth(&self) -> usize {
162        self.depth
163    }
164
165    #[must_use]
166    pub const fn operation(&self) -> WalkOperation {
167        self.operation
168    }
169
170    #[must_use]
171    pub const fn io_error(&self) -> &io::Error {
172        &self.source
173    }
174
175    pub(crate) fn into_parts(self) -> (PathBuf, io::Error) {
176        (self.path, self.source)
177    }
178
179    pub(crate) fn rebase_depth(&mut self, depth_offset: usize) {
180        self.depth += depth_offset;
181    }
182}
183
184impl fmt::Display for WalkError {
185    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(
187            formatter,
188            "{} at depth {} for {}: {}",
189            operation_name(self.operation),
190            self.depth,
191            self.path.display(),
192            self.source
193        )
194    }
195}
196
197impl std::error::Error for WalkError {
198    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
199        Some(&self.source)
200    }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum WalkSkipReason {
205    MaxDepth,
206    FileSystemBoundary,
207    PathEscape,
208    SymlinkLoop,
209}
210
211#[derive(Debug, Clone)]
212pub struct WalkEntry {
213    pub(crate) root_components: usize,
214    pub(crate) path: PathBuf,
215    pub(crate) depth: usize,
216    pub(crate) is_file: bool,
217    pub(crate) is_directory: bool,
218    pub(crate) is_symlink: bool,
219    pub(crate) bytes: Option<u64>,
220    pub(crate) version: Option<FileVersion>,
221    pub(crate) hidden: Option<bool>,
222    pub(crate) directory_identity: Option<DirectoryIdentity>,
223    pub(crate) skip_reason: Option<WalkSkipReason>,
224}
225
226const fn operation_name(operation: WalkOperation) -> &'static str {
227    match operation {
228        WalkOperation::Canonicalize => "canonicalize",
229        WalkOperation::ReadDirectory => "read directory",
230        WalkOperation::ReadEntry => "read entry",
231        WalkOperation::ReadMetadata => "read metadata",
232        WalkOperation::ScheduleWorker => "schedule worker",
233    }
234}