1use crate::api::{Attributes, DepthLimit, ReadDirOptions, ResourceLimits, WalkOptions};
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 #[bits(4)]
12 _reserved: u8,
13}
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub struct ListDirOptions {
21 flags: VisitFlags,
22 entries: Option<NonZeroUsize>,
23 bytes: Option<NonZeroUsize>,
24 depth: Option<DepthLimit>,
25 fields: Option<Attributes>,
26}
27
28impl ListDirOptions {
29 pub const fn unlimited() -> Self {
32 let mut flags = VisitFlags::new();
33 flags.set_entries_unlimited(true);
34 flags.set_bytes_unlimited(true);
35 Self {
36 flags,
37 entries: None,
38 bytes: None,
39 depth: Some(DepthLimit::unlimited()),
40 fields: None,
41 }
42 }
43 pub const fn new() -> Self {
44 Self {
45 flags: VisitFlags::new(),
46 entries: None,
47 bytes: None,
48 depth: None,
49 fields: None,
50 }
51 }
52 pub const fn recursive(mut self, value: bool) -> Self {
54 self.flags.set_recursive(value);
55 self
56 }
57 pub const fn max_entries(mut self, value: usize) -> Self {
59 self.entries = match value.checked_add(1) {
60 Some(encoded) => NonZeroUsize::new(encoded),
61 None => None,
62 };
63 self.flags.set_entries_unlimited(value == usize::MAX);
64 self
65 }
66 pub const fn max_path_bytes(mut self, value: usize) -> Self {
68 self.bytes = match value.checked_add(1) {
69 Some(encoded) => NonZeroUsize::new(encoded),
70 None => None,
71 };
72 self.flags.set_bytes_unlimited(value == usize::MAX);
73 self
74 }
75 pub const fn max_depth(mut self, value: usize) -> Self {
82 self.depth = Some(DepthLimit::new(value));
83 self
84 }
85 pub const fn truncate_at_max_depth(mut self, value: bool) -> Self {
87 self.flags.set_truncate(value);
88 self
89 }
90 pub const fn fields(mut self, value: Attributes) -> Self {
94 self.fields = Some(value);
95 self
96 }
97 pub const fn is_recursive(self) -> bool {
98 self.flags.recursive()
99 }
100 pub fn attributes(self) -> Attributes {
101 self.fields.unwrap_or_else(|| {
102 Attributes::stat()
103 | Attributes::UID
104 | Attributes::GID
105 | Attributes::ATIME
106 | Attributes::MTIME
107 | Attributes::CTIME
108 | Attributes::CHANGE
109 }) | Attributes::MODE
110 }
111 pub fn walk_options(self, limits: ResourceLimits) -> WalkOptions {
113 let mut options = limits.walk_options();
114 if self.flags.entries_unlimited() {
115 options = options.max_entries(usize::MAX);
116 } else if let Some(encoded) = self.entries {
117 options = options.max_entries(encoded.get() - 1);
118 }
119 if self.flags.bytes_unlimited() {
120 options = options.max_path_bytes(usize::MAX);
121 } else if let Some(encoded) = self.bytes {
122 options = options.max_path_bytes(encoded.get() - 1);
123 }
124 if let Some(depth) = self.depth {
125 options = options.max_depth(depth.get());
126 }
127 options.truncate_at_max_depth(self.flags.truncate())
128 }
129 pub fn directory_options(self, limits: ResourceLimits) -> ReadDirOptions {
131 let mut options = limits.directory_options();
132 if self.flags.entries_unlimited() {
133 options = options.max_entries(usize::MAX);
134 } else if let Some(encoded) = self.entries {
135 options = options.max_entries(encoded.get() - 1);
136 }
137 if self.flags.bytes_unlimited() {
138 options = options.max_path_bytes(usize::MAX);
139 } else if let Some(encoded) = self.bytes {
140 options = options.max_path_bytes(encoded.get() - 1);
141 }
142 options
143 }
144}
145
146impl From<ReadDirOptions> for ListDirOptions {
147 fn from(options: ReadDirOptions) -> Self {
148 Self::new()
149 .max_entries(options.entry_limit())
150 .max_path_bytes(options.path_byte_limit())
151 }
152}
153impl From<WalkOptions> for ListDirOptions {
154 fn from(options: WalkOptions) -> Self {
155 Self::new()
156 .recursive(true)
157 .max_entries(options.entry_limit())
158 .max_path_bytes(options.path_byte_limit())
159 .max_depth(options.depth_limit())
160 .truncate_at_max_depth(options.truncates_at_depth_limit())
161 }
162}
163
164#[cfg(test)]
165mod option_layout_tests {
166 use super::*;
167 #[allow(dead_code)]
168 struct PreviousLayout {
169 recursive: bool,
170 entries: Option<usize>,
171 bytes: Option<usize>,
172 depth: Option<usize>,
173 truncate: bool,
174 fields: Option<Attributes>,
175 }
176 #[test]
177 fn compact_traversal_options_preserve_zero_overrides_and_independent_bits() {
178 assert_eq!(std::mem::size_of::<VisitFlags>(), 1);
179 assert!(std::mem::size_of::<ListDirOptions>() < std::mem::size_of::<PreviousLayout>());
180 let limits = ResourceLimits::default();
181 let inherited = ListDirOptions::new().walk_options(limits);
182 assert_eq!(inherited.entry_limit(), limits.walk_options().entry_limit());
183 assert_eq!(inherited.depth_limit(), limits.walk_options().depth_limit());
184 for value in [0, 1, 200, usize::MAX] {
185 let options = ListDirOptions::new()
186 .max_entries(value)
187 .max_path_bytes(value)
188 .max_depth(value)
189 .recursive(true)
190 .truncate_at_max_depth(true)
191 .fields(Attributes::SIZE);
192 let walk = options.walk_options(limits);
193 assert_eq!(walk.entry_limit(), value);
194 assert_eq!(walk.path_byte_limit(), value);
195 assert_eq!(walk.depth_limit(), value);
196 assert!(walk.truncates_at_depth_limit());
197 assert!(options.is_recursive());
198 assert_eq!(options.attributes(), Attributes::SIZE | Attributes::MODE);
199 let changed = options.recursive(false).truncate_at_max_depth(false);
200 assert!(!changed.is_recursive());
201 assert!(!changed.walk_options(limits).truncates_at_depth_limit());
202 assert_eq!(changed.directory_options(limits).entry_limit(), value);
203 let zero = changed.max_entries(0).max_path_bytes(0).max_depth(0);
204 assert_eq!(zero.walk_options(limits).entry_limit(), 0);
205 assert_eq!(zero.walk_options(limits).path_byte_limit(), 0);
206 assert_eq!(zero.walk_options(limits).depth_limit(), 0);
207 }
208 assert_eq!(
209 ListDirOptions::unlimited()
210 .walk_options(limits)
211 .depth_limit(),
212 usize::MAX
213 );
214 eprintln!(
215 "ListDirOptions: {} -> {} bytes",
216 std::mem::size_of::<PreviousLayout>(),
217 std::mem::size_of::<ListDirOptions>()
218 );
219 }
220}