Skip to main content

vfsi_core/api/
io.rs

1//! Explicit standard-I/O interoperability through the portable vector API.
2use super::{
3    Attributes, AttrsOptions, Error, ReadOp, ReadOptions, Result, Target, Vfsi, WriteOp,
4    WriteOptions,
5};
6use std::io::{self, Read, Seek, SeekFrom, Write};
7
8/// Durability requested for every handle in a synchronization vector.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum SyncMode {
11    /// File data, using fdatasync where available.
12    Data,
13    /// File data and metadata supported by the backend.
14    All,
15}
16
17/// Borrowed standard-I/O adapter with its own cursor, initially zero.
18/// Every I/O request uses the supplied client's vector engine. The file stays
19/// owned by the caller. `read_to_end` and `read_to_string` bound newly collected
20/// bytes by the client's read budget; on failure the cursor may have advanced.
21/// Explicitly allocated caller buffers remain caller-managed. `write_all` uses
22/// the standard `Write` loop over single-wave vector writes: acknowledged short
23/// writes advance the cursor, and interruption retries only the remaining bytes.
24/// Transport failures stop the loop without replaying the failed request.
25pub(super) struct StdIo<'a, C: Vfsi + ?Sized> {
26    client: &'a C,
27    file: &'a C::File,
28    position: u64,
29}
30impl<'a, C: Vfsi + ?Sized> StdIo<'a, C> {
31    pub(super) fn new(client: &'a C, file: &'a C::File) -> Self {
32        Self {
33            client,
34            file,
35            position: 0,
36        }
37    }
38
39    fn read_at(&mut self, buffer: &mut [u8], budget: usize) -> Result<(usize, bool)> {
40        if buffer.is_empty() {
41            return Ok((0, false));
42        }
43        let length = buffer.len().min(budget);
44        if !buffer.is_empty() && length == 0 {
45            return Err(Error::client(0, libc::EFBIG as u32));
46        }
47        self.position
48            .checked_add(length as u64)
49            .ok_or_else(|| Error::client(0, libc::EOVERFLOW as u32))?;
50        let values = self.client.vread(
51            [ReadOp::into(
52                self.file,
53                self.position,
54                &mut buffer[..length],
55            )],
56            ReadOptions::new().with_max_total_bytes(std::num::NonZeroUsize::new(budget).unwrap()),
57        )?;
58        let [value] = values.as_slice() else {
59            return Err(invalid("invalid read count"));
60        };
61        if value.offset() != self.position
62            || value.data().is_some()
63            || value.bytes_read() > length
64            || (length != 0 && value.bytes_read() == 0 && !value.eof())
65        {
66            return Err(invalid("invalid read progress"));
67        }
68        self.position += value.bytes_read() as u64;
69        Ok((value.bytes_read(), value.eof()))
70    }
71    fn read_uninterrupted(&mut self, buffer: &mut [u8], budget: usize) -> Result<(usize, bool)> {
72        loop {
73            match self.read_at(buffer, budget) {
74                Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
75                result => return result,
76            }
77        }
78    }
79    fn write_at(&mut self, buffer: &[u8]) -> Result<usize> {
80        self.position
81            .checked_add(buffer.len() as u64)
82            .ok_or_else(|| Error::client(0, libc::EOVERFLOW as u32))?;
83        let values = self.client.vwrite(
84            &[WriteOp::at(self.file, self.position, buffer)],
85            WriteOptions::new(),
86        )?;
87        let [value] = values.as_slice() else {
88            return Err(invalid("invalid write count"));
89        };
90        if value.bytes_written > buffer.len() {
91            return Err(invalid("invalid write progress"));
92        }
93        // Append descriptors report the actual position selected by the backend.
94        self.position = value
95            .offset
96            .checked_add(value.bytes_written as u64)
97            .ok_or_else(|| invalid("write offset overflow"))?;
98        Ok(value.bytes_written)
99    }
100}
101fn invalid(message: &'static str) -> Error {
102    Error::transport_with_kind(None, crate::TransportKind::InvalidReply, message)
103}
104impl<C: Vfsi + ?Sized> Read for StdIo<'_, C> {
105    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
106        if buffer.is_empty() {
107            return Ok(0);
108        }
109        self.read_at(buffer, self.client.limits().max_read_bytes())
110            .map(|(count, _)| count)
111            .map_err(Into::into)
112    }
113    fn read_to_end(&mut self, output: &mut Vec<u8>) -> io::Result<usize> {
114        let budget = self.client.limits().max_read_bytes();
115        let mut added = 0;
116        loop {
117            let remaining = budget - added;
118            let length = remaining.min(super::DEFAULT_READ_STREAM_CHUNK_BYTES);
119            if length == 0 {
120                // Distinguish exact-budget EOF from oversized content without
121                // allocating beyond the budget. A failed probe advances cursor.
122                let (count, _) = self.read_uninterrupted(&mut [0; 1], 1)?;
123                return if count == 0 {
124                    Ok(added)
125                } else {
126                    Err(Error::client(0, libc::EFBIG as u32).into())
127                };
128            }
129            // Reserve before I/O and fill caller storage directly. Reuse the
130            // stream chunk policy without an additional scratch allocation.
131            output
132                .try_reserve(length)
133                .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
134            let start = output.len();
135            output.resize(start + length, 0);
136            let result = self.read_uninterrupted(&mut output[start..], remaining);
137            output.truncate(start + result.as_ref().map_or(0, |(count, _)| *count));
138            let (count, eof) = result?;
139            added += count;
140            if eof {
141                return Ok(added);
142            }
143        }
144    }
145    fn read_to_string(&mut self, output: &mut String) -> io::Result<usize> {
146        let mut bytes = Vec::new();
147        let count = self.read_to_end(&mut bytes)?;
148        let text =
149            std::str::from_utf8(&bytes).map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?;
150        output.push_str(text);
151        Ok(count)
152    }
153}
154impl<C: Vfsi + ?Sized> Write for StdIo<'_, C> {
155    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
156        self.write_at(buffer).map_err(Into::into)
157    }
158    fn flush(&mut self) -> io::Result<()> {
159        self.client
160            .vfsync(&[self.file], SyncMode::Data)
161            .map_err(Into::into)
162    }
163}
164impl<C: Vfsi + ?Sized> Seek for StdIo<'_, C> {
165    fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
166        let (base, offset) = match position {
167            SeekFrom::Start(position) => {
168                self.position = position;
169                return Ok(position);
170            }
171            SeekFrom::Current(offset) => (self.position, offset),
172            SeekFrom::End(offset) => {
173                let values = self.client.vgetattrs(
174                    &[Target::file(self.file)],
175                    AttrsOptions::new().with_fields(Attributes::SIZE),
176                )?;
177                let [value] = values.as_slice() else {
178                    return Err(invalid("invalid metadata count").into());
179                };
180                (value.len().ok_or_else(|| Error::unsupported(0))?, offset)
181            }
182        };
183        let position = i128::from(base) + i128::from(offset);
184        self.position =
185            u64::try_from(position).map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
186        Ok(self.position)
187    }
188}