use std::ffi::{CString, OsStr};
use std::fs::File;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::{Component, Path, PathBuf};
#[derive(Debug)]
pub struct Anchor {
directory: File,
}
fn c_name(name: &OsStr) -> std::io::Result<CString> {
return CString::new(name.as_bytes()).map_err(|_| { return std::io::Error::from_raw_os_error(libc::EINVAL); });
}
fn open_directory(parent: RawFd, name: &OsStr, flags: libc::c_int) -> std::io::Result<File> {
let text: CString = c_name(name)?;
let descriptor: libc::c_int = unsafe { libc::openat(parent, text.as_ptr(), flags | libc::O_DIRECTORY | libc::O_CLOEXEC) };
if descriptor < 0 {
return Err(std::io::Error::last_os_error());
}
return Ok(unsafe { File::from_raw_fd(descriptor) });
}
fn open_component(parent: RawFd, name: &OsStr, follow: bool) -> std::io::Result<File> {
let nofollow: libc::c_int = if follow { 0 } else { libc::O_NOFOLLOW };
let reading: libc::c_int = libc::O_RDONLY | nofollow;
match open_directory(parent, name, reading) {
Err(error) if error.raw_os_error() == Some(libc::EACCES) => {
let handle: File = open_directory(parent, name, libc::O_PATH | nofollow)?;
let metadata: std::fs::Metadata = handle.metadata()?;
let widened: std::fs::Permissions = std::fs::Permissions::from_mode(metadata.mode() | 0o700);
if std::fs::set_permissions(Anchor::descriptor_path(handle.as_raw_fd()), widened).is_err() {
return Err(error);
}
return open_directory(parent, name, reading);
}
other => return other,
}
}
impl Anchor {
fn descriptor_path(descriptor: RawFd) -> PathBuf {
return PathBuf::from(format!("/proc/self/fd/{descriptor}"));
}
pub fn open_below(root: &Path, relative: &Path) -> std::io::Result<Anchor> {
let mut current: File = open_component(libc::AT_FDCWD, root.as_os_str(), true)?;
for component in relative.components() {
let Component::Normal(name) = component else { return Err(std::io::Error::from_raw_os_error(libc::EINVAL)) };
current = open_component(current.as_raw_fd(), name, false)?;
}
return Ok(Anchor { directory: current });
}
pub fn file(&self) -> &File {
return &self.directory;
}
pub fn path(&self) -> PathBuf {
return Anchor::descriptor_path(self.directory.as_raw_fd());
}
pub fn child(&self, name: &OsStr) -> PathBuf {
return Anchor::descriptor_path(self.directory.as_raw_fd()).join(name);
}
}
#[cfg(test)]
#[path = "subvolume_anchor_tests.rs"]
mod tests;