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_into_results, validate_read_results, validate_write_results};
7use crate::*;
8
9/// Core synchronous scalar filesystem operations.
10pub trait FileSystem {
11    fn capabilities(&self) -> Capabilities;
12    /// Read a complete opened file with a logical payload limit. Specialized backends
13    /// override this to share their optimized vector whole-file path.
14    fn read_file(&mut self, file: &VfFile, max_bytes: usize) -> VfResult<Vec<u8>> {
15        let mut output = Vec::new();
16        loop {
17            let remaining = max_bytes.saturating_sub(output.len());
18            let request = ReadOp::new(
19                file.clone(),
20                VfOffset::At(output.len() as u64),
21                remaining.clamp(1, 1024 * 1024),
22            );
23            let result = self.read_one(&request)?;
24            validate_read_results(
25                "read_file",
26                std::slice::from_ref(&request),
27                std::slice::from_ref(&result),
28            )?;
29            if result.data.len() > remaining {
30                return Err(VfError::client(0, libc::EFBIG as u32));
31            }
32            if result.data.is_empty() && !result.eof {
33                return Err(VfError::client(0, ERR_IO));
34            }
35            output.extend_from_slice(&result.data);
36            if result.eof {
37                return Ok(output);
38            }
39        }
40    }
41    fn open_one(&mut self, request: &OpenRequest) -> VfResult<VfFile>;
42    fn close_one(&mut self, file: &VfFile) -> VfResult<()>;
43    fn sync_data(&mut self, file: &VfFile) -> VfResult<()>;
44    fn sync_all(&mut self, file: &VfFile) -> VfResult<()>;
45    fn read_one(&mut self, request: &ReadOp) -> VfResult<ReadResult>;
46    /// Read directly into caller storage when supported by the backend.
47    fn read_one_into(&mut self, request: &ReadOp, buffer: &mut [u8]) -> VfResult<ReadIntoResult> {
48        if request.length != buffer.len() {
49            return Err(VfError::client(0, ERR_INVAL));
50        }
51        let result = self.read_one(request)?;
52        if result.data.len() > buffer.len() {
53            return Err(VfError::client(0, ERR_IO));
54        }
55        validate_read_results(
56            "read_one_into",
57            std::slice::from_ref(request),
58            std::slice::from_ref(&result),
59        )?;
60        buffer[..result.data.len()].copy_from_slice(&result.data);
61        Ok(ReadIntoResult {
62            file: result.file,
63            offset: result.offset,
64            read: result.data.len(),
65            eof: result.eof,
66        })
67    }
68    fn write_one(&mut self, request: WriteOpRef<'_>) -> VfResult<WriteResult>;
69    fn seek_one(&mut self, file: &VfFile, position: std::io::SeekFrom) -> VfResult<u64>;
70    fn metadata(&mut self, query: MetadataQuery) -> VfResult<VfAttrs>;
71    fn set_attributes(&mut self, update: SetAttributes) -> VfResult<()>;
72}
73
74/// Path metadata operations independent of open descriptors.
75pub trait MetadataFileSystem: FileSystem {
76    fn metadata_path(&mut self, path: &std::path::Path, follow: bool) -> VfResult<Metadata>;
77    fn set_metadata_path(
78        &mut self,
79        path: &std::path::Path,
80        update: MetadataUpdate,
81        follow: bool,
82    ) -> VfResult<()>;
83}
84
85/// Directory creation and enumeration.
86pub trait DirectoryFileSystem: FileSystem {
87    fn create_dir_one(&mut self, path: &std::path::Path, mode: u32) -> VfResult<()>;
88    fn read_dir_one(
89        &mut self,
90        path: &std::path::Path,
91        options: ReadDirOptions,
92    ) -> VfResult<Vec<DirEntry>>;
93
94    /// Return one bounded page without invoking application code under a
95    /// mutable backend borrow. The continuation token is backend-specific.
96    fn read_dir_page(
97        &mut self,
98        path: &std::path::Path,
99        cursor: Option<DirPageCursor>,
100        page_size: usize,
101        max_entries: usize,
102    ) -> VfResult<(Vec<DirEntry>, Option<DirPageCursor>)>;
103}
104
105/// Namespace mutations shared by files and directories.
106pub trait NamespaceFileSystem: FileSystem {
107    fn remove_one(&mut self, path: &std::path::Path, recursive: bool) -> VfResult<()>;
108    /// Remove the contents of a directory, keeping the directory itself.
109    fn remove_dir_contents(&mut self, path: &std::path::Path) -> VfResult<()>;
110    fn rename_one(&mut self, from: &std::path::Path, to: &std::path::Path) -> VfResult<()>;
111}
112
113/// Symbolic and hard-link operations.
114pub trait LinkFileSystem: FileSystem {
115    fn symlink_one(&mut self, target: &std::path::Path, link: &std::path::Path) -> VfResult<()>;
116    fn hard_link_one(&mut self, source: &std::path::Path, link: &std::path::Path) -> VfResult<()>;
117    fn read_link_one(&mut self, path: &std::path::Path) -> VfResult<std::path::PathBuf>;
118}
119
120/// File-copy operations. Backends may accelerate this server-side.
121pub trait CopyFileSystem: FileSystem {
122    fn copy_one(&mut self, source: &std::path::Path, destination: &std::path::Path)
123    -> VfResult<()>;
124}
125
126/// Complete scalar filesystem surface used by ordinary native applications.
127pub trait NativeFileSystem:
128    FileSystem
129    + MetadataFileSystem
130    + DirectoryFileSystem
131    + NamespaceFileSystem
132    + LinkFileSystem
133    + CopyFileSystem
134{
135}
136
137impl<T> NativeFileSystem for T where
138    T: FileSystem
139        + MetadataFileSystem
140        + DirectoryFileSystem
141        + NamespaceFileSystem
142        + LinkFileSystem
143        + CopyFileSystem
144        + ?Sized
145{
146}
147
148/// Optimized ordered vectors. This is deliberately separate from the scalar
149/// contract so a backend can implement scalar semantics without pretending
150/// to support native batching.
151pub trait VectorFileSystem: FileSystem {
152    /// Backend adapter used by [`FsClient::openv`](crate::FsClient::openv).
153    ///
154    /// Success must return exactly one handle per request, in request order.
155    /// On failure, the implementation must release every handle it confirmed
156    /// open before returning; the strict application API cannot receive a
157    /// partial handle vector and perform that cleanup itself.
158    #[doc(hidden)]
159    fn open_many(&mut self, requests: &[OpenRequest]) -> VfResult<Vec<VfFile>>;
160
161    /// Close each handle in request order and attribute failures accordingly.
162    #[doc(hidden)]
163    fn close_many(&mut self, files: &[VfFile]) -> VfResult<()>;
164
165    /// Return exactly one result per request, in request order.
166    #[doc(hidden)]
167    fn read_many(&mut self, requests: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
168
169    /// Vector read into caller storage. Backends can override the copying
170    /// fallback without changing the owned-result `read_many` contract.
171    fn read_many_into(
172        &mut self,
173        requests: &[ReadOp],
174        buffers: &mut [&mut [u8]],
175    ) -> VfResult<Vec<ReadIntoResult>> {
176        if requests.len() != buffers.len() {
177            return Err(VfError::client(0, ERR_INVAL));
178        }
179        for (index, (request, buffer)) in requests.iter().zip(buffers.iter()).enumerate() {
180            if request.length != buffer.len() {
181                return Err(VfError::client(index, ERR_INVAL));
182            }
183        }
184        let results = self.read_many(requests)?;
185        validate_read_results("read_many_into", requests, &results)?;
186        Ok(results
187            .into_iter()
188            .zip(buffers.iter_mut())
189            .map(|(result, buffer)| {
190                buffer[..result.data.len()].copy_from_slice(&result.data);
191                ReadIntoResult {
192                    file: result.file,
193                    offset: result.offset,
194                    read: result.data.len(),
195                    eof: result.eof,
196                }
197            })
198            .collect())
199    }
200
201    /// Return exactly one result per request, in request order.
202    #[doc(hidden)]
203    fn write_many(&mut self, requests: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>>;
204}
205
206fn metadata_mask() -> AttrMask {
207    AttrMask::MODE
208        | AttrMask::SIZE
209        | AttrMask::NLINK
210        | AttrMask::FILEID
211        | AttrMask::UID
212        | AttrMask::GID
213        | AttrMask::ATIME
214        | AttrMask::MTIME
215        | AttrMask::CTIME
216        | AttrMask::CHANGE
217}
218
219impl<T: VecFs + ?Sized> FileSystem for T {
220    fn capabilities(&self) -> Capabilities {
221        self.typed_capabilities()
222    }
223
224    fn read_file(&mut self, file: &VfFile, max_bytes: usize) -> VfResult<Vec<u8>> {
225        self.read_allv_with_options(
226            std::slice::from_ref(file),
227            ReadAllOptions::new().max_total_bytes(max_bytes),
228        )
229        .and_then(|mut results| {
230            if results.len() != 1 {
231                return Err(VfError::transport(
232                    None,
233                    "read_file backend returned an invalid result count",
234                ));
235            }
236            let data = results.pop().expect("validated result count");
237            if data.len() > max_bytes {
238                return Err(VfError::client(0, libc::EFBIG as u32));
239            }
240            Ok(data)
241        })
242    }
243
244    fn open_one(&mut self, request: &OpenRequest) -> VfResult<VfFile> {
245        self.open(
246            request.path.as_path(),
247            vfsi_core::open_flags_to_libc(request.flags)?,
248            request.mode,
249        )
250        .map_err(|error| error.with_context("open", &request.path))
251    }
252
253    fn close_one(&mut self, file: &VfFile) -> VfResult<()> {
254        self.close(file)
255    }
256    fn sync_data(&mut self, file: &VfFile) -> VfResult<()> {
257        VecFs::sync_data(self, file)
258    }
259    fn sync_all(&mut self, file: &VfFile) -> VfResult<()> {
260        VecFs::sync_all(self, file)
261    }
262
263    fn read_one(&mut self, request: &ReadOp) -> VfResult<ReadResult> {
264        let mut results = self.readv(std::slice::from_ref(request))?;
265        validate_read_results("read_one", std::slice::from_ref(request), &results)?;
266        Ok(results.pop().expect("validated one result"))
267    }
268
269    fn read_one_into(&mut self, request: &ReadOp, buffer: &mut [u8]) -> VfResult<ReadIntoResult> {
270        let mut results = self.readv_into(std::slice::from_ref(request), &mut [buffer])?;
271        validate_read_into_results("read_one_into", std::slice::from_ref(request), &results)?;
272        Ok(results.pop().expect("validated one result"))
273    }
274
275    fn write_one(&mut self, request: WriteOpRef<'_>) -> VfResult<WriteResult> {
276        let mut results = self.writev_borrowed(std::slice::from_ref(&request))?;
277        validate_write_results("write_one", std::slice::from_ref(&request), &results)?;
278        Ok(results.pop().expect("validated one result"))
279    }
280
281    fn seek_one(&mut self, file: &VfFile, position: std::io::SeekFrom) -> VfResult<u64> {
282        let (offset, whence) = match position {
283            std::io::SeekFrom::Start(offset) => (
284                i64::try_from(offset).map_err(|_| VfError::failure(0, libc::EOVERFLOW as u32))?,
285                SeekFrom::Set,
286            ),
287            std::io::SeekFrom::End(offset) => (offset, SeekFrom::End),
288            std::io::SeekFrom::Current(offset) => (offset, SeekFrom::Cur),
289        };
290        u64::try_from(self.fseek(file, offset, whence)?).map_err(|_| VfError::failure(0, ERR_INVAL))
291    }
292
293    fn metadata(&mut self, query: MetadataQuery) -> VfResult<VfAttrs> {
294        let path = query.file.path().map(std::path::Path::to_path_buf);
295        let mut attrs = VfAttrs {
296            file: query.file,
297            masks: query.attributes,
298            ..VfAttrs::default()
299        };
300        let result = if query.follow_symlinks {
301            self.getattrsv(std::slice::from_mut(&mut attrs))
302        } else {
303            self.lgetattrsv(std::slice::from_mut(&mut attrs))
304        };
305        result.map_err(|error| match path {
306            Some(path) => error.with_context("metadata", path),
307            None => error,
308        })?;
309        Ok(attrs)
310    }
311
312    fn set_attributes(&mut self, update: SetAttributes) -> VfResult<()> {
313        let follow = update.follow_symlinks;
314        let attrs = update.into_legacy();
315        let path = attrs.file.path().map(std::path::Path::to_path_buf);
316        let result = if follow {
317            self.setattrsv(&[attrs])
318        } else {
319            self.lsetattrsv(&[attrs])
320        };
321        result.map_err(|error| match path {
322            Some(path) => error.with_context("set_attributes", path),
323            None => error,
324        })
325    }
326}
327
328impl<T: VecFs + ?Sized> MetadataFileSystem for T {
329    fn metadata_path(&mut self, path: &std::path::Path, follow: bool) -> VfResult<Metadata> {
330        let mut attributes = VfAttrs {
331            file: VfFile::from_os_path(path),
332            masks: metadata_mask(),
333            ..VfAttrs::default()
334        };
335        let result = if follow {
336            self.getattrsv(std::slice::from_mut(&mut attributes))
337        } else {
338            self.lgetattrsv(std::slice::from_mut(&mut attributes))
339        };
340        result
341            .map_err(|error| error.with_context("metadata", path))
342            .map(|()| vfsi_core::metadata_from_attrs(attributes))
343    }
344
345    fn set_metadata_path(
346        &mut self,
347        path: &std::path::Path,
348        update: MetadataUpdate,
349        follow: bool,
350    ) -> VfResult<()> {
351        let mut attributes = SetAttributes::new(VfFile::from_os_path(path));
352        attributes.follow_symlinks = follow;
353        attributes.mode = update.permissions.map(Permissions::mode);
354        attributes.size = update.len;
355        attributes.atime = update.accessed.map(system_time_parts).transpose()?;
356        attributes.mtime = update.modified.map(system_time_parts).transpose()?;
357        self.set_attributes(attributes)
358            .map_err(|error| error.with_context("set_metadata", path))
359    }
360}
361
362fn system_time_parts(time: std::time::SystemTime) -> VfResult<(i64, u32)> {
363    match time.duration_since(std::time::UNIX_EPOCH) {
364        Ok(duration) => Ok((
365            i64::try_from(duration.as_secs())
366                .map_err(|_| VfError::client(0, libc::EOVERFLOW as u32))?,
367            duration.subsec_nanos(),
368        )),
369        Err(error) => {
370            let duration = error.duration();
371            let seconds = i64::try_from(duration.as_secs())
372                .map_err(|_| VfError::client(0, libc::EOVERFLOW as u32))?;
373            if duration.subsec_nanos() == 0 {
374                Ok((-seconds, 0))
375            } else {
376                let seconds = seconds
377                    .checked_add(1)
378                    .ok_or_else(|| VfError::client(0, libc::EOVERFLOW as u32))?;
379                Ok((-seconds, 1_000_000_000 - duration.subsec_nanos()))
380            }
381        }
382    }
383}
384
385impl<T: VecFs + ?Sized> DirectoryFileSystem for T {
386    fn create_dir_one(&mut self, path: &std::path::Path, mode: u32) -> VfResult<()> {
387        self.mkdir(path, mode)
388            .map_err(|error| error.with_context("create_dir", path))
389    }
390
391    fn read_dir_one(
392        &mut self,
393        path: &std::path::Path,
394        options: ReadDirOptions,
395    ) -> VfResult<Vec<DirEntry>> {
396        let requested = options.entry_limit().saturating_add(1);
397        let entries = self
398            .listdir(path, metadata_mask(), requested, false)
399            .map_err(|error| error.with_context("read_dir", path))?;
400        if entries.len() > options.entry_limit() {
401            return Err(VfError::failure(options.entry_limit(), libc::EFBIG as u32)
402                .with_context("read_dir", path));
403        }
404        let mut path_bytes = 0usize;
405        entries
406            .into_iter()
407            .enumerate()
408            .map(|(index, attributes)| {
409                let entry_path = attributes
410                    .file
411                    .path()
412                    .map(std::path::Path::to_path_buf)
413                    .ok_or_else(|| VfError::client(index, ERR_IO).with_context("read_dir", path))?;
414                path_bytes = path_bytes
415                    .checked_add(entry_path.as_os_str().len())
416                    .ok_or_else(|| {
417                        VfError::failure(index, libc::EFBIG as u32).with_context("read_dir", path)
418                    })?;
419                if path_bytes > options.path_byte_limit() {
420                    return Err(
421                        VfError::failure(index, libc::EFBIG as u32).with_context("read_dir", path)
422                    );
423                }
424                Ok(DirEntry::new(
425                    entry_path,
426                    vfsi_core::metadata_from_attrs(attributes),
427                ))
428            })
429            .collect()
430    }
431
432    fn read_dir_page(
433        &mut self,
434        path: &std::path::Path,
435        cursor: Option<DirPageCursor>,
436        page_size: usize,
437        max_entries: usize,
438    ) -> VfResult<(Vec<DirEntry>, Option<DirPageCursor>)> {
439        let (attributes, next) = self
440            .listdir_page(path, metadata_mask(), cursor, page_size, max_entries)
441            .map_err(|error| error.with_context("visit_dir", path))?;
442        if attributes.len() > page_size {
443            return Err(VfError::transport(
444                None,
445                "listdir_page returned more entries than requested",
446            ));
447        }
448        let entries = attributes
449            .into_iter()
450            .enumerate()
451            .map(|(index, attributes)| {
452                let entry_path = attributes
453                    .file
454                    .path()
455                    .map(std::path::Path::to_path_buf)
456                    .ok_or_else(|| {
457                        VfError::client(index, ERR_IO).with_context("visit_dir", path)
458                    })?;
459                Ok(DirEntry::new(
460                    entry_path,
461                    vfsi_core::metadata_from_attrs(attributes),
462                ))
463            })
464            .collect::<VfResult<Vec<_>>>()?;
465        Ok((entries, next))
466    }
467}
468
469impl<T: VecFs + ?Sized> NamespaceFileSystem for T {
470    fn remove_one(&mut self, path: &std::path::Path, recursive: bool) -> VfResult<()> {
471        self.rm(&[path], recursive)
472            .map_err(|error| error.with_context("remove", path))
473    }
474
475    fn remove_dir_contents(&mut self, path: &std::path::Path) -> VfResult<()> {
476        self.rm_contents(path)
477            .map_err(|error| error.with_context("remove_dir_contents", path))
478    }
479
480    fn rename_one(&mut self, from: &std::path::Path, to: &std::path::Path) -> VfResult<()> {
481        self.renamev(&[(VfFile::from_os_path(from), VfFile::from_os_path(to))])
482            .map_err(|error| error.with_context("rename", from))
483    }
484}
485
486impl<T: VecFs + ?Sized> LinkFileSystem for T {
487    fn symlink_one(&mut self, target: &std::path::Path, link: &std::path::Path) -> VfResult<()> {
488        self.symlink(target, link)
489            .map_err(|error| error.with_context("symlink", link))
490    }
491
492    fn hard_link_one(&mut self, source: &std::path::Path, link: &std::path::Path) -> VfResult<()> {
493        self.hardlinkv(&[source], &[link])
494            .map_err(|error| error.with_context("hard_link", link))
495    }
496
497    fn read_link_one(&mut self, path: &std::path::Path) -> VfResult<std::path::PathBuf> {
498        self.readlink(path)
499            .map(bytes_to_path)
500            .map_err(|error| error.with_context("read_link", path))
501    }
502}
503
504#[cfg(unix)]
505fn bytes_to_path(bytes: Vec<u8>) -> std::path::PathBuf {
506    use std::os::unix::ffi::OsStringExt;
507    std::ffi::OsString::from_vec(bytes).into()
508}
509
510#[cfg(not(unix))]
511fn bytes_to_path(bytes: Vec<u8>) -> std::path::PathBuf {
512    String::from_utf8_lossy(&bytes).into_owned().into()
513}
514
515impl<T: VecFs + ?Sized> CopyFileSystem for T {
516    fn copy_one(
517        &mut self,
518        source: &std::path::Path,
519        destination: &std::path::Path,
520    ) -> VfResult<()> {
521        self.copyv(&[ExtentPair::from_os_paths(source, 0, destination, 0, None)])
522            .map_err(|error| error.with_context("copy", source))
523    }
524}
525
526impl<T: VecFs + ?Sized> VectorFileSystem for T {
527    fn open_many(&mut self, requests: &[OpenRequest]) -> VfResult<Vec<VfFile>> {
528        let paths: Vec<&std::path::Path> = requests
529            .iter()
530            .map(|request| request.path.as_path())
531            .collect();
532        let flags = translate_open_flags(requests)?;
533        let modes: Vec<u32> = requests.iter().map(|request| request.mode).collect();
534        VecFs::openv(self, &paths, &flags, &modes)
535    }
536
537    fn close_many(&mut self, files: &[VfFile]) -> VfResult<()> {
538        self.closev(files)
539    }
540
541    fn read_many(&mut self, requests: &[ReadOp]) -> VfResult<Vec<ReadResult>> {
542        self.readv(requests)
543    }
544
545    fn read_many_into(
546        &mut self,
547        requests: &[ReadOp],
548        buffers: &mut [&mut [u8]],
549    ) -> VfResult<Vec<ReadIntoResult>> {
550        self.readv_into(requests, buffers)
551    }
552
553    fn write_many(&mut self, requests: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
554        self.writev_borrowed(requests)
555    }
556}
557
558fn translate_open_flags(requests: &[OpenRequest]) -> VfResult<Vec<i32>> {
559    requests
560        .iter()
561        .enumerate()
562        .map(|(index, request)| {
563            vfsi_core::open_flags_to_libc(request.flags).map_err(|error| error.with_index(index))
564        })
565        .collect()
566}
567
568#[cfg(test)]
569mod tests {
570    use super::*;
571
572    #[test]
573    fn invalid_open_flags_retain_their_request_index() {
574        let error = translate_open_flags(&[
575            OpenRequest::new("/valid", OpenFlags::READ),
576            OpenRequest::new("/invalid", OpenFlags::empty()),
577        ])
578        .unwrap_err();
579        assert_eq!(error.index(), Some(1));
580    }
581}