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