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 enter_leave: bool,
12 sort_by_name: bool,
13 no_follow: bool,
14 #[bits(1)]
15 _reserved: u8,
16}
17
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
30#[must_use = "configuration and requests have no effect until used"]
31pub struct ListDirOptions {
32 flags: VisitFlags,
33 entries: Option<NonZeroUsize>,
34 bytes: Option<NonZeroUsize>,
35 depth: Option<DepthLimit>,
36 fields: Option<Attributes>,
37}
38
39impl ListDirOptions {
40 pub const fn unlimited() -> Self {
43 let mut flags = VisitFlags::new();
44 flags.set_entries_unlimited(true);
45 flags.set_bytes_unlimited(true);
46 Self {
47 flags,
48 entries: None,
49 bytes: None,
50 depth: Some(DepthLimit::unlimited()),
51 fields: None,
52 }
53 }
54 pub const fn new() -> Self {
55 Self {
56 flags: VisitFlags::new(),
57 entries: None,
58 bytes: None,
59 depth: None,
60 fields: None,
61 }
62 }
63 pub const fn with_recursive(mut self, value: bool) -> Self {
65 self.flags.set_recursive(value);
66 self
67 }
68 pub const fn with_enter_leave(mut self, value: bool) -> Self {
73 self.flags.set_enter_leave(value);
74 self
75 }
76 pub const fn with_sort_by_name(mut self, value: bool) -> Self {
80 self.flags.set_sort_by_name(value);
81 self
82 }
83 pub const fn with_allow_symlink_components(mut self, value: bool) -> Self {
89 self.flags.set_no_follow(!value);
90 self
91 }
92 pub const fn with_max_entries(mut self, value: usize) -> Self {
94 self.entries = match value.checked_add(1) {
95 Some(encoded) => NonZeroUsize::new(encoded),
96 None => None,
97 };
98 self.flags.set_entries_unlimited(value == usize::MAX);
99 self
100 }
101 pub const fn with_max_path_bytes(mut self, value: usize) -> Self {
103 self.bytes = match value.checked_add(1) {
104 Some(encoded) => NonZeroUsize::new(encoded),
105 None => None,
106 };
107 self.flags.set_bytes_unlimited(value == usize::MAX);
108 self
109 }
110 pub const fn with_max_depth(mut self, value: DepthLimit) -> Self {
116 self.depth = Some(value);
117 self
118 }
119 pub const fn with_truncate_at_max_depth(mut self, value: bool) -> Self {
121 self.flags.set_truncate(value);
122 self
123 }
124 pub const fn with_fields(mut self, value: Attributes) -> Self {
128 self.fields = Some(value);
129 self
130 }
131 pub const fn recursive(self) -> bool {
132 self.flags.recursive()
133 }
134 pub const fn enter_leave(self) -> bool {
135 self.flags.enter_leave()
136 }
137 pub const fn sort_by_name(self) -> bool {
138 self.flags.sort_by_name()
139 }
140 pub const fn allow_symlink_components(self) -> bool {
141 !self.flags.no_follow()
142 }
143 pub fn fields(self) -> Attributes {
144 self.fields.unwrap_or_else(Attributes::stat) | Attributes::MODE
145 }
146 pub fn max_entries(self) -> usize {
148 if self.flags.entries_unlimited() {
149 usize::MAX
150 } else {
151 self.entries
152 .map_or(super::DEFAULT_DIRECTORY_MAX_ENTRIES, |v| v.get() - 1)
153 }
154 }
155 pub fn max_path_bytes(self) -> usize {
157 if self.flags.bytes_unlimited() {
158 usize::MAX
159 } else {
160 self.bytes
161 .map_or(super::DEFAULT_DIRECTORY_MAX_PATH_BYTES, |v| v.get() - 1)
162 }
163 }
164 pub fn max_depth(self) -> DepthLimit {
165 self.depth
166 .unwrap_or_else(|| DepthLimit::new(super::DEFAULT_WALK_MAX_DEPTH).unwrap())
167 }
168 pub const fn truncate_at_max_depth(self) -> bool {
169 self.flags.truncate()
170 }
171 pub fn resolve(mut self, limits: ResourceLimits) -> Self {
173 let entries = if self.entries.is_some() || self.flags.entries_unlimited() {
174 self.max_entries()
175 } else {
176 limits.max_directory_entries()
177 };
178 let bytes = if self.bytes.is_some() || self.flags.bytes_unlimited() {
179 self.max_path_bytes()
180 } else {
181 limits.max_directory_path_bytes()
182 };
183 self.depth = Some(self.depth.unwrap_or_else(|| limits.max_walk_depth()));
184 self.with_max_entries(entries).with_max_path_bytes(bytes)
185 }
186 pub fn walk_options(self, limits: ResourceLimits) -> Self {
188 self.with_recursive(true).resolve(limits)
189 }
190 pub fn directory_options(self, limits: ResourceLimits) -> Self {
192 self.with_recursive(false).resolve(limits)
193 }
194}
195
196#[cfg(test)]
197mod option_layout_tests {
198 use super::*;
199 #[allow(dead_code)]
200 struct PreviousLayout {
201 recursive: bool,
202 entries: Option<usize>,
203 bytes: Option<usize>,
204 depth: Option<usize>,
205 truncate: bool,
206 fields: Option<Attributes>,
207 }
208 #[test]
209 fn compact_traversal_options_preserve_zero_overrides_and_independent_bits() {
210 assert_eq!(std::mem::size_of::<VisitFlags>(), 1);
211 assert!(std::mem::size_of::<ListDirOptions>() < std::mem::size_of::<PreviousLayout>());
212 let limits = ResourceLimits::new()
213 .with_max_directory_entries(usize::MAX)
214 .with_max_directory_path_bytes(13)
215 .with_max_walk_depth(crate::api::DepthLimit::new(3).expect("finite depth"));
216 let inherited = ListDirOptions::new().walk_options(limits);
217 assert!(!ListDirOptions::new().enter_leave());
218 assert!(!ListDirOptions::new().sort_by_name());
219 assert!(ListDirOptions::new().allow_symlink_components());
220 assert_eq!(inherited.max_entries(), limits.walk_options().max_entries());
221 assert_eq!(
222 inherited.max_depth().get(),
223 limits.walk_options().max_depth().get()
224 );
225 assert_eq!(inherited.max_path_bytes(), 13);
226 for value in [0, 1, 200, usize::MAX] {
227 let options = ListDirOptions::new()
228 .with_max_entries(value)
229 .with_max_path_bytes(value)
230 .with_max_depth(if value == usize::MAX {
231 crate::api::DepthLimit::unlimited()
232 } else {
233 crate::api::DepthLimit::new(value).expect("finite depth")
234 })
235 .with_recursive(true)
236 .with_enter_leave(true)
237 .with_sort_by_name(true)
238 .with_allow_symlink_components(false)
239 .with_truncate_at_max_depth(true)
240 .with_fields(Attributes::SIZE);
241 let walk = options.walk_options(limits);
242 assert_eq!(walk.max_entries(), value);
243 assert_eq!(walk.fields(), options.fields());
244 assert!(!walk.allow_symlink_components());
245 assert_eq!(walk.max_path_bytes(), value);
246 assert_eq!(walk.max_depth().get(), value);
247 assert!(walk.truncate_at_max_depth());
248 assert!(options.recursive());
249 assert!(options.enter_leave());
250 assert!(options.sort_by_name());
251 assert!(!options.allow_symlink_components());
252 assert_eq!(options.fields(), Attributes::SIZE | Attributes::MODE);
253 let changed = options
254 .with_recursive(false)
255 .with_truncate_at_max_depth(false)
256 .with_enter_leave(false)
257 .with_sort_by_name(false);
258 let changed = changed.with_allow_symlink_components(true);
259 assert!(!changed.recursive());
260 assert!(!changed.enter_leave());
261 assert!(!changed.sort_by_name());
262 assert!(changed.allow_symlink_components());
263 assert!(!changed.walk_options(limits).truncate_at_max_depth());
264 assert_eq!(changed.directory_options(limits).max_entries(), value);
265 let zero = changed
266 .with_max_entries(0)
267 .with_max_path_bytes(0)
268 .with_max_depth(crate::api::DepthLimit::new(0).expect("finite depth"));
269 assert_eq!(zero.walk_options(limits).max_entries(), 0);
270 assert_eq!(zero.walk_options(limits).max_path_bytes(), 0);
271 assert_eq!(zero.walk_options(limits).max_depth().get(), 0);
272 }
273 assert_eq!(
274 ListDirOptions::unlimited()
275 .walk_options(limits)
276 .max_depth()
277 .get(),
278 usize::MAX
279 );
280 eprintln!(
281 "ListDirOptions: {} -> {} bytes",
282 std::mem::size_of::<PreviousLayout>(),
283 std::mem::size_of::<ListDirOptions>()
284 );
285 }
286}