use crate::api::{Attributes, DepthLimit, ResourceLimits};
use std::num::NonZeroUsize;
#[bitfields::bitfield(u8)]
#[derive(PartialEq, Eq)]
struct VisitFlags {
recursive: bool,
truncate: bool,
entries_unlimited: bool,
bytes_unlimited: bool,
no_follow: bool,
#[bits(3)]
_reserved: u8,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[must_use = "configuration and requests have no effect until used"]
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 with_recursive(mut self, value: bool) -> Self {
self.flags.set_recursive(value);
self
}
pub const fn with_allow_symlink_components(mut self, value: bool) -> Self {
self.flags.set_no_follow(!value);
self
}
pub const fn with_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 with_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 with_max_depth(mut self, value: DepthLimit) -> Self {
self.depth = Some(value);
self
}
pub const fn with_truncate_at_max_depth(mut self, value: bool) -> Self {
self.flags.set_truncate(value);
self
}
pub const fn with_fields(mut self, value: Attributes) -> Self {
self.fields = Some(value);
self
}
pub const fn recursive(self) -> bool {
self.flags.recursive()
}
pub const fn allow_symlink_components(self) -> bool {
!self.flags.no_follow()
}
pub fn fields(self) -> Attributes {
self.fields.unwrap_or_else(Attributes::stat) | Attributes::MODE
}
pub fn max_entries(self) -> Option<usize> {
if self.flags.entries_unlimited() {
Some(usize::MAX)
} else {
self.entries.map(|v| v.get() - 1)
}
}
pub fn max_path_bytes(self) -> Option<usize> {
if self.flags.bytes_unlimited() {
Some(usize::MAX)
} else {
self.bytes.map(|v| v.get() - 1)
}
}
pub const fn max_depth(self) -> Option<DepthLimit> {
self.depth
}
pub fn max_entries_or(self, default: usize) -> usize {
self.max_entries().unwrap_or(default)
}
pub fn max_path_bytes_or(self, default: usize) -> usize {
self.max_path_bytes().unwrap_or(default)
}
pub fn max_depth_or(self, default: DepthLimit) -> DepthLimit {
self.max_depth().unwrap_or(default)
}
pub const fn truncate_at_max_depth(self) -> bool {
self.flags.truncate()
}
pub fn resolve(mut self, limits: ResourceLimits) -> Self {
let entries = self.max_entries_or(limits.max_directory_entries());
let bytes = self.max_path_bytes_or(limits.max_directory_path_bytes());
self.depth = Some(self.max_depth_or(limits.max_walk_depth()));
self.with_max_entries(entries).with_max_path_bytes(bytes)
}
pub fn walk_options(self, limits: ResourceLimits) -> Self {
self.with_recursive(true).resolve(limits)
}
pub fn directory_options(self, limits: ResourceLimits) -> Self {
self.with_recursive(false).resolve(limits)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[must_use = "configuration and requests have no effect until used"]
pub struct WalkOptions {
listing: ListDirOptions,
enter_leave: bool,
sort_by_name: bool,
}
impl WalkOptions {
pub const fn new(listing: ListDirOptions) -> Self {
Self {
listing,
enter_leave: false,
sort_by_name: false,
}
}
pub const fn with_listing_options(mut self, listing: ListDirOptions) -> Self {
self.listing = listing;
self
}
pub const fn listing_options(self) -> ListDirOptions {
self.listing
}
pub const fn with_enter_leave(mut self, value: bool) -> Self {
self.enter_leave = value;
self
}
pub const fn with_sort_by_name(mut self, value: bool) -> Self {
self.sort_by_name = value;
self
}
pub const fn enter_leave(self) -> bool {
self.enter_leave
}
pub const fn sort_by_name(self) -> bool {
self.sort_by_name
}
}
impl From<ListDirOptions> for WalkOptions {
fn from(listing: ListDirOptions) -> Self {
Self::new(listing)
}
}
#[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::new()
.with_max_directory_entries(usize::MAX)
.with_max_directory_path_bytes(13)
.with_max_walk_depth(crate::api::DepthLimit::new(3).expect("finite depth"));
let inherited = ListDirOptions::new().walk_options(limits);
assert!(ListDirOptions::new().allow_symlink_components());
assert_eq!(
inherited.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
limits
.walk_options()
.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES)
);
assert_eq!(
inherited.max_depth_or(Default::default()).get(),
limits.walk_options().max_depth_or(Default::default()).get()
);
assert_eq!(
inherited.max_path_bytes_or(crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES),
13
);
for value in [0, 1, 200, usize::MAX] {
let options = ListDirOptions::new()
.with_max_entries(value)
.with_max_path_bytes(value)
.with_max_depth(if value == usize::MAX {
crate::api::DepthLimit::unlimited()
} else {
crate::api::DepthLimit::new(value).expect("finite depth")
})
.with_recursive(true)
.with_allow_symlink_components(false)
.with_truncate_at_max_depth(true)
.with_fields(Attributes::SIZE);
let walk = options.walk_options(limits);
assert_eq!(
walk.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
value
);
assert_eq!(walk.fields(), options.fields());
assert!(!walk.allow_symlink_components());
assert_eq!(
walk.max_path_bytes_or(crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES),
value
);
assert_eq!(walk.max_depth_or(Default::default()).get(), value);
assert!(walk.truncate_at_max_depth());
assert!(options.recursive());
assert!(!options.allow_symlink_components());
assert_eq!(options.fields(), Attributes::SIZE | Attributes::MODE);
let changed = options
.with_recursive(false)
.with_truncate_at_max_depth(false);
let changed = changed.with_allow_symlink_components(true);
assert!(!changed.recursive());
assert!(changed.allow_symlink_components());
assert!(!changed.walk_options(limits).truncate_at_max_depth());
assert_eq!(
changed
.directory_options(limits)
.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
value
);
let zero = changed
.with_max_entries(0)
.with_max_path_bytes(0)
.with_max_depth(crate::api::DepthLimit::new(0).expect("finite depth"));
assert_eq!(
zero.walk_options(limits)
.max_entries_or(crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES),
0
);
assert_eq!(
zero.walk_options(limits)
.max_path_bytes_or(crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES),
0
);
assert_eq!(
zero.walk_options(limits)
.max_depth_or(Default::default())
.get(),
0
);
}
assert_eq!(
ListDirOptions::unlimited()
.walk_options(limits)
.max_depth_or(Default::default())
.get(),
usize::MAX
);
eprintln!(
"ListDirOptions: {} -> {} bytes",
std::mem::size_of::<PreviousLayout>(),
std::mem::size_of::<ListDirOptions>()
);
}
}