cap_std_ext/
rootdir.rs

1use std::fs;
2use std::io;
3use std::io::Read;
4use std::path::Path;
5
6use cap_std::fs::Dir;
7use cap_tempfile::cap_std;
8use rustix::fd::AsFd;
9use rustix::fd::BorrowedFd;
10use rustix::fs::OFlags;
11use rustix::fs::ResolveFlags;
12use rustix::path::Arg;
13
14pub(crate) fn open_beneath_rdonly(start: &BorrowedFd, path: &Path) -> io::Result<fs::File> {
15    // We loop forever on EAGAIN right now. The cap-std version loops just 4 times,
16    // which seems really arbitrary.
17    let r = path.into_with_c_str(|path_c_str| 'start: loop {
18        match rustix::fs::openat2(
19            start,
20            path_c_str,
21            OFlags::CLOEXEC | OFlags::RDONLY,
22            rustix::fs::Mode::empty(),
23            ResolveFlags::IN_ROOT | ResolveFlags::NO_MAGICLINKS,
24        ) {
25            Ok(file) => {
26                return Ok(file);
27            }
28            Err(rustix::io::Errno::AGAIN | rustix::io::Errno::INTR) => {
29                continue 'start;
30            }
31            Err(e) => {
32                return Err(e);
33            }
34        }
35    })?;
36    Ok(r.into())
37}
38
39/// Wrapper for a [`cap_std::fs::Dir`] that is defined to use `RESOLVE_IN_ROOT``
40/// semantics when opening files and subdirectories. This currently only
41/// offers a subset of the methods, primarily reading.
42///
43/// # When and how to use this
44///
45/// In general, if your use case possibly involves reading files that may be
46/// absolute symlinks, or relative symlinks that may go outside the provided
47/// directory, you will need to use this API instead of [`cap_std::fs::Dir`].
48///
49/// # Performing writes
50///
51/// If you want to simultaneously perform other operations (such as writing), at the moment
52/// it requires explicitly maintaining a duplicate copy of a [`cap_std::fs::Dir`]
53/// instance, or using direct [`rustix::fs`] APIs.
54#[derive(Debug)]
55pub struct RootDir(Dir);
56
57impl RootDir {
58    /// Create a new instance from an existing [`cap_std::fs::Dir`] instance.
59    pub fn new(src: &Dir, path: impl AsRef<Path>) -> io::Result<Self> {
60        src.open_dir(path).map(Self)
61    }
62
63    /// Create a new instance from an ambient path.
64    pub fn open_ambient_root(
65        path: impl AsRef<Path>,
66        authority: cap_std::AmbientAuthority,
67    ) -> io::Result<Self> {
68        Dir::open_ambient_dir(path, authority).map(Self)
69    }
70
71    /// Open a file in this root, read-only.
72    pub fn open(&self, path: impl AsRef<Path>) -> io::Result<fs::File> {
73        let path = path.as_ref();
74        open_beneath_rdonly(&self.0.as_fd(), path)
75    }
76
77    /// Open a file read-only, but return `Ok(None)` if it does not exist.
78    pub fn open_optional(&self, path: impl AsRef<Path>) -> io::Result<Option<fs::File>> {
79        crate::dirext::map_optional(self.open(path))
80    }
81
82    /// Read the contents of a file into a vector.
83    pub fn read(&self, path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
84        let mut f = self.open(path.as_ref())?;
85        let mut r = Vec::new();
86        f.read_to_end(&mut r)?;
87        Ok(r)
88    }
89
90    /// Read the contents of a file as a string.
91    pub fn read_to_string(&self, path: impl AsRef<Path>) -> io::Result<String> {
92        let mut f = self.open(path.as_ref())?;
93        let mut s = String::new();
94        f.read_to_string(&mut s)?;
95        Ok(s)
96    }
97
98    /// Return the directory entries.
99    pub fn entries(&self) -> io::Result<cap_std::fs::ReadDir> {
100        self.0.entries()
101    }
102
103    /// Return the directory entries of the target subdirectory.
104    pub fn read_dir(&self, path: impl AsRef<Path>) -> io::Result<cap_std::fs::ReadDir> {
105        self.0.read_dir(path.as_ref())
106    }
107
108    /// Create a [`cap_std::fs::Dir`] pointing to the same directory as `self`.
109    /// This view will *not* use `RESOLVE_IN_ROOT`.
110    pub fn reopen_cap_std(&self) -> io::Result<Dir> {
111        Dir::reopen_dir(&self.0.as_fd())
112    }
113}
114
115impl From<Dir> for RootDir {
116    fn from(dir: Dir) -> Self {
117        Self(dir)
118    }
119}