#![forbid(unsafe_code)]
use crate::{
error::{self, Error},
syscalls, OpenFlags,
};
use std::{
ffi::{CString, OsStr},
fs::File,
os::unix::{
ffi::OsStrExt,
fs::MetadataExt,
io::{AsRawFd, RawFd},
},
path::{Path, PathBuf},
};
use snafu::ResultExt;
const PROC_ROOT_INO: u64 = 1;
lazy_static! {
static ref PROCFS_HANDLE: File = {
let proc = syscalls::openat(
libc::AT_FDCWD,
"/proc",
libc::O_PATH | libc::O_DIRECTORY,
0
).expect("/proc should be available");
let fs_type = syscalls::fstatfs(proc.as_raw_fd()).expect("fstatfs(/proc) should work").f_type;
if fs_type != libc::PROC_SUPER_MAGIC {
panic!("/proc is not procfs (f_type is 0x{:X}, not 0x{:X})", fs_type, libc::PROC_SUPER_MAGIC)
}
let ino = proc.metadata().expect("fstat(/proc) should work").ino();
if ino != PROC_ROOT_INO {
panic!("/proc is not root of a procfs mount (ino is 0x{:X}, not 0x{:X})", ino, PROC_ROOT_INO)
}
proc
};
}
pub(crate) trait ToCString {
fn to_c_string(&self) -> CString;
}
impl ToCString for OsStr {
fn to_c_string(&self) -> CString {
let filtered: Vec<_> = self
.as_bytes()
.iter()
.copied()
.take_while(|&c| c != b'\0')
.collect();
CString::new(filtered).expect("nul bytes should've been excluded")
}
}
impl ToCString for Path {
fn to_c_string(&self) -> CString {
self.as_os_str().to_c_string()
}
}
pub(crate) trait RawFdExt {
fn reopen(&self, flags: OpenFlags) -> Result<File, Error>;
fn as_unsafe_path(&self) -> Result<PathBuf, Error>;
fn try_clone_hotfix(&self) -> Result<File, Error>;
}
fn proc_subpath(fd: RawFd) -> Result<String, Error> {
if fd == libc::AT_FDCWD {
Ok("self/cwd".to_string())
} else if fd.is_positive() {
Ok(format!("self/fd/{}", fd))
} else {
error::InvalidArgument {
name: "fd",
description: "must be positive or AT_FDCWD",
}
.fail()
}
}
impl RawFdExt for RawFd {
fn reopen(&self, flags: OpenFlags) -> Result<File, Error> {
syscalls::openat_follow(PROCFS_HANDLE.as_raw_fd(), proc_subpath(*self)?, flags.0, 0)
.context(error::RawOsError {
operation: "reopen fd through procfs",
})
}
fn as_unsafe_path(&self) -> Result<PathBuf, Error> {
syscalls::readlinkat(PROCFS_HANDLE.as_raw_fd(), proc_subpath(*self)?).context(
error::RawOsError {
operation: "get fd's path through procfs",
},
)
}
fn try_clone_hotfix(&self) -> Result<File, Error> {
syscalls::fcntl_dupfd_cloxec(*self).context(error::RawOsError {
operation: "clone fd",
})
}
}
impl RawFdExt for File {
fn reopen(&self, flags: OpenFlags) -> Result<File, Error> {
self.as_raw_fd().reopen(flags)
}
fn as_unsafe_path(&self) -> Result<PathBuf, Error> {
self.as_raw_fd().as_unsafe_path()
}
fn try_clone_hotfix(&self) -> Result<File, Error> {
self.as_raw_fd().try_clone_hotfix()
}
}
pub(crate) trait FileExt {
fn is_dangerous(&self) -> Result<bool, Error>;
}
lazy_static! {
static ref DANGEROUS_FILESYSTEMS: Vec<i64> = vec![
libc::PROC_SUPER_MAGIC, 0x5a3c_69f0 , ];
}
impl FileExt for File {
fn is_dangerous(&self) -> Result<bool, Error> {
let stat = syscalls::fstatfs(self.as_raw_fd()).context(error::RawOsError {
operation: "check fstype of fd",
})?;
Ok(DANGEROUS_FILESYSTEMS.contains(&stat.f_type))
}
}