Skip to main content

vfsi_sync/
native.rs

1//! Rust-native scalar and vector contracts.
2//!
3//! [`VecFs`] remains the compatibility/backend implementation trait. New
4//! applications should bound generic code by these smaller interfaces.
5
6use crate::traits::{validate_read_results, validate_write_results};
7use crate::*;
8
9/// Core synchronous scalar filesystem operations.
10pub trait FileSystem {
11    fn capabilities(&self) -> Capabilities;
12    fn open_one(&mut self, request: &OpenRequest) -> VfResult<VfFile>;
13    fn close_one(&mut self, file: &VfFile) -> VfResult<()>;
14    fn sync_data(&mut self, file: &VfFile) -> VfResult<()>;
15    fn sync_all(&mut self, file: &VfFile) -> VfResult<()>;
16    fn read_one(&mut self, request: &ReadOp) -> VfResult<ReadResult>;
17    fn write_one(&mut self, request: WriteOpRef<'_>) -> VfResult<WriteResult>;
18    fn seek_one(&mut self, file: &VfFile, position: std::io::SeekFrom) -> VfResult<u64>;
19    fn metadata(&mut self, query: MetadataQuery) -> VfResult<VfAttrs>;
20    fn set_attributes(&mut self, update: SetAttributes) -> VfResult<()>;
21}
22
23/// Path metadata operations independent of open descriptors.
24pub trait MetadataFileSystem: FileSystem {
25    fn metadata_path(&mut self, path: &std::path::Path, follow: bool) -> VfResult<Metadata>;
26    fn set_metadata_path(
27        &mut self,
28        path: &std::path::Path,
29        update: MetadataUpdate,
30        follow: bool,
31    ) -> VfResult<()>;
32}
33
34/// Directory creation and enumeration.
35pub trait DirectoryFileSystem: FileSystem {
36    fn create_dir_one(&mut self, path: &std::path::Path, mode: u32) -> VfResult<()>;
37    fn read_dir_one(
38        &mut self,
39        path: &std::path::Path,
40        options: ReadDirOptions,
41    ) -> VfResult<Vec<DirEntry>>;
42}
43
44/// Namespace mutations shared by files and directories.
45pub trait NamespaceFileSystem: FileSystem {
46    fn remove_one(&mut self, path: &std::path::Path, recursive: bool) -> VfResult<()>;
47    fn rename_one(&mut self, from: &std::path::Path, to: &std::path::Path) -> VfResult<()>;
48}
49
50/// Symbolic and hard-link operations.
51pub trait LinkFileSystem: FileSystem {
52    fn symlink_one(&mut self, target: &std::path::Path, link: &std::path::Path) -> VfResult<()>;
53    fn hard_link_one(&mut self, source: &std::path::Path, link: &std::path::Path) -> VfResult<()>;
54    fn read_link_one(&mut self, path: &std::path::Path) -> VfResult<std::path::PathBuf>;
55}
56
57/// File-copy operations. Backends may accelerate this server-side.
58pub trait CopyFileSystem: FileSystem {
59    fn copy_one(&mut self, source: &std::path::Path, destination: &std::path::Path)
60    -> VfResult<()>;
61}
62
63/// Complete scalar filesystem surface used by ordinary native applications.
64pub trait NativeFileSystem:
65    FileSystem
66    + MetadataFileSystem
67    + DirectoryFileSystem
68    + NamespaceFileSystem
69    + LinkFileSystem
70    + CopyFileSystem
71{
72}
73
74impl<T> NativeFileSystem for T where
75    T: FileSystem
76        + MetadataFileSystem
77        + DirectoryFileSystem
78        + NamespaceFileSystem
79        + LinkFileSystem
80        + CopyFileSystem
81        + ?Sized
82{
83}
84
85/// Optimized ordered vectors. This is deliberately separate from the scalar
86/// contract so a backend can implement scalar semantics without pretending
87/// to support native batching.
88pub trait VectorFileSystem: FileSystem {
89    /// Backend adapter used by [`FsClient::openv`](crate::FsClient::openv).
90    ///
91    /// Success must return exactly one handle per request, in request order.
92    /// On failure, the implementation must release every handle it confirmed
93    /// open before returning; the strict application API cannot receive a
94    /// partial handle vector and perform that cleanup itself.
95    #[doc(hidden)]
96    fn open_many(&mut self, requests: &[OpenRequest]) -> VfResult<Vec<VfFile>>;
97
98    /// Close each handle in request order and attribute failures accordingly.
99    #[doc(hidden)]
100    fn close_many(&mut self, files: &[VfFile]) -> VfResult<()>;
101
102    /// Return exactly one result per request, in request order.
103    #[doc(hidden)]
104    fn read_many(&mut self, requests: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
105
106    /// Return exactly one result per request, in request order.
107    #[doc(hidden)]
108    fn write_many(&mut self, requests: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>>;
109}
110
111fn metadata_mask() -> AttrMask {
112    AttrMask::MODE
113        | AttrMask::SIZE
114        | AttrMask::NLINK
115        | AttrMask::FILEID
116        | AttrMask::UID
117        | AttrMask::GID
118        | AttrMask::ATIME
119        | AttrMask::MTIME
120        | AttrMask::CTIME
121        | AttrMask::CHANGE
122}
123
124impl<T: VecFs + ?Sized> FileSystem for T {
125    fn capabilities(&self) -> Capabilities {
126        self.typed_capabilities()
127    }
128
129    fn open_one(&mut self, request: &OpenRequest) -> VfResult<VfFile> {
130        self.open(
131            request.path.as_path(),
132            request.flags.to_libc()?,
133            request.mode,
134        )
135        .map_err(|error| error.with_context("open", &request.path))
136    }
137
138    fn close_one(&mut self, file: &VfFile) -> VfResult<()> {
139        self.close(file)
140    }
141    fn sync_data(&mut self, file: &VfFile) -> VfResult<()> {
142        VecFs::sync_data(self, file)
143    }
144    fn sync_all(&mut self, file: &VfFile) -> VfResult<()> {
145        VecFs::sync_all(self, file)
146    }
147
148    fn read_one(&mut self, request: &ReadOp) -> VfResult<ReadResult> {
149        let mut results = self.readv(std::slice::from_ref(request))?;
150        validate_read_results("read_one", std::slice::from_ref(request), &results)?;
151        Ok(results.pop().expect("validated one result"))
152    }
153
154    fn write_one(&mut self, request: WriteOpRef<'_>) -> VfResult<WriteResult> {
155        let mut results = self.writev_borrowed(std::slice::from_ref(&request))?;
156        validate_write_results("write_one", std::slice::from_ref(&request), &results)?;
157        Ok(results.pop().expect("validated one result"))
158    }
159
160    fn seek_one(&mut self, file: &VfFile, position: std::io::SeekFrom) -> VfResult<u64> {
161        let (offset, whence) = match position {
162            std::io::SeekFrom::Start(offset) => (
163                i64::try_from(offset).map_err(|_| VfError::failure(0, libc::EOVERFLOW as u32))?,
164                SeekFrom::Set,
165            ),
166            std::io::SeekFrom::End(offset) => (offset, SeekFrom::End),
167            std::io::SeekFrom::Current(offset) => (offset, SeekFrom::Cur),
168        };
169        u64::try_from(self.fseek(file, offset, whence)?).map_err(|_| VfError::failure(0, ERR_INVAL))
170    }
171
172    fn metadata(&mut self, query: MetadataQuery) -> VfResult<VfAttrs> {
173        let path = query.file.path().map(std::path::Path::to_path_buf);
174        let mut attrs = VfAttrs {
175            file: query.file,
176            masks: query.attributes,
177            ..VfAttrs::default()
178        };
179        let result = if query.follow_symlinks {
180            self.getattrsv(std::slice::from_mut(&mut attrs))
181        } else {
182            self.lgetattrsv(std::slice::from_mut(&mut attrs))
183        };
184        result.map_err(|error| match path {
185            Some(path) => error.with_context("metadata", path),
186            None => error,
187        })?;
188        Ok(attrs)
189    }
190
191    fn set_attributes(&mut self, update: SetAttributes) -> VfResult<()> {
192        let follow = update.follow_symlinks;
193        let attrs = update.into_legacy();
194        let path = attrs.file.path().map(std::path::Path::to_path_buf);
195        let result = if follow {
196            self.setattrsv(&[attrs])
197        } else {
198            self.lsetattrsv(&[attrs])
199        };
200        result.map_err(|error| match path {
201            Some(path) => error.with_context("set_attributes", path),
202            None => error,
203        })
204    }
205}
206
207impl<T: VecFs + ?Sized> MetadataFileSystem for T {
208    fn metadata_path(&mut self, path: &std::path::Path, follow: bool) -> VfResult<Metadata> {
209        let mut attributes = VfAttrs {
210            file: VfFile::from_os_path(path),
211            masks: metadata_mask(),
212            ..VfAttrs::default()
213        };
214        let result = if follow {
215            self.getattrsv(std::slice::from_mut(&mut attributes))
216        } else {
217            self.lgetattrsv(std::slice::from_mut(&mut attributes))
218        };
219        result
220            .map_err(|error| error.with_context("metadata", path))
221            .map(|()| attributes.into())
222    }
223
224    fn set_metadata_path(
225        &mut self,
226        path: &std::path::Path,
227        update: MetadataUpdate,
228        follow: bool,
229    ) -> VfResult<()> {
230        let mut attributes = SetAttributes::new(VfFile::from_os_path(path));
231        attributes.follow_symlinks = follow;
232        attributes.mode = update.permissions.map(Permissions::mode);
233        attributes.size = update.len;
234        attributes.atime = update.accessed.map(system_time_parts).transpose()?;
235        attributes.mtime = update.modified.map(system_time_parts).transpose()?;
236        self.set_attributes(attributes)
237            .map_err(|error| error.with_context("set_metadata", path))
238    }
239}
240
241fn system_time_parts(time: std::time::SystemTime) -> VfResult<(i64, u32)> {
242    match time.duration_since(std::time::UNIX_EPOCH) {
243        Ok(duration) => Ok((
244            i64::try_from(duration.as_secs())
245                .map_err(|_| VfError::client(0, libc::EOVERFLOW as u32))?,
246            duration.subsec_nanos(),
247        )),
248        Err(error) => {
249            let duration = error.duration();
250            let seconds = i64::try_from(duration.as_secs())
251                .map_err(|_| VfError::client(0, libc::EOVERFLOW as u32))?;
252            if duration.subsec_nanos() == 0 {
253                Ok((-seconds, 0))
254            } else {
255                let seconds = seconds
256                    .checked_add(1)
257                    .ok_or_else(|| VfError::client(0, libc::EOVERFLOW as u32))?;
258                Ok((-seconds, 1_000_000_000 - duration.subsec_nanos()))
259            }
260        }
261    }
262}
263
264impl<T: VecFs + ?Sized> DirectoryFileSystem for T {
265    fn create_dir_one(&mut self, path: &std::path::Path, mode: u32) -> VfResult<()> {
266        self.mkdir(path, mode)
267            .map_err(|error| error.with_context("create_dir", path))
268    }
269
270    fn read_dir_one(
271        &mut self,
272        path: &std::path::Path,
273        options: ReadDirOptions,
274    ) -> VfResult<Vec<DirEntry>> {
275        let requested = options.entry_limit().saturating_add(1);
276        let entries = self
277            .listdir(path, metadata_mask(), requested, false)
278            .map_err(|error| error.with_context("read_dir", path))?;
279        if entries.len() > options.entry_limit() {
280            return Err(VfError::failure(options.entry_limit(), libc::EFBIG as u32)
281                .with_context("read_dir", path));
282        }
283        let mut path_bytes = 0usize;
284        entries
285            .into_iter()
286            .enumerate()
287            .map(|(index, attributes)| {
288                let entry_path = attributes
289                    .file
290                    .path()
291                    .map(std::path::Path::to_path_buf)
292                    .ok_or_else(|| VfError::client(index, ERR_IO).with_context("read_dir", path))?;
293                path_bytes = path_bytes
294                    .checked_add(entry_path.as_os_str().len())
295                    .ok_or_else(|| {
296                        VfError::failure(index, libc::EFBIG as u32).with_context("read_dir", path)
297                    })?;
298                if path_bytes > options.path_byte_limit() {
299                    return Err(
300                        VfError::failure(index, libc::EFBIG as u32).with_context("read_dir", path)
301                    );
302                }
303                Ok(DirEntry::new(entry_path, attributes.into()))
304            })
305            .collect()
306    }
307}
308
309impl<T: VecFs + ?Sized> NamespaceFileSystem for T {
310    fn remove_one(&mut self, path: &std::path::Path, recursive: bool) -> VfResult<()> {
311        self.rm(&[path], recursive)
312            .map_err(|error| error.with_context("remove", path))
313    }
314
315    fn rename_one(&mut self, from: &std::path::Path, to: &std::path::Path) -> VfResult<()> {
316        self.renamev(&[(VfFile::from_os_path(from), VfFile::from_os_path(to))])
317            .map_err(|error| error.with_context("rename", from))
318    }
319}
320
321impl<T: VecFs + ?Sized> LinkFileSystem for T {
322    fn symlink_one(&mut self, target: &std::path::Path, link: &std::path::Path) -> VfResult<()> {
323        self.symlink(target, link)
324            .map_err(|error| error.with_context("symlink", link))
325    }
326
327    fn hard_link_one(&mut self, source: &std::path::Path, link: &std::path::Path) -> VfResult<()> {
328        self.hardlinkv(&[source], &[link])
329            .map_err(|error| error.with_context("hard_link", link))
330    }
331
332    fn read_link_one(&mut self, path: &std::path::Path) -> VfResult<std::path::PathBuf> {
333        self.readlink(path)
334            .map(bytes_to_path)
335            .map_err(|error| error.with_context("read_link", path))
336    }
337}
338
339#[cfg(unix)]
340fn bytes_to_path(bytes: Vec<u8>) -> std::path::PathBuf {
341    use std::os::unix::ffi::OsStringExt;
342    std::ffi::OsString::from_vec(bytes).into()
343}
344
345#[cfg(not(unix))]
346fn bytes_to_path(bytes: Vec<u8>) -> std::path::PathBuf {
347    String::from_utf8_lossy(&bytes).into_owned().into()
348}
349
350impl<T: VecFs + ?Sized> CopyFileSystem for T {
351    fn copy_one(
352        &mut self,
353        source: &std::path::Path,
354        destination: &std::path::Path,
355    ) -> VfResult<()> {
356        self.copyv(&[ExtentPair::from_os_paths(source, 0, destination, 0, None)])
357            .map_err(|error| error.with_context("copy", source))
358    }
359}
360
361impl<T: VecFs + ?Sized> VectorFileSystem for T {
362    fn open_many(&mut self, requests: &[OpenRequest]) -> VfResult<Vec<VfFile>> {
363        let paths: Vec<&std::path::Path> = requests
364            .iter()
365            .map(|request| request.path.as_path())
366            .collect();
367        let flags = translate_open_flags(requests)?;
368        let modes: Vec<u32> = requests.iter().map(|request| request.mode).collect();
369        VecFs::openv(self, &paths, &flags, &modes)
370    }
371
372    fn close_many(&mut self, files: &[VfFile]) -> VfResult<()> {
373        self.closev(files)
374    }
375
376    fn read_many(&mut self, requests: &[ReadOp]) -> VfResult<Vec<ReadResult>> {
377        self.readv(requests)
378    }
379
380    fn write_many(&mut self, requests: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
381        self.writev_borrowed(requests)
382    }
383}
384
385fn translate_open_flags(requests: &[OpenRequest]) -> VfResult<Vec<i32>> {
386    requests
387        .iter()
388        .enumerate()
389        .map(|(index, request)| {
390            request
391                .flags
392                .to_libc()
393                .map_err(|error| error.with_index(index))
394        })
395        .collect()
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    #[test]
403    fn invalid_open_flags_retain_their_request_index() {
404        let error = translate_open_flags(&[
405            OpenRequest::new("/valid", OpenFlags::READ),
406            OpenRequest::new("/invalid", OpenFlags::empty()),
407        ])
408        .unwrap_err();
409        assert_eq!(error.index_opt(), Some(1));
410    }
411}