1use super::support::FsReadResult as OwnedReadResult;
2use super::*;
3use std::path::Path;
4
5#[must_use = "configuration and requests have no effect until used"]
8pub struct ReadOp<'a, H: crate::api::FileHandle + 'a> {
9 source: OpSource<'a, H>,
10}
11enum OpSource<'a, H: crate::api::FileHandle + 'a> {
12 Whole(&'a Path),
13 Range(&'a H, u64, usize),
14 Into(&'a H, u64, &'a mut [u8]),
15}
16impl<H: crate::api::FileHandle> std::fmt::Debug for ReadOp<'_, H> {
17 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match &self.source {
19 OpSource::Whole(path) => formatter.debug_tuple("ReadOp::whole").field(path).finish(),
20 OpSource::Range(_, _, length) => formatter
21 .debug_struct("ReadOp::range")
22 .field("length", length)
23 .finish_non_exhaustive(),
24 OpSource::Into(_, _, buffer) => formatter
25 .debug_struct("ReadOp::into")
26 .field("length", &buffer.len())
27 .finish_non_exhaustive(),
28 }
29 }
30}
31impl<'a, H: crate::api::FileHandle + 'a> From<&'a str> for ReadOp<'a, H> {
32 fn from(path: &'a str) -> Self {
33 Self::whole(path)
34 }
35}
36impl<'a, H: crate::api::FileHandle + 'a> From<&'a Path> for ReadOp<'a, H> {
37 fn from(path: &'a Path) -> Self {
38 Self::whole(path)
39 }
40}
41impl<'a, H: crate::api::FileHandle + 'a> ReadOp<'a, H> {
42 pub fn whole<P: AsRef<Path> + ?Sized>(path: &'a P) -> Self {
44 Self {
45 source: OpSource::Whole(path.as_ref()),
46 }
47 }
48 pub fn range(file: &'a H, offset: u64, length: usize) -> Self {
50 Self {
51 source: OpSource::Range(file, offset, length),
52 }
53 }
54 pub fn into(file: &'a H, offset: u64, buffer: &'a mut [u8]) -> Self {
57 Self {
58 source: OpSource::Into(file, offset, buffer),
59 }
60 }
61 pub fn whole_file_path(&self) -> Option<&'a Path> {
63 match &self.source {
64 OpSource::Whole(path) => Some(*path),
65 _ => None,
66 }
67 }
68 pub fn range_parts(&self) -> Option<(&'a H, u64, usize)> {
70 match &self.source {
71 OpSource::Range(file, offset, length) => Some((*file, *offset, *length)),
72 _ => None,
73 }
74 }
75 pub fn buffer_parts_mut(&mut self) -> Option<(&'a H, u64, &mut [u8])> {
77 match &mut self.source {
78 OpSource::Into(file, offset, buffer) => Some((*file, *offset, buffer)),
79 _ => None,
80 }
81 }
82}
83
84#[derive(Clone, PartialEq, Eq)]
86pub struct ReadResult {
87 offset: u64,
88 eof: bool,
89 payload: ReadPayload,
90}
91#[derive(Clone, PartialEq, Eq)]
92enum ReadPayload {
93 Owned(Vec<u8>),
94 Buffered(usize),
95}
96impl std::fmt::Debug for ReadResult {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("ReadResult")
99 .field("offset", &self.offset())
100 .field("bytes_read", &self.bytes_read())
101 .field("eof", &self.eof())
102 .field("data", &self.data())
103 .finish()
104 }
105}
106impl ReadResult {
107 pub fn owned(offset: u64, data: Vec<u8>, eof: bool) -> Self {
109 Self {
110 offset,
111 eof,
112 payload: ReadPayload::Owned(data),
113 }
114 }
115 pub const fn buffered(offset: u64, bytes_read: usize, eof: bool) -> Self {
118 Self {
119 offset,
120 eof,
121 payload: ReadPayload::Buffered(bytes_read),
122 }
123 }
124 pub const fn offset(&self) -> u64 {
125 self.offset
126 }
127 pub const fn bytes_read(&self) -> usize {
129 match &self.payload {
130 ReadPayload::Owned(data) => data.len(),
131 ReadPayload::Buffered(count) => *count,
132 }
133 }
134 pub const fn eof(&self) -> bool {
135 self.eof
136 }
137 pub fn data(&self) -> Option<&[u8]> {
138 match &self.payload {
139 ReadPayload::Owned(data) => Some(data.as_slice()),
140 ReadPayload::Buffered(_) => None,
141 }
142 }
143 pub fn into_data(self) -> Option<Vec<u8>> {
144 match self.payload {
145 ReadPayload::Owned(data) => Some(data),
146 ReadPayload::Buffered(_) => None,
147 }
148 }
149 pub const fn is_buffered(&self) -> bool {
150 matches!(&self.payload, ReadPayload::Buffered(_))
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184#[must_use = "configuration and requests have no effect until used"]
185pub struct ReadOptions {
186 max_total_bytes: Option<std::num::NonZeroUsize>,
187}
188impl ReadOptions {
189 pub const fn new() -> Self {
191 Self {
192 max_total_bytes: None,
193 }
194 }
195 pub const fn with_max_total_bytes(mut self, bytes: std::num::NonZeroUsize) -> Self {
199 self.max_total_bytes = Some(bytes);
200 self
201 }
202 pub const fn max_total_bytes(self) -> Option<std::num::NonZeroUsize> {
205 self.max_total_bytes
206 }
207 pub fn max_total_bytes_or(self, default: usize) -> usize {
209 self.max_total_bytes
210 .map_or(default, std::num::NonZeroUsize::get)
211 }
212}
213
214impl Default for ReadOptions {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219pub fn consume_ops<'a, H: crate::api::FileHandle + 'a, R, B>(
220 ops: impl IntoIterator<Item = ReadOp<'a, H>>,
221 budget: usize,
222 range: impl Fn(&'a H, u64, usize) -> R,
223 into: impl Fn(&'a H, u64, &'a mut [u8]) -> B,
224 owned: impl FnOnce(&[ReadRequest<'a, R>], usize) -> Result<Vec<OwnedReadResult>>,
225 buffers: impl FnOnce(&mut [B], usize) -> Result<Vec<crate::api::ReadIntoResult>>,
226) -> Result<Vec<ReadResult>> {
227 let mut ops = ops.into_iter();
228 let Some(first) = ops.next() else {
229 return Ok(Vec::new());
230 };
231 let second = ops.next();
232 if second.is_none() {
233 let length = match &first.source {
234 OpSource::Whole(_) => 0,
235 OpSource::Range(_, _, length) => *length,
236 OpSource::Into(_, _, buffer) => buffer.len(),
237 };
238 if length > budget {
239 return Err(Error::client(0, libc::EFBIG as u32));
240 }
241 let remap = |error: Error| match error.index() {
242 Some(0) | None => error,
243 Some(_) => Error::transport(None, "invalid vread_native backend error index"),
244 };
245 let request = match first.source {
246 OpSource::Whole(path) => ReadRequest::whole_file(path),
247 OpSource::Range(file, offset, length) => {
248 ReadRequest::range(range(file, offset, length))
249 }
250 OpSource::Into(file, offset, buffer) => {
251 let values = buffers(&mut [into(file, offset, buffer)], budget).map_err(remap)?;
252 if values.len() != 1 {
253 return Err(Error::transport(
254 None,
255 "invalid vread_native buffer result count",
256 ));
257 }
258 if values[0].bytes_read > budget {
259 return Err(Error::transport(
260 None,
261 "vread_native backend exceeded byte budget",
262 ));
263 }
264 return Ok(values
265 .into_iter()
266 .map(|result| {
267 ReadResult::buffered(result.offset, result.bytes_read, result.eof)
268 })
269 .collect());
270 }
271 };
272 let values = owned(&[request], budget).map_err(remap)?;
273 if values.len() != 1 {
274 return Err(Error::transport(
275 None,
276 "invalid vread_native owned result count",
277 ));
278 }
279 if values[0].data.len() > budget {
280 return Err(Error::transport(
281 None,
282 "vread_native backend exceeded byte budget",
283 ));
284 }
285 return Ok(values
286 .into_iter()
287 .map(|result| ReadResult::owned(result.offset, result.data, result.eof))
288 .collect());
289 }
290 let ops = std::iter::once(first).chain(second).chain(ops);
291 let mut owned_ops = Vec::new();
292 let mut owned_indices = Vec::new();
293 let mut buffer_ops = Vec::new();
294 let mut buffer_indices = Vec::new();
295 let mut requested = 0usize;
296 let mut count = 0usize;
297 for (index, op) in ops.enumerate() {
298 count += 1;
299 let length = match &op.source {
300 OpSource::Whole(_) => 0,
301 OpSource::Range(_, _, length) => *length,
302 OpSource::Into(_, _, buffer) => buffer.len(),
303 };
304 requested = requested
305 .checked_add(length)
306 .filter(|bytes| *bytes <= budget)
307 .ok_or_else(|| Error::client(index, libc::EFBIG as u32))?;
308 match op.source {
309 OpSource::Whole(path) => {
310 owned_ops.push(ReadRequest::whole_file(path));
311 owned_indices.push(index);
312 }
313 OpSource::Range(file, offset, length) => {
314 owned_ops.push(ReadRequest::range(range(file, offset, length)));
315 owned_indices.push(index);
316 }
317 OpSource::Into(file, offset, buffer) => {
318 buffer_ops.push(into(file, offset, buffer));
319 buffer_indices.push(index);
320 }
321 }
322 }
323 let remap = |error: Error, indices: &[usize]| match error.index() {
324 Some(index) if index < indices.len() => error.with_index(indices[index]),
325 Some(_) => Error::transport(None, "invalid vread_native backend error index"),
326 None => error,
327 };
328 let buffered = if buffer_ops.is_empty() {
329 Vec::new()
330 } else {
331 buffers(&mut buffer_ops, budget).map_err(|e| remap(e, &buffer_indices))?
332 };
333 drop(buffer_ops);
334 if buffered.len() != buffer_indices.len() {
335 return Err(Error::transport(
336 None,
337 "invalid vread_native buffer result count",
338 ));
339 }
340 let mut remaining = budget;
341 for result in &buffered {
342 remaining = remaining
343 .checked_sub(result.bytes_read)
344 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
345 }
346 let allocated = if owned_ops.is_empty() {
347 Vec::new()
348 } else {
349 owned(&owned_ops, remaining).map_err(|e| remap(e, &owned_indices))?
350 };
351 if allocated.len() != owned_indices.len() {
352 return Err(Error::transport(
353 None,
354 "invalid vread_native owned result count",
355 ));
356 }
357 for result in &allocated {
358 remaining = remaining
359 .checked_sub(result.data.len())
360 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
361 }
362 if buffer_indices.is_empty() {
363 return Ok(allocated
364 .into_iter()
365 .map(|result| ReadResult::owned(result.offset, result.data, result.eof))
366 .collect());
367 }
368 if owned_indices.is_empty() {
369 return Ok(buffered
370 .into_iter()
371 .map(|result| ReadResult::buffered(result.offset, result.bytes_read, result.eof))
372 .collect());
373 }
374 let mut output: Vec<Option<ReadResult>> = (0..count).map(|_| None).collect();
375 for (index, result) in buffer_indices.into_iter().zip(buffered) {
376 output[index] = Some(ReadResult::buffered(
377 result.offset,
378 result.bytes_read,
379 result.eof,
380 ));
381 }
382 for (index, result) in owned_indices.into_iter().zip(allocated) {
383 output[index] = Some(ReadResult::owned(result.offset, result.data, result.eof));
384 }
385 output
386 .into_iter()
387 .map(|result| result.ok_or_else(|| Error::transport(None, "vread_native omitted a result")))
388 .collect()
389}
390pub struct ReadRequest<'a, R> {
392 pub(crate) source: Source<'a, R>,
393}
394pub(crate) enum Source<'a, R> {
395 Whole(&'a Path),
396 Range(R),
397}
398impl<'a, R> ReadRequest<'a, R> {
399 pub fn whole_file<P: AsRef<Path> + ?Sized>(path: &'a P) -> Self {
401 Self {
402 source: Source::Whole(path.as_ref()),
403 }
404 }
405 pub fn range(request: R) -> Self {
407 Self {
408 source: Source::Range(request),
409 }
410 }
411 pub fn range_ref(&self) -> Option<&R> {
415 match &self.source {
416 Source::Range(request) => Some(request),
417 _ => None,
418 }
419 }
420}
421impl<'a, R> From<R> for ReadRequest<'a, R> {
422 fn from(request: R) -> Self {
423 Self::range(request)
424 }
425}
426pub fn read_batch<R>(
427 requests: &[ReadRequest<'_, R>],
428 budget: usize,
429 read_ranges: impl FnOnce(&[&R], usize) -> Result<Vec<OwnedReadResult>>,
430 read_paths: impl FnOnce(&[&Path], usize) -> Result<Vec<Vec<u8>>>,
431) -> Result<Vec<OwnedReadResult>> {
432 if requests.is_empty() {
433 return Ok(Vec::new());
434 }
435 let (mut paths, mut path_indices, mut ranges, mut range_indices) =
436 (Vec::new(), Vec::new(), Vec::new(), Vec::new());
437 for (i, r) in requests.iter().enumerate() {
438 match &r.source {
439 Source::Whole(path) => {
440 paths.push(*path);
441 path_indices.push(i);
442 }
443 Source::Range(range) => {
444 ranges.push(range);
445 range_indices.push(i);
446 }
447 }
448 }
449 let remap = |e: Error, indices: &[usize]| match e.index() {
450 Some(i) if i < indices.len() => e.with_index(indices[i]),
451 Some(_) => Error::transport(None, "invalid vread_native backend error index"),
452 None => e,
453 };
454 let range_results = if ranges.is_empty() {
456 Vec::new()
457 } else {
458 read_ranges(&ranges, budget).map_err(|e| remap(e, &range_indices))?
459 };
460 if range_results.len() != ranges.len() {
461 return Err(Error::transport(
462 None,
463 "invalid vread_native range result count",
464 ));
465 }
466 let mut remaining = budget;
467 for r in &range_results {
468 remaining = remaining
469 .checked_sub(r.data.len())
470 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
471 }
472 let path_results = if paths.is_empty() {
473 Vec::new()
474 } else {
475 read_paths(&paths, remaining).map_err(|e| remap(e, &path_indices))?
476 };
477 if path_results.len() != paths.len() {
478 return Err(Error::transport(
479 None,
480 "invalid vread_native path result count",
481 ));
482 }
483 for r in &path_results {
484 remaining = remaining
485 .checked_sub(r.len())
486 .ok_or_else(|| Error::transport(None, "vread_native backend exceeded byte budget"))?;
487 }
488 if path_indices.is_empty() {
489 return Ok(range_results);
490 }
491 if range_indices.is_empty() {
492 return Ok(path_results
493 .into_iter()
494 .map(|data| OwnedReadResult {
495 offset: 0,
496 data,
497 eof: true,
498 })
499 .collect());
500 }
501 let mut output: Vec<Option<OwnedReadResult>> = (0..requests.len()).map(|_| None).collect();
502 for (i, r) in range_indices.into_iter().zip(range_results) {
503 output[i] = Some(r);
504 }
505 for (i, data) in path_indices.into_iter().zip(path_results) {
506 output[i] = Some(OwnedReadResult {
507 offset: 0,
508 data,
509 eof: true,
510 });
511 }
512 output
513 .into_iter()
514 .map(|r| r.ok_or_else(|| Error::transport(None, "vread_native omitted a result")))
515 .collect()
516}
517
518#[cfg(test)]
519mod option_layout_tests {
520 use super::ReadOptions;
521 use std::num::NonZeroUsize;
522 #[test]
523 fn read_options_are_one_word_and_keep_inheritance_distinct() {
524 assert_eq!(
525 std::mem::size_of::<ReadOptions>(),
526 std::mem::size_of::<usize>()
527 );
528 let mut options = ReadOptions::new();
529 assert_eq!(options.max_total_bytes_or(7), 7);
530 for bytes in [1, 42, usize::MAX] {
531 let limit = NonZeroUsize::new(bytes).unwrap();
532 options = options.with_max_total_bytes(limit);
533 assert_eq!(options.max_total_bytes(), Some(limit));
534 assert_eq!(options.max_total_bytes_or(7), bytes);
535 }
536 options = ReadOptions::new();
537 assert_eq!(options.max_total_bytes(), None);
538 assert_eq!(options.max_total_bytes_or(7), 7);
539 assert_eq!(
540 options.max_total_bytes_or(0),
541 0,
542 "an inherited zero policy must not become unlimited"
543 );
544 }
545}
546
547#[cfg(test)]
548mod compact_read_tests {
549 use super::*;
550 #[test]
551 fn compact_read_results_preserve_owned_and_buffered_empty_progress() {
552 if cfg!(target_pointer_width = "64") {
553 assert_eq!(std::mem::size_of::<ReadResult>(), 40);
554 }
555 let owned = ReadResult::owned(7, Vec::new(), true);
556 const BUFFERED: ReadResult = ReadResult::buffered(7, 0, true);
557 assert_eq!(owned.bytes_read(), 0);
558 assert_eq!(owned.offset(), BUFFERED.offset());
559 assert_eq!(owned.eof(), BUFFERED.eof());
560 assert_eq!(owned.data(), Some(b"".as_slice()));
561 assert_eq!(BUFFERED.data(), None);
562 assert_ne!(owned, BUFFERED);
563 assert!(!owned.is_buffered());
564 assert!(BUFFERED.is_buffered());
565 assert_eq!(owned.clone().into_data(), Some(Vec::new()));
566 assert_eq!(BUFFERED.clone().into_data(), None);
567 let data = vec![1, 2, 3];
568 let pointer = data.as_ptr();
569 let result = ReadResult::owned(9, data, false);
570 assert_eq!(result.bytes_read(), 3);
571 let extracted = result.into_data().unwrap();
572 assert_eq!(extracted.as_ptr(), pointer);
573 assert_eq!(
574 ReadResult::buffered(0, usize::MAX, false).bytes_read(),
575 usize::MAX
576 );
577 }
578}