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        let length = buffer.len().min(budget);
41        if !buffer.is_empty() && length == 0 {
42            return Err(Error::client(0, libc::EFBIG as u32));
43        }
44        self.position
45            .checked_add(length as u64)
46            .ok_or_else(|| Error::client(0, libc::EOVERFLOW as u32))?;
47        let values = self.client.vread(
48            [ReadOp::into(
49                self.file,
50                self.position,
51                &mut buffer[..length],
52            )],
53            ReadOptions::new().max_total_bytes(std::num::NonZeroUsize::new(budget)),
54        )?;
55        let [value] = values.as_slice() else {
56            return Err(invalid("invalid read count"));
57        };
58        if value.offset() != self.position
59            || value.data().is_some()
60            || value.read() > length
61            || (length != 0 && value.read() == 0 && !value.eof())
62        {
63            return Err(invalid("invalid read progress"));
64        }
65        self.position += value.read() as u64;
66        Ok((value.read(), value.eof()))
67    }
68    fn read_uninterrupted(&mut self, buffer: &mut [u8], budget: usize) -> Result<(usize, bool)> {
69        loop {
70            match self.read_at(buffer, budget) {
71                Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
72                result => return result,
73            }
74        }
75    }
76    fn write_at(&mut self, buffer: &[u8]) -> Result<usize> {
77        self.position
78            .checked_add(buffer.len() as u64)
79            .ok_or_else(|| Error::client(0, libc::EOVERFLOW as u32))?;
80        let values = self.client.vwrite(
81            &[WriteOp::at(self.file, self.position, buffer)],
82            WriteOptions::new(),
83        )?;
84        let [value] = values.as_slice() else {
85            return Err(invalid("invalid write count"));
86        };
87        if value.written > buffer.len() {
88            return Err(invalid("invalid write progress"));
89        }
90        // Append descriptors report the actual position selected by the backend.
91        self.position = value
92            .offset
93            .checked_add(value.written as u64)
94            .ok_or_else(|| invalid("write offset overflow"))?;
95        Ok(value.written)
96    }
97}
98fn invalid(message: &'static str) -> Error {
99    Error::transport_with_kind(None, crate::TransportKind::InvalidReply, message)
100}
101impl<C: Vfsi + ?Sized> Read for StdIo<'_, C> {
102    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
103        if buffer.is_empty() {
104            return Ok(0);
105        }
106        self.read_at(buffer, self.client.limits().read_byte_limit())
107            .map(|(count, _)| count)
108            .map_err(Into::into)
109    }
110    fn read_to_end(&mut self, output: &mut Vec<u8>) -> io::Result<usize> {
111        let budget = self.client.limits().read_byte_limit();
112        let mut added = 0;
113        loop {
114            let remaining = budget - added;
115            let length = remaining.min(super::DEFAULT_READ_STREAM_CHUNK_BYTES);
116            if length == 0 {
117                // Distinguish exact-budget EOF from oversized content without
118                // allocating beyond the budget. A failed probe advances cursor.
119                let (count, _) = self.read_uninterrupted(&mut [0; 1], 1)?;
120                return if count == 0 {
121                    Ok(added)
122                } else {
123                    Err(Error::client(0, libc::EFBIG as u32).into())
124                };
125            }
126            // Reserve before I/O and fill caller storage directly. Reuse the
127            // stream chunk policy without an additional scratch allocation.
128            output
129                .try_reserve(length)
130                .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?;
131            let start = output.len();
132            output.resize(start + length, 0);
133            let result = self.read_uninterrupted(&mut output[start..], remaining);
134            output.truncate(start + result.as_ref().map_or(0, |(count, _)| *count));
135            let (count, eof) = result?;
136            added += count;
137            if eof {
138                return Ok(added);
139            }
140        }
141    }
142    fn read_to_string(&mut self, output: &mut String) -> io::Result<usize> {
143        let mut bytes = Vec::new();
144        let count = self.read_to_end(&mut bytes)?;
145        let text =
146            std::str::from_utf8(&bytes).map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?;
147        output.push_str(text);
148        Ok(count)
149    }
150}
151impl<C: Vfsi + ?Sized> Write for StdIo<'_, C> {
152    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
153        self.write_at(buffer).map_err(Into::into)
154    }
155    fn flush(&mut self) -> io::Result<()> {
156        self.client
157            .vfsync(&[self.file], SyncMode::Data)
158            .map_err(Into::into)
159    }
160}
161impl<C: Vfsi + ?Sized> Seek for StdIo<'_, C> {
162    fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
163        let (base, offset) = match position {
164            SeekFrom::Start(position) => {
165                self.position = position;
166                return Ok(position);
167            }
168            SeekFrom::Current(offset) => (self.position, offset),
169            SeekFrom::End(offset) => {
170                let values = self.client.vgetattrs(
171                    &[Target::file(self.file)],
172                    AttrsOptions::new().fields(Attributes::SIZE),
173                )?;
174                let [value] = values.as_slice() else {
175                    return Err(invalid("invalid metadata count").into());
176                };
177                (value.len().ok_or_else(|| Error::unsupported(0))?, offset)
178            }
179        };
180        let position = i128::from(base) + i128::from(offset);
181        self.position =
182            u64::try_from(position).map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?;
183        Ok(self.position)
184    }
185}