1use super::support::FsReadResult as OwnedReadResult;
2use super::*;
3use std::path::Path;
4
5pub struct ReadOp<'a, H: crate::api::FileHandle + 'a> {
8 source: OpSource<'a, H>,
9}
10enum OpSource<'a, H: crate::api::FileHandle + 'a> {
11 Whole(&'a Path),
12 Range(&'a H, u64, usize),
13 Into(&'a H, u64, &'a mut [u8]),
14}
15impl<H: crate::api::FileHandle> std::fmt::Debug for ReadOp<'_, H> {
16 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match &self.source {
18 OpSource::Whole(path) => formatter.debug_tuple("ReadOp::whole").field(path).finish(),
19 OpSource::Range(_, _, length) => formatter
20 .debug_struct("ReadOp::range")
21 .field("length", length)
22 .finish_non_exhaustive(),
23 OpSource::Into(_, _, buffer) => formatter
24 .debug_struct("ReadOp::into")
25 .field("length", &buffer.len())
26 .finish_non_exhaustive(),
27 }
28 }
29}
30impl<'a, H: crate::api::FileHandle + 'a> From<&'a str> for ReadOp<'a, H> {
31 fn from(path: &'a str) -> Self {
32 Self::whole(path)
33 }
34}
35impl<'a, H: crate::api::FileHandle + 'a> From<&'a Path> for ReadOp<'a, H> {
36 fn from(path: &'a Path) -> Self {
37 Self::whole(path)
38 }
39}
40impl<'a, H: crate::api::FileHandle + 'a> ReadOp<'a, H> {
41 pub fn whole<P: AsRef<Path> + ?Sized>(path: &'a P) -> Self {
43 Self {
44 source: OpSource::Whole(path.as_ref()),
45 }
46 }
47 pub fn range(file: &'a H, offset: u64, length: usize) -> Self {
49 Self {
50 source: OpSource::Range(file, offset, length),
51 }
52 }
53 pub fn into(file: &'a H, offset: u64, buffer: &'a mut [u8]) -> Self {
56 Self {
57 source: OpSource::Into(file, offset, buffer),
58 }
59 }
60 pub fn whole_file_path(&self) -> Option<&'a Path> {
62 match &self.source {
63 OpSource::Whole(path) => Some(*path),
64 _ => None,
65 }
66 }
67 pub fn range_parts(&self) -> Option<(&'a H, u64, usize)> {
69 match &self.source {
70 OpSource::Range(file, offset, length) => Some((*file, *offset, *length)),
71 _ => None,
72 }
73 }
74 pub fn buffer_parts_mut(&mut self) -> Option<(&'a H, u64, &mut [u8])> {
76 match &mut self.source {
77 OpSource::Into(file, offset, buffer) => Some((*file, *offset, buffer)),
78 _ => None,
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct ReadResult {
86 pub(crate) offset: u64,
88 pub(crate) read: usize,
90 pub(crate) eof: bool,
92 pub(crate) data: Option<Vec<u8>>,
94}
95
96impl ReadResult {
97 pub fn owned(offset: u64, data: Vec<u8>, eof: bool) -> Self {
99 Self {
100 offset,
101 read: data.len(),
102 eof,
103 data: Some(data),
104 }
105 }
106 pub const fn buffered(offset: u64, read: usize, eof: bool) -> Self {
109 Self {
110 offset,
111 read,
112 eof,
113 data: None,
114 }
115 }
116 pub const fn offset(&self) -> u64 {
117 self.offset
118 }
119 pub const fn read(&self) -> usize {
120 self.read
121 }
122 pub const fn eof(&self) -> bool {
123 self.eof
124 }
125 pub fn data(&self) -> Option<&[u8]> {
126 self.data.as_deref()
127 }
128 pub fn into_data(self) -> Option<Vec<u8>> {
129 self.data
130 }
131 pub const fn is_buffered(&self) -> bool {
132 self.data.is_none()
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct ReadOptions {
141 max_total_bytes: Option<std::num::NonZeroUsize>,
142}
143impl ReadOptions {
144 pub const fn new() -> Self {
146 Self {
147 max_total_bytes: None,
148 }
149 }
150 pub const fn max_total_bytes(mut self, bytes: Option<std::num::NonZeroUsize>) -> Self {
154 self.max_total_bytes = bytes;
155 self
156 }
157 pub const fn total_byte_limit(self) -> Option<std::num::NonZeroUsize> {
159 self.max_total_bytes
160 }
161 pub fn limit_or(self, default: usize) -> usize {
163 self.max_total_bytes
164 .map_or(default, std::num::NonZeroUsize::get)
165 }
166}
167
168impl Default for ReadOptions {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173pub fn consume_ops<'a, H: crate::api::FileHandle + 'a, R, B>(
174 ops: impl IntoIterator<Item = ReadOp<'a, H>>,
175 budget: usize,
176 range: impl Fn(&'a H, u64, usize) -> R,
177 into: impl Fn(&'a H, u64, &'a mut [u8]) -> B,
178 owned: impl FnOnce(&[ReadRequest<'a, R>], usize) -> Result<Vec<OwnedReadResult>>,
179 buffers: impl FnOnce(&mut [B], usize) -> Result<Vec<crate::api::ReadIntoResult>>,
180) -> Result<Vec<ReadResult>> {
181 let mut owned_ops = Vec::new();
182 let mut owned_indices = Vec::new();
183 let mut buffer_ops = Vec::new();
184 let mut buffer_indices = Vec::new();
185 let mut requested = 0usize;
186 let mut count = 0usize;
187 for (index, op) in ops.into_iter().enumerate() {
188 count += 1;
189 let length = match &op.source {
190 OpSource::Whole(_) => 0,
191 OpSource::Range(_, _, length) => *length,
192 OpSource::Into(_, _, buffer) => buffer.len(),
193 };
194 requested = requested
195 .checked_add(length)
196 .filter(|bytes| *bytes <= budget)
197 .ok_or_else(|| Error::client(index, libc::EFBIG as u32))?;
198 match op.source {
199 OpSource::Whole(path) => {
200 owned_ops.push(ReadRequest::whole_file(path));
201 owned_indices.push(index);
202 }
203 OpSource::Range(file, offset, length) => {
204 owned_ops.push(ReadRequest::range(range(file, offset, length)));
205 owned_indices.push(index);
206 }
207 OpSource::Into(file, offset, buffer) => {
208 buffer_ops.push(into(file, offset, buffer));
209 buffer_indices.push(index);
210 }
211 }
212 }
213 let remap = |error: Error, indices: &[usize]| match error.index() {
214 Some(index) if index < indices.len() => error.with_index(indices[index]),
215 Some(_) => Error::transport(None, "invalid vread_native backend error index"),
216 None => error,
217 };
218 let buffered = if buffer_ops.is_empty() {
219 Vec::new()
220 } else {
221 buffers(&mut buffer_ops, budget).map_err(|e| remap(e, &buffer_indices))?
222 };
223 drop(buffer_ops);
224 if buffered.len() != buffer_indices.len() {
225 return Err(Error::transport(
226 None,
227 "invalid vread_native buffer result count",
228 ));
229 }
230 let mut remaining = budget;
231 for result in &buffered {
232 remaining = remaining
233 .checked_sub(result.read)
234 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
235 }
236 let allocated = if owned_ops.is_empty() {
237 Vec::new()
238 } else {
239 owned(&owned_ops, remaining).map_err(|e| remap(e, &owned_indices))?
240 };
241 if allocated.len() != owned_indices.len() {
242 return Err(Error::transport(
243 None,
244 "invalid vread_native owned result count",
245 ));
246 }
247 let mut output: Vec<Option<ReadResult>> = (0..count).map(|_| None).collect();
248 for (index, result) in buffer_indices.into_iter().zip(buffered) {
249 output[index] = Some(ReadResult {
250 offset: result.offset,
251 read: result.read,
252 eof: result.eof,
253 data: None,
254 });
255 }
256 for (index, result) in owned_indices.into_iter().zip(allocated) {
257 remaining = remaining
258 .checked_sub(result.data.len())
259 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
260 output[index] = Some(ReadResult {
261 offset: result.offset,
262 read: result.data.len(),
263 eof: result.eof,
264 data: Some(result.data),
265 });
266 }
267 output
268 .into_iter()
269 .map(|result| result.ok_or_else(|| Error::transport(None, "vread_native omitted a result")))
270 .collect()
271}
272pub struct ReadRequest<'a, R> {
274 pub(crate) source: Source<'a, R>,
275}
276pub(crate) enum Source<'a, R> {
277 Whole(&'a Path),
278 Range(R),
279}
280impl<'a, R> ReadRequest<'a, R> {
281 pub fn whole_file<P: AsRef<Path> + ?Sized>(path: &'a P) -> Self {
283 Self {
284 source: Source::Whole(path.as_ref()),
285 }
286 }
287 pub fn range(request: R) -> Self {
289 Self {
290 source: Source::Range(request),
291 }
292 }
293 pub fn range_ref(&self) -> Option<&R> {
297 match &self.source {
298 Source::Range(request) => Some(request),
299 _ => None,
300 }
301 }
302}
303impl<'a, R> From<R> for ReadRequest<'a, R> {
304 fn from(request: R) -> Self {
305 Self::range(request)
306 }
307}
308pub fn read_batch<R>(
309 requests: &[ReadRequest<'_, R>],
310 budget: usize,
311 read_ranges: impl FnOnce(&[&R], usize) -> Result<Vec<OwnedReadResult>>,
312 read_paths: impl FnOnce(&[&Path], usize) -> Result<Vec<Vec<u8>>>,
313) -> Result<Vec<OwnedReadResult>> {
314 if requests.is_empty() {
315 return Ok(Vec::new());
316 }
317 let (mut paths, mut path_indices, mut ranges, mut range_indices) =
318 (Vec::new(), Vec::new(), Vec::new(), Vec::new());
319 for (i, r) in requests.iter().enumerate() {
320 match &r.source {
321 Source::Whole(path) => {
322 paths.push(*path);
323 path_indices.push(i);
324 }
325 Source::Range(range) => {
326 ranges.push(range);
327 range_indices.push(i);
328 }
329 }
330 }
331 let remap = |e: Error, indices: &[usize]| match e.index() {
332 Some(i) if i < indices.len() => e.with_index(indices[i]),
333 Some(_) => Error::transport(None, "invalid vread_native backend error index"),
334 None => e,
335 };
336 let range_results = if ranges.is_empty() {
338 Vec::new()
339 } else {
340 read_ranges(&ranges, budget).map_err(|e| remap(e, &range_indices))?
341 };
342 if range_results.len() != ranges.len() {
343 return Err(Error::transport(
344 None,
345 "invalid vread_native range result count",
346 ));
347 }
348 let mut remaining = budget;
349 for r in &range_results {
350 remaining = remaining
351 .checked_sub(r.data.len())
352 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
353 }
354 let path_results = if paths.is_empty() {
355 Vec::new()
356 } else {
357 read_paths(&paths, remaining).map_err(|e| remap(e, &path_indices))?
358 };
359 if path_results.len() != paths.len() {
360 return Err(Error::transport(
361 None,
362 "invalid vread_native path result count",
363 ));
364 }
365 for r in &path_results {
366 remaining = remaining
367 .checked_sub(r.len())
368 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
369 }
370 let mut output: Vec<Option<OwnedReadResult>> = (0..requests.len()).map(|_| None).collect();
371 for (i, r) in range_indices.into_iter().zip(range_results) {
372 output[i] = Some(r);
373 }
374 for (i, data) in path_indices.into_iter().zip(path_results) {
375 output[i] = Some(OwnedReadResult {
376 offset: 0,
377 data,
378 eof: true,
379 });
380 }
381 output
382 .into_iter()
383 .map(|r| r.ok_or_else(|| Error::transport(None, "vread_native omitted a result")))
384 .collect()
385}
386
387#[cfg(test)]
388mod option_layout_tests {
389 use super::ReadOptions;
390 use std::num::NonZeroUsize;
391 #[test]
392 fn read_options_are_one_word_and_keep_inheritance_distinct() {
393 assert_eq!(
394 std::mem::size_of::<ReadOptions>(),
395 std::mem::size_of::<usize>()
396 );
397 let mut options = ReadOptions::new();
398 assert_eq!(options.limit_or(7), 7);
399 for bytes in [1, 42, usize::MAX] {
400 let limit = NonZeroUsize::new(bytes);
401 options = options.max_total_bytes(limit);
402 assert_eq!(options.total_byte_limit(), limit);
403 assert_eq!(options.limit_or(7), bytes);
404 }
405 options = options.max_total_bytes(None);
406 assert_eq!(options.total_byte_limit(), None);
407 assert_eq!(options.limit_or(7), 7);
408 assert_eq!(
409 options.limit_or(0),
410 0,
411 "an inherited zero policy must not become unlimited"
412 );
413 }
414}