use crate::api::{Attributes, DepthLimit, ReadDirOptions, ResourceLimits, WalkOptions};
use std::num::NonZeroUsize;
#[bitfields::bitfield(u8)]
#[derive(PartialEq, Eq)]
struct VisitFlags {
recursive: bool,
truncate: bool,
entries_unlimited: bool,
bytes_unlimited: bool,
#[bits(4)]
_reserved: u8,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ListDirOptions {
flags: VisitFlags,
entries: Option<NonZeroUsize>,
bytes: Option<NonZeroUsize>,
depth: Option<DepthLimit>,
fields: Option<Attributes>,
}
impl ListDirOptions {
pub const fn unlimited() -> Self {
let mut flags = VisitFlags::new();
flags.set_entries_unlimited(true);
flags.set_bytes_unlimited(true);
Self {
flags,
entries: None,
bytes: None,
depth: Some(DepthLimit::unlimited()),
fields: None,
}
}
pub const fn new() -> Self {
Self {
flags: VisitFlags::new(),
entries: None,
bytes: None,
depth: None,
fields: None,
}
}
pub const fn recursive(mut self, value: bool) -> Self {
self.flags.set_recursive(value);
self
}
pub const fn max_entries(mut self, value: usize) -> Self {
self.entries = match value.checked_add(1) {
Some(encoded) => NonZeroUsize::new(encoded),
None => None,
};
self.flags.set_entries_unlimited(value == usize::MAX);
self
}
pub const fn max_path_bytes(mut self, value: usize) -> Self {
self.bytes = match value.checked_add(1) {
Some(encoded) => NonZeroUsize::new(encoded),
None => None,
};
self.flags.set_bytes_unlimited(value == usize::MAX);
self
}
pub const fn max_depth(mut self, value: usize) -> Self {
self.depth = Some(DepthLimit::new(value));
self
}
pub const fn truncate_at_max_depth(mut self, value: bool) -> Self {
self.flags.set_truncate(value);
self
}
pub const fn fields(mut self, value: Attributes) -> Self {
self.fields = Some(value);
self
}
pub const fn is_recursive(self) -> bool {
self.flags.recursive()
}
pub fn attributes(self) -> Attributes {
self.fields.unwrap_or_else(|| {
Attributes::stat()
| Attributes::UID
| Attributes::GID
| Attributes::ATIME
| Attributes::MTIME
| Attributes::CTIME
| Attributes::CHANGE
}) | Attributes::MODE
}
pub fn walk_options(self, limits: ResourceLimits) -> WalkOptions {
let mut options = limits.walk_options();
if self.flags.entries_unlimited() {
options = options.max_entries(usize::MAX);
} else if let Some(encoded) = self.entries {
options = options.max_entries(encoded.get() - 1);
}
if self.flags.bytes_unlimited() {
options = options.max_path_bytes(usize::MAX);
} else if let Some(encoded) = self.bytes {
options = options.max_path_bytes(encoded.get() - 1);
}
if let Some(depth) = self.depth {
options = options.max_depth(depth.get());
}
options.truncate_at_max_depth(self.flags.truncate())
}
pub fn directory_options(self, limits: ResourceLimits) -> ReadDirOptions {
let mut options = limits.directory_options();
if self.flags.entries_unlimited() {
options = options.max_entries(usize::MAX);
} else if let Some(encoded) = self.entries {
options = options.max_entries(encoded.get() - 1);
}
if self.flags.bytes_unlimited() {
options = options.max_path_bytes(usize::MAX);
} else if let Some(encoded) = self.bytes {
options = options.max_path_bytes(encoded.get() - 1);
}
options
}
}
impl From<ReadDirOptions> for ListDirOptions {
fn from(options: ReadDirOptions) -> Self {
Self::new()
.max_entries(options.entry_limit())
.max_path_bytes(options.path_byte_limit())
}
}
impl From<WalkOptions> for ListDirOptions {
fn from(options: WalkOptions) -> Self {
Self::new()
.recursive(true)
.max_entries(options.entry_limit())
.max_path_bytes(options.path_byte_limit())
.max_depth(options.depth_limit())
.truncate_at_max_depth(options.truncates_at_depth_limit())
}
}
#[cfg(test)]
mod option_layout_tests {
use super::*;
#[allow(dead_code)]
struct PreviousLayout {
recursive: bool,
entries: Option<usize>,
bytes: Option<usize>,
depth: Option<usize>,
truncate: bool,
fields: Option<Attributes>,
}
#[test]
fn compact_traversal_options_preserve_zero_overrides_and_independent_bits() {
assert_eq!(std::mem::size_of::<VisitFlags>(), 1);
assert!(std::mem::size_of::<ListDirOptions>() < std::mem::size_of::<PreviousLayout>());
let limits = ResourceLimits::default();
let inherited = ListDirOptions::new().walk_options(limits);
assert_eq!(inherited.entry_limit(), limits.walk_options().entry_limit());
assert_eq!(inherited.depth_limit(), limits.walk_options().depth_limit());
for value in [0, 1, 200, usize::MAX] {
let options = ListDirOptions::new()
.max_entries(value)
.max_path_bytes(value)
.max_depth(value)
.recursive(true)
.truncate_at_max_depth(true)
.fields(Attributes::SIZE);
let walk = options.walk_options(limits);
assert_eq!(walk.entry_limit(), value);
assert_eq!(walk.path_byte_limit(), value);
assert_eq!(walk.depth_limit(), value);
assert!(walk.truncates_at_depth_limit());
assert!(options.is_recursive());
assert_eq!(options.attributes(), Attributes::SIZE | Attributes::MODE);
let changed = options.recursive(false).truncate_at_max_depth(false);
assert!(!changed.is_recursive());
assert!(!changed.walk_options(limits).truncates_at_depth_limit());
assert_eq!(changed.directory_options(limits).entry_limit(), value);
let zero = changed.max_entries(0).max_path_bytes(0).max_depth(0);
assert_eq!(zero.walk_options(limits).entry_limit(), 0);
assert_eq!(zero.walk_options(limits).path_byte_limit(), 0);
assert_eq!(zero.walk_options(limits).depth_limit(), 0);
}
assert_eq!(
ListDirOptions::unlimited()
.walk_options(limits)
.depth_limit(),
usize::MAX
);
eprintln!(
"ListDirOptions: {} -> {} bytes",
std::mem::size_of::<PreviousLayout>(),
std::mem::size_of::<ListDirOptions>()
);
}
}