1use crate::api::{DepthLimit, MetadataFields, 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 VisitOptions {
21 flags: VisitFlags,
22 entries: Option<NonZeroUsize>,
23 bytes: Option<NonZeroUsize>,
24 depth: Option<DepthLimit>,
25 fields: Option<MetadataFields>,
26}
27
28impl VisitOptions {
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: MetadataFields) -> 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 metadata_fields(self) -> MetadataFields {
101 self.fields.unwrap_or_else(|| {
102 MetadataFields::stat()
103 | MetadataFields::UID
104 | MetadataFields::GID
105 | MetadataFields::ATIME
106 | MetadataFields::MTIME
107 | MetadataFields::CTIME
108 | MetadataFields::CHANGE
109 }) | MetadataFields::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 VisitOptions {
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 VisitOptions {
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<MetadataFields>,
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::<VisitOptions>() < std::mem::size_of::<PreviousLayout>());
180 let limits = ResourceLimits::default();
181 let inherited = VisitOptions::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 = VisitOptions::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(MetadataFields::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!(
199 options.metadata_fields(),
200 MetadataFields::SIZE | MetadataFields::MODE
201 );
202 let changed = options.recursive(false).truncate_at_max_depth(false);
203 assert!(!changed.is_recursive());
204 assert!(!changed.walk_options(limits).truncates_at_depth_limit());
205 assert_eq!(changed.directory_options(limits).entry_limit(), value);
206 let zero = changed.max_entries(0).max_path_bytes(0).max_depth(0);
207 assert_eq!(zero.walk_options(limits).entry_limit(), 0);
208 assert_eq!(zero.walk_options(limits).path_byte_limit(), 0);
209 assert_eq!(zero.walk_options(limits).depth_limit(), 0);
210 }
211 assert_eq!(
212 VisitOptions::unlimited().walk_options(limits).depth_limit(),
213 usize::MAX
214 );
215 eprintln!(
216 "VisitOptions: {} -> {} bytes",
217 std::mem::size_of::<PreviousLayout>(),
218 std::mem::size_of::<VisitOptions>()
219 );
220 }
221}