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