#![allow(non_camel_case_types)]
use std::{
alloc::{alloc, dealloc, Layout},
cell::RefCell,
cmp::Ordering,
os::fd::{AsFd, AsRawFd, RawFd},
ptr::NonNull,
sync::LazyLock,
};
use bitflags::bitflags;
use libseccomp::ScmpSyscall;
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
sys::{
epoll::EpollOp,
socket::SockaddrLike,
wait::{Id, WaitPidFlag, WaitStatus as NixWaitStatus},
},
unistd::Pid,
NixPath,
};
use serde::{ser::SerializeMap, Serialize, Serializer};
use crate::{config::*, fs::FileType, XPath};
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
#[expect(non_camel_case_types)]
pub(crate) type timespec_tv_nsec_t = i64;
#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
#[expect(non_camel_case_types)]
pub(crate) type timespec_tv_nsec_t = libc::c_long;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub(crate) struct TimeSpec64 {
pub(crate) tv_sec: i64,
pub(crate) tv_nsec: i64,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub(crate) struct TimeSpec32 {
pub(crate) tv_sec: i32,
pub(crate) tv_nsec: i32,
}
pub(crate) const fn cmsg_align_32(len: usize) -> usize {
len.saturating_add(3) & !3
}
pub(crate) const fn cmsg_space_32(length: u32) -> usize {
cmsg_align_32((length as usize).saturating_add(cmsg_align_32(std::mem::size_of::<cmsghdr32>())))
}
pub(crate) const fn cmsg_len_32(length: u32) -> usize {
cmsg_align_32(std::mem::size_of::<cmsghdr32>()).saturating_add(length as usize)
}
#[repr(C)]
pub struct stat32 {
pub st_dev: libc::dev_t,
pub st_ino: u32,
pub st_nlink: libc::nlink_t,
pub st_mode: libc::mode_t,
pub st_uid: libc::uid_t,
pub st_gid: libc::gid_t,
__pad0: libc::c_int,
pub st_rdev: libc::dev_t,
pub st_size: i32,
pub st_blksize: libc::blksize_t,
pub st_blocks: i32,
pub st_atime: i32,
pub st_atime_nsec: i32,
pub st_mtime: i32,
pub st_mtime_nsec: i32,
pub st_ctime: i32,
pub st_ctime_nsec: i32,
__unused: [i32; 3],
}
impl From<libc::stat64> for stat32 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::unnecessary_cast)]
fn from(stat: libc::stat64) -> Self {
Self {
st_dev: stat.st_dev as u64,
st_ino: stat.st_ino as u32,
st_nlink: stat.st_nlink,
st_mode: stat.st_mode,
st_uid: stat.st_uid,
st_gid: stat.st_gid,
__pad0: 0,
st_rdev: stat.st_rdev as u64,
st_size: stat.st_size as i32,
st_blksize: stat.st_blksize,
st_blocks: stat.st_blocks as i32,
st_atime: stat.st_atime as i32,
st_atime_nsec: stat.st_atime_nsec as i32,
st_mtime: stat.st_mtime as i32,
st_mtime_nsec: stat.st_mtime_nsec as i32,
st_ctime: stat.st_ctime as i32,
st_ctime_nsec: stat.st_ctime_nsec as i32,
__unused: [0; 3],
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct iovec32 {
iov_base: u32,
iov_len: u32,
}
impl From<iovec32> for libc::iovec {
fn from(src: iovec32) -> Self {
libc::iovec {
iov_base: src.iov_base as *mut _,
iov_len: src.iov_len as usize,
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mmsghdr32 {
pub msg_hdr: msghdr32,
pub msg_len: u32,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mmsghdr {
pub msg_hdr: msghdr,
pub msg_len: libc::c_uint,
}
impl From<mmsghdr32> for mmsghdr {
fn from(src: mmsghdr32) -> Self {
mmsghdr {
msg_hdr: msghdr::from(src.msg_hdr),
msg_len: src.msg_len,
}
}
}
impl From<mmsghdr> for mmsghdr32 {
fn from(src: mmsghdr) -> Self {
mmsghdr32 {
msg_hdr: msghdr32::from(src.msg_hdr),
msg_len: src.msg_len,
}
}
}
const _: () = {
assert!(
size_of::<libc::mmsghdr>() == size_of::<mmsghdr>(),
"Size mismatch between libc::mmsghdr and compat::mmsghdr"
);
};
impl From<libc::mmsghdr> for mmsghdr {
fn from(msg: libc::mmsghdr) -> Self {
unsafe { std::mem::transmute(msg) }
}
}
impl From<libc::mmsghdr> for mmsghdr32 {
fn from(msg: libc::mmsghdr) -> Self {
mmsghdr::from(msg).into()
}
}
impl From<mmsghdr> for libc::mmsghdr {
fn from(msg: mmsghdr) -> Self {
unsafe { std::mem::transmute(msg) }
}
}
impl From<mmsghdr32> for libc::mmsghdr {
fn from(msg: mmsghdr32) -> Self {
mmsghdr::from(msg).into()
}
}
#[repr(C)]
pub union mmsghdr_union {
pub m32: mmsghdr32,
pub m64: mmsghdr,
}
#[repr(C)]
pub struct cmsghdr32 {
pub cmsg_len: u32,
pub cmsg_level: i32,
pub cmsg_type: i32,
}
#[repr(C)]
pub struct cmsghdr {
pub cmsg_len: libc::size_t,
pub cmsg_level: libc::c_int,
pub cmsg_type: libc::c_int,
}
impl From<cmsghdr32> for cmsghdr {
fn from(src: cmsghdr32) -> Self {
cmsghdr {
cmsg_len: src.cmsg_len as libc::size_t,
cmsg_level: src.cmsg_level,
cmsg_type: src.cmsg_type,
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct msghdr32 {
pub msg_name: u32, pub msg_namelen: u32, pub msg_iov: u32, pub msg_iovlen: u32, pub msg_control: u32, pub msg_controllen: u32, pub msg_flags: i32, }
#[derive(Copy, Clone)]
#[repr(C)]
pub struct msghdr {
pub msg_name: *mut libc::c_void,
pub msg_namelen: libc::socklen_t,
pub msg_iov: *mut libc::iovec,
pub msg_iovlen: libc::size_t,
pub msg_control: *mut libc::c_void,
pub msg_controllen: libc::size_t,
pub msg_flags: libc::c_int,
}
impl From<msghdr32> for msghdr {
fn from(msg: msghdr32) -> Self {
msghdr {
msg_name: msg.msg_name as *mut libc::c_void,
msg_namelen: msg.msg_namelen as libc::socklen_t,
msg_iov: msg.msg_iov as *mut libc::iovec,
msg_iovlen: msg.msg_iovlen as libc::size_t,
msg_control: msg.msg_control as *mut libc::c_void,
msg_controllen: msg.msg_controllen as libc::size_t,
msg_flags: msg.msg_flags as libc::c_int,
}
}
}
#[expect(clippy::unnecessary_cast)]
#[expect(clippy::cast_possible_truncation)]
impl From<msghdr> for msghdr32 {
fn from(msg: msghdr) -> Self {
msghdr32 {
msg_name: msg.msg_name as u32,
msg_namelen: msg.msg_namelen as u32,
msg_iov: msg.msg_iov as u32,
msg_iovlen: msg.msg_iovlen as u32,
msg_control: msg.msg_control as u32,
msg_controllen: msg.msg_controllen as u32,
msg_flags: msg.msg_flags as i32,
}
}
}
const _: () = {
assert!(
size_of::<libc::msghdr>() == size_of::<msghdr>(),
"Size mismatch between libc::msghdr and compat::msghdr"
);
};
impl From<libc::msghdr> for msghdr {
fn from(msg: libc::msghdr) -> Self {
unsafe { std::mem::transmute(msg) }
}
}
impl From<libc::msghdr> for msghdr32 {
fn from(msg: libc::msghdr) -> Self {
msghdr::from(msg).into()
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct XattrArgs {
pub value: u64,
pub size: u32,
pub flags: u32,
}
static SYS_GETXATTRAT: LazyLock<libc::c_long> = LazyLock::new(|| {
ScmpSyscall::from_name("getxattrat")
.map(i32::from)
.map(libc::c_long::from)
.unwrap_or(0)
});
pub unsafe fn getxattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
name: *const libc::c_char,
args: &mut XattrArgs,
flags: AtFlags,
) -> Result<usize, Errno> {
let sysno = if *SYS_GETXATTRAT > 0 {
*SYS_GETXATTRAT
} else {
return Err(Errno::ENOSYS);
};
path.with_nix_path(|c_path| {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
Errno::result(
unsafe {
libc::syscall(
sysno as libc::c_long,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(),
name,
args as *mut XattrArgs,
std::mem::size_of::<XattrArgs>(),
)
},
)
.map(|r| r as usize)
})?
}
static SYS_SETXATTRAT: LazyLock<libc::c_long> = LazyLock::new(|| {
ScmpSyscall::from_name("setxattrat")
.map(i32::from)
.map(libc::c_long::from)
.unwrap_or(0)
});
pub unsafe fn setxattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
name: *const libc::c_char,
args: &XattrArgs,
flags: AtFlags,
) -> Result<(), Errno> {
let sysno = if *SYS_SETXATTRAT > 0 {
*SYS_SETXATTRAT
} else {
return Err(Errno::ENOSYS);
};
path.with_nix_path(|c_path| {
Errno::result(unsafe {
libc::syscall(
sysno as libc::c_long,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(),
name,
args as *const XattrArgs,
std::mem::size_of::<XattrArgs>(),
)
})
.map(drop)
})?
}
static SYS_LISTXATTRAT: LazyLock<libc::c_long> = LazyLock::new(|| {
ScmpSyscall::from_name("listxattrat")
.map(i32::from)
.map(libc::c_long::from)
.unwrap_or(0)
});
pub unsafe fn listxattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
flags: AtFlags,
addr: *mut libc::c_char,
size: usize,
) -> Result<usize, Errno> {
let sysno = if *SYS_LISTXATTRAT > 0 {
*SYS_LISTXATTRAT
} else {
return Err(Errno::ENOSYS);
};
path.with_nix_path(|c_path| {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
Errno::result(
unsafe {
libc::syscall(
sysno as libc::c_long,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(),
addr,
size,
)
},
)
.map(|r| r as usize)
})?
}
static SYS_REMOVEXATTRAT: LazyLock<libc::c_long> = LazyLock::new(|| {
ScmpSyscall::from_name("removexattrat")
.map(i32::from)
.map(libc::c_long::from)
.unwrap_or(0)
});
pub unsafe fn removexattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
name: *const libc::c_char,
flags: AtFlags,
) -> Result<(), Errno> {
let sysno = if *SYS_REMOVEXATTRAT > 0 {
*SYS_REMOVEXATTRAT
} else {
return Err(Errno::ENOSYS);
};
path.with_nix_path(|c_path| {
Errno::result(unsafe {
libc::syscall(
sysno as libc::c_long,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(),
name,
)
})
.map(drop)
})?
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct FileStatxTimestamp {
pub tv_sec: i64,
pub tv_nsec: u32,
__statx_timestamp_pad1: [i32; 1],
}
impl PartialEq for FileStatxTimestamp {
fn eq(&self, other: &Self) -> bool {
self.tv_sec == other.tv_sec && self.tv_nsec == other.tv_nsec
}
}
impl Eq for FileStatxTimestamp {}
impl PartialOrd for FileStatxTimestamp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FileStatxTimestamp {
fn cmp(&self, other: &Self) -> Ordering {
match self.tv_sec.cmp(&other.tv_sec) {
Ordering::Equal => self.tv_nsec.cmp(&other.tv_nsec),
ord => ord,
}
}
}
impl Serialize for FileStatxTimestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("sec", &self.tv_sec)?;
map.serialize_entry("nsec", &self.tv_nsec)?;
map.end()
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct FileStatx {
pub stx_mask: u32, stx_blksize: u32, stx_attributes: u64,
pub stx_nlink: u32, pub stx_uid: u32, pub stx_gid: u32, pub stx_mode: u16, __statx_pad1: [u16; 1],
pub stx_ino: u64, pub stx_size: u64, stx_blocks: u64, stx_attributes_mask: u64,
pub stx_atime: FileStatxTimestamp, stx_btime: FileStatxTimestamp, pub stx_ctime: FileStatxTimestamp, pub stx_mtime: FileStatxTimestamp,
pub stx_rdev_major: u32, pub stx_rdev_minor: u32,
pub(crate) stx_dev_major: u32, pub(crate) stx_dev_minor: u32,
pub stx_mnt_id: u64,
stx_dio_mem_align: u32, stx_dio_offset_align: u32,
stx_subvol: u64,
stx_atomic_write_unit_min: u32, stx_atomic_write_unit_max: u32, stx_atomic_write_segments_max: u32,
stx_dio_read_offset_align: u32,
stx_atomic_write_unit_max_opt: u32, __statx_spare2: [u32; 1],
__statx_spare3: [u64; 8], }
impl FileStatx {
pub(crate) fn file_mode(&self) -> libc::mode_t {
libc::mode_t::from(self.stx_mode) & !libc::S_IFMT
}
pub(crate) fn file_type(&self) -> FileType {
FileType::from(libc::mode_t::from(self.stx_mode))
}
}
impl Serialize for FileStatx {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(15))?;
map.serialize_entry("mask", &self.stx_mask)?;
map.serialize_entry("nlink", &self.stx_nlink)?;
map.serialize_entry("uid", &self.stx_uid)?;
map.serialize_entry("gid", &self.stx_gid)?;
map.serialize_entry("mode", &self.stx_mode)?;
map.serialize_entry("file_mode", &self.file_mode())?;
map.serialize_entry("file_type", &self.file_type())?;
map.serialize_entry("ino", &self.stx_ino)?;
map.serialize_entry("size", &self.stx_size)?;
map.serialize_entry("atime", &self.stx_atime)?;
map.serialize_entry("ctime", &self.stx_ctime)?;
map.serialize_entry("mtime", &self.stx_mtime)?;
map.serialize_entry("rdev_major", &self.stx_rdev_major)?;
map.serialize_entry("rdev_minor", &self.stx_rdev_minor)?;
map.serialize_entry("mnt_id", &self.stx_mnt_id)?;
map.end()
}
}
#[derive(Clone)]
pub struct DirIter {
buffer: NonNull<u8>,
bufsiz: usize,
memsiz: usize,
offset: usize,
}
const DIRENT_ALIGN: usize = std::mem::align_of::<libc::dirent64>();
impl DirIter {
pub fn new(bufsiz: usize) -> Result<Self, Errno> {
let layout = Layout::from_size_align(bufsiz, DIRENT_ALIGN).or(Err(Errno::EINVAL))?;
let buffer = unsafe { alloc(layout) };
let buffer = NonNull::new(buffer).ok_or(Errno::ENOMEM)?;
Ok(Self {
buffer,
memsiz: bufsiz,
bufsiz: 0,
offset: 0,
})
}
pub fn readdir<Fd: AsFd>(&mut self, fd: Fd, read_bufsiz: usize) -> Result<&mut Self, Errno> {
self.offset = 0;
let bufsiz = read_bufsiz.min(self.memsiz);
let retsiz = sys_getdents64(fd, self.buffer.as_ptr().cast(), bufsiz)?;
if retsiz == 0 {
return Err(Errno::ECANCELED); }
self.bufsiz = retsiz;
Ok(self)
}
}
impl<'a> Iterator for &'a mut DirIter {
type Item = DirEntry<'a>;
#[expect(clippy::arithmetic_side_effects)]
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.bufsiz {
return None;
}
unsafe {
#[expect(clippy::cast_ptr_alignment)]
let dirent_ptr = self
.buffer
.as_ptr()
.add(self.offset)
.cast::<libc::dirent64>();
let d_reclen = (*dirent_ptr).d_reclen as usize;
let namelen = libc::strlen((*dirent_ptr).d_name.as_ptr());
let dirent = std::slice::from_raw_parts(dirent_ptr.cast::<u8>(), d_reclen);
self.offset += d_reclen;
Some(DirEntry { dirent, namelen })
}
}
}
impl Drop for DirIter {
fn drop(&mut self) {
#[expect(clippy::disallowed_methods)]
let layout = Layout::from_size_align(self.memsiz, DIRENT_ALIGN).unwrap();
unsafe { dealloc(self.buffer.as_ptr(), layout) };
}
}
impl std::fmt::Debug for DirIter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DirIter")
.field("bufsiz", &self.bufsiz)
.field("memsiz", &self.memsiz)
.field("offset", &self.offset)
.finish()
}
}
#[derive(Clone)]
pub struct DirEntry<'a> {
dirent: &'a [u8],
namelen: usize,
}
impl std::fmt::Debug for DirEntry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("DirEntry")
.field(&self.as_xpath())
.field(&self.file_type())
.finish()
}
}
impl DirEntry<'_> {
pub fn as_xpath(&self) -> &XPath {
XPath::from_bytes(self.name_bytes())
}
pub fn as_bytes(&self) -> &[u8] {
self.dirent
}
pub fn is_dot(&self) -> bool {
if !self.is_dir() {
return false;
}
let name = self.name_bytes();
is_equal(name, b"..") || is_equal(name, b".")
}
pub fn is_dir(&self) -> bool {
self.file_type().is_dir()
}
pub fn is_file(&self) -> bool {
self.file_type().is_file()
}
pub fn is_symlink(&self) -> bool {
self.file_type().is_symlink()
}
pub fn is_block_device(&self) -> bool {
self.file_type().is_block_device()
}
pub fn is_char_device(&self) -> bool {
self.file_type().is_char_device()
}
pub fn is_fifo(&self) -> bool {
self.file_type().is_fifo()
}
pub fn is_socket(&self) -> bool {
self.file_type().is_socket()
}
pub fn is_unknown(&self) -> bool {
self.file_type().is_unknown()
}
pub fn file_type(&self) -> FileType {
let dirent = self.dirent64();
FileType::from(unsafe { (*dirent).d_type })
}
pub fn ino(&self) -> u64 {
let dirent = self.dirent64();
unsafe { (*dirent).d_ino }
}
pub fn size(&self) -> usize {
let dirent = self.dirent64();
unsafe { (*dirent).d_reclen as usize }
}
pub fn name_bytes(&self) -> &[u8] {
let dirent = self.dirent64();
unsafe {
let d_name = (*dirent).d_name.as_ptr() as *const u8;
std::slice::from_raw_parts(d_name, self.namelen)
}
}
fn dirent64(&self) -> *const libc::dirent64 {
#![allow(clippy::cast_ptr_alignment)]
self.dirent.as_ptr() as *const libc::dirent64
}
}
pub fn getdents64<Fd: AsFd>(
fd: Fd,
bufsiz: usize,
) -> Result<impl Iterator<Item = DirEntry<'static>>, Errno> {
thread_local! {
static DIR_ITER: RefCell<Option<DirIter>> = const { RefCell::new(None) };
}
let iter: &'static mut DirIter = DIR_ITER.with(|cell| {
let mut borrow = cell.borrow_mut();
if borrow.is_none() {
*borrow = Some(DirIter::new(DIRENT_BUF_SIZE)?);
}
let iter: &'static mut DirIter = unsafe {
std::mem::transmute::<&mut DirIter, &'static mut DirIter>(
borrow.as_mut().unwrap_unchecked(),
)
};
Ok::<&'static mut DirIter, Errno>(iter)
})?;
iter.readdir(fd, bufsiz)?;
Ok(iter)
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn sys_getdents64<Fd: AsFd>(fd: Fd, buf: *mut libc::c_void, bytes: usize) -> Result<usize, Errno> {
Errno::result(unsafe {
libc::syscall(libc::SYS_getdents64, fd.as_fd().as_raw_fd(), buf, bytes)
})
.map(|size| size as usize)
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WaitStatus {
Exited(Pid, i32),
Signaled(Pid, i32, bool),
Stopped(Pid, i32),
PtraceEvent(Pid, i32, libc::c_int),
PtraceSyscall(Pid),
Continued(Pid),
StillAlive,
}
impl From<NixWaitStatus> for WaitStatus {
fn from(status: NixWaitStatus) -> Self {
match status {
NixWaitStatus::Exited(pid, code) => WaitStatus::Exited(pid, code),
NixWaitStatus::Signaled(pid, signal, core_dump) => {
WaitStatus::Signaled(pid, signal as i32, core_dump)
}
NixWaitStatus::Stopped(pid, signal) => WaitStatus::Stopped(pid, signal as i32),
NixWaitStatus::PtraceEvent(pid, signal, event) => {
WaitStatus::PtraceEvent(pid, signal as i32, event)
}
NixWaitStatus::PtraceSyscall(pid) => WaitStatus::PtraceSyscall(pid),
NixWaitStatus::Continued(pid) => WaitStatus::Continued(pid),
NixWaitStatus::StillAlive => WaitStatus::StillAlive,
}
}
}
fn exited(status: i32) -> bool {
libc::WIFEXITED(status)
}
fn exit_status(status: i32) -> i32 {
libc::WEXITSTATUS(status)
}
fn signaled(status: i32) -> bool {
libc::WIFSIGNALED(status)
}
fn term_signal(status: i32) -> i32 {
libc::WTERMSIG(status)
}
fn dumped_core(status: i32) -> bool {
libc::WCOREDUMP(status)
}
fn stopped(status: i32) -> bool {
libc::WIFSTOPPED(status)
}
fn stop_signal(status: i32) -> i32 {
libc::WSTOPSIG(status)
}
fn syscall_stop(status: i32) -> bool {
libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
}
fn stop_additional(status: i32) -> libc::c_int {
(status >> 16) as libc::c_int
}
fn continued(status: i32) -> bool {
libc::WIFCONTINUED(status)
}
impl WaitStatus {
pub(crate) fn from_raw(pid: Pid, status: i32) -> WaitStatus {
if exited(status) {
WaitStatus::Exited(pid, exit_status(status))
} else if signaled(status) {
WaitStatus::Signaled(pid, term_signal(status), dumped_core(status))
} else if stopped(status) {
let status_additional = stop_additional(status);
if syscall_stop(status) {
WaitStatus::PtraceSyscall(pid)
} else if status_additional == 0 {
WaitStatus::Stopped(pid, stop_signal(status))
} else {
WaitStatus::PtraceEvent(pid, stop_signal(status), stop_additional(status))
}
} else {
assert!(continued(status));
WaitStatus::Continued(pid)
}
}
}
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus, Errno> {
#[expect(clippy::cast_sign_loss)]
let (idtype, idval) = match id {
Id::All => (libc::P_ALL, 0),
Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t),
Id::PIDFd(fd) => (libc::P_PIDFD, fd.as_raw_fd() as libc::id_t),
_ => unreachable!(),
};
let siginfo = unsafe {
let mut siginfo: libc::siginfo_t = std::mem::zeroed();
Errno::result(libc::waitid(idtype, idval, &raw mut siginfo, flags.bits()))?;
siginfo
};
let si_pid = unsafe { siginfo.si_pid() };
if si_pid == 0 {
return Ok(WaitStatus::StillAlive);
}
assert_eq!(siginfo.si_signo, libc::SIGCHLD);
let pid = Pid::from_raw(si_pid);
let si_status = unsafe { siginfo.si_status() };
let status = match siginfo.si_code {
libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
libc::CLD_KILLED | libc::CLD_DUMPED => {
WaitStatus::Signaled(pid, si_status, siginfo.si_code == libc::CLD_DUMPED)
}
libc::CLD_STOPPED => WaitStatus::Stopped(pid, si_status),
libc::CLD_CONTINUED => WaitStatus::Continued(pid),
libc::CLD_TRAPPED => {
if si_status == libc::SIGTRAP | 0x80 {
WaitStatus::PtraceSyscall(pid)
} else {
WaitStatus::PtraceEvent(pid, si_status & 0xff, (si_status >> 8) as libc::c_int)
}
}
_ => return Err(Errno::EINVAL),
};
Ok(status)
}
pub(crate) fn pipe2_raw(flags: OFlag) -> Result<(RawFd, RawFd), Errno> {
let mut fds = std::mem::MaybeUninit::<[RawFd; 2]>::uninit();
let res = unsafe { libc::pipe2(fds.as_mut_ptr().cast(), flags.bits()) };
Errno::result(res)?;
let [read, write] = unsafe { fds.assume_init() };
Ok((read, write))
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) const PF_UNSPEC: libc::sa_family_t = libc::AF_UNSPEC as libc::sa_family_t;
#[expect(clippy::cast_possible_truncation)]
pub(crate) const PF_UNIX: libc::sa_family_t = libc::AF_UNIX as libc::sa_family_t;
#[expect(clippy::cast_possible_truncation)]
pub(crate) const PF_INET: libc::sa_family_t = libc::AF_INET as libc::sa_family_t;
#[expect(clippy::cast_possible_truncation)]
pub(crate) const PF_INET6: libc::sa_family_t = libc::AF_INET6 as libc::sa_family_t;
#[expect(clippy::cast_possible_truncation)]
pub(crate) const PF_ALG: libc::sa_family_t = libc::AF_ALG as libc::sa_family_t;
#[expect(clippy::cast_possible_truncation)]
pub(crate) const PF_NETLINK: libc::sa_family_t = libc::AF_NETLINK as libc::sa_family_t;
pub(crate) const PF_MAX: libc::sa_family_t = 46;
pub(crate) fn addr_family<T: SockaddrLike>(addr: &T) -> libc::sa_family_t {
unsafe { (*addr.as_ptr()).sa_family }
}
pub const STATX_TYPE: libc::c_uint = 0x00000001;
pub const STATX_MODE: libc::c_uint = 0x00000002;
pub const STATX_NLINK: libc::c_uint = 0x00000004;
pub const STATX_UID: libc::c_uint = 0x00000008;
pub const STATX_GID: libc::c_uint = 0x00000010;
pub const STATX_ATIME: libc::c_uint = 0x00000020;
pub const STATX_MTIME: libc::c_uint = 0x00000040;
pub const STATX_CTIME: libc::c_uint = 0x00000080;
pub const STATX_INO: libc::c_uint = 0x00000100;
pub const STATX_SIZE: libc::c_uint = 0x00000200;
pub const STATX_BLOCKS: libc::c_uint = 0x00000400;
pub const STATX_BASIC_STATS: libc::c_uint = 0x000007ff;
pub const STATX_BTIME: libc::c_uint = 0x00000800;
pub const STATX_MNT_ID: libc::c_uint = 0x00001000;
pub const STATX_DIOALIGN: libc::c_uint = 0x00002000;
pub const STATX_MNT_ID_UNIQUE: libc::c_uint = 0x00004000;
pub const STATX_SUBVOL: libc::c_uint = 0x00008000;
pub const STATX_WRITE_ATOMIC: libc::c_uint = 0x00010000;
pub const STATX_DIO_READ_ALIGN: libc::c_uint = 0x00020000;
pub const AT_STATX_SYNC_AS_STAT: libc::c_int = 0x0000;
pub const AT_STATX_FORCE_SYNC: libc::c_int = 0x2000;
pub const AT_STATX_DONT_SYNC: libc::c_int = 0x4000;
pub fn statx<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
pathname: &P,
flags: libc::c_int,
mask: libc::c_uint,
) -> Result<FileStatx, Errno> {
let dirfd = dirfd.as_fd().as_raw_fd();
let mut dst = std::mem::MaybeUninit::uninit();
Errno::result(pathname.with_nix_path(|cstr| unsafe {
libc::syscall(
libc::SYS_statx,
dirfd,
cstr.as_ptr(),
flags,
mask,
dst.as_mut_ptr(),
)
})?)?;
Ok(unsafe { dst.assume_init() })
}
pub fn fstatx<Fd: AsFd>(fd: Fd, mask: libc::c_uint) -> Result<FileStatx, Errno> {
let fd = fd.as_fd().as_raw_fd();
let mut dst = std::mem::MaybeUninit::uninit();
Errno::result(unsafe {
libc::syscall(
libc::SYS_statx,
fd,
c"".as_ptr(),
libc::AT_EMPTY_PATH,
mask,
dst.as_mut_ptr(),
)
})?;
Ok(unsafe { dst.assume_init() })
}
pub(crate) use libc::stat64 as FileStat64;
pub(crate) fn fstatat64<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
pathname: &P,
flags: libc::c_int,
) -> Result<FileStat64, Errno> {
let dirfd = dirfd.unwrap_or(libc::AT_FDCWD);
let mut dst = std::mem::MaybeUninit::uninit();
Errno::result(pathname.with_nix_path(|cstr| unsafe {
libc::fstatat64(dirfd, cstr.as_ptr(), dst.as_mut_ptr(), flags)
})?)?;
Ok(unsafe { dst.assume_init() })
}
#[cfg(target_os = "freebsd")]
type fs_type_t = u32;
#[cfg(target_os = "android")]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_arch = "s390x", not(target_env = "musl")))]
type fs_type_t = libc::c_uint;
#[cfg(all(target_os = "linux", target_env = "musl"))]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "ohos"))]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "uclibc"))]
type fs_type_t = libc::c_int;
#[cfg(all(
target_os = "linux",
not(any(
target_arch = "s390x",
target_env = "musl",
target_env = "ohos",
target_env = "uclibc"
))
))]
type fs_type_t = libc::__fsword_t;
const BTRFS_SUPER_MAGIC: fs_type_t = libc::BTRFS_SUPER_MAGIC as fs_type_t;
const HUGETLBFS_MAGIC: fs_type_t = libc::HUGETLBFS_MAGIC as fs_type_t;
const OVERLAYFS_SUPER_MAGIC: fs_type_t = libc::OVERLAYFS_SUPER_MAGIC as fs_type_t;
const PROC_SUPER_MAGIC: fs_type_t = libc::PROC_SUPER_MAGIC as fs_type_t;
pub(crate) struct Statfs64(libc::statfs64);
impl Statfs64 {
pub(crate) fn has_broken_device_ids(&self) -> bool {
matches!(self.0.f_type, OVERLAYFS_SUPER_MAGIC | BTRFS_SUPER_MAGIC)
}
pub(crate) fn is_huge_file(&self) -> bool {
self.0.f_type == HUGETLBFS_MAGIC
}
pub(crate) fn is_proc(&self) -> bool {
self.0.f_type == PROC_SUPER_MAGIC
}
}
pub(crate) fn fstatfs64<Fd: AsFd>(fd: Fd) -> Result<Statfs64, Errno> {
let mut dst = std::mem::MaybeUninit::uninit();
Errno::result(unsafe { libc::fstatfs64(fd.as_fd().as_raw_fd(), dst.as_mut_ptr()) })?;
Ok(Statfs64(unsafe { dst.assume_init() }))
}
pub fn epoll_ctl_safe<E: AsFd>(
epoll: &E,
fd: RawFd,
event: Option<libc::epoll_event>,
) -> Result<(), Errno> {
let (result, ignore_errno) = if let Some(mut event) = event {
(
Errno::result(unsafe {
libc::epoll_ctl(
epoll.as_fd().as_raw_fd(),
EpollOp::EpollCtlAdd as libc::c_int,
fd,
&raw mut event,
)
}),
Errno::EEXIST,
)
} else {
(
Errno::result(unsafe {
libc::epoll_ctl(
epoll.as_fd().as_raw_fd(),
EpollOp::EpollCtlDel as libc::c_int,
fd,
std::ptr::null_mut(),
)
}),
Errno::ENOENT,
)
};
match result {
Ok(_) => Ok(()),
Err(errno) if errno == ignore_errno => Ok(()),
Err(errno) => Err(errno),
}
}
pub fn epoll_ctl_mod_safe<E: AsFd>(
epoll: &E,
fd: RawFd,
mut event: libc::epoll_event,
) -> Result<(), Errno> {
Errno::result(unsafe {
libc::epoll_ctl(
epoll.as_fd().as_raw_fd(),
EpollOp::EpollCtlMod as libc::c_int,
fd,
&raw mut event,
)
})
.map(drop)
}
const EPIOCSPARAMS: u64 = 0x40088a01;
const EPIOCGPARAMS: u64 = 0x80088a02;
#[repr(C)]
pub struct EpollParams {
pub busy_poll_usecs: u32,
pub busy_poll_budget: u16,
pub prefer_busy_poll: u16,
pad: u8,
}
impl EpollParams {
pub fn new(busy_poll_usecs: u32, busy_poll_budget: u16, prefer_busy_poll: bool) -> Self {
let prefer_busy_poll = if prefer_busy_poll { 1 } else { 0 };
Self {
busy_poll_usecs,
busy_poll_budget,
prefer_busy_poll,
pad: 0,
}
}
}
impl Serialize for EpollParams {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("busy_poll_usecs", &self.busy_poll_usecs)?;
map.serialize_entry("busy_poll_budget", &self.busy_poll_budget)?;
map.serialize_entry("prefer_busy_poll", &(self.prefer_busy_poll != 0))?;
map.end()
}
}
pub fn epoll_set_params<Fd: AsFd>(fd: Fd, params: &EpollParams) -> Result<(), Errno> {
Errno::result(unsafe {
libc::syscall(
libc::SYS_ioctl,
fd.as_fd().as_raw_fd(),
EPIOCSPARAMS,
params,
)
})
.map(drop)
}
pub fn epoll_get_params<Fd: AsFd>(fd: Fd) -> Result<EpollParams, Errno> {
let mut params = std::mem::MaybeUninit::uninit();
Errno::result(unsafe {
libc::syscall(
libc::SYS_ioctl,
fd.as_fd().as_raw_fd(),
EPIOCGPARAMS,
params.as_mut_ptr(),
)
})?;
Ok(unsafe { params.assume_init() })
}
pub fn getsockdomain<Fd: AsFd>(fd: Fd) -> Result<libc::c_int, Errno> {
#[expect(clippy::cast_possible_truncation)]
let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
let mut fml: libc::c_int = 0;
Errno::result(unsafe {
libc::getsockopt(
fd.as_fd().as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_DOMAIN,
std::ptr::addr_of_mut!(fml) as *mut _,
&raw mut len,
)
})?;
Ok(fml)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LsmId {
Undef,
Capability,
Selinux,
Smack,
Tomoyo,
AppArmor,
Yama,
LoadPin,
SafeSetID,
Lockdown,
Bpf,
Landlock,
Ima,
Evm,
Ipe,
Unknown(u64),
}
impl From<u64> for LsmId {
fn from(id: u64) -> Self {
match id {
0 => LsmId::Undef,
100 => LsmId::Capability,
101 => LsmId::Selinux,
102 => LsmId::Smack,
103 => LsmId::Tomoyo,
104 => LsmId::AppArmor,
105 => LsmId::Yama,
106 => LsmId::LoadPin,
107 => LsmId::SafeSetID,
108 => LsmId::Lockdown,
109 => LsmId::Bpf,
110 => LsmId::Landlock,
111 => LsmId::Ima,
112 => LsmId::Evm,
113 => LsmId::Ipe,
other => LsmId::Unknown(other),
}
}
}
impl std::fmt::Display for LsmId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LsmId::Undef => write!(f, "undef"),
LsmId::Capability => write!(f, "capability"),
LsmId::Selinux => write!(f, "selinux"),
LsmId::Smack => write!(f, "smack"),
LsmId::Tomoyo => write!(f, "tomoyo"),
LsmId::AppArmor => write!(f, "apparmor"),
LsmId::Yama => write!(f, "yama"),
LsmId::LoadPin => write!(f, "loadpin"),
LsmId::SafeSetID => write!(f, "safesetid"),
LsmId::Lockdown => write!(f, "lockdown"),
LsmId::Bpf => write!(f, "bpf"),
LsmId::Landlock => write!(f, "landlock"),
LsmId::Ima => write!(f, "ima"),
LsmId::Evm => write!(f, "evm"),
LsmId::Ipe => write!(f, "ipe"),
LsmId::Unknown(id) => write!(f, "unknown({id})"),
}
}
}
pub static SYS_LSM_LIST_MODULES: LazyLock<libc::c_long> = LazyLock::new(|| {
ScmpSyscall::from_name("lsm_list_modules")
.map(i32::from)
.map(libc::c_long::from)
.unwrap_or(0)
});
pub fn lsm_list_modules() -> Result<Vec<LsmId>, Errno> {
let sysno = *SYS_LSM_LIST_MODULES;
if sysno == 0 {
return Err(Errno::ENOSYS);
}
let mut size: u32 = 0;
let res = Errno::result(
unsafe {
libc::syscall(
sysno as libc::c_long,
std::ptr::null_mut::<u64>(),
std::ptr::addr_of_mut!(size),
0u32,
)
},
);
match res {
Ok(0) => return Err(Errno::ENOENT),
Ok(_) => return Err(Errno::EINVAL),
Err(Errno::E2BIG) => {} Err(errno) => return Err(errno),
}
if size == 0 {
return Err(Errno::ENOENT);
}
let count = (size / 8) as usize;
let mut buf = vec![0u64; count];
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let count = Errno::result(
unsafe {
libc::syscall(
sysno as libc::c_long,
buf.as_mut_ptr(),
std::ptr::addr_of_mut!(size),
0u32,
)
},
)
.map(|res| res as usize)?;
if count == 0 {
return Err(Errno::ENOENT);
}
let mut out = Vec::with_capacity(count);
for item in buf.iter().take(count).copied().map(LsmId::from) {
out.push(item)
}
Ok(out)
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct RenameFlags: u32 {
const RENAME_NOREPLACE = 1;
const RENAME_EXCHANGE = 2;
const RENAME_WHITEOUT = 4;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct MsgFlags: i32 {
const MSG_OOB = 0x01;
const MSG_PEEK = 0x02;
const MSG_DONTROUTE = 0x04;
const MSG_TRYHARD = Self::MSG_DONTROUTE.bits();
const MSG_CTRUNC = 0x08;
const MSG_PROXY = 0x10;
const MSG_TRUNC = 0x20;
const MSG_DONTWAIT = 0x40;
const MSG_EOR = 0x80;
const MSG_WAITALL = 0x100;
const MSG_FIN = 0x200;
const MSG_SYN = 0x400;
const MSG_CONFIRM = 0x800;
const MSG_RST = 0x1000;
const MSG_ERRQUEUE = 0x2000;
const MSG_NOSIGNAL = 0x4000;
const MSG_MORE = 0x8000;
const MSG_WAITFORONE = 0x10000;
const MSG_BATCH = 0x40000;
const MSG_SOCK_DEVMEM = 0x2000000;
const MSG_ZEROCOPY = 0x4000000;
const MSG_FASTOPEN = 0x20000000;
const MSG_CMSG_CLOEXEC = 0x40000000;
const MSG_NOTIFICATION = Self::MSG_MORE.bits();
}
}
#[expect(clippy::disallowed_types)]
use nix::sys::socket::MsgFlags as NixMsgFlags;
#[expect(clippy::disallowed_types)]
impl From<MsgFlags> for NixMsgFlags {
fn from(msgflags: MsgFlags) -> Self {
Self::from_bits_retain(msgflags.bits())
}
}
#[expect(clippy::disallowed_types)]
impl From<NixMsgFlags> for MsgFlags {
fn from(msgflags: NixMsgFlags) -> Self {
Self::from_bits_retain(msgflags.bits())
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct AddWatchFlags: u32 {
const IN_ACCESS = 0x00000001;
const IN_MODIFY = 0x00000002;
const IN_ATTRIB = 0x00000004;
const IN_CLOSE_WRITE = 0x00000008;
const IN_CLOSE_NOWRITE = 0x00000010;
const IN_OPEN = 0x00000020;
const IN_MOVED_FROM = 0x00000040;
const IN_MOVED_TO = 0x00000080;
const IN_CREATE = 0x00000100;
const IN_DELETE = 0x00000200;
const IN_DELETE_SELF = 0x00000400;
const IN_MOVE_SELF = 0x00000800;
const IN_UNMOUNT = 0x00002000;
const IN_Q_OVERFLOW = 0x00004000;
const IN_IGNORED = 0x00008000;
const IN_CLOSE = Self::IN_CLOSE_WRITE.bits() | Self::IN_CLOSE_NOWRITE.bits();
const IN_MOVE = Self::IN_MOVED_FROM.bits() | Self::IN_MOVED_TO.bits();
const IN_ONLYDIR = 0x01000000;
const IN_DONT_FOLLOW = 0x02000000;
const IN_EXCL_UNLINK = 0x04000000;
const IN_MASK_CREATE = 0x10000000;
const IN_MASK_ADD = 0x20000000;
const IN_ISDIR = 0x40000000;
const IN_ONESHOT = 0x80000000;
const IN_ALL_EVENTS =
Self::IN_ACCESS.bits() |
Self::IN_MODIFY.bits() |
Self::IN_ATTRIB.bits() |
Self::IN_CLOSE_WRITE.bits() |
Self::IN_CLOSE_NOWRITE.bits() |
Self::IN_OPEN.bits() |
Self::IN_MOVED_FROM.bits() |
Self::IN_MOVED_TO.bits() |
Self::IN_DELETE.bits() |
Self::IN_CREATE.bits() |
Self::IN_DELETE_SELF.bits() |
Self::IN_MOVE_SELF.bits();
}
}
#[expect(clippy::disallowed_types)]
use nix::sys::inotify::AddWatchFlags as NixAddWatchFlags;
#[expect(clippy::disallowed_types)]
impl From<AddWatchFlags> for NixAddWatchFlags {
fn from(addwatchflags: AddWatchFlags) -> Self {
Self::from_bits_retain(addwatchflags.bits())
}
}
#[expect(clippy::disallowed_types)]
impl From<NixAddWatchFlags> for AddWatchFlags {
fn from(addwatchflags: NixAddWatchFlags) -> Self {
Self::from_bits_retain(addwatchflags.bits())
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct MFdFlags: libc::c_uint {
const MFD_CLOEXEC = libc::MFD_CLOEXEC;
const MFD_ALLOW_SEALING = libc::MFD_ALLOW_SEALING;
const MFD_NOEXEC_SEAL = libc::MFD_NOEXEC_SEAL;
const MFD_EXEC = libc::MFD_EXEC;
const MFD_HUGETLB = libc::MFD_HUGETLB;
const MFD_HUGE_1MB = libc::MFD_HUGE_1MB;
const MFD_HUGE_2MB = libc::MFD_HUGE_2MB;
const MFD_HUGE_8MB = libc::MFD_HUGE_8MB;
const MFD_HUGE_16MB = libc::MFD_HUGE_16MB;
const MFD_HUGE_32MB = libc::MFD_HUGE_32MB;
const MFD_HUGE_256MB = libc::MFD_HUGE_256MB;
const MFD_HUGE_512MB = libc::MFD_HUGE_512MB;
const MFD_HUGE_1GB = libc::MFD_HUGE_1GB;
const MFD_HUGE_2GB = libc::MFD_HUGE_2GB;
const MFD_HUGE_16GB = libc::MFD_HUGE_16GB;
}
}
#[expect(clippy::disallowed_types)]
use nix::sys::memfd::MFdFlags as NixMFdFlags;
#[expect(clippy::disallowed_types)]
impl From<MFdFlags> for NixMFdFlags {
fn from(mfdflags: MFdFlags) -> Self {
Self::from_bits_retain(mfdflags.bits())
}
}
#[expect(clippy::disallowed_types)]
impl From<NixMFdFlags> for MFdFlags {
fn from(mfdflags: NixMFdFlags) -> Self {
Self::from_bits_retain(mfdflags.bits())
}
}
pub(crate) const XATTR_NAME_MAX: usize = 255;
pub(crate) const XATTR_SIZE_MAX: usize = 1 << 16;
pub(crate) const XATTR_LIST_MAX: usize = 1 << 16;