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