// SPDX-License-Identifier: EUPL-1.2+ // SPDX-FileCopyrightText: 2022 Alyssa Ross use std::ffi::CString; use std::fs::{read_link, File}; use std::io::{Error, Result}; use std::os::raw::c_char; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; extern "C" { fn openat_path_resolve_in_root(dirfd: BorrowedFd, pathname: *const c_char) -> Option; } // We can't make a const Path yet. // https://github.com/rust-lang/rust/issues/102619 const PROC_FD_PATH: &str = "/proc/self/fd"; pub trait OwnedFdExt: AsFd + AsRawFd + Sized { /// Returns the path in proc(5) that corresponds to this file descriptor. fn fd_path(&self) -> PathBuf { let mut path = PathBuf::from(PROC_FD_PATH); path.push(self.as_raw_fd().to_string()); path } /// Returns the path to this file, according to the magic link in proc(5). fn path(&self) -> Result { read_link(self.fd_path()) } } impl OwnedFdExt for OwnedFd {} /// An `O_PATH` file descriptor. #[derive(Debug)] #[repr(transparent)] pub struct Root(OwnedFd); fn c_string(path: impl Into) -> CString { let bytes = path.into().into_os_string().into_vec(); CString::new(bytes).unwrap() } impl Root { /// Panics if `path` contains NUL bytes. pub fn open(path: impl AsRef) -> Result { Ok(Self(OwnedFd::from(File::open(path)?))) } /// Resolves `path` as if the directory represented by `self` was the root directory /// (including for symlink resolution). The returned file descriptor will not have /// the `O_CLOEXEC` flag set. /// /// Panics if `path` contains NUL bytes. pub fn resolve_no_cloexec(&self, path: impl Into) -> Result { let c_path = c_string(path); unsafe { openat_path_resolve_in_root(self.as_fd(), c_path.as_ptr()) } .ok_or_else(Error::last_os_error) } } impl AsFd for Root { fn as_fd(&self) -> BorrowedFd { self.0.as_fd() } }