Skip to main content

vfsi_sync/
io.rs

1use std::io::{self, Read, Seek, SeekFrom as IoSeekFrom, Write};
2use std::path::Path;
3
4use crate::traits::{validate_read_results, validate_write_results};
5use crate::{ReadOp, SeekFrom, VecFs, VfFile, VfOffset};
6
7fn io_error(error: crate::VfError) -> io::Error {
8    let kind = match error.err_no() {
9        crate::ERR_NOENT => io::ErrorKind::NotFound,
10        crate::ERR_EXIST => io::ErrorKind::AlreadyExists,
11        crate::ERR_ACCES => io::ErrorKind::PermissionDenied,
12        crate::ERR_NOTDIR => io::ErrorKind::NotADirectory,
13        crate::ERR_ISDIR => io::ErrorKind::IsADirectory,
14        crate::ERR_INVAL | crate::ERR_EBADF => io::ErrorKind::InvalidInput,
15        _ => io::ErrorKind::Other,
16    };
17    io::Error::new(kind, error)
18}
19
20/// Idiomatic Rust open options for a VFSI filesystem.
21#[derive(Clone, Debug)]
22pub struct VfOpenOptions {
23    read: bool,
24    write: bool,
25    append: bool,
26    truncate: bool,
27    create: bool,
28    create_new: bool,
29    mode: u32,
30}
31
32impl Default for VfOpenOptions {
33    fn default() -> Self {
34        Self {
35            read: false,
36            write: false,
37            append: false,
38            truncate: false,
39            create: false,
40            create_new: false,
41            mode: 0o666,
42        }
43    }
44}
45
46impl VfOpenOptions {
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    pub fn read(&mut self, value: bool) -> &mut Self {
52        self.read = value;
53        self
54    }
55
56    pub fn write(&mut self, value: bool) -> &mut Self {
57        self.write = value;
58        self
59    }
60
61    pub fn append(&mut self, value: bool) -> &mut Self {
62        self.append = value;
63        self
64    }
65
66    pub fn truncate(&mut self, value: bool) -> &mut Self {
67        self.truncate = value;
68        self
69    }
70
71    pub fn create(&mut self, value: bool) -> &mut Self {
72        self.create = value;
73        self
74    }
75
76    pub fn create_new(&mut self, value: bool) -> &mut Self {
77        self.create_new = value;
78        self
79    }
80
81    /// Unix permission bits used when a file is created.
82    pub fn mode(&mut self, mode: u32) -> &mut Self {
83        self.mode = mode;
84        self
85    }
86
87    fn flags(&self) -> io::Result<i32> {
88        let writable = self.write || self.append;
89        let mut flags = match (self.read, writable) {
90            (true, true) => libc::O_RDWR,
91            (false, true) => libc::O_WRONLY,
92            (true, false) => libc::O_RDONLY,
93            (false, false) => {
94                return Err(io::Error::new(
95                    io::ErrorKind::InvalidInput,
96                    "at least one of read, write, or append must be enabled",
97                ));
98            }
99        };
100        if self.truncate && !writable {
101            return Err(io::Error::new(
102                io::ErrorKind::InvalidInput,
103                "truncate requires write or append access",
104            ));
105        }
106        if (self.create || self.create_new) && !writable {
107            return Err(io::Error::new(
108                io::ErrorKind::InvalidInput,
109                "create requires write or append access",
110            ));
111        }
112        if self.append {
113            flags |= libc::O_APPEND;
114        }
115        if self.truncate {
116            flags |= libc::O_TRUNC;
117        }
118        if self.create || self.create_new {
119            flags |= libc::O_CREAT;
120        }
121        if self.create_new {
122            flags |= libc::O_EXCL;
123        }
124        Ok(flags)
125    }
126
127    /// Open a file whose lifetime is tied to the mutable filesystem borrow.
128    pub fn open<'a, F: VecFs + ?Sized>(
129        &self,
130        filesystem: &'a mut F,
131        path: impl AsRef<Path>,
132    ) -> io::Result<VfFileHandle<'a, F>> {
133        let file = filesystem
134            .open(path.as_ref(), self.flags()?, self.mode)
135            .map_err(io_error)?;
136        Ok(VfFileHandle {
137            filesystem,
138            file: Some(file),
139        })
140    }
141}
142
143/// RAII descriptor adapter implementing standard synchronous Rust I/O traits.
144///
145/// Dropping the handle closes the remote descriptor on a best-effort basis.
146/// Use [`close`](Self::close) when a close error must be observed.
147pub struct VfFileHandle<'a, F: VecFs + ?Sized> {
148    filesystem: &'a mut F,
149    file: Option<VfFile>,
150}
151
152impl<F: VecFs + ?Sized> VfFileHandle<'_, F> {
153    /// Descriptor of an open handle. Use [`try_descriptor`](Self::try_descriptor)
154    /// when the handle might already have been closed with `try_close`.
155    pub fn descriptor(&self) -> &VfFile {
156        self.file.as_ref().expect("open handle has a descriptor")
157    }
158
159    pub fn try_descriptor(&self) -> io::Result<&VfFile> {
160        self.file
161            .as_ref()
162            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "file handle is closed"))
163    }
164
165    /// Attempt CLOSE while retaining this handle if the backend reports an
166    /// error. A second attempt should follow the backend's recovery policy.
167    pub fn try_close(&mut self) -> io::Result<()> {
168        let Some(file) = self.file.as_ref() else {
169            return Ok(());
170        };
171        self.filesystem.close(file).map_err(io_error)?;
172        self.file = None;
173        Ok(())
174    }
175
176    /// Consume and close the handle. On failure, `Drop` attempts cleanup.
177    pub fn close(mut self) -> io::Result<()> {
178        self.try_close()
179    }
180}
181
182impl<F: VecFs + ?Sized> Read for VfFileHandle<'_, F> {
183    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
184        if buffer.is_empty() {
185            return Ok(0);
186        }
187        let file = self.try_descriptor()?.clone();
188        let requests = [ReadOp::new(file, VfOffset::Cur, buffer.len())];
189        let mut results = self.filesystem.readv(&requests).map_err(io_error)?;
190        validate_read_results("Read::read", &requests, &results).map_err(io_error)?;
191        let result = results.pop().expect("validated one read result");
192        buffer[..result.data.len()].copy_from_slice(&result.data);
193        Ok(result.data.len())
194    }
195}
196
197impl<F: VecFs + ?Sized> Write for VfFileHandle<'_, F> {
198    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
199        if buffer.is_empty() {
200            return Ok(0);
201        }
202        let file = self.try_descriptor()?.clone();
203        let requests = [crate::WriteOpRef::new(&file, VfOffset::Cur, buffer)];
204        let mut results = self
205            .filesystem
206            .writev_borrowed(&requests)
207            .map_err(io_error)?;
208        validate_write_results("Write::write", &requests, &results).map_err(io_error)?;
209        Ok(results.pop().expect("validated one write result").written)
210    }
211
212    fn flush(&mut self) -> io::Result<()> {
213        let file = self.try_descriptor()?.clone();
214        self.filesystem.sync_data(&file).map_err(io_error)
215    }
216}
217
218impl<F: VecFs + ?Sized> Seek for VfFileHandle<'_, F> {
219    fn seek(&mut self, position: IoSeekFrom) -> io::Result<u64> {
220        let file = self.try_descriptor()?.clone();
221        let (offset, whence) = match position {
222            IoSeekFrom::Start(offset) => (
223                i64::try_from(offset).map_err(|_| {
224                    io::Error::new(io::ErrorKind::InvalidInput, "seek offset exceeds i64")
225                })?,
226                SeekFrom::Set,
227            ),
228            IoSeekFrom::End(offset) => (offset, SeekFrom::End),
229            IoSeekFrom::Current(offset) => (offset, SeekFrom::Cur),
230        };
231        let result = self
232            .filesystem
233            .fseek(&file, offset, whence)
234            .map_err(io_error)?;
235        u64::try_from(result)
236            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "negative seek result"))
237    }
238}
239
240impl<F: VecFs + ?Sized> Drop for VfFileHandle<'_, F> {
241    fn drop(&mut self) {
242        if let Some(file) = self.file.take() {
243            let _ = self.filesystem.close(&file);
244        }
245    }
246}