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