use crate::{
capi::{
ret::IntoCReturn,
utils::{self, CBorrowedFd},
},
error::{Error, ErrorExt, ErrorImpl},
flags::OpenFlags,
procfs::{ProcfsBase, ProcfsHandle, ProcfsHandleBuilder, ProcfsHandleRef},
};
use std::os::unix::io::{AsRawFd, IntoRawFd, OwnedFd, RawFd};
use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
use libc::{c_char, c_int, size_t};
use open_enum::open_enum;
pub const __PATHRS_PROC_TYPE_MASK: u64 = 0xFFFF_FFFF_0000_0000;
const __PATHRS_PROC_TYPE_SPECIAL: u64 = 0xFFFF_FFFE_0000_0000;
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL,
__PATHRS_PROC_TYPE_SPECIAL & __PATHRS_PROC_TYPE_MASK,
);
pub const __PATHRS_PROC_TYPE_PID: u64 = 0x8000_0000_0000_0000;
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_PID,
__PATHRS_PROC_TYPE_PID & __PATHRS_PROC_TYPE_MASK,
);
#[open_enum]
#[repr(u64)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(non_camel_case_types, dead_code)]
#[allow(clippy::unusual_byte_groupings)] pub enum CProcfsBase {
PATHRS_PROC_ROOT = 0xFFFF_FFFE_7072_6F63u64,
PATHRS_PROC_SELF = 0xFFFF_FFFE_091D_5E1Fu64,
PATHRS_PROC_THREAD_SELF = 0xFFFF_FFFE_3EAD_5E1Fu64, }
static_assertions::const_assert_eq!(0xFFFFFFFE70726F63, CProcfsBase::PATHRS_PROC_ROOT.0);
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL | 0x7072_6F63,
CProcfsBase::PATHRS_PROC_ROOT.0,
);
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL,
CProcfsBase::PATHRS_PROC_ROOT.0 & __PATHRS_PROC_TYPE_MASK,
);
static_assertions::const_assert_eq!(0xFFFFFFFE091D5E1F, CProcfsBase::PATHRS_PROC_SELF.0);
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL | 0x091D_5E1F,
CProcfsBase::PATHRS_PROC_SELF.0,
);
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL,
CProcfsBase::PATHRS_PROC_SELF.0 & __PATHRS_PROC_TYPE_MASK,
);
static_assertions::const_assert_eq!(0xFFFFFFFE3EAD5E1F, CProcfsBase::PATHRS_PROC_THREAD_SELF.0);
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL | 0x3EAD_5E1F,
CProcfsBase::PATHRS_PROC_THREAD_SELF.0,
);
static_assertions::const_assert_eq!(
__PATHRS_PROC_TYPE_SPECIAL,
CProcfsBase::PATHRS_PROC_THREAD_SELF.0 & __PATHRS_PROC_TYPE_MASK,
);
impl TryFrom<CProcfsBase> for ProcfsBase {
type Error = Error;
fn try_from(c_base: CProcfsBase) -> Result<Self, Self::Error> {
const U32_MAX: u64 = u32::MAX as _;
const U32_MAX_PLUS_ONE: u64 = U32_MAX + 1;
match c_base {
CProcfsBase::PATHRS_PROC_ROOT => Ok(ProcfsBase::ProcRoot),
CProcfsBase::PATHRS_PROC_SELF => Ok(ProcfsBase::ProcSelf),
CProcfsBase::PATHRS_PROC_THREAD_SELF => Ok(ProcfsBase::ProcThreadSelf),
CProcfsBase(arg) => match (
arg & __PATHRS_PROC_TYPE_MASK,
arg & !__PATHRS_PROC_TYPE_MASK,
) {
(_, value @ U32_MAX_PLUS_ONE..) => {
static_assertions::const_assert_eq!(!__PATHRS_PROC_TYPE_MASK, u32::MAX as _);
unreachable!("the value portion of CProcfsBase({arg:#x}) cannot be larger than u32 (but {value:#x} is)");
}
(__PATHRS_PROC_TYPE_PID, pid @ 1..=U32_MAX) => {
static_assertions::const_assert_eq!(U32_MAX, u32::MAX as u64);
Ok(ProcfsBase::ProcPid(pid as u32))
}
(base_type, value) => Err(ErrorImpl::InvalidArgument {
name: "procfs base".into(),
description: match base_type {
__PATHRS_PROC_TYPE_SPECIAL => format!("{arg:#X} is an invalid special procfs base (unknown sub-value {value:#X})"),
__PATHRS_PROC_TYPE_PID => format!("pid {value} is an invalid value for PATHRS_PROC_PID"),
_ => format!("{arg:#X} has an unknown procfs base type {base_type:#X}"),
}.into(),
}.into()),
}
.wrap("the procfs base must be one of the PATHRS_PROC_* values or PATHRS_PROC_PID(n)")
}
}
}
#[cfg(test)]
impl From<ProcfsBase> for CProcfsBase {
fn from(base: ProcfsBase) -> Self {
match base {
ProcfsBase::ProcPid(pid) => {
#[allow(clippy::absurd_extreme_comparisons)]
{
assert!(pid <= u32::MAX, "pid in CProcfsBase must fit inside a u32");
}
assert_eq!(
pid as u64 & __PATHRS_PROC_TYPE_MASK, 0,
"invalid pid found when converting to CProcfsBase -- pid {pid} includes type bits ({__PATHRS_PROC_TYPE_MASK:#X})"
);
CProcfsBase(__PATHRS_PROC_TYPE_PID | pid as u64)
}
ProcfsBase::ProcRoot => CProcfsBase::PATHRS_PROC_ROOT,
ProcfsBase::ProcSelf => CProcfsBase::PATHRS_PROC_SELF,
ProcfsBase::ProcThreadSelf => CProcfsBase::PATHRS_PROC_THREAD_SELF,
}
}
}
pub const PATHRS_PROC_DEFAULT_ROOTFD: CBorrowedFd<'static> =
unsafe { CBorrowedFd::from_raw_fd(-libc::EBADF) };
fn parse_proc_rootfd<'fd>(fd: CBorrowedFd<'fd>) -> Result<ProcfsHandleRef<'fd>, Error> {
match fd {
PATHRS_PROC_DEFAULT_ROOTFD => ProcfsHandle::new(),
_ => ProcfsHandleRef::try_from_borrowed_fd(fd.try_as_borrowed_fd()?),
}
}
pub const PATHRS_PROCFS_NEW_UNMASKED: u64 = 0x0000_0000_0000_0001;
bitflags! {
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
pub struct ProcfsOpenFlags: u64 {
const PATHRS_PROCFS_NEW_UNMASKED = PATHRS_PROCFS_NEW_UNMASKED;
}
}
impl ProcfsOpenFlags {
const fn contains_unknown_bits(&self) -> bool {
Self::from_bits(self.bits()).is_none()
}
}
static_assertions::const_assert_eq!(
ProcfsOpenFlags::PATHRS_PROCFS_NEW_UNMASKED.contains_unknown_bits(),
false,
);
static_assertions::const_assert_eq!(
ProcfsOpenFlags::from_bits_retain(0x1000_0000).contains_unknown_bits(),
true,
);
static_assertions::const_assert_eq!(
ProcfsOpenFlags::from_bits_retain(0xF000_0001).contains_unknown_bits(),
true,
);
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
pub struct ProcfsOpenHow {
pub flags: ProcfsOpenFlags,
}
impl ProcfsOpenHow {
fn into_builder(self) -> Result<ProcfsHandleBuilder, Error> {
let mut builder = ProcfsHandleBuilder::new();
if self.flags.contains_unknown_bits() {
return Err(ErrorImpl::InvalidArgument {
name: "flags".into(),
description: format!(
"contains unknown flag bits {:#x}",
self.flags.difference(ProcfsOpenFlags::all())
)
.into(),
})?;
}
if self
.flags
.contains(ProcfsOpenFlags::PATHRS_PROCFS_NEW_UNMASKED)
{
builder.set_unmasked();
}
Ok(builder)
}
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_procfs_open(args: *const ProcfsOpenHow, size: usize) -> RawFd {
|| -> Result<_, Error> {
unsafe { utils::copy_from_extensible_struct(args, size) }?
.into_builder()?
.build()
.map(ProcfsHandle::into_owned_fd)
}()
.map(|fd| match fd {
Some(fd) => fd.into_raw_fd(),
None => PATHRS_PROC_DEFAULT_ROOTFD.as_raw_fd(),
})
.into_c_return()
}
utils::symver! {
fn pathrs_procfs_open <- (pathrs_procfs_open, version = "LIBPATHRS_0.2", default);
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_proc_openat(
proc_rootfd: CBorrowedFd<'_>,
base: CProcfsBase,
path: *const c_char,
flags: c_int,
) -> RawFd {
|| -> Result<_, Error> {
let base = base.try_into()?;
let path = unsafe { utils::parse_path(path) }?; let oflags = OpenFlags::from_bits_retain(flags);
let procfs = parse_proc_rootfd(proc_rootfd)?;
if oflags.contains(OpenFlags::O_NOFOLLOW) {
procfs.open(base, path, oflags)
} else {
procfs.open_follow(base, path, oflags)
}
}()
.map(OwnedFd::from)
.into_c_return()
}
utils::symver! {
fn pathrs_proc_openat <- (pathrs_proc_openat, version = "LIBPATHRS_0.2", default);
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_proc_open(
base: CProcfsBase,
path: *const c_char,
flags: c_int,
) -> RawFd {
pathrs_proc_openat(PATHRS_PROC_DEFAULT_ROOTFD, base, path, flags)
}
utils::symver! {
fn pathrs_proc_open <- (pathrs_proc_open, version = "LIBPATHRS_0.1", default);
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_proc_readlinkat(
proc_rootfd: CBorrowedFd<'_>,
base: CProcfsBase,
path: *const c_char,
linkbuf: *mut c_char,
linkbuf_size: size_t,
) -> c_int {
|| -> Result<_, Error> {
let base = base.try_into()?;
let path = unsafe { utils::parse_path(path) }?; let procfs = parse_proc_rootfd(proc_rootfd)?;
let link_target = procfs.readlink(base, path)?;
unsafe { utils::copy_path_into_buffer(link_target, linkbuf, linkbuf_size) }
}()
.into_c_return()
}
utils::symver! {
fn pathrs_proc_readlinkat <- (pathrs_proc_readlinkat, version = "LIBPATHRS_0.2", default);
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_proc_readlink(
base: CProcfsBase,
path: *const c_char,
linkbuf: *mut c_char,
linkbuf_size: size_t,
) -> c_int {
pathrs_proc_readlinkat(
PATHRS_PROC_DEFAULT_ROOTFD,
base,
path,
linkbuf,
linkbuf_size,
)
}
utils::symver! {
fn pathrs_proc_readlink <- (pathrs_proc_readlink, version = "LIBPATHRS_0.1", default);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
capi::{error as capi_error, utils::Leakable},
error::ErrorKind,
procfs::ProcfsBase,
};
use std::{
mem,
os::unix::io::{FromRawFd, OwnedFd},
};
use pretty_assertions::assert_eq;
#[test]
fn procfsbase_try_from_crepr_procroot() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase::PATHRS_PROC_ROOT).map_err(|e| e.kind()),
Ok(ProcfsBase::ProcRoot),
"PATHRS_PROC_ROOT.try_into()"
);
}
#[test]
fn procfsbase_try_from_crepr_procself() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase::PATHRS_PROC_SELF).map_err(|e| e.kind()),
Ok(ProcfsBase::ProcSelf),
"PATHRS_PROC_SELF.try_into()"
);
}
#[test]
fn procfsbase_try_from_crepr_procthreadself() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase::PATHRS_PROC_THREAD_SELF).map_err(|e| e.kind()),
Ok(ProcfsBase::ProcThreadSelf),
"PATHRS_PROC_THREAD_SELF.try_into()"
);
}
#[test]
fn procfsbase_try_from_crepr_procpid() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_PID | 1)).map_err(|e| e.kind()),
Ok(ProcfsBase::ProcPid(1)),
"PATHRS_PROC_PID(12345).try_into()"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_PID | 12345)).map_err(|e| e.kind()),
Ok(ProcfsBase::ProcPid(12345)),
"PATHRS_PROC_PID(12345).try_into()"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_PID | u32::MAX as u64))
.map_err(|e| e.kind()),
Ok(ProcfsBase::ProcPid(u32::MAX)),
"PATHRS_PROC_PID(u32::MAX).try_into()"
);
}
#[test]
fn procfsbase_try_from_crepr_procspecial_invalid() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_SPECIAL)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"__PATHRS_PROC_TYPE_SPECIAL.try_into() -- invalid type"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_SPECIAL | 0xDEADBEEF))
.map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"(__PATHRS_PROC_TYPE_SPECIAL | 0xDEADBEEF).try_into() -- invalid type"
);
}
#[test]
fn procfsbase_try_from_crepr_procpid_invalid() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_PID)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"PATHRS_PROC_PID(0).try_into() -- invalid pid"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_PID | (u32::MAX as u64 + 1)))
.map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"PATHRS_PROC_PID(u32::MAX + 1).try_into() -- invalid pid"
);
}
#[test]
fn procfsbase_try_from_crepr_proctype_invalid() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase(0xDEAD_BEEF_0000_0001)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"0xDEAD_BEEF_0000_0001.try_into() -- invalid type"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(0xDEAD_BEEF_3EAD_5E1F)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"0xDEAD_BEEF_3EAD_5E1F.try_into() -- invalid type"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(__PATHRS_PROC_TYPE_MASK)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"__PATHRS_PROC_TYPE_MASK.try_into() -- invalid type"
);
}
#[test]
fn procfsbase_try_from_crepr_invalid() {
assert_eq!(
ProcfsBase::try_from(CProcfsBase(0)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"(0).try_into() -- invalid value"
);
assert_eq!(
ProcfsBase::try_from(CProcfsBase(0xDEADBEEF)).map_err(|e| e.kind()),
Err(ErrorKind::InvalidArgument),
"(0xDEADBEEF).try_into() -- invalid value"
);
}
#[test]
fn procfsbase_into_crepr_procroot() {
assert_eq!(
CProcfsBase::from(ProcfsBase::ProcRoot),
CProcfsBase::PATHRS_PROC_ROOT,
"ProcRoot.into() == PATHRS_PROC_ROOT"
);
}
#[test]
fn procfsbase_into_crepr_procself() {
assert_eq!(
CProcfsBase::from(ProcfsBase::ProcSelf),
CProcfsBase::PATHRS_PROC_SELF,
"ProcSelf.into() == PATHRS_PROC_SELF"
);
}
#[test]
fn procfsbase_into_crepr_procthreadself() {
assert_eq!(
CProcfsBase::from(ProcfsBase::ProcThreadSelf),
CProcfsBase::PATHRS_PROC_THREAD_SELF,
"ProcThreadSelf.into() == PATHRS_PROC_THREAD_SELF"
);
}
#[test]
fn procfsbase_into_crepr_procpid() {
assert_eq!(
CProcfsBase::from(ProcfsBase::ProcPid(1)),
CProcfsBase(__PATHRS_PROC_TYPE_PID | 1),
"ProcPid(1).into() == 1"
);
assert_eq!(
CProcfsBase::from(ProcfsBase::ProcPid(1122334455)),
CProcfsBase(__PATHRS_PROC_TYPE_PID | 1122334455),
"ProcPid(1122334455).into() == 1122334455"
);
}
fn check_round_trip(rust: ProcfsBase, c: CProcfsBase) {
let c_to_rust: ProcfsBase = c.try_into().expect("should be valid value");
assert_eq!(
rust, c_to_rust,
"c-to-rust ProcfsBase conversion ({c:?}.try_into())"
);
let rust_to_c: CProcfsBase = rust.into();
assert_eq!(
c, rust_to_c,
"rust-to-c ProcfsBase conversion ({rust:?}.into())"
);
let c_to_rust_to_c: CProcfsBase = c_to_rust.into();
assert_eq!(
c, c_to_rust_to_c,
"rust-to-c-to-rust ProcfsBase conversion ({c_to_rust:?}.into())"
);
let rust_to_c_to_rust: ProcfsBase = rust_to_c
.try_into()
.expect("must be valid value when round-tripping");
assert_eq!(
rust, rust_to_c_to_rust,
"rust-to-c-to-rust ProcfsBase conversion ({rust_to_c:?}.try_into())"
);
}
#[test]
fn procfsbase_round_trip_procroot() {
check_round_trip(ProcfsBase::ProcRoot, CProcfsBase::PATHRS_PROC_ROOT);
}
#[test]
fn procfsbase_round_trip_procself() {
check_round_trip(ProcfsBase::ProcSelf, CProcfsBase::PATHRS_PROC_SELF);
}
#[test]
fn procfsbase_round_trip_procthreadself() {
check_round_trip(
ProcfsBase::ProcThreadSelf,
CProcfsBase::PATHRS_PROC_THREAD_SELF,
);
}
#[test]
fn procfsbase_round_trip_procpid() {
check_round_trip(
ProcfsBase::ProcPid(1),
CProcfsBase(__PATHRS_PROC_TYPE_PID | 1),
);
check_round_trip(
ProcfsBase::ProcPid(12345),
CProcfsBase(__PATHRS_PROC_TYPE_PID | 12345),
);
check_round_trip(
ProcfsBase::ProcPid(1122334455),
CProcfsBase(__PATHRS_PROC_TYPE_PID | 1122334455),
);
check_round_trip(
ProcfsBase::ProcPid(u32::MAX),
CProcfsBase(__PATHRS_PROC_TYPE_PID | u32::MAX as u64),
);
}
#[test]
fn pathrs_procfs_open_cached() {
let procfs_is_cached = ProcfsHandle::new()
.expect("ProcfsHandle::new should not fail")
.into_owned_fd()
.is_none();
let how = ProcfsOpenHow::default();
let fd = unsafe { pathrs_procfs_open(&how as *const _, mem::size_of::<ProcfsOpenHow>()) };
let procfs = if procfs_is_cached {
assert_eq!(
fd,
PATHRS_PROC_DEFAULT_ROOTFD.as_raw_fd(),
"if ProcfsHandle::new() is cached then pathrs_procfs_open() should return PATHRS_PROC_DEFAULT_ROOTFD",
);
ProcfsHandle::new()
} else {
ProcfsHandle::try_from_fd(unsafe { OwnedFd::from_raw_fd(fd) })
}.expect("pathrs_procfs_open should return a valid procfs fd");
let _ = procfs
.open(ProcfsBase::ProcSelf, ".", OpenFlags::O_PATH)
.expect("open(.) should always succeed");
}
#[test]
fn pathrs_procfs_open_unmasked() {
let how = ProcfsOpenHow {
flags: ProcfsOpenFlags::PATHRS_PROCFS_NEW_UNMASKED,
};
let fd = unsafe { pathrs_procfs_open(&how as *const _, mem::size_of::<ProcfsOpenHow>()) };
assert!(fd >= 0, "fd value {fd:#x} should be >= 0");
let procfs = ProcfsHandle::try_from_fd(unsafe { OwnedFd::from_raw_fd(fd) })
.expect("pathrs_procfs_open should return a valid procfs fd");
let _ = procfs
.open(ProcfsBase::ProcSelf, ".", OpenFlags::O_PATH)
.expect("open(.) should always succeed");
}
#[test]
fn pathrs_procfs_open_bad_flag() {
let how_bad_flags = ProcfsOpenHow {
flags: ProcfsOpenFlags::from_bits_retain(0xF000),
};
let ret = unsafe {
pathrs_procfs_open(&how_bad_flags as *const _, mem::size_of::<ProcfsOpenHow>())
};
assert!(
ret < capi_error::__PATHRS_MAX_ERR_VALUE,
"ret value {ret:#x} should be error value"
);
{
let err = unsafe {
capi_error::pathrs_errorinfo(ret)
.expect("error must be retrievable")
.unleak()
};
assert_eq!(
err.saved_errno,
libc::EINVAL as _,
"invalid flag should return EINVAL"
);
}
}
#[test]
fn pathrs_procfs_open_bad_struct() {
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
struct ProcfsOpenHowV2 {
inner: ProcfsOpenHow,
extra: u64,
}
let how_ok_struct = ProcfsOpenHowV2 {
inner: ProcfsOpenHow {
flags: ProcfsOpenFlags::PATHRS_PROCFS_NEW_UNMASKED,
},
extra: 0,
};
let fd = unsafe {
pathrs_procfs_open(
&how_ok_struct as *const _ as *const _,
mem::size_of::<ProcfsOpenHowV2>(),
)
};
assert!(fd >= 0, "fd value {fd:#x} should be >= 0");
{
let _ = unsafe { OwnedFd::from_raw_fd(fd) };
}
let how_bad_struct = ProcfsOpenHowV2 {
inner: ProcfsOpenHow {
flags: ProcfsOpenFlags::PATHRS_PROCFS_NEW_UNMASKED,
},
extra: 0xFF,
};
let ret = unsafe {
pathrs_procfs_open(
&how_bad_struct as *const _ as *const _,
mem::size_of::<ProcfsOpenHowV2>(),
)
};
assert!(
ret < capi_error::__PATHRS_MAX_ERR_VALUE,
"ret value {ret:#x} should be error value"
);
{
let err = unsafe {
capi_error::pathrs_errorinfo(ret)
.expect("error must be retrievable")
.unleak()
};
assert_eq!(
err.saved_errno,
libc::E2BIG as _,
"structure with extra trailing bytes should return E2BIG"
);
}
}
}