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