1use crate::api::{Attributes, DepthLimit, ResourceLimits};
2use std::num::NonZeroUsize;
3
4#[bitfields::bitfield(u8)]
5#[derive(PartialEq, Eq)]
6struct VisitFlags {
7 recursive: bool,
8 truncate: bool,
9 entries_unlimited: bool,
10 bytes_unlimited: bool,
11 no_follow: bool,
12 #[bits(3)]
13 _reserved: u8,
14}
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28#[must_use = "configuration and requests have no effect until used"]
29pub struct ListDirOptions {
30 flags: VisitFlags,
31 entries: Option<NonZeroUsize>,
32 bytes: Option<NonZeroUsize>,
33 depth: Option<DepthLimit>,
34 fields: Option<Attributes>,
35}
36
37impl ListDirOptions {
38 pub const fn unlimited() -> Self {
41 let mut flags = VisitFlags::new();
42 flags.set_entries_unlimited(true);
43 flags.set_bytes_unlimited(true);
44 Self {
45 flags,
46 entries: None,
47 bytes: None,
48 depth: Some(DepthLimit::unlimited()),
49 fields: None,
50 }
51 }
52 pub const fn new() -> Self {
53 Self {
54 flags: VisitFlags::new(),
55 entries: None,
56 bytes: None,
57 depth: None,
58 fields: None,
59 }
60 }
61 pub const fn with_recursive(mut self, value: bool) -> Self {
63 self.flags.set_recursive(value);
64 self
65 }
66 pub const fn with_allow_symlink_components(mut self, value: bool) -> Self {
72 self.flags.set_no_follow(!value);
73 self
74 }
75 pub const fn with_max_entries(mut self, value: usize) -> Self {
77 self.entries = match value.checked_add(1) {
78 Some(encoded) => NonZeroUsize::new(encoded),
79 None => None,
80 };
81 self.flags.set_entries_unlimited(value == usize::MAX);
82 self
83 }
84 pub const fn with_max_path_bytes(mut self, value: usize) -> Self {
86 self.bytes = match value.checked_add(1) {
87 Some(encoded) => NonZeroUsize::new(encoded),
88 None => None,
89 };
90 self.flags.set_bytes_unlimited(value == usize::MAX);
91 self
92 }
93 pub const fn with_max_depth(mut self, value: DepthLimit) -> Self {
99 self.depth = Some(value);
100 self
101 }
102 pub const fn with_truncate_at_max_depth(mut self, value: bool) -> Self {
104 self.flags.set_truncate(value);
105 self
106 }
107 pub const fn with_fields(mut self, value: Attributes) -> Self {
111 self.fields = Some(value);
112 self
113 }
114 pub const fn recursive(self) -> bool {
115 self.flags.recursive()
116 }
117 pub const fn allow_symlink_components(self) -> bool {
118 !self.flags.no_follow()
119 }
120 pub fn fields(self) -> Attributes {
121 self.fields.unwrap_or_else(Attributes::stat) | Attributes::MODE
122 }
123 pub fn max_entries(self) -> Option<usize> {
125 if self.flags.entries_unlimited() {
126 Some(usize::MAX)
127 } else {
128 self.entries.map(|v| v.get() - 1)
129 }
130 }
131 pub fn max_path_bytes(self) -> Option<usize> {
133 if self.flags.bytes_unlimited() {
134 Some(usize::MAX)
135 } else {
136 self.bytes.map(|v| v.get() - 1)
137 }
138 }
139 pub const fn max_depth(self) -> Option<DepthLimit> {
141 self.depth
142 }
143 pub fn max_entries_or(self, default: usize) -> usize {
144 self.max_entries().unwrap_or(default)
145 }
146 pub fn max_path_bytes_or(self, default: usize) -> usize {
147 self.max_path_bytes().unwrap_or(default)
148 }
149 pub fn max_depth_or(self, default: DepthLimit) -> DepthLimit {
150 self.max_depth().unwrap_or(default)
151 }
152 pub const fn truncate_at_max_depth(self) -> bool {
153 self.flags.truncate()
154 }
155 pub fn resolve(mut self, limits: ResourceLimits) -> Self {
157 let entries = self.max_entries_or(limits.max_directory_entries());
158 let bytes = self.max_path_bytes_or(limits.max_directory_path_bytes());
159 self.depth = Some(self.max_depth_or(limits.max_walk_depth()));
160 self.with_max_entries(entries).with_max_path_bytes(bytes)
161 }
162 pub fn walk_options(self, limits: ResourceLimits) -> Self {
164 self.with_recursive(true).resolve(limits)
165 }
166 pub fn directory_options(self, limits: ResourceLimits) -> Self {
168 self.with_recursive(false).resolve(limits)
169 }
170}
171
172#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
188#[must_use = "configuration and requests have no effect until used"]
189pub struct WalkOptions {
190 listing: ListDirOptions,
191 enter_leave: bool,
192 sort_by_name: bool,
193}
194impl WalkOptions {
195 pub const fn new(listing: ListDirOptions) -> Self {
196 Self {
197 listing,
198 enter_leave: false,
199 sort_by_name: false,
200 }
201 }
202 pub const fn with_listing_options(mut self, listing: ListDirOptions) -> Self {
204 self.listing = listing;
205 self
206 }
207 pub const fn listing_options(self) -> ListDirOptions {
208 self.listing
209 }
210 pub const fn with_enter_leave(mut self, value: bool) -> Self {
212 self.enter_leave = value;
213 self
214 }
215 pub const fn with_sort_by_name(mut self, value: bool) -> Self {
217 self.sort_by_name = value;
218 self
219 }
220 pub const fn enter_leave(self) -> bool {
221 self.enter_leave
222 }
223 pub const fn sort_by_name(self) -> bool {
224 self.sort_by_name
225 }
226}
227impl From<ListDirOptions> for WalkOptions {
228 fn from(listing: ListDirOptions) -> Self {
229 Self::new(listing)
230 }
231}
232
233#[cfg(test)]
234mod option_layout_tests {
235 use super::*;
236 #[allow(dead_code)]
237 struct PreviousLayout {
238 recursive: bool,
239 entries: Option<usize>,
240 bytes: Option<usize>,
241 depth: Option<usize>,
242 truncate: bool,
243 fields: Option<Attributes>,
244 }
245 #[test]
246 fn compact_traversal_options_preserve_zero_overrides_and_independent_bits() {
247 assert_eq!(std::mem::size_of::<VisitFlags>(), 1);
248 assert!(std::mem::size_of::<ListDirOptions>() < std::mem::size_of::<PreviousLayout>());
249 let limits = ResourceLimits::new()
250 .with_max_directory_entries(usize::MAX)
251 .with_max_directory_path_bytes(13)
252 .with_max_walk_depth(crate::api::DepthLimit::new(3).expect("finite depth"));
253 let inherited = ListDirOptions::new().walk_options(limits);
254 assert!(ListDirOptions::new().allow_symlink_components());
255 assert_eq!(
256 inherited.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
257 limits
258 .walk_options()
259 .max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES)
260 );
261 assert_eq!(
262 inherited.max_depth_or(Default::default()).get(),
263 limits.walk_options().max_depth_or(Default::default()).get()
264 );
265 assert_eq!(
266 inherited.max_path_bytes_or(crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES),
267 13
268 );
269 for value in [0, 1, 200, usize::MAX] {
270 let options = ListDirOptions::new()
271 .with_max_entries(value)
272 .with_max_path_bytes(value)
273 .with_max_depth(if value == usize::MAX {
274 crate::api::DepthLimit::unlimited()
275 } else {
276 crate::api::DepthLimit::new(value).expect("finite depth")
277 })
278 .with_recursive(true)
279 .with_allow_symlink_components(false)
280 .with_truncate_at_max_depth(true)
281 .with_fields(Attributes::SIZE);
282 let walk = options.walk_options(limits);
283 assert_eq!(
284 walk.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
285 value
286 );
287 assert_eq!(walk.fields(), options.fields());
288 assert!(!walk.allow_symlink_components());
289 assert_eq!(
290 walk.max_path_bytes_or(crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES),
291 value
292 );
293 assert_eq!(walk.max_depth_or(Default::default()).get(), value);
294 assert!(walk.truncate_at_max_depth());
295 assert!(options.recursive());
296 assert!(!options.allow_symlink_components());
297 assert_eq!(options.fields(), Attributes::SIZE | Attributes::MODE);
298 let changed = options
299 .with_recursive(false)
300 .with_truncate_at_max_depth(false);
301 let changed = changed.with_allow_symlink_components(true);
302 assert!(!changed.recursive());
303 assert!(changed.allow_symlink_components());
304 assert!(!changed.walk_options(limits).truncate_at_max_depth());
305 assert_eq!(
306 changed
307 .directory_options(limits)
308 .max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
309 value
310 );
311 let zero = changed
312 .with_max_entries(0)
313 .with_max_path_bytes(0)
314 .with_max_depth(crate::api::DepthLimit::new(0).expect("finite depth"));
315 assert_eq!(
316 zero.walk_options(limits)
317 .max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
318 0
319 );
320 assert_eq!(
321 zero.walk_options(limits)
322 .max_path_bytes_or(crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES),
323 0
324 );
325 assert_eq!(
326 zero.walk_options(limits)
327 .max_depth_or(Default::default())
328 .get(),
329 0
330 );
331 }
332 assert_eq!(
333 ListDirOptions::unlimited()
334 .walk_options(limits)
335 .max_depth_or(Default::default())
336 .get(),
337 usize::MAX
338 );
339 eprintln!(
340 "ListDirOptions: {} -> {} bytes",
341 std::mem::size_of::<PreviousLayout>(),
342 std::mem::size_of::<ListDirOptions>()
343 );
344 }
345}