use crate::fd::{AsFd, BorrowedFd, OwnedFd};
use crate::ffi::CStr;
use crate::fs::{
cwd, fstat, fstatfs, major, openat, renameat, Dir, FileType, Mode, OFlags, Stat,
PROC_SUPER_MAGIC,
};
use crate::io;
use crate::path::DecInt;
use crate::process::getpid;
#[cfg(feature = "rustc-dep-of-std")]
use core::lazy::OnceCell;
#[cfg(not(feature = "rustc-dep-of-std"))]
use once_cell::sync::OnceCell;
const PROC_ROOT_INO: u64 = 1;
#[derive(Copy, Clone, Debug)]
enum Kind {
Proc,
Pid,
Fd,
File,
}
fn check_proc_entry(
kind: Kind,
entry: BorrowedFd<'_>,
proc_stat: Option<&Stat>,
) -> io::Result<Stat> {
let entry_stat = fstat(entry)?;
check_proc_entry_with_stat(kind, entry, entry_stat, proc_stat)
}
fn check_proc_entry_with_stat(
kind: Kind,
entry: BorrowedFd<'_>,
entry_stat: Stat,
proc_stat: Option<&Stat>,
) -> io::Result<Stat> {
check_procfs(entry)?;
match kind {
Kind::Proc => check_proc_root(entry, &entry_stat)?,
Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?,
Kind::File => check_proc_file(&entry_stat, proc_stat)?,
}
let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 };
if entry_stat.st_mode & 0o777 & !expected_mode != 0 {
return Err(io::Errno::NOTSUP);
}
match kind {
Kind::Fd => {
if entry_stat.st_nlink != 2 {
return Err(io::Errno::NOTSUP);
}
}
Kind::Pid | Kind::Proc => {
if entry_stat.st_nlink <= 2 {
return Err(io::Errno::NOTSUP);
}
}
Kind::File => {
if entry_stat.st_nlink != 1 {
return Err(io::Errno::NOTSUP);
}
}
}
Ok(entry_stat)
}
fn check_proc_root(entry: BorrowedFd<'_>, stat: &Stat) -> io::Result<()> {
assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
if stat.st_ino != PROC_ROOT_INO {
return Err(io::Errno::NOTSUP);
}
if major(stat.st_dev) != 0 {
return Err(io::Errno::NOTSUP);
}
if !is_mountpoint(entry) {
return Err(io::Errno::NOTSUP);
}
Ok(())
}
fn check_proc_subdir(
entry: BorrowedFd<'_>,
stat: &Stat,
proc_stat: Option<&Stat>,
) -> io::Result<()> {
assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
check_proc_nonroot(stat, proc_stat)?;
if is_mountpoint(entry) {
return Err(io::Errno::NOTSUP);
}
Ok(())
}
fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile {
return Err(io::Errno::NOTSUP);
}
check_proc_nonroot(stat, proc_stat)?;
Ok(())
}
fn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
if stat.st_ino == PROC_ROOT_INO {
return Err(io::Errno::NOTSUP);
}
if stat.st_dev != proc_stat.unwrap().st_dev {
return Err(io::Errno::NOTSUP);
}
Ok(())
}
fn check_procfs(file: BorrowedFd<'_>) -> io::Result<()> {
let statfs = fstatfs(file)?;
let f_type = statfs.f_type;
if f_type != PROC_SUPER_MAGIC {
return Err(io::Errno::NOTSUP);
}
Ok(())
}
fn is_mountpoint(file: BorrowedFd<'_>) -> bool {
let err = renameat(file, cstr!("../."), file, cstr!(".")).unwrap_err();
match err {
io::Errno::XDEV => true, io::Errno::BUSY => false, _ => panic!("Unexpected error from `renameat`: {:?}", err),
}
}
fn proc_opendirat<P: crate::path::Arg, Fd: AsFd>(dirfd: Fd, path: P) -> io::Result<OwnedFd> {
let oflags = OFlags::NOFOLLOW | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOCTTY;
openat(dirfd, path, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)
}
fn proc() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
static PROC: StaticFd = StaticFd::new();
PROC.get_or_try_init(|| {
let proc = proc_opendirat(cwd(), cstr!("/proc"))?;
let proc_stat =
check_proc_entry(Kind::Proc, proc.as_fd(), None).map_err(|_err| io::Errno::NOTSUP)?;
Ok(new_static_fd(proc, proc_stat))
})
.map(|(fd, stat)| (fd.as_fd(), stat))
}
fn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
static PROC_SELF: StaticFd = StaticFd::new();
PROC_SELF
.get_or_try_init(|| {
let (proc, proc_stat) = proc()?;
let pid = getpid();
let proc_self = proc_opendirat(proc, DecInt::new(pid.as_raw_nonzero().get()))?;
let proc_self_stat = check_proc_entry(Kind::Pid, proc_self.as_fd(), Some(proc_stat))
.map_err(|_err| io::Errno::NOTSUP)?;
Ok(new_static_fd(proc_self, proc_self_stat))
})
.map(|(owned, stat)| (owned.as_fd(), stat))
}
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
pub fn proc_self_fd() -> io::Result<BorrowedFd<'static>> {
static PROC_SELF_FD: StaticFd = StaticFd::new();
PROC_SELF_FD
.get_or_try_init(|| {
let (_, proc_stat) = proc()?;
let (proc_self, _proc_self_stat) = proc_self()?;
let proc_self_fd = proc_opendirat(proc_self, cstr!("fd"))?;
let proc_self_fd_stat =
check_proc_entry(Kind::Fd, proc_self_fd.as_fd(), Some(proc_stat))
.map_err(|_err| io::Errno::NOTSUP)?;
Ok(new_static_fd(proc_self_fd, proc_self_fd_stat))
})
.map(|(owned, _stat)| owned.as_fd())
}
type StaticFd = OnceCell<(OwnedFd, Stat)>;
#[inline]
fn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) {
(fd, stat)
}
fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
static PROC_SELF_FDINFO: StaticFd = StaticFd::new();
PROC_SELF_FDINFO
.get_or_try_init(|| {
let (_, proc_stat) = proc()?;
let (proc_self, _proc_self_stat) = proc_self()?;
let proc_self_fdinfo = proc_opendirat(proc_self, cstr!("fdinfo"))?;
let proc_self_fdinfo_stat =
check_proc_entry(Kind::Fd, proc_self_fdinfo.as_fd(), Some(proc_stat))
.map_err(|_err| io::Errno::NOTSUP)?;
Ok((proc_self_fdinfo, proc_self_fdinfo_stat))
})
.map(|(owned, stat)| (owned.as_fd(), stat))
}
#[inline]
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
pub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
_proc_self_fdinfo(fd.as_fd())
}
fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?;
let fd_str = DecInt::from_fd(fd);
open_and_check_file(proc_self_fdinfo, proc_self_fdinfo_stat, fd_str.as_c_str())
}
#[inline]
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
pub fn proc_self_pagemap() -> io::Result<OwnedFd> {
proc_self_file(cstr!("pagemap"))
}
#[inline]
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
pub fn proc_self_maps() -> io::Result<OwnedFd> {
proc_self_file(cstr!("maps"))
}
#[inline]
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
pub fn proc_self_status() -> io::Result<OwnedFd> {
proc_self_file(cstr!("status"))
}
fn proc_self_file(name: &CStr) -> io::Result<OwnedFd> {
let (proc_self, proc_self_stat) = proc_self()?;
open_and_check_file(proc_self, proc_self_stat, name)
}
fn open_and_check_file(dir: BorrowedFd, dir_stat: &Stat, name: &CStr) -> io::Result<OwnedFd> {
let (_, proc_stat) = proc()?;
let oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY;
let file = openat(dir, name, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?;
let file_stat = fstat(&file)?;
let dir = Dir::read_from(dir).map_err(|_err| io::Errno::NOTSUP)?;
let dot_stat = dir.stat().map_err(|_err| io::Errno::NOTSUP)?;
if (dot_stat.st_dev, dot_stat.st_ino) != (dir_stat.st_dev, dir_stat.st_ino) {
return Err(io::Errno::NOTSUP);
}
let mut found_file = false;
let mut found_dot = false;
for entry in dir {
let entry = entry.map_err(|_err| io::Errno::NOTSUP)?;
if entry.ino() == file_stat.st_ino
&& entry.file_type() == FileType::RegularFile
&& entry.file_name() == name
{
let _ =
check_proc_entry_with_stat(Kind::File, file.as_fd(), file_stat, Some(proc_stat))?;
found_file = true;
} else if entry.ino() == dir_stat.st_ino
&& entry.file_type() == FileType::Directory
&& entry.file_name() == cstr!(".")
{
found_dot = true;
}
}
if found_file && found_dot {
Ok(file)
} else {
Err(io::Errno::NOTSUP)
}
}