Skip to main content

vfsi_core/api/
support.rs

1use super::*;
2use std::path::PathBuf;
3
4/// Default payload limit for APIs that allocate and return complete contents.
5pub const DEFAULT_READ_MAX_BYTES: usize = 16 * 1024 * 1024;
6
7/// Default size of each chunk delivered by the bounded single-file stream API.
8pub const DEFAULT_READ_STREAM_CHUNK_BYTES: usize = 1024 * 1024;
9
10/// Default aggregate payload limit for complete-file vector reads.
11pub const DEFAULT_READ_ALLV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
12
13/// Tuning options for bounded single-file streaming reads.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct ReadStreamOptions {
16    chunk_size: usize,
17}
18
19impl ReadStreamOptions {
20    pub const fn new() -> Self {
21        Self {
22            chunk_size: DEFAULT_READ_STREAM_CHUNK_BYTES,
23        }
24    }
25
26    /// Set the maximum bytes delivered to the callback at once.
27    ///
28    /// NFS and other backends may return smaller chunks due to negotiated
29    /// protocol limits. A larger setting does not bypass those limits.
30    pub const fn chunk_size(mut self, bytes: usize) -> Self {
31        self.chunk_size = bytes;
32        self
33    }
34
35    pub const fn chunk_size_bytes(self) -> usize {
36        self.chunk_size
37    }
38}
39
40impl Default for ReadStreamOptions {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46/// Default maximum number of entries returned by allocating directory APIs.
47pub const DEFAULT_DIRECTORY_MAX_ENTRIES: usize = 100_000;
48
49/// Default combined path-storage budget for allocating directory APIs.
50pub const DEFAULT_DIRECTORY_MAX_PATH_BYTES: usize = 16 * 1024 * 1024;
51
52/// Default recursion depth for recursive walks.
53pub const DEFAULT_WALK_MAX_DEPTH: usize = 128;
54
55/// Resource limits for one allocating directory listing.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct ReadDirOptions {
58    max_entries: usize,
59    max_path_bytes: usize,
60}
61
62impl ReadDirOptions {
63    pub const fn new() -> Self {
64        Self {
65            max_entries: DEFAULT_DIRECTORY_MAX_ENTRIES,
66            max_path_bytes: DEFAULT_DIRECTORY_MAX_PATH_BYTES,
67        }
68    }
69
70    /// Explicitly opt out of the default allocation limits.
71    pub const fn unlimited() -> Self {
72        Self {
73            max_entries: usize::MAX,
74            max_path_bytes: usize::MAX,
75        }
76    }
77
78    pub const fn max_entries(mut self, entries: usize) -> Self {
79        self.max_entries = entries;
80        self
81    }
82
83    pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
84        self.max_path_bytes = bytes;
85        self
86    }
87
88    pub const fn entry_limit(self) -> usize {
89        self.max_entries
90    }
91
92    pub const fn path_byte_limit(self) -> usize {
93        self.max_path_bytes
94    }
95}
96
97impl Default for ReadDirOptions {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103#[bitfields::bitfield(u8)]
104#[derive(PartialEq, Eq)]
105struct WalkFlags {
106    truncate: bool,
107    #[bits(7)]
108    _reserved: u8,
109}
110
111/// Compact traversal depth: 0 through 200 are finite, larger inputs are unlimited.
112/// The root has depth zero. Unlimited depth does not disable entry/byte budgets.
113/// Both this type and `Option<DepthLimit>` occupy one byte.
114#[repr(transparent)]
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub struct DepthLimit(std::num::NonZeroU8);
117
118impl DepthLimit {
119    /// Normalize a requested depth. Every value above 200 means unlimited.
120    pub const fn new(depth: usize) -> Self {
121        let encoded = if depth <= 200 { (depth + 1) as u8 } else { 202 };
122        Self(std::num::NonZeroU8::new(encoded).expect("depth encoding is nonzero"))
123    }
124    /// Disable the depth limit while retaining other traversal budgets.
125    pub const fn unlimited() -> Self {
126        Self::new(usize::MAX)
127    }
128    /// Effective finite limit, or `usize::MAX` for unlimited traversal.
129    pub const fn get(self) -> usize {
130        if self.is_unlimited() {
131            usize::MAX
132        } else {
133            self.0.get() as usize - 1
134        }
135    }
136    /// Whether traversal has no depth limit.
137    pub const fn is_unlimited(self) -> bool {
138        self.0.get() == 202
139    }
140}
141
142/// Resource limits for an allocating recursive directory walk.
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub struct WalkOptions {
145    directory: ReadDirOptions,
146    max_depth: DepthLimit,
147    flags: WalkFlags,
148}
149
150impl WalkOptions {
151    pub const fn new() -> Self {
152        Self {
153            directory: ReadDirOptions::new(),
154            max_depth: DepthLimit::new(DEFAULT_WALK_MAX_DEPTH),
155            flags: WalkFlags::new(),
156        }
157    }
158
159    /// Explicitly opt out of the default allocation and recursion limits.
160    pub const fn unlimited() -> Self {
161        Self {
162            directory: ReadDirOptions::unlimited(),
163            max_depth: DepthLimit::unlimited(),
164            flags: WalkFlags::new(),
165        }
166    }
167
168    pub const fn max_entries(mut self, entries: usize) -> Self {
169        self.directory = self.directory.max_entries(entries);
170        self
171    }
172
173    pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
174        self.directory = self.directory.max_path_bytes(bytes);
175        self
176    }
177
178    /// Root depth is zero. Values from 0 through 200 are finite limits;
179    /// any larger value disables the depth limit (other budgets still apply).
180    pub const fn max_depth(mut self, depth: usize) -> Self {
181        self.max_depth = DepthLimit::new(depth);
182        self
183    }
184
185    /// Stop descending at `max_depth` instead of treating a deeper subtree
186    /// as a safety-limit violation. Intended for caller-requested shallow walks.
187    pub const fn truncate_at_max_depth(mut self, truncate: bool) -> Self {
188        self.flags.set_truncate(truncate);
189        self
190    }
191
192    pub const fn entry_limit(self) -> usize {
193        self.directory.entry_limit()
194    }
195
196    pub const fn path_byte_limit(self) -> usize {
197        self.directory.path_byte_limit()
198    }
199
200    /// Effective depth limit, or `usize::MAX` when unlimited.
201    pub const fn depth_limit(self) -> usize {
202        self.max_depth.get()
203    }
204
205    pub const fn truncates_at_depth_limit(self) -> bool {
206        self.flags.truncate()
207    }
208}
209
210impl Default for WalkOptions {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216/// Default aggregate payload limit for owned vector reads.
217pub const DEFAULT_READV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
218
219/// Application result for one positional vector read, in request order.
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct FsReadResult {
222    pub offset: u64,
223    pub data: Vec<u8>,
224    pub eof: bool,
225}
226
227/// Application result for one read into caller storage, in request order.
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub struct ReadIntoResult {
230    pub offset: u64,
231    pub read: usize,
232    pub eof: bool,
233}
234
235/// Application result for one positional vector write, in request order.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub struct WriteResult {
238    pub offset: u64,
239    pub written: usize,
240    pub stable: bool,
241}
242
243/// Completion of a streaming read. Stopping does not mean EOF.
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum StreamCompletion {
246    Complete,
247    Stopped { next_offset: u64 },
248}
249
250/// Completion of a callback-based directory traversal.
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum TraversalCompletion {
253    Complete,
254    Stopped,
255}
256
257/// Default resource policy for a client. Per-call options override it.
258/// Byte limits bound logical payload, not allocator capacity or RPC overhead.
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub struct ResourceLimits {
261    /// Maximum collected scalar payload or aggregate vector read buffers.
262    /// Not a process-memory cap; standard `Read::read_to_end` and explicit
263    /// positional file reads remain caller-managed.
264    pub max_read_bytes: usize,
265    /// Default request size for client `read_stream`, not file `Read` or pools.
266    pub stream_chunk_bytes: usize,
267    /// Aggregate entries delivered/collected by a directory call or tree walk.
268    pub max_directory_entries: usize,
269    /// Aggregate logical path bytes, excluding allocator and metadata overhead.
270    pub max_directory_path_bytes: usize,
271    /// Maximum descent depth; the starting directory is depth zero.
272    pub max_walk_depth: usize,
273}
274
275impl Default for ResourceLimits {
276    fn default() -> Self {
277        Self {
278            max_read_bytes: DEFAULT_READ_MAX_BYTES,
279            stream_chunk_bytes: crate::api::DEFAULT_READ_STREAM_CHUNK_BYTES,
280            max_directory_entries: crate::api::DEFAULT_DIRECTORY_MAX_ENTRIES,
281            max_directory_path_bytes: crate::api::DEFAULT_DIRECTORY_MAX_PATH_BYTES,
282            max_walk_depth: crate::api::DEFAULT_WALK_MAX_DEPTH,
283        }
284    }
285}
286
287impl ResourceLimits {
288    pub fn directory_options(self) -> ReadDirOptions {
289        ReadDirOptions::new()
290            .max_entries(self.max_directory_entries)
291            .max_path_bytes(self.max_directory_path_bytes)
292    }
293
294    pub fn walk_options(self) -> crate::api::WalkOptions {
295        crate::api::WalkOptions::new()
296            .max_entries(self.max_directory_entries)
297            .max_path_bytes(self.max_directory_path_bytes)
298            .max_depth(self.max_walk_depth)
299    }
300}
301
302/// One directory and its entries, with attributes fetched during enumeration.
303#[derive(Clone, Debug, PartialEq, Eq)]
304pub struct DirectoryListing {
305    pub path: PathBuf,
306    pub entries: Vec<DirEntry>,
307}