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)]
23pub struct ListDirOptions {
24 flags: VisitFlags,
25 entries: Option<NonZeroUsize>,
26 bytes: Option<NonZeroUsize>,
27 depth: Option<DepthLimit>,
28 fields: Option<Attributes>,
29}
30
31impl ListDirOptions {
32 pub const fn unlimited() -> Self {
35 let mut flags = VisitFlags::new();
36 flags.set_entries_unlimited(true);
37 flags.set_bytes_unlimited(true);
38 Self {
39 flags,
40 entries: None,
41 bytes: None,
42 depth: Some(DepthLimit::unlimited()),
43 fields: None,
44 }
45 }
46 pub const fn new() -> Self {
47 Self {
48 flags: VisitFlags::new(),
49 entries: None,
50 bytes: None,
51 depth: None,
52 fields: None,
53 }
54 }
55 pub const fn recursive(mut self, value: bool) -> Self {
57 self.flags.set_recursive(value);
58 self
59 }
60 pub const fn enter_leave(mut self, value: bool) -> Self {
65 self.flags.set_enter_leave(value);
66 self
67 }
68 pub const fn sort_by_name(mut self, value: bool) -> Self {
72 self.flags.set_sort_by_name(value);
73 self
74 }
75 pub const fn follow_symlinks(mut self, value: bool) -> Self {
81 self.flags.set_no_follow(!value);
82 self
83 }
84 pub const fn max_entries(mut self, value: usize) -> Self {
86 self.entries = match value.checked_add(1) {
87 Some(encoded) => NonZeroUsize::new(encoded),
88 None => None,
89 };
90 self.flags.set_entries_unlimited(value == usize::MAX);
91 self
92 }
93 pub const fn max_path_bytes(mut self, value: usize) -> Self {
95 self.bytes = match value.checked_add(1) {
96 Some(encoded) => NonZeroUsize::new(encoded),
97 None => None,
98 };
99 self.flags.set_bytes_unlimited(value == usize::MAX);
100 self
101 }
102 pub const fn max_depth(mut self, value: usize) -> Self {
109 self.depth = Some(DepthLimit::new(value));
110 self
111 }
112 pub const fn truncate_at_max_depth(mut self, value: bool) -> Self {
114 self.flags.set_truncate(value);
115 self
116 }
117 pub const fn fields(mut self, value: Attributes) -> Self {
121 self.fields = Some(value);
122 self
123 }
124 pub const fn is_recursive(self) -> bool {
125 self.flags.recursive()
126 }
127 pub const fn emits_enter_leave(self) -> bool {
128 self.flags.enter_leave()
129 }
130 pub const fn sorts_by_name(self) -> bool {
131 self.flags.sort_by_name()
132 }
133 pub const fn follows_symlinks(self) -> bool {
134 !self.flags.no_follow()
135 }
136 pub fn attributes(self) -> Attributes {
137 self.fields.unwrap_or_else(|| {
138 Attributes::stat()
139 | Attributes::UID
140 | Attributes::GID
141 | Attributes::ATIME
142 | Attributes::MTIME
143 | Attributes::CTIME
144 | Attributes::CHANGE
145 }) | Attributes::MODE
146 }
147 pub fn entry_limit(self) -> usize {
149 if self.flags.entries_unlimited() {
150 usize::MAX
151 } else {
152 self.entries
153 .map_or(super::DEFAULT_DIRECTORY_MAX_ENTRIES, |v| v.get() - 1)
154 }
155 }
156 pub fn path_byte_limit(self) -> usize {
158 if self.flags.bytes_unlimited() {
159 usize::MAX
160 } else {
161 self.bytes
162 .map_or(super::DEFAULT_DIRECTORY_MAX_PATH_BYTES, |v| v.get() - 1)
163 }
164 }
165 pub fn depth_limit(self) -> usize {
166 self.depth
167 .map_or(super::DEFAULT_WALK_MAX_DEPTH, DepthLimit::get)
168 }
169 pub const fn truncates_at_depth_limit(self) -> bool {
170 self.flags.truncate()
171 }
172 pub fn resolve(mut self, limits: ResourceLimits) -> Self {
174 let entries = if self.entries.is_some() || self.flags.entries_unlimited() {
175 self.entry_limit()
176 } else {
177 limits.directory_entry_limit()
178 };
179 let bytes = if self.bytes.is_some() || self.flags.bytes_unlimited() {
180 self.path_byte_limit()
181 } else {
182 limits.directory_path_byte_limit()
183 };
184 self.depth = Some(
185 self.depth
186 .unwrap_or_else(|| DepthLimit::new(limits.walk_depth_limit())),
187 );
188 self.max_entries(entries).max_path_bytes(bytes)
189 }
190 pub fn walk_options(self, limits: ResourceLimits) -> Self {
192 self.recursive(true).resolve(limits)
193 }
194 pub fn directory_options(self, limits: ResourceLimits) -> Self {
196 self.recursive(false).resolve(limits)
197 }
198}
199
200#[cfg(test)]
201mod option_layout_tests {
202 use super::*;
203 #[allow(dead_code)]
204 struct PreviousLayout {
205 recursive: bool,
206 entries: Option<usize>,
207 bytes: Option<usize>,
208 depth: Option<usize>,
209 truncate: bool,
210 fields: Option<Attributes>,
211 }
212 #[test]
213 fn compact_traversal_options_preserve_zero_overrides_and_independent_bits() {
214 assert_eq!(std::mem::size_of::<VisitFlags>(), 1);
215 assert!(std::mem::size_of::<ListDirOptions>() < std::mem::size_of::<PreviousLayout>());
216 let limits = ResourceLimits::new()
217 .max_directory_entries(usize::MAX)
218 .max_directory_path_bytes(13)
219 .max_walk_depth(3);
220 let inherited = ListDirOptions::new().walk_options(limits);
221 assert!(!ListDirOptions::new().emits_enter_leave());
222 assert!(!ListDirOptions::new().sorts_by_name());
223 assert!(ListDirOptions::new().follows_symlinks());
224 assert_eq!(inherited.entry_limit(), limits.walk_options().entry_limit());
225 assert_eq!(inherited.depth_limit(), limits.walk_options().depth_limit());
226 assert_eq!(inherited.path_byte_limit(), 13);
227 for value in [0, 1, 200, usize::MAX] {
228 let options = ListDirOptions::new()
229 .max_entries(value)
230 .max_path_bytes(value)
231 .max_depth(value)
232 .recursive(true)
233 .enter_leave(true)
234 .sort_by_name(true)
235 .follow_symlinks(false)
236 .truncate_at_max_depth(true)
237 .fields(Attributes::SIZE);
238 let walk = options.walk_options(limits);
239 assert_eq!(walk.entry_limit(), value);
240 assert_eq!(walk.attributes(), options.attributes());
241 assert!(!walk.follows_symlinks());
242 assert_eq!(walk.path_byte_limit(), value);
243 assert_eq!(walk.depth_limit(), value);
244 assert!(walk.truncates_at_depth_limit());
245 assert!(options.is_recursive());
246 assert!(options.emits_enter_leave());
247 assert!(options.sorts_by_name());
248 assert!(!options.follows_symlinks());
249 assert_eq!(options.attributes(), Attributes::SIZE | Attributes::MODE);
250 let changed = options
251 .recursive(false)
252 .truncate_at_max_depth(false)
253 .enter_leave(false)
254 .sort_by_name(false);
255 let changed = changed.follow_symlinks(true);
256 assert!(!changed.is_recursive());
257 assert!(!changed.emits_enter_leave());
258 assert!(!changed.sorts_by_name());
259 assert!(changed.follows_symlinks());
260 assert!(!changed.walk_options(limits).truncates_at_depth_limit());
261 assert_eq!(changed.directory_options(limits).entry_limit(), value);
262 let zero = changed.max_entries(0).max_path_bytes(0).max_depth(0);
263 assert_eq!(zero.walk_options(limits).entry_limit(), 0);
264 assert_eq!(zero.walk_options(limits).path_byte_limit(), 0);
265 assert_eq!(zero.walk_options(limits).depth_limit(), 0);
266 }
267 assert_eq!(
268 ListDirOptions::unlimited()
269 .walk_options(limits)
270 .depth_limit(),
271 usize::MAX
272 );
273 eprintln!(
274 "ListDirOptions: {} -> {} bytes",
275 std::mem::size_of::<PreviousLayout>(),
276 std::mem::size_of::<ListDirOptions>()
277 );
278 }
279}