use super::file_metadata;
use crate::fs::{
errors, open, open_unchecked, read_link_unchecked, set_times_follow_unchecked, FollowSymlinks,
Metadata, OpenOptions, SystemTimeSpec,
};
use once_cell::sync::Lazy;
use posish::{
fs::{chmodat, fstatfs, major, renameat, Mode},
path::DecInt,
process::{getgid, getpid, getuid},
};
use std::{
fs, io,
os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt},
path::{Path, PathBuf},
};
const PROC_ROOT_INO: u64 = 1;
#[cfg(not(target_env = "musl"))]
const PROC_SUPER_MAGIC: libc::__fsword_t = 0x0000_9fa0;
#[cfg(target_env = "musl")]
const PROC_SUPER_MAGIC: libc::c_ulong = 0x0000_9fa0;
enum Subdir {
Proc,
Pid,
Fd,
}
fn check_proc_dir(
kind: Subdir,
dir: &fs::File,
proc_metadata: Option<&Metadata>,
uid: u32,
gid: u32,
) -> io::Result<Metadata> {
check_procfs(dir)?;
let dir_metadata = file_metadata(dir)?;
assert!(dir_metadata.is_dir());
if let Subdir::Proc = kind {
if dir_metadata.ino() != PROC_ROOT_INO {
return Err(io::Error::new(
io::ErrorKind::Other,
"unexpected root inode in /proc",
));
}
if major(dir_metadata.dev()) != 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc isn't a non-device mount",
));
}
if !is_mountpoint(dir)? {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc isn't a mount point",
));
}
} else {
if dir_metadata.ino() == PROC_ROOT_INO {
return Err(io::Error::new(
io::ErrorKind::Other,
"unexpected non-root inode in /proc subdirectory",
));
}
if dir_metadata.dev() != proc_metadata.unwrap().dev() {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc subdirectory is on a different filesystem from /proc",
));
}
if is_mountpoint(dir)? {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc subdirectory is a mount point",
));
}
}
if (dir_metadata.uid(), dir_metadata.gid()) != (uid, gid) {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc subdirectory has unexpected ownership",
));
}
let expected_mode = if let Subdir::Fd = kind { 0o500 } else { 0o555 };
if dir_metadata.mode() & 0o777 & !expected_mode != 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc subdirectory has unexpected permissions",
));
}
if let Subdir::Fd = kind {
if dir_metadata.nlink() != 2 {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc/self/fd has unexpected subdirectories or links",
));
}
} else {
if dir_metadata.nlink() <= 2 {
return Err(io::Error::new(
io::ErrorKind::Other,
"/proc subdirectory is unexpectedly empty",
));
}
}
Ok(dir_metadata)
}
fn check_procfs(file: &fs::File) -> io::Result<()> {
let statfs = fstatfs(file)?;
let f_type = statfs.f_type;
if f_type != PROC_SUPER_MAGIC {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("unexpected filesystem type in /proc ({:#x})", f_type),
));
}
Ok(())
}
fn is_mountpoint(file: &fs::File) -> io::Result<bool> {
let e = renameat(file, "../.", file, ".").unwrap_err();
match e.raw_os_error() {
Some(libc::EXDEV) => Ok(true), Some(libc::EBUSY) => Ok(false), _ => panic!("Unexpected error from `renameat`: {:?}", e),
}
}
fn proc_self_fd() -> io::Result<&'static fs::File> {
#[allow(clippy::useless_conversion)]
static PROC_SELF_FD: Lazy<io::Result<fs::File>> = Lazy::new(|| {
#[cfg(not(target_env = "musl"))]
assert_eq!(
PROC_SUPER_MAGIC,
libc::__fsword_t::from(libc::PROC_SUPER_MAGIC)
);
let proc = fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_PATH | libc::O_DIRECTORY | libc::O_NOFOLLOW)
.open("/proc")?;
let proc_metadata = check_proc_dir(Subdir::Proc, &proc, None, 0, 0)?;
let (uid, gid, pid) = (getuid(), getgid(), getpid());
let mut options = OpenOptions::new();
let options = options
.read(true)
.follow(FollowSymlinks::No)
.custom_flags(libc::O_PATH | libc::O_DIRECTORY);
let proc_self = open_unchecked(&proc, &DecInt::new(pid), options)?;
drop(proc);
check_proc_dir(Subdir::Pid, &proc_self, Some(&proc_metadata), uid, gid)?;
let proc_self_fd = open_unchecked(&proc_self, Path::new("fd"), options)?;
drop(proc_self);
check_proc_dir(Subdir::Fd, &proc_self_fd, Some(&proc_metadata), uid, gid)?;
Ok(proc_self_fd)
});
PROC_SELF_FD.as_ref().map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("error opening /proc/self/fd: {}", e),
)
})
}
pub(crate) fn get_path_from_proc_self_fd(file: &fs::File) -> io::Result<PathBuf> {
read_link_unchecked(proc_self_fd()?, &DecInt::from_fd(file), PathBuf::new())
}
pub(crate) fn set_permissions_through_proc_self_fd(
start: &fs::File,
path: &Path,
perm: fs::Permissions,
) -> io::Result<()> {
let opath = open(
start,
path,
OpenOptions::new().read(true).custom_flags(libc::O_PATH),
)?;
let dirfd = proc_self_fd()?;
let mode = Mode::from_bits(perm.mode()).ok_or_else(errors::invalid_flags)?;
chmodat(dirfd, DecInt::from_fd(&opath), mode)
}
pub(crate) fn set_times_through_proc_self_fd(
start: &fs::File,
path: &Path,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let opath = open(
start,
path,
OpenOptions::new().read(true).custom_flags(libc::O_PATH),
)?;
let dirfd = proc_self_fd()?;
set_times_follow_unchecked(dirfd, &DecInt::from_fd(&opath), atime, mtime)
}