Skip to main content

nix_nar/enc/
filesystem.rs

1#[cfg(unix)]
2use std::os::unix::fs::MetadataExt;
3use std::{fs, io};
4
5use camino::{Utf8Path, Utf8PathBuf};
6#[cfg(not(unix))]
7use is_executable::IsExecutable;
8
9#[derive(Debug, Clone)]
10pub enum FileSystemMetadata {
11    Directory,
12    File { executable: bool, length: u64 },
13    Symlink { target_path: Utf8PathBuf },
14}
15
16/// File system operations needed by [`crate::Encoder`] to encode a NAR file.
17///
18/// [`crate::Encoder`] will call this trait's methods only with `path`s that are:
19/// - The archive's root (given to the [`crate::Encoder`]'s constructor), or
20/// - Constructed by appending [`Self::read_dir`] iterator's items to
21///   the archive's root to descend into directories
22///
23/// # Consistency
24///
25/// Implementation is responsible for returning a consistent view of the filesystem.
26///
27/// The reader returned by [`Self::open`] must read exactly the number
28/// of bytes that was declared in its corresponding
29/// [`Self::metadata`]. In case of a mismatch, [`crate::Encoder`] will
30/// return an error to avoid creating an invalid NAR file.
31pub trait FileSystem {
32    /// Reader returned by [`Self::open`]
33    type File: io::Read;
34
35    /// Return type of a filesystem object.
36    ///
37    /// # Errors
38    ///
39    /// May return an I/O error.
40    ///
41    /// Implementation should return an error (or avoid listing that
42    /// object in [`Self::read_dir`]) if the object is not one of the
43    /// types enumerated in [`FileSystemMetadata`].
44    fn metadata(&self, path: &Utf8Path) -> io::Result<FileSystemMetadata>;
45
46    /// Iterate over entry names of a directory at the given path.
47    ///
48    /// [`crate::Encoder`] will call this method only on paths, for
49    /// which [`Self::metadata`] returned [`FileSystemMetadata::Directory`].
50    ///
51    /// # Errors
52    ///
53    /// May return an I/O error
54    fn read_dir(&self, path: &Utf8Path) -> io::Result<Vec<String>>;
55
56    /// Open a file at the given path, return its size in bytes and
57    /// a [`Self::File`] reader
58    ///
59    /// [`crate::Encoder`] will call this method only on paths for
60    /// which [`Self::metadata`] returned [`FileSystemMetadata::File`].
61    ///
62    /// # Errors
63    ///
64    /// May return an I/O error
65    fn open(&self, path: &Utf8Path) -> io::Result<Self::File>;
66}
67
68/// Default implementation of the [`FileSystem`] trait, using the OS
69/// filesystem directly
70#[derive(Debug, Clone, Copy, Default)]
71pub struct NativeFileSystem {}
72
73impl FileSystem for NativeFileSystem {
74    type File = fs::File;
75
76    fn open(&self, path: &Utf8Path) -> io::Result<Self::File> {
77        fs::File::open(path)
78    }
79
80    fn read_dir(&self, path: &Utf8Path) -> io::Result<Vec<String>> {
81        path.read_dir_utf8()?
82            .map(|i| i.map(|p| p.file_name().into()))
83            .collect()
84    }
85
86    fn metadata(&self, path: &Utf8Path) -> io::Result<FileSystemMetadata> {
87        let metadata = path.symlink_metadata()?;
88        if metadata.is_dir() {
89            Ok(FileSystemMetadata::Directory)
90        } else if metadata.is_symlink() {
91            Ok(FileSystemMetadata::Symlink {
92                target_path: path.read_link_utf8()?,
93            })
94        } else if metadata.is_file() {
95            // On Unix, we can reuse existing metadata, saving a syscall
96            #[cfg(unix)]
97            let executable = metadata.mode() & 0o111 != 0;
98            // On non-Unix, defer to is_executable crate
99            #[cfg(not(unix))]
100            let executable = path.as_std_path().is_executable();
101            Ok(FileSystemMetadata::File {
102                executable,
103                length: metadata.len(),
104            })
105        } else {
106            Err(io::Error::other(format!("unknown file type {path}")))
107        }
108    }
109}