#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
use std::{
alloc::{alloc, dealloc, Layout},
cell::RefCell,
cmp::{self, Ordering},
ffi::{CStr, CString},
fmt,
io::{IoSlice, IoSliceMut, Read, Seek, SeekFrom},
mem::{offset_of, MaybeUninit},
num::NonZeroUsize,
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd},
ptr::NonNull,
sync::LazyLock,
};
use bitflags::bitflags;
use libc::{c_int, c_void, socklen_t};
use libseccomp::ScmpSyscall;
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
sys::{
epoll::EpollOp,
signal::Signal,
socket::{SockaddrLike, SockaddrStorage, UnixCredentials},
stat::Mode,
wait::{Id, WaitPidFlag, WaitStatus as NixWaitStatus},
},
unistd::Pid,
NixPath,
};
use serde::{ser::SerializeMap, Serialize, Serializer};
use zeroize::Zeroizing;
use crate::{
config::*,
confine::resolve_syscall,
cookie::{
safe_getdents64, safe_memfd_create, safe_recvmmsg, safe_recvmsg, safe_sendmmsg,
safe_sendmsg, CookieIdx, SYSCOOKIE_POOL,
},
err::{err2no, scmp2no},
fd::SafeOwnedFd,
ioctl::Ioctl,
lookup::FileType,
parsers::sandbox::str2u64,
retry::retry_on_eintr,
rng::shm_open_anon,
XPath,
};
macro_rules! syscall_ll {
($nr:expr, $a:expr, $b:expr, @ll($ll:expr), $d:expr, $e:expr) => {{
#[cfg(all(
target_pointer_width = "32",
not(target_arch = "x86_64"),
not(target_arch = "mips64")
))]
{
let val = $ll as libc::c_ulonglong;
let lo = (val & 0xffff_ffff) as libc::c_ulong;
let hi = (val >> 32) as libc::c_ulong;
#[cfg(all(target_arch = "mips", target_pointer_width = "32"))]
{
if cfg!(target_endian = "little") {
crate::compat::syscall_mips_o32(
$nr as libc::c_long,
$a as libc::c_long,
$b as libc::c_long,
lo as libc::c_long,
hi as libc::c_long,
$d as libc::c_long,
$e as libc::c_long,
)
} else {
crate::compat::syscall_mips_o32(
$nr as libc::c_long,
$a as libc::c_long,
$b as libc::c_long,
hi as libc::c_long,
lo as libc::c_long,
$d as libc::c_long,
$e as libc::c_long,
)
}
}
#[cfg(not(all(target_arch = "mips", target_pointer_width = "32")))]
{
if cfg!(target_endian = "little") {
libc::syscall($nr, $a, $b, lo, hi, $d, $e)
} else {
libc::syscall($nr, $a, $b, hi, lo, $d, $e)
}
}
}
#[cfg(not(all(
target_pointer_width = "32",
not(target_arch = "x86_64"),
not(target_arch = "mips64")
)))]
{
libc::syscall($nr, $a, $b, $ll, $d, $e)
}
}};
($nr:expr, $a:expr, @ll($ll:expr), $c:expr, $d:expr, $e:expr) => {{
#[cfg(all(
target_pointer_width = "32",
not(target_arch = "x86_64"),
not(target_arch = "mips64")
))]
{
let val = $ll as libc::c_ulonglong;
let lo = (val & 0xffff_ffff) as libc::c_ulong;
let hi = (val >> 32) as libc::c_ulong;
#[cfg(all(target_arch = "mips", target_pointer_width = "32"))]
{
if cfg!(target_endian = "little") {
crate::compat::syscall_mips_o32(
$nr as libc::c_long,
$a as libc::c_long,
lo as libc::c_long,
hi as libc::c_long,
$c as libc::c_long,
$d as libc::c_long,
$e as libc::c_long,
)
} else {
crate::compat::syscall_mips_o32(
$nr as libc::c_long,
$a as libc::c_long,
hi as libc::c_long,
lo as libc::c_long,
$c as libc::c_long,
$d as libc::c_long,
$e as libc::c_long,
)
}
}
#[cfg(not(all(target_arch = "mips", target_pointer_width = "32")))]
{
if cfg!(target_endian = "little") {
libc::syscall($nr, $a, lo, hi, $c, $d, $e)
} else {
libc::syscall($nr, $a, hi, lo, $c, $d, $e)
}
}
}
#[cfg(not(all(
target_pointer_width = "32",
not(target_arch = "x86_64"),
not(target_arch = "mips64")
)))]
{
libc::syscall($nr, $a, $ll, $c, $d, $e)
}
}};
}
#[cfg(all(target_arch = "mips", target_pointer_width = "32"))]
extern "C" {
#[link_name = "syscall"]
pub(crate) fn syscall_mips_o32(
nr: libc::c_long,
a: libc::c_long,
b: libc::c_long,
c: libc::c_long,
d: libc::c_long,
e: libc::c_long,
f: libc::c_long,
) -> libc::c_long;
}
pub(crate) const AT_EACCESS: nix::fcntl::AtFlags = nix::fcntl::AtFlags::from_bits_retain(0x200);
pub(crate) const UIO_MAXIOV: usize = libc::UIO_MAXIOV as usize;
pub(crate) const IPC_SET: i32 = 1;
pub(crate) const SO_TIMESTAMP_OLD: c_int = 29; #[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
pub(crate) const SO_TIMESTAMPNS_OLD: c_int = 35;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const SO_TIMESTAMPNS_OLD: c_int = 33;
#[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
pub(crate) const SO_TIMESTAMPING_OLD: c_int = 37;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const SO_TIMESTAMPING_OLD: c_int = 35;
#[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
pub(crate) const SO_TIMESTAMP_NEW: c_int = 63;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const SO_TIMESTAMP_NEW: c_int = 70;
#[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
pub(crate) const SO_TIMESTAMPNS_NEW: c_int = 64;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const SO_TIMESTAMPNS_NEW: c_int = 66;
#[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
pub(crate) const SO_TIMESTAMPING_NEW: c_int = 65;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const SO_TIMESTAMPING_NEW: c_int = 67;
pub(crate) const IP_ORIGDSTADDR: c_int = 20;
pub(crate) const IPV6_ORIGDSTADDR: c_int = 74;
pub(crate) const UDP_GRO: c_int = 104;
pub(crate) const IPV6_2292PKTINFO: c_int = 2;
pub(crate) const SCM_PIDFD: c_int = 0x04;
pub(crate) const SCM_SECURITY: c_int = 0x03;
pub(crate) const SCM_MAX_FD: usize = 253;
#[cfg(any(target_arch = "arm", target_arch = "aarch64", target_arch = "m68k"))]
pub(crate) const O_LARGEFILE: OFlag = OFlag::from_bits_retain(0o400000);
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
pub(crate) const O_LARGEFILE: OFlag = OFlag::from_bits_retain(0x2000);
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
pub(crate) const O_LARGEFILE: OFlag = OFlag::from_bits_retain(0o200000);
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const O_LARGEFILE: OFlag = OFlag::from_bits_retain(0x40000);
#[cfg(not(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "m68k",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
)))]
pub(crate) const O_LARGEFILE: OFlag = OFlag::from_bits_retain(0o100000);
#[cfg(not(target_os = "android"))]
pub use libc::{NT_PRSTATUS, PTRACE_LISTEN, PTRACE_SEIZE};
#[cfg(target_os = "android")]
pub const NT_PRSTATUS: c_int = 1;
#[cfg(target_os = "android")]
pub const PTRACE_SEIZE: c_int = 0x4206;
#[cfg(target_os = "android")]
pub const PTRACE_LISTEN: c_int = 0x4208;
#[expect(non_camel_case_types)]
pub type time32_t = i32;
#[expect(non_camel_case_types)]
pub type time64_t = i64;
pub const UTIME_NOW: i64 = (1 << 30) - 1;
pub const UTIME_OMIT: i64 = (1 << 30) - 2;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct TimeSpec64 {
pub tv_sec: i64,
pub tv_nsec: i64,
}
impl TimeSpec64 {
pub const UTIME_OMIT: Self = Self {
tv_sec: 0,
tv_nsec: UTIME_OMIT,
};
pub const UTIME_NOW: Self = Self {
tv_sec: 0,
tv_nsec: UTIME_NOW,
};
pub const fn new(seconds: time64_t, nanoseconds: i64) -> Self {
Self {
tv_sec: seconds,
tv_nsec: nanoseconds,
}
}
pub const fn tv_sec(&self) -> time64_t {
self.tv_sec
}
pub const fn tv_nsec(&self) -> i64 {
self.tv_nsec
}
}
impl Ord for TimeSpec64 {
fn cmp(&self, other: &Self) -> cmp::Ordering {
if self.tv_sec() == other.tv_sec() {
self.tv_nsec().cmp(&other.tv_nsec())
} else {
self.tv_sec().cmp(&other.tv_sec())
}
}
}
impl PartialOrd for TimeSpec64 {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct TimeSpec32 {
pub tv_sec: i32,
pub tv_nsec: i32,
}
impl TimeSpec32 {
#[expect(clippy::cast_possible_truncation)]
pub const UTIME_OMIT: Self = Self {
tv_sec: 0,
tv_nsec: UTIME_OMIT as i32,
};
#[expect(clippy::cast_possible_truncation)]
pub const UTIME_NOW: Self = Self {
tv_sec: 0,
tv_nsec: UTIME_NOW as i32,
};
pub const fn new(seconds: time32_t, nanoseconds: i32) -> Self {
Self {
tv_sec: seconds,
tv_nsec: nanoseconds,
}
}
pub const fn tv_sec(&self) -> time32_t {
self.tv_sec
}
pub const fn tv_nsec(&self) -> i32 {
self.tv_nsec
}
}
impl Ord for TimeSpec32 {
fn cmp(&self, other: &Self) -> cmp::Ordering {
if self.tv_sec() == other.tv_sec() {
self.tv_nsec().cmp(&other.tv_nsec())
} else {
self.tv_sec().cmp(&other.tv_sec())
}
}
}
impl PartialOrd for TimeSpec32 {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl TryFrom<TimeSpec64> for TimeSpec32 {
type Error = Errno;
fn try_from(timespec64: TimeSpec64) -> Result<Self, Errno> {
Ok(Self {
tv_sec: i32::try_from(timespec64.tv_sec).or(Err(Errno::EOVERFLOW))?,
tv_nsec: i32::try_from(timespec64.tv_nsec).or(Err(Errno::EOVERFLOW))?,
})
}
}
impl From<TimeSpec32> for TimeSpec64 {
fn from(timespec32: TimeSpec32) -> Self {
Self {
tv_sec: time64_t::from(timespec32.tv_sec),
tv_nsec: i64::from(timespec32.tv_nsec),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct Timeval64 {
pub tv_sec: i64,
pub tv_usec: i64,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct Timeval32 {
pub tv_sec: i32,
pub tv_usec: i32,
}
impl TryFrom<Timeval64> for Timeval32 {
type Error = Errno;
fn try_from(timeval64: Timeval64) -> Result<Self, Errno> {
Ok(Self {
tv_sec: i32::try_from(timeval64.tv_sec).or(Err(Errno::EOVERFLOW))?,
tv_usec: i32::try_from(timeval64.tv_usec).or(Err(Errno::EOVERFLOW))?,
})
}
}
impl TryFrom<Timeval64> for TimeSpec64 {
type Error = Errno;
fn try_from(tv: Timeval64) -> Result<Self, Errno> {
let nsec = tv.tv_usec.checked_mul(1_000).ok_or(Errno::EOVERFLOW)?;
Ok(Self {
tv_sec: tv.tv_sec,
tv_nsec: nsec,
})
}
}
impl TryFrom<Timeval32> for TimeSpec64 {
type Error = Errno;
fn try_from(tv: Timeval32) -> Result<Self, Errno> {
let nsec = i64::from(tv.tv_usec)
.checked_mul(1_000)
.ok_or(Errno::EOVERFLOW)?;
Ok(Self {
tv_sec: time64_t::from(tv.tv_sec),
tv_nsec: nsec,
})
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct Utimbuf64 {
pub actime: i64,
pub modtime: i64,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct Utimbuf32 {
pub actime: i32,
pub modtime: i32,
}
impl From<Utimbuf64> for [TimeSpec64; 2] {
fn from(buf: Utimbuf64) -> Self {
[
TimeSpec64 {
tv_sec: buf.actime,
tv_nsec: 0,
},
TimeSpec64 {
tv_sec: buf.modtime,
tv_nsec: 0,
},
]
}
}
impl From<Utimbuf32> for [TimeSpec64; 2] {
fn from(buf: Utimbuf32) -> Self {
[
TimeSpec64 {
tv_sec: time64_t::from(buf.actime),
tv_nsec: 0,
},
TimeSpec64 {
tv_sec: time64_t::from(buf.modtime),
tv_nsec: 0,
},
]
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
#[repr(C)]
pub(crate) struct flock64 {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i64,
pub(crate) l_len: i64,
pub(crate) l_pid: i32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
#[repr(C)]
pub(crate) struct flock64 {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i64,
pub(crate) l_len: i64,
pub(crate) l_pid: i32,
__unused: i16,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
#[repr(C)]
pub(crate) struct flock64_compat {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i64,
pub(crate) l_len: i64,
pub(crate) l_pid: i32,
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86",))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C, packed)]
pub(crate) struct flock64_32 {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i64,
pub(crate) l_len: i64,
pub(crate) l_pid: i32,
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "sparc",
target_arch = "sparc64",
)))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct flock32 {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i32,
pub(crate) l_len: i32,
pub(crate) l_pid: i32,
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct flock32 {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i32,
pub(crate) l_len: i32,
pub(crate) l_pid: i32,
pub(crate) l_sysid: i32,
l_pad: [i32; 4],
}
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct flock32 {
pub(crate) l_type: i16,
pub(crate) l_whence: i16,
pub(crate) l_start: i32,
pub(crate) l_len: i32,
pub(crate) l_pid: i32,
__unused: i16,
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
target_arch = "sparc",
target_arch = "sparc64",
)))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct stat32 {
st_dev: u32,
st_ino: u32,
st_mode: u16,
st_nlink: u16,
st_uid: u16,
st_gid: u16,
st_rdev: u32,
st_size: u32,
st_blksize: u32,
st_blocks: u32,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
__unused4: u32,
__unused5: u32,
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
target_arch = "sparc",
target_arch = "sparc64",
)))]
impl TryFrom<libc::stat64> for stat32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(stat: libc::stat64) -> Result<Self, Errno> {
let st_ino = u32::try_from(stat.st_ino).or(Err(Errno::EOVERFLOW))?;
let st_nlink = u16::try_from(stat.st_nlink).or(Err(Errno::EOVERFLOW))?;
if stat.st_size as u64 > 0x7fff_ffff {
return Err(Errno::EOVERFLOW);
}
Ok(Self {
st_dev: stat.st_dev as u32,
st_ino,
st_mode: stat.st_mode as u16,
st_nlink,
st_uid: stat.st_uid as u16,
st_gid: stat.st_gid as u16,
st_rdev: stat.st_rdev as u32,
st_size: stat.st_size as u32,
st_blksize: stat.st_blksize as u32,
st_blocks: stat.st_blocks as u32,
st_atime: stat.st_atime as u32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as u32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as u32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
__unused4: 0,
__unused5: 0,
})
}
}
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct stat32 {
st_dev: u16,
st_ino: u32,
st_mode: u16,
st_nlink: i16,
st_uid: u16,
st_gid: u16,
st_rdev: u16,
st_size: i32,
st_atime: i32,
st_atime_nsec: u32,
st_mtime: i32,
st_mtime_nsec: u32,
st_ctime: i32,
st_ctime_nsec: u32,
st_blksize: i32,
st_blocks: i32,
__unused4: [u32; 2],
}
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
impl TryFrom<libc::stat64> for stat32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(stat: libc::stat64) -> Result<Self, Errno> {
let st_ino = u32::try_from(stat.st_ino).or(Err(Errno::EOVERFLOW))?;
let st_nlink = i16::try_from(stat.st_nlink).or(Err(Errno::EOVERFLOW))?;
if stat.st_size as u64 > 0x7fff_ffff {
return Err(Errno::EOVERFLOW);
}
let st_dev = old_encode_dev(stat.st_dev)?;
let st_rdev = old_encode_dev(stat.st_rdev)?;
Ok(Self {
st_dev,
st_ino,
st_mode: stat.st_mode as u16,
st_nlink,
st_uid: stat.st_uid as u16,
st_gid: stat.st_gid as u16,
st_rdev,
st_size: stat.st_size as i32,
st_atime: stat.st_atime as i32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as i32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as i32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
st_blksize: stat.st_blksize as i32,
st_blocks: stat.st_blocks as i32,
__unused4: [0; 2],
})
}
}
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
fn old_encode_dev(dev: libc::dev_t) -> Result<u16, Errno> {
let major = libc::major(dev);
let minor = libc::minor(dev);
if major >= 256 || minor >= 256 {
return Err(Errno::EOVERFLOW);
}
#[expect(clippy::cast_possible_truncation)]
Ok(((major << 8) | minor) as u16)
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct stat32 {
pub st_dev: u32,
_pad1: [u32; 3],
st_ino: u32,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u32,
_pad2: [u32; 2],
st_size: i32,
_pad3: i32,
st_atime: i32,
st_atime_nsec: i32,
st_mtime: i32,
st_mtime_nsec: i32,
st_ctime: i32,
st_ctime_nsec: i32,
st_blksize: i32,
st_blocks: i32,
_pad4: [i32; 14],
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
impl TryFrom<libc::stat64> for stat32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(stat: libc::stat64) -> Result<Self, Errno> {
let st_ino = u32::try_from(stat.st_ino).or(Err(Errno::EOVERFLOW))?;
let st_nlink = u32::try_from(stat.st_nlink).or(Err(Errno::EOVERFLOW))?;
if stat.st_size as u64 > 0x7fff_ffff {
return Err(Errno::EOVERFLOW);
}
Ok(Self {
st_dev: stat.st_dev as u32,
_pad1: [0; 3],
st_ino,
st_mode: stat.st_mode as u32,
st_nlink,
st_uid: stat.st_uid as u32,
st_gid: stat.st_gid as u32,
st_rdev: stat.st_rdev as u32,
_pad2: [0; 2],
st_size: stat.st_size as i32,
_pad3: 0,
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,
st_blksize: stat.st_blksize as i32,
st_blocks: stat.st_blocks as i32,
_pad4: [0; 14],
})
}
}
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct stat32 {
st_dev: u32,
st_ino: u32,
st_mode: u32,
st_nlink: u16,
_pad: u16,
st_uid: u32,
st_gid: u32,
st_rdev: u32,
st_size: i32,
st_blksize: u32,
st_blocks: u32,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
__unused4: u32,
__unused5: u32,
}
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
impl TryFrom<libc::stat64> for stat32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(stat: libc::stat64) -> Result<Self, Errno> {
let st_ino = u32::try_from(stat.st_ino).or(Err(Errno::EOVERFLOW))?;
let st_nlink = u16::try_from(stat.st_nlink).or(Err(Errno::EOVERFLOW))?;
if stat.st_size as u64 > 0x7fff_ffff {
return Err(Errno::EOVERFLOW);
}
Ok(Self {
st_dev: stat.st_dev as u32,
st_ino,
st_mode: stat.st_mode as u32,
st_nlink,
_pad: 0,
st_uid: stat.st_uid as u32,
st_gid: stat.st_gid as u32,
st_rdev: stat.st_rdev as u32,
st_size: stat.st_size as i32,
st_blksize: stat.st_blksize as u32,
st_blocks: stat.st_blocks as u32,
st_atime: stat.st_atime as u32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as u32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as u32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
__unused4: 0,
__unused5: 0,
})
}
}
#[cfg(target_arch = "s390x")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct stat32 {
st_dev: u32,
st_ino: u32,
st_nlink: u32,
st_mode: u32,
st_uid: u32,
st_gid: u32,
__pad1: u32,
st_rdev: u32,
st_size: u32,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
st_blksize: u32,
st_blocks: i32,
__unused: [u32; 3],
}
#[cfg(target_arch = "s390x")]
impl TryFrom<libc::stat64> for stat32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(stat: libc::stat64) -> Result<Self, Errno> {
let st_ino = u32::try_from(stat.st_ino).or(Err(Errno::EOVERFLOW))?;
let st_nlink = u32::try_from(stat.st_nlink).or(Err(Errno::EOVERFLOW))?;
if stat.st_size as u64 > 0x7fff_ffff {
return Err(Errno::EOVERFLOW);
}
Ok(Self {
st_dev: stat.st_dev as u32,
st_ino,
st_nlink,
st_mode: stat.st_mode as u32,
st_uid: stat.st_uid as u32,
st_gid: stat.st_gid as u32,
__pad1: 0,
st_rdev: stat.st_rdev as u32,
st_size: stat.st_size as u32,
st_atime: stat.st_atime as u32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as u32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as u32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
st_blksize: stat.st_blksize as u32,
st_blocks: stat.st_blocks as i32,
__unused: [0; 3],
})
}
}
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "m68k",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
target_arch = "sparc",
target_arch = "sparc64",
)))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C, packed)]
pub(crate) struct stat64 {
st_dev: u64,
__pad0: [u8; 4],
__st_ino: u32,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u64,
__pad3: [u8; 4],
st_size: i64,
st_blksize: u32,
st_blocks: u64,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
st_ino: u64,
}
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct stat64 {
st_dev: u64,
__pad0: [u8; 4],
__st_ino: u32,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u64,
__pad3: [u8; 4],
st_size: i64,
st_blksize: u32,
st_blocks: u64,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
st_ino: u64,
}
#[cfg(target_arch = "m68k")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C, packed)]
pub(crate) struct stat64 {
st_dev: u64,
__pad1: [u8; 2],
__st_ino: u32,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u64,
__pad3: [u8; 2],
st_size: i64,
st_blksize: u32,
st_blocks: u64,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
st_ino: u64,
}
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct stat64 {
st_dev: u64,
st_ino: u64,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u64,
__pad3: [u8; 8],
st_size: i64,
st_blksize: u32,
__pad4: [u8; 8],
st_blocks: u32,
st_atime: u32,
st_atime_nsec: u32,
st_mtime: u32,
st_mtime_nsec: u32,
st_ctime: u32,
st_ctime_nsec: u32,
__unused4: u32,
__unused5: u32,
}
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "m68k",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
target_arch = "sparc",
target_arch = "sparc64",
)))]
impl From<FileStat64> for stat64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(stat: FileStat64) -> Self {
Self {
st_dev: stat.st_dev,
__pad0: [0; 4],
__st_ino: stat.st_ino as u32,
st_mode: stat.st_mode,
st_nlink: stat.st_nlink as u32,
st_uid: stat.st_uid,
st_gid: stat.st_gid,
st_rdev: stat.st_rdev,
__pad3: [0; 4],
st_size: stat.st_size,
st_blksize: stat.st_blksize as u32,
st_blocks: stat.st_blocks as u64,
st_atime: stat.st_atime as u32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as u32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as u32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
st_ino: stat.st_ino,
}
}
}
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
impl From<FileStat64> for stat64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(stat: FileStat64) -> Self {
let mut rstat: Self = unsafe { std::mem::zeroed() };
rstat.st_dev = stat.st_dev;
rstat.__st_ino = stat.st_ino as u32;
rstat.st_mode = stat.st_mode;
rstat.st_nlink = stat.st_nlink as u32;
rstat.st_uid = stat.st_uid;
rstat.st_gid = stat.st_gid;
rstat.st_rdev = stat.st_rdev;
rstat.st_size = stat.st_size;
rstat.st_blksize = stat.st_blksize as u32;
rstat.st_blocks = stat.st_blocks as u64;
rstat.st_atime = stat.st_atime as u32;
rstat.st_atime_nsec = stat.st_atime_nsec as u32;
rstat.st_mtime = stat.st_mtime as u32;
rstat.st_mtime_nsec = stat.st_mtime_nsec as u32;
rstat.st_ctime = stat.st_ctime as u32;
rstat.st_ctime_nsec = stat.st_ctime_nsec as u32;
rstat.st_ino = stat.st_ino;
rstat
}
}
#[cfg(target_arch = "m68k")]
impl From<FileStat64> for stat64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(stat: FileStat64) -> Self {
Self {
st_dev: stat.st_dev,
__pad1: [0; 2],
__st_ino: stat.st_ino as u32,
st_mode: stat.st_mode,
st_nlink: stat.st_nlink as u32,
st_uid: stat.st_uid,
st_gid: stat.st_gid,
st_rdev: stat.st_rdev,
__pad3: [0; 2],
st_size: stat.st_size,
st_blksize: stat.st_blksize as u32,
st_blocks: stat.st_blocks as u64,
st_atime: stat.st_atime as u32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as u32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as u32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
st_ino: stat.st_ino,
}
}
}
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
impl From<FileStat64> for stat64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(stat: FileStat64) -> Self {
Self {
st_dev: stat.st_dev,
st_ino: stat.st_ino,
st_mode: stat.st_mode,
st_nlink: stat.st_nlink as u32,
st_uid: stat.st_uid,
st_gid: stat.st_gid,
st_rdev: stat.st_rdev,
__pad3: [0; 8],
st_size: stat.st_size,
st_blksize: stat.st_blksize as u32,
__pad4: [0; 8],
st_blocks: stat.st_blocks as u32,
st_atime: stat.st_atime as u32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as u32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as u32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
__unused4: 0,
__unused5: 0,
}
}
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct stat64 {
st_dev: u32,
_pad0: [u32; 3],
st_ino: u64,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u32,
_pad1: [u32; 3],
st_size: i64,
st_atime: i32,
st_atime_nsec: u32,
st_mtime: i32,
st_mtime_nsec: u32,
st_ctime: i32,
st_ctime_nsec: u32,
st_blksize: u32,
_pad2: u32,
st_blocks: i64,
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
impl From<FileStat64> for stat64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(stat: FileStat64) -> Self {
Self {
st_dev: stat.st_dev as u32,
_pad0: [0; 3],
st_ino: stat.st_ino,
st_mode: stat.st_mode,
st_nlink: stat.st_nlink as u32,
st_uid: stat.st_uid,
st_gid: stat.st_gid,
st_rdev: stat.st_rdev as u32,
_pad1: [0; 3],
st_size: stat.st_size,
st_atime: stat.st_atime as i32,
st_atime_nsec: stat.st_atime_nsec as u32,
st_mtime: stat.st_mtime as i32,
st_mtime_nsec: stat.st_mtime_nsec as u32,
st_ctime: stat.st_ctime as i32,
st_ctime_nsec: stat.st_ctime_nsec as u32,
st_blksize: stat.st_blksize as u32,
_pad2: 0,
st_blocks: stat.st_blocks,
}
}
}
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct stat64 {
st_dev: u64,
st_ino: u64,
st_mode: u32,
st_nlink: u32,
st_uid: u32,
st_gid: u32,
st_rdev: u64,
__pad2: u16,
st_size: i64,
st_blksize: i32,
st_blocks: i64,
st_atime: i32,
st_atime_nsec: u32,
st_mtime: i32,
st_mtime_nsec: u32,
st_ctime: i32,
st_ctime_nsec: u32,
__unused4: u32,
__unused5: u32,
}
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
impl From<FileStat64> for stat64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(stat: FileStat64) -> Self {
let mut rstat: Self = unsafe { std::mem::zeroed() };
rstat.st_dev = stat.st_dev;
rstat.st_ino = stat.st_ino;
rstat.st_mode = stat.st_mode;
rstat.st_nlink = stat.st_nlink as u32;
rstat.st_uid = stat.st_uid;
rstat.st_gid = stat.st_gid;
rstat.st_rdev = stat.st_rdev;
rstat.st_size = stat.st_size;
rstat.st_blksize = stat.st_blksize as i32;
rstat.st_blocks = stat.st_blocks;
rstat.st_atime = stat.st_atime as i32;
rstat.st_atime_nsec = stat.st_atime_nsec as u32;
rstat.st_mtime = stat.st_mtime as i32;
rstat.st_mtime_nsec = stat.st_mtime_nsec as u32;
rstat.st_ctime = stat.st_ctime as i32;
rstat.st_ctime_nsec = stat.st_ctime_nsec as u32;
rstat
}
}
#[cfg(target_arch = "s390x")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct stat64;
#[cfg(target_arch = "s390x")]
impl From<FileStat64> for stat64 {
fn from(_: FileStat64) -> Self {
Self
}
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
)))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct statfs32 {
f_type: i32,
f_bsize: i32,
f_blocks: i32,
f_bfree: i32,
f_bavail: i32,
f_files: i32,
f_ffree: i32,
f_fsid: [i32; 2],
f_namelen: i32,
f_frsize: i32,
f_flags: i32,
f_spare: [i32; 4],
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct statfs32 {
f_type: i32,
f_bsize: i32,
f_frsize: i32,
f_blocks: i32,
f_bfree: i32,
f_files: i32,
f_ffree: i32,
f_bavail: i32,
f_fsid: [i32; 2],
f_namelen: i32,
f_flags: i32,
f_spare: [i32; 5],
}
#[cfg(target_arch = "s390x")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct statfs32 {
f_type: i32,
f_bsize: i32,
f_blocks: i32,
f_bfree: i32,
f_bavail: i32,
f_files: i32,
f_ffree: i32,
f_fsid: [i32; 2],
f_namelen: i32,
f_frsize: i32,
f_flags: i32,
f_spare: [i32; 4],
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub(crate) struct Statfs(libc::statfs64);
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
#[repr(C)]
pub(crate) struct statfs64 {
f_type: u32,
f_bsize: u32,
f_blocks: u64,
f_bfree: u64,
f_bavail: u64,
f_files: u64,
f_ffree: u64,
f_fsid: [i32; 2],
f_namelen: u32,
f_frsize: u32,
f_flags: u32,
f_spare: [u32; 4],
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
)))]
#[repr(C, packed)]
pub(crate) struct statfs64 {
f_type: u32,
f_bsize: u32,
f_blocks: u64,
f_bfree: u64,
f_bavail: u64,
f_files: u64,
f_ffree: u64,
f_fsid: [i32; 2],
f_namelen: u32,
f_frsize: u32,
f_flags: u32,
f_spare: [u32; 4],
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct statfs64 {
f_type: u32,
f_bsize: u32,
f_frsize: u32,
__pad: u32,
f_blocks: u64,
f_bfree: u64,
f_files: u64,
f_ffree: u64,
f_bavail: u64,
f_fsid: [i32; 2],
f_namelen: u32,
f_flags: u32,
f_spare: [u32; 5],
}
#[cfg(target_arch = "s390x")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct statfs64 {
f_type: u32,
f_bsize: u32,
f_blocks: u64,
f_bfree: u64,
f_bavail: u64,
f_files: u64,
f_ffree: u64,
f_fsid: [i32; 2],
f_namelen: u32,
f_frsize: u32,
f_flags: u32,
f_spare: [u32; 4],
}
fn statfs_fsid(statfs64: &libc::statfs64) -> [i32; 2] {
unsafe { std::mem::transmute(statfs64.f_fsid) }
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
target_arch = "powerpc",
target_arch = "powerpc64",
)))]
impl From<Statfs> for statfs64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::unnecessary_cast)]
fn from(statfs: Statfs) -> Self {
Self {
f_type: statfs.0.f_type as u32,
f_bsize: statfs.0.f_bsize as u32,
f_blocks: statfs.0.f_blocks as u64,
f_bfree: statfs.0.f_bfree as u64,
f_bavail: statfs.0.f_bavail as u64,
f_files: statfs.0.f_files as u64,
f_ffree: statfs.0.f_ffree as u64,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as u32,
f_frsize: statfs.0.f_frsize as u32,
f_flags: statfs.0.f_flags as u32,
f_spare: [0; 4],
}
}
}
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
impl From<Statfs> for statfs64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::unnecessary_cast)]
fn from(statfs: Statfs) -> Self {
Self {
f_type: statfs.0.f_type as u32,
f_bsize: statfs.0.f_bsize as u32,
f_blocks: statfs.0.f_blocks as u64,
f_bfree: statfs.0.f_bfree as u64,
f_bavail: statfs.0.f_bavail as u64,
f_files: statfs.0.f_files as u64,
f_ffree: statfs.0.f_ffree as u64,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as u32,
f_frsize: statfs.0.f_frsize as u32,
f_flags: statfs.0.f_flags as u32,
f_spare: [0; 4],
}
}
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
)))]
impl TryFrom<Statfs> for statfs32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(statfs: Statfs) -> Result<Self, Errno> {
if (statfs.0.f_blocks
| statfs.0.f_bfree
| statfs.0.f_bavail
| (statfs.0.f_bsize as u64)
| (statfs.0.f_frsize as u64))
& 0xffff_ffff_0000_0000
!= 0
{
return Err(Errno::EOVERFLOW);
}
if statfs.0.f_files != 0xffff_ffff_ffff_ffff
&& statfs.0.f_files & 0xffff_ffff_0000_0000 != 0
{
return Err(Errno::EOVERFLOW);
}
if statfs.0.f_ffree != 0xffff_ffff_ffff_ffff
&& statfs.0.f_ffree & 0xffff_ffff_0000_0000 != 0
{
return Err(Errno::EOVERFLOW);
}
Ok(Self {
f_type: statfs.0.f_type as i32,
f_bsize: statfs.0.f_bsize as i32,
f_blocks: statfs.0.f_blocks as i32,
f_bfree: statfs.0.f_bfree as i32,
f_bavail: statfs.0.f_bavail as i32,
f_files: statfs.0.f_files as i32,
f_ffree: statfs.0.f_ffree as i32,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as i32,
f_frsize: statfs.0.f_frsize as i32,
f_flags: statfs.0.f_flags as i32,
f_spare: [0; 4],
})
}
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
)))]
impl From<statfs64> for statfs32 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
fn from(statfs64: statfs64) -> Self {
Self {
f_type: statfs64.f_type as i32,
f_bsize: statfs64.f_bsize as i32,
f_blocks: statfs64.f_blocks as i32,
f_bfree: statfs64.f_bfree as i32,
f_bavail: statfs64.f_bavail as i32,
f_files: statfs64.f_files as i32,
f_ffree: statfs64.f_ffree as i32,
f_fsid: statfs64.f_fsid,
f_namelen: statfs64.f_namelen as i32,
f_frsize: statfs64.f_frsize as i32,
f_flags: statfs64.f_flags as i32,
f_spare: [0; 4],
}
}
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
impl From<Statfs> for statfs64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::unnecessary_cast)]
fn from(statfs: Statfs) -> Self {
Self {
f_type: statfs.0.f_type as u32,
f_bsize: statfs.0.f_bsize as u32,
f_frsize: statfs.0.f_frsize as u32,
__pad: 0,
f_blocks: statfs.0.f_blocks as u64,
f_bfree: statfs.0.f_bfree as u64,
f_files: statfs.0.f_files as u64,
f_ffree: statfs.0.f_ffree as u64,
f_bavail: statfs.0.f_bavail as u64,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as u32,
f_flags: statfs.0.f_flags as u32,
f_spare: [0; 5],
}
}
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
impl TryFrom<Statfs> for statfs32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(statfs: Statfs) -> Result<Self, Errno> {
if (statfs.0.f_blocks
| statfs.0.f_bfree
| statfs.0.f_bavail
| (statfs.0.f_bsize as u64)
| (statfs.0.f_frsize as u64))
& 0xffff_ffff_0000_0000
!= 0
{
return Err(Errno::EOVERFLOW);
}
if statfs.0.f_files != 0xffff_ffff_ffff_ffff
&& statfs.0.f_files & 0xffff_ffff_0000_0000 != 0
{
return Err(Errno::EOVERFLOW);
}
if statfs.0.f_ffree != 0xffff_ffff_ffff_ffff
&& statfs.0.f_ffree & 0xffff_ffff_0000_0000 != 0
{
return Err(Errno::EOVERFLOW);
}
Ok(Self {
f_type: statfs.0.f_type as i32,
f_bsize: statfs.0.f_bsize as i32,
f_frsize: statfs.0.f_frsize as i32,
f_blocks: statfs.0.f_blocks as i32,
f_bfree: statfs.0.f_bfree as i32,
f_files: statfs.0.f_files as i32,
f_ffree: statfs.0.f_ffree as i32,
f_bavail: statfs.0.f_bavail as i32,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as i32,
f_flags: statfs.0.f_flags as i32,
f_spare: [0; 5],
})
}
}
#[cfg(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6"
))]
impl From<statfs64> for statfs32 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
fn from(statfs64: statfs64) -> Self {
Self {
f_type: statfs64.f_type as i32,
f_bsize: statfs64.f_bsize as i32,
f_frsize: statfs64.f_frsize as i32,
f_blocks: statfs64.f_blocks as i32,
f_bfree: statfs64.f_bfree as i32,
f_files: statfs64.f_files as i32,
f_ffree: statfs64.f_ffree as i32,
f_bavail: statfs64.f_bavail as i32,
f_fsid: statfs64.f_fsid,
f_namelen: statfs64.f_namelen as i32,
f_flags: statfs64.f_flags as i32,
f_spare: [0; 5],
}
}
}
#[cfg(target_arch = "s390x")]
impl From<Statfs> for statfs64 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::unnecessary_cast)]
fn from(statfs: Statfs) -> Self {
Self {
f_type: statfs.0.f_type as u32,
f_bsize: statfs.0.f_bsize as u32,
f_blocks: statfs.0.f_blocks as u64,
f_bfree: statfs.0.f_bfree as u64,
f_bavail: statfs.0.f_bavail as u64,
f_files: statfs.0.f_files as u64,
f_ffree: statfs.0.f_ffree as u64,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as u32,
f_frsize: statfs.0.f_frsize as u32,
f_flags: statfs.0.f_flags as u32,
f_spare: [0; 4],
}
}
}
#[cfg(target_arch = "s390x")]
impl TryFrom<Statfs> for statfs32 {
type Error = Errno;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn try_from(statfs: Statfs) -> Result<Self, Errno> {
if (statfs.0.f_blocks
| statfs.0.f_bfree
| statfs.0.f_bavail
| (statfs.0.f_bsize as u64)
| (statfs.0.f_frsize as u64))
& 0xffff_ffff_0000_0000
!= 0
{
return Err(Errno::EOVERFLOW);
}
if statfs.0.f_files != 0xffff_ffff_ffff_ffff
&& statfs.0.f_files & 0xffff_ffff_0000_0000 != 0
{
return Err(Errno::EOVERFLOW);
}
if statfs.0.f_ffree != 0xffff_ffff_ffff_ffff
&& statfs.0.f_ffree & 0xffff_ffff_0000_0000 != 0
{
return Err(Errno::EOVERFLOW);
}
Ok(Self {
f_type: statfs.0.f_type as i32,
f_bsize: statfs.0.f_bsize as i32,
f_blocks: statfs.0.f_blocks as i32,
f_bfree: statfs.0.f_bfree as i32,
f_bavail: statfs.0.f_bavail as i32,
f_files: statfs.0.f_files as i32,
f_ffree: statfs.0.f_ffree as i32,
f_fsid: statfs_fsid(&statfs.0),
f_namelen: statfs.0.f_namelen as i32,
f_frsize: statfs.0.f_frsize as i32,
f_flags: statfs.0.f_flags as i32,
f_spare: [0; 4],
})
}
}
#[cfg(target_arch = "s390x")]
impl From<statfs64> for statfs32 {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
fn from(statfs64: statfs64) -> Self {
Self {
f_type: statfs64.f_type as i32,
f_bsize: statfs64.f_bsize as i32,
f_blocks: statfs64.f_blocks as i32,
f_bfree: statfs64.f_bfree as i32,
f_bavail: statfs64.f_bavail as i32,
f_files: statfs64.f_files as i32,
f_ffree: statfs64.f_ffree as i32,
f_fsid: statfs64.f_fsid,
f_namelen: statfs64.f_namelen as i32,
f_frsize: statfs64.f_frsize as i32,
f_flags: statfs64.f_flags as i32,
f_spare: [0; 4],
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[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, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct mmsghdr32 {
pub msg_hdr: msghdr32,
pub msg_len: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[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 TryFrom<mmsghdr> for mmsghdr32 {
type Error = Errno;
fn try_from(src: mmsghdr) -> Result<Self, Errno> {
Ok(mmsghdr32 {
msg_hdr: msghdr32::try_from(src.msg_hdr)?,
msg_len: src.msg_len,
})
}
}
impl From<libc::mmsghdr> for mmsghdr {
fn from(msg: libc::mmsghdr) -> Self {
const _: () = {
assert!(
size_of::<libc::mmsghdr>() == size_of::<mmsghdr>(),
"Size mismatch between libc::mmsghdr and compat::mmsghdr"
);
assert!(
align_of::<libc::mmsghdr>() == align_of::<mmsghdr>(),
"Alignment mismatch between libc::mmsghdr and compat::mmsghdr"
);
};
unsafe { std::mem::transmute(msg) }
}
}
impl TryFrom<libc::mmsghdr> for mmsghdr32 {
type Error = Errno;
fn try_from(msg: libc::mmsghdr) -> Result<Self, Errno> {
mmsghdr::from(msg).try_into()
}
}
impl From<mmsghdr> for libc::mmsghdr {
fn from(msg: mmsghdr) -> Self {
const _: () = {
assert!(
size_of::<libc::mmsghdr>() == size_of::<mmsghdr>(),
"Size mismatch between libc::mmsghdr and compat::mmsghdr"
);
assert!(
align_of::<libc::mmsghdr>() == align_of::<mmsghdr>(),
"Alignment mismatch between libc::mmsghdr and compat::mmsghdr"
);
};
unsafe { std::mem::transmute(msg) }
}
}
impl From<mmsghdr32> for libc::mmsghdr {
fn from(msg: mmsghdr32) -> Self {
mmsghdr::from(msg).into()
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union mmsghdr_union {
pub m32: mmsghdr32,
pub m64: mmsghdr,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct cmsghdr32 {
pub cmsg_len: u32,
pub cmsg_level: i32,
pub cmsg_type: i32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct cmsghdr {
pub cmsg_len: libc::size_t,
pub cmsg_level: c_int,
pub cmsg_type: 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, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct msghdr32 {
pub msg_name: u32,
pub msg_namelen: i32,
pub msg_iov: u32,
pub msg_iovlen: u32,
pub msg_control: u32,
pub msg_controllen: u32,
pub msg_flags: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct msghdr {
pub msg_name: *mut libc::c_void,
pub msg_namelen: c_int,
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_uint,
}
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 c_int,
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,
}
}
}
impl TryFrom<msghdr> for msghdr32 {
type Error = Errno;
fn try_from(msg: msghdr) -> Result<Self, Errno> {
Ok(msghdr32 {
msg_name: u32::try_from(msg.msg_name as usize).or(Err(Errno::EOVERFLOW))?,
msg_namelen: msg.msg_namelen,
msg_iov: u32::try_from(msg.msg_iov as usize).or(Err(Errno::EOVERFLOW))?,
msg_iovlen: u32::try_from(msg.msg_iovlen).or(Err(Errno::EOVERFLOW))?,
msg_control: u32::try_from(msg.msg_control as usize).or(Err(Errno::EOVERFLOW))?,
msg_controllen: u32::try_from(msg.msg_controllen).or(Err(Errno::EOVERFLOW))?,
msg_flags: msg.msg_flags,
})
}
}
impl From<libc::msghdr> for msghdr {
fn from(msg: libc::msghdr) -> Self {
const _: () = {
assert!(
size_of::<libc::msghdr>() == size_of::<msghdr>(),
"Size mismatch between libc::msghdr and compat::msghdr"
);
assert!(
align_of::<libc::msghdr>() == align_of::<msghdr>(),
"Alignment mismatch between libc::msghdr and compat::msghdr"
);
};
unsafe { std::mem::transmute(msg) }
}
}
impl TryFrom<libc::msghdr> for msghdr32 {
type Error = Errno;
fn try_from(msg: libc::msghdr) -> Result<Self, Errno> {
msghdr::from(msg).try_into()
}
}
#[repr(transparent)]
pub(crate) struct MsgHdr(libc::msghdr);
impl Default for MsgHdr {
fn default() -> Self {
Self(unsafe { std::mem::zeroed() })
}
}
impl MsgHdr {
pub(crate) fn as_mut_ptr(&mut self) -> *mut libc::msghdr {
std::ptr::addr_of_mut!(self.0)
}
#[expect(clippy::as_underscore)]
pub(crate) fn set_iov(&mut self, iov: &[std::io::IoSlice<'_>]) {
self.0.msg_iov = iov.as_ptr().cast_mut().cast();
self.0.msg_iovlen = iov.len() as _;
}
#[expect(clippy::as_underscore)]
pub(crate) fn set_iov_mut(&mut self, iov: &mut [std::io::IoSliceMut<'_>]) {
self.0.msg_iov = iov.as_mut_ptr().cast();
self.0.msg_iovlen = iov.len() as _;
}
#[expect(clippy::as_underscore)]
pub(crate) fn set_control(&mut self, buf: &mut [u8]) {
if buf.is_empty() {
self.0.msg_control = std::ptr::null_mut();
self.0.msg_controllen = 0;
} else {
self.0.msg_control = buf.as_mut_ptr().cast();
self.0.msg_controllen = buf.len() as _;
}
}
pub(crate) fn set_addr<S: SockaddrLike>(&mut self, addr: &S) {
self.0.msg_name = addr.as_ptr().cast_mut().cast();
self.0.msg_namelen = addr.len();
}
pub(crate) fn set_flags(&mut self, flags: c_int) {
self.0.msg_flags = flags;
}
pub(crate) fn into_inner(self) -> libc::msghdr {
self.0
}
pub(crate) fn msg_flags(&self) -> c_int {
self.0.msg_flags
}
#[expect(clippy::as_underscore)]
pub(crate) fn cmsg_bytes(&self) -> &[u8] {
if self.0.msg_control.is_null() || self.0.msg_controllen == 0 {
return &[];
}
unsafe {
std::slice::from_raw_parts(self.0.msg_control as *const u8, self.0.msg_controllen as _)
}
}
}
impl AsMut<MsgHdr> for msghdr {
fn as_mut(&mut self) -> &mut MsgHdr {
unsafe { &mut *(self as *mut msghdr as *mut MsgHdr) }
}
}
pub(crate) struct MmsgHdr(libc::mmsghdr);
impl Default for MmsgHdr {
fn default() -> Self {
Self(unsafe { std::mem::zeroed() })
}
}
impl MmsgHdr {
pub(crate) fn from_raw(m: libc::mmsghdr) -> Self {
Self(m)
}
pub(crate) fn as_inner_mut(&mut self) -> &mut libc::mmsghdr {
&mut self.0
}
pub(crate) fn msg_len(&self) -> u32 {
self.0.msg_len
}
pub(crate) fn set_msg_hdr(&mut self, hdr: libc::msghdr) {
self.0.msg_hdr = hdr;
}
pub(crate) fn to_msg_bytes(&self, is32: bool) -> Result<Vec<u8>, Errno> {
if is32 {
let m32 = mmsghdr32::try_from(self.0)?;
let bytes: [u8; size_of::<mmsghdr32>()] = m32.to_byte_array();
Ok(bytes.to_vec())
} else {
let m64: mmsghdr = self.0.into();
let bytes: [u8; size_of::<mmsghdr>()] = m64.to_byte_array();
Ok(bytes.to_vec())
}
}
#[cfg(test)]
#[expect(clippy::unnecessary_cast)]
pub(crate) fn cmsg_bytes(&self) -> &[u8] {
if self.0.msg_hdr.msg_control.is_null() || self.0.msg_hdr.msg_controllen == 0 {
return &[];
}
unsafe {
std::slice::from_raw_parts(
self.0.msg_hdr.msg_control as *const u8,
self.0.msg_hdr.msg_controllen as usize,
)
}
}
}
#[expect(clippy::arithmetic_side_effects)]
const fn cmsg_align(len: usize) -> usize {
let mask = size_of::<usize>() - 1;
len.saturating_add(mask) & !mask
}
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(size_of::<cmsghdr32>())))
}
const fn cmsg_len_32(length: u32) -> usize {
cmsg_align_32(size_of::<cmsghdr32>()).saturating_add(length as usize)
}
trait CastBytePair {
fn cast_byte_pair(self) -> (*const u8, usize);
}
impl<T> CastBytePair for *const T {
fn cast_byte_pair(self) -> (*const u8, usize) {
(self.cast::<u8>(), size_of::<T>())
}
}
pub trait TryFromBytes: Copy {
fn try_from_bytes(buf: &[u8]) -> Result<Self, Errno> {
if buf.len() < size_of::<Self>() {
return Err(Errno::EINVAL);
}
Ok(unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const Self) })
}
}
impl<T: Copy> TryFromBytes for T {}
pub fn try_from_bytes<T: TryFromBytes>(buf: &[u8]) -> Result<T, Errno> {
T::try_from_bytes(buf)
}
pub(crate) trait ToByteArray: Copy {
fn to_byte_array<const N: usize>(&self) -> [u8; N] {
const { assert!(N == size_of::<Self>()) };
unsafe { std::mem::transmute_copy(self) }
}
}
impl<T: Copy> ToByteArray for T {}
pub(crate) trait CmsgSpace {
fn cmsg_space() -> usize;
}
impl<T> CmsgSpace for T {
#[expect(clippy::cast_possible_truncation)]
fn cmsg_space() -> usize {
unsafe { libc::CMSG_SPACE(size_of::<T>() as libc::c_uint) as usize }
}
}
pub(crate) fn pack_cmsg_buf(cmsgs: &[Cmsg<'_>]) -> Result<Vec<u8>, Errno> {
let mut buf = Vec::new();
let last = cmsgs.len().checked_sub(1);
for (idx, cmsg) in cmsgs.iter().enumerate() {
cmsg.encode_into(&mut buf, last == Some(idx))?;
}
Ok(buf)
}
pub(crate) fn pack_cmsg_buf_32(cmsgs: &[Cmsg<'_>]) -> Result<Vec<u8>, Errno> {
let mut buf = Vec::new();
let last = cmsgs.len().checked_sub(1);
for (idx, cmsg) in cmsgs.iter().enumerate() {
cmsg.encode_into_32(&mut buf, last == Some(idx))?;
}
Ok(buf)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct RawCmsg<'a> {
pub(crate) level: c_int,
pub(crate) cmsg_type: c_int,
pub(crate) data: &'a [u8],
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct RawCmsgOwned {
pub(crate) level: c_int,
pub(crate) cmsg_type: c_int,
pub(crate) data: Vec<u8>,
}
impl<'a> From<&'a RawCmsgOwned> for RawCmsg<'a> {
fn from(owned: &'a RawCmsgOwned) -> Self {
Self {
level: owned.level,
cmsg_type: owned.cmsg_type,
data: &owned.data,
}
}
}
pub(crate) enum Cmsg<'a> {
AlgSetAeadAssoclen(&'a u32),
AlgSetIv(&'a [u8]),
AlgSetOp(&'a c_int),
Ipv4OrigDstAddr(&'a libc::sockaddr_in),
Ipv4PacketInfo(&'a libc::in_pktinfo),
Ipv4RecvErr(&'a [u8]),
Ipv4RecvFragsize(&'a i32),
Ipv4RecvOpts(&'a [u8]),
Ipv4ReturnOpts(&'a [u8]),
Ipv4Tos(&'a u8),
Ipv4Ttl(&'a c_int),
Ipv6FlowInfo(&'a u32),
Ipv6HopLimit(&'a c_int),
Ipv6OrigDstAddr(&'a libc::sockaddr_in6),
Ipv6PacketInfo(&'a libc::in6_pktinfo),
Ipv6PathMtu(&'a [u8]),
Ipv6RecvErr(&'a [u8]),
Ipv6RecvFragsize(&'a i32),
Ipv6RoutingHdr(&'a [u8]),
Ipv6TClass(&'a i32),
RxqOvfl(&'a u32),
ScmCredentials(&'a UnixCredentials),
ScmPidFd(BorrowedFd<'a>),
ScmRights(&'a [BorrowedFd<'a>]),
ScmSecurity(&'a [u8]),
ScmTimestamp(&'a Timeval64),
ScmTimestampNew(&'a TimeSpec64),
ScmTimestamping(&'a [TimeSpec64; 3]),
ScmTimestampingNew(&'a [TimeSpec64; 3]),
ScmTimestampns(&'a TimeSpec64),
ScmTimestampnsNew(&'a TimeSpec64),
SoMark(&'a u32),
SoPriority(&'a u32),
TcpInq(&'a i32),
TxTime(&'a u64),
UdpGroSegments(&'a i32),
UdpGsoSegments(&'a u16),
WifiStatus(&'a u32),
Unknown(RawCmsg<'a>),
}
pub(crate) enum CmsgOwned {
AlgSetAeadAssoclen(u32),
AlgSetIv(Zeroizing<Vec<u8>>),
AlgSetOp(c_int),
Ipv4OrigDstAddr(libc::sockaddr_in),
Ipv4PacketInfo(libc::in_pktinfo),
Ipv4RecvErr(Vec<u8>),
Ipv4RecvFragsize(i32),
Ipv4RecvOpts(Vec<u8>),
Ipv4ReturnOpts(Vec<u8>),
Ipv4Tos(u8),
Ipv4Ttl(c_int),
Ipv6FlowInfo(u32),
Ipv6HopLimit(c_int),
Ipv6OrigDstAddr(libc::sockaddr_in6),
Ipv6PacketInfo(libc::in6_pktinfo),
Ipv6PathMtu(Vec<u8>),
Ipv6RecvErr(Vec<u8>),
Ipv6RecvFragsize(i32),
Ipv6RoutingHdr(Vec<u8>),
Ipv6TClass(i32),
RxqOvfl(u32),
ScmCredentials(UnixCredentials),
ScmPidFd(SafeOwnedFd),
ScmRights(Vec<SafeOwnedFd>),
ScmSecurity(Vec<u8>),
ScmTimestamp(Timeval64),
ScmTimestampNew(TimeSpec64),
ScmTimestamping([TimeSpec64; 3]),
ScmTimestampingNew([TimeSpec64; 3]),
ScmTimestampns(TimeSpec64),
ScmTimestampnsNew(TimeSpec64),
SoMark(u32),
SoPriority(u32),
TcpInq(i32),
TxTime(u64),
UdpGroSegments(i32),
UdpGsoSegments(u16),
WifiStatus(u32),
Unknown(RawCmsgOwned),
}
impl<'a> From<&'a CmsgOwned> for Cmsg<'a> {
fn from(owned: &'a CmsgOwned) -> Self {
match owned {
CmsgOwned::AlgSetAeadAssoclen(v) => Self::AlgSetAeadAssoclen(v),
CmsgOwned::AlgSetIv(v) => Self::AlgSetIv(v),
CmsgOwned::AlgSetOp(v) => Self::AlgSetOp(v),
CmsgOwned::Ipv4OrigDstAddr(v) => Self::Ipv4OrigDstAddr(v),
CmsgOwned::Ipv4PacketInfo(v) => Self::Ipv4PacketInfo(v),
CmsgOwned::Ipv4RecvErr(v) => Self::Ipv4RecvErr(v),
CmsgOwned::Ipv4RecvFragsize(v) => Self::Ipv4RecvFragsize(v),
CmsgOwned::Ipv4RecvOpts(v) => Self::Ipv4RecvOpts(v),
CmsgOwned::Ipv4ReturnOpts(v) => Self::Ipv4ReturnOpts(v),
CmsgOwned::Ipv4Tos(v) => Self::Ipv4Tos(v),
CmsgOwned::Ipv4Ttl(v) => Self::Ipv4Ttl(v),
CmsgOwned::Ipv6FlowInfo(v) => Self::Ipv6FlowInfo(v),
CmsgOwned::Ipv6HopLimit(v) => Self::Ipv6HopLimit(v),
CmsgOwned::Ipv6OrigDstAddr(v) => Self::Ipv6OrigDstAddr(v),
CmsgOwned::Ipv6PacketInfo(v) => Self::Ipv6PacketInfo(v),
CmsgOwned::Ipv6PathMtu(v) => Self::Ipv6PathMtu(v),
CmsgOwned::Ipv6RecvErr(v) => Self::Ipv6RecvErr(v),
CmsgOwned::Ipv6RecvFragsize(v) => Self::Ipv6RecvFragsize(v),
CmsgOwned::Ipv6RoutingHdr(v) => Self::Ipv6RoutingHdr(v),
CmsgOwned::Ipv6TClass(v) => Self::Ipv6TClass(v),
CmsgOwned::RxqOvfl(v) => Self::RxqOvfl(v),
CmsgOwned::ScmCredentials(v) => Self::ScmCredentials(v),
CmsgOwned::ScmPidFd(v) => Self::ScmPidFd(v.as_fd()),
CmsgOwned::ScmRights(fds) => Self::ScmRights(SafeOwnedFd::as_borrowed_slice(fds)),
CmsgOwned::ScmSecurity(v) => Self::ScmSecurity(v),
CmsgOwned::ScmTimestamp(v) => Self::ScmTimestamp(v),
CmsgOwned::ScmTimestampNew(v) => Self::ScmTimestampNew(v),
CmsgOwned::ScmTimestamping(v) => Self::ScmTimestamping(v),
CmsgOwned::ScmTimestampingNew(v) => Self::ScmTimestampingNew(v),
CmsgOwned::ScmTimestampns(v) => Self::ScmTimestampns(v),
CmsgOwned::ScmTimestampnsNew(v) => Self::ScmTimestampnsNew(v),
CmsgOwned::SoMark(v) => Self::SoMark(v),
CmsgOwned::SoPriority(v) => Self::SoPriority(v),
CmsgOwned::TcpInq(v) => Self::TcpInq(v),
CmsgOwned::TxTime(v) => Self::TxTime(v),
CmsgOwned::UdpGroSegments(v) => Self::UdpGroSegments(v),
CmsgOwned::UdpGsoSegments(v) => Self::UdpGsoSegments(v),
CmsgOwned::WifiStatus(v) => Self::WifiStatus(v),
CmsgOwned::Unknown(raw) => Self::Unknown(RawCmsg::from(raw)),
}
}
}
impl Cmsg<'_> {
pub(crate) fn len(&self) -> usize {
#[expect(clippy::arithmetic_side_effects)]
match *self {
Self::AlgSetAeadAssoclen(v) => size_of_val(v),
Self::AlgSetIv(iv) => size_of::<u32>() + iv.len(),
Self::AlgSetOp(op) => size_of_val(op),
Self::Ipv4OrigDstAddr(v) => size_of_val(v),
Self::Ipv4PacketInfo(v) => size_of_val(v),
Self::Ipv4RecvErr(v) => v.len(),
Self::Ipv4RecvFragsize(v) => size_of_val(v),
Self::Ipv4RecvOpts(v) => v.len(),
Self::Ipv4ReturnOpts(v) => v.len(),
Self::Ipv4Tos(v) => size_of_val(v),
Self::Ipv4Ttl(v) => size_of_val(v),
Self::Ipv6FlowInfo(v) => size_of_val(v),
Self::Ipv6HopLimit(v) => size_of_val(v),
Self::Ipv6OrigDstAddr(v) => size_of_val(v),
Self::Ipv6PacketInfo(v) => size_of_val(v),
Self::Ipv6PathMtu(v) => v.len(),
Self::Ipv6RecvErr(v) => v.len(),
Self::Ipv6RecvFragsize(v) => size_of_val(v),
Self::Ipv6RoutingHdr(v) => v.len(),
Self::Ipv6TClass(v) => size_of_val(v),
Self::RxqOvfl(v) => size_of_val(v),
Self::ScmCredentials(_) => size_of::<libc::ucred>(),
Self::ScmPidFd(_) => size_of::<RawFd>(),
Self::ScmRights(fds) => size_of_val(fds),
Self::ScmSecurity(v) => v.len(),
Self::ScmTimestamp(v) => size_of_val(v),
Self::ScmTimestampNew(v) => size_of_val(v),
Self::ScmTimestamping(v) => size_of_val(v),
Self::ScmTimestampingNew(v) => size_of_val(v),
Self::ScmTimestampns(v) => size_of_val(v),
Self::ScmTimestampnsNew(v) => size_of_val(v),
Self::SoMark(v) => size_of_val(v),
Self::SoPriority(v) => size_of_val(v),
Self::TcpInq(v) => size_of_val(v),
Self::TxTime(v) => size_of_val(v),
Self::UdpGroSegments(v) => size_of_val(v),
Self::UdpGsoSegments(v) => size_of_val(v),
Self::WifiStatus(v) => size_of_val(v),
Self::Unknown(raw) => raw.data.len(),
}
}
pub(crate) fn cmsg_level(&self) -> c_int {
match *self {
Self::AlgSetIv(_) | Self::AlgSetOp(_) | Self::AlgSetAeadAssoclen(_) => libc::SOL_ALG,
Self::Ipv4PacketInfo(_)
| Self::Ipv4Tos(_)
| Self::Ipv4Ttl(_)
| Self::Ipv4OrigDstAddr(_)
| Self::Ipv4RecvErr(_)
| Self::Ipv4RecvOpts(_)
| Self::Ipv4ReturnOpts(_)
| Self::Ipv4RecvFragsize(_) => libc::IPPROTO_IP,
Self::Ipv6PacketInfo(_)
| Self::Ipv6TClass(_)
| Self::Ipv6HopLimit(_)
| Self::Ipv6OrigDstAddr(_)
| Self::Ipv6RecvErr(_)
| Self::Ipv6FlowInfo(_)
| Self::Ipv6PathMtu(_)
| Self::Ipv6RecvFragsize(_)
| Self::Ipv6RoutingHdr(_) => libc::IPPROTO_IPV6,
Self::RxqOvfl(_)
| Self::ScmCredentials(_)
| Self::ScmPidFd(_)
| Self::ScmRights(_)
| Self::ScmSecurity(_)
| Self::ScmTimestamp(_)
| Self::ScmTimestampNew(_)
| Self::ScmTimestamping(_)
| Self::ScmTimestampingNew(_)
| Self::ScmTimestampns(_)
| Self::ScmTimestampnsNew(_)
| Self::SoMark(_)
| Self::SoPriority(_)
| Self::TxTime(_)
| Self::WifiStatus(_) => libc::SOL_SOCKET,
Self::TcpInq(_) => libc::SOL_TCP,
Self::UdpGsoSegments(_) | Self::UdpGroSegments(_) => libc::SOL_UDP,
Self::Unknown(raw) => raw.level,
}
}
pub(crate) fn cmsg_type(&self) -> c_int {
match *self {
Self::AlgSetAeadAssoclen(_) => libc::ALG_SET_AEAD_ASSOCLEN,
Self::AlgSetIv(_) => libc::ALG_SET_IV,
Self::AlgSetOp(_) => libc::ALG_SET_OP,
Self::Ipv4OrigDstAddr(_) => IP_ORIGDSTADDR,
Self::Ipv4PacketInfo(_) => libc::IP_PKTINFO,
Self::Ipv4RecvErr(_) => libc::IP_RECVERR,
Self::Ipv4RecvFragsize(_) => libc::IP_RECVFRAGSIZE,
Self::Ipv4RecvOpts(_) => libc::IP_RECVOPTS,
Self::Ipv4ReturnOpts(_) => libc::IP_RETOPTS,
Self::Ipv4Tos(_) => libc::IP_TOS,
Self::Ipv4Ttl(_) => libc::IP_TTL,
Self::Ipv6FlowInfo(_) => libc::IPV6_FLOWINFO,
Self::Ipv6HopLimit(_) => libc::IPV6_HOPLIMIT,
Self::Ipv6OrigDstAddr(_) => IPV6_ORIGDSTADDR,
Self::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO,
Self::Ipv6PathMtu(_) => libc::IPV6_PATHMTU,
Self::Ipv6RecvErr(_) => libc::IPV6_RECVERR,
Self::Ipv6RecvFragsize(_) => libc::IPV6_RECVFRAGSIZE,
Self::Ipv6RoutingHdr(_) => libc::IPV6_RTHDR,
Self::Ipv6TClass(_) => libc::IPV6_TCLASS,
Self::RxqOvfl(_) => libc::SO_RXQ_OVFL,
Self::ScmCredentials(_) => libc::SCM_CREDENTIALS,
Self::ScmPidFd(_) => SCM_PIDFD,
Self::ScmRights(_) => libc::SCM_RIGHTS,
Self::ScmSecurity(_) => SCM_SECURITY,
Self::ScmTimestamp(_) => SO_TIMESTAMP_OLD,
Self::ScmTimestampNew(_) => SO_TIMESTAMP_NEW,
Self::ScmTimestamping(_) => SO_TIMESTAMPING_OLD,
Self::ScmTimestampingNew(_) => SO_TIMESTAMPING_NEW,
Self::ScmTimestampns(_) => SO_TIMESTAMPNS_OLD,
Self::ScmTimestampnsNew(_) => SO_TIMESTAMPNS_NEW,
Self::SoMark(_) => libc::SO_MARK,
Self::SoPriority(_) => libc::SO_PRIORITY,
Self::TcpInq(_) => libc::TCP_CM_INQ,
Self::TxTime(_) => libc::SCM_TXTIME,
Self::UdpGroSegments(_) => UDP_GRO,
Self::UdpGsoSegments(_) => libc::UDP_SEGMENT,
Self::WifiStatus(_) => libc::SCM_WIFI_STATUS,
Self::Unknown(raw) => raw.cmsg_type,
}
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) fn cmsg_space(&self) -> usize {
unsafe { libc::CMSG_SPACE(self.len() as libc::c_uint) as usize }
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) fn cmsg_len(&self) -> usize {
unsafe { libc::CMSG_LEN(self.len() as libc::c_uint) as usize }
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) fn cmsg_space_32(&self) -> usize {
cmsg_space_32(self.len() as u32)
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) fn cmsg_len_32(&self) -> usize {
cmsg_len_32(self.len() as u32)
}
#[expect(clippy::cast_possible_truncation)]
unsafe fn copy_to_cmsg_data(&self, dst: *mut u8) {
let (ptr, len) = match self {
Self::AlgSetIv(iv) => {
let ivlen = iv.len() as u32;
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::addr_of!(ivlen).cast(),
dst,
size_of::<u32>(),
);
std::ptr::copy_nonoverlapping(iv.as_ptr(), dst.add(size_of::<u32>()), iv.len());
}
return;
}
Self::ScmCredentials(v) => {
let ucred: libc::ucred = (**v).into();
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::addr_of!(ucred).cast::<u8>(),
dst,
size_of::<libc::ucred>(),
);
}
return;
}
Self::ScmPidFd(v) => {
let raw_fd = v.as_raw_fd();
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::addr_of!(raw_fd).cast::<u8>(),
dst,
size_of::<RawFd>(),
);
}
return;
}
Self::AlgSetAeadAssoclen(v) => (*v as *const u32).cast_byte_pair(),
Self::AlgSetOp(v) => (*v as *const c_int).cast_byte_pair(),
Self::Ipv4OrigDstAddr(v) => (*v as *const libc::sockaddr_in).cast_byte_pair(),
Self::Ipv4PacketInfo(v) => (*v as *const libc::in_pktinfo).cast_byte_pair(),
Self::Ipv4RecvErr(v) => (v.as_ptr(), v.len()),
Self::Ipv4RecvFragsize(v) => (*v as *const i32).cast_byte_pair(),
Self::Ipv4RecvOpts(v) => (v.as_ptr(), v.len()),
Self::Ipv4ReturnOpts(v) => (v.as_ptr(), v.len()),
Self::Ipv4Tos(v) => (*v as *const u8, size_of_val(*v)),
Self::Ipv4Ttl(v) => (*v as *const c_int).cast_byte_pair(),
Self::Ipv6FlowInfo(v) => (*v as *const u32).cast_byte_pair(),
Self::Ipv6HopLimit(v) => (*v as *const c_int).cast_byte_pair(),
Self::Ipv6OrigDstAddr(v) => (*v as *const libc::sockaddr_in6).cast_byte_pair(),
Self::Ipv6PacketInfo(v) => (*v as *const libc::in6_pktinfo).cast_byte_pair(),
Self::Ipv6PathMtu(v) => (v.as_ptr(), v.len()),
Self::Ipv6RecvErr(v) => (v.as_ptr(), v.len()),
Self::Ipv6RecvFragsize(v) => (*v as *const i32).cast_byte_pair(),
Self::Ipv6RoutingHdr(v) => (v.as_ptr(), v.len()),
Self::Ipv6TClass(v) => (*v as *const i32).cast_byte_pair(),
Self::RxqOvfl(v) => (*v as *const u32).cast_byte_pair(),
Self::ScmRights(v) => (v.as_ptr().cast::<u8>(), size_of_val(*v)),
Self::ScmSecurity(v) => (v.as_ptr(), v.len()),
Self::ScmTimestamp(v) => (*v as *const Timeval64).cast_byte_pair(),
Self::ScmTimestampNew(v) => (*v as *const TimeSpec64).cast_byte_pair(),
Self::ScmTimestamping(v) => (*v as *const [TimeSpec64; 3]).cast_byte_pair(),
Self::ScmTimestampingNew(v) => (*v as *const [TimeSpec64; 3]).cast_byte_pair(),
Self::ScmTimestampns(v) => (*v as *const TimeSpec64).cast_byte_pair(),
Self::ScmTimestampnsNew(v) => (*v as *const TimeSpec64).cast_byte_pair(),
Self::SoMark(v) => (*v as *const u32).cast_byte_pair(),
Self::SoPriority(v) => (*v as *const u32).cast_byte_pair(),
Self::TcpInq(v) => (*v as *const i32).cast_byte_pair(),
Self::TxTime(v) => (*v as *const u64).cast_byte_pair(),
Self::UdpGroSegments(v) => (*v as *const i32).cast_byte_pair(),
Self::UdpGsoSegments(v) => (*v as *const u16).cast_byte_pair(),
Self::WifiStatus(v) => (*v as *const u32).cast_byte_pair(),
Self::Unknown(raw) => (raw.data.as_ptr(), raw.data.len()),
};
unsafe { std::ptr::copy_nonoverlapping(ptr, dst, len) };
}
#[expect(clippy::as_underscore)]
fn encode_into(&self, buf: &mut Vec<u8>, is_last: bool) -> Result<(), Errno> {
let hdr_len = self.cmsg_len();
let total = if is_last { hdr_len } else { self.cmsg_space() };
let start = buf.len();
let end = start.checked_add(total).ok_or(Errno::EOVERFLOW)?;
buf.try_reserve(total).or(Err(Errno::ENOMEM))?;
buf.resize(end, 0);
let mut hdr = unsafe { std::mem::zeroed::<libc::cmsghdr>() };
hdr.cmsg_len = hdr_len as _;
hdr.cmsg_level = self.cmsg_level();
hdr.cmsg_type = self.cmsg_type();
let hdr_buf: [u8; size_of::<libc::cmsghdr>()] = unsafe { std::mem::transmute(hdr) };
let hdr_end = start
.checked_add(size_of::<libc::cmsghdr>())
.ok_or(Errno::EOVERFLOW)?;
buf[start..hdr_end].copy_from_slice(&hdr_buf);
let data_off = start
.checked_add(cmsg_align(size_of::<libc::cmsghdr>()))
.ok_or(Errno::EOVERFLOW)?;
unsafe { self.copy_to_cmsg_data(buf[data_off..].as_mut_ptr()) };
Ok(())
}
fn encode_into_32(&self, buf: &mut Vec<u8>, is_last: bool) -> Result<(), Errno> {
let hdr_len = self.cmsg_len_32();
let total = if is_last {
hdr_len
} else {
self.cmsg_space_32()
};
let start = buf.len();
let end = start.checked_add(total).ok_or(Errno::EOVERFLOW)?;
buf.try_reserve(total).or(Err(Errno::ENOMEM))?;
buf.resize(end, 0);
let hdr = cmsghdr32 {
cmsg_len: u32::try_from(hdr_len).or(Err(Errno::EOVERFLOW))?,
cmsg_level: self.cmsg_level(),
cmsg_type: self.cmsg_type(),
};
let hdr_buf: [u8; size_of::<cmsghdr32>()] = unsafe { std::mem::transmute(hdr) };
let hdr_end = start
.checked_add(size_of::<cmsghdr32>())
.ok_or(Errno::EOVERFLOW)?;
buf[start..hdr_end].copy_from_slice(&hdr_buf);
let data_off = start
.checked_add(cmsg_align_32(size_of::<cmsghdr32>()))
.ok_or(Errno::EOVERFLOW)?;
unsafe { self.copy_to_cmsg_data(buf[data_off..].as_mut_ptr()) };
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct CmsgIterator<'a> {
cmsghdr: Option<&'a libc::cmsghdr>,
mhdr: libc::msghdr,
buf: &'a [u8],
}
impl<'a> CmsgIterator<'a> {
#[expect(clippy::as_underscore)]
pub(crate) fn new(buf: &'a [u8]) -> Self {
let mut mhdr = unsafe { std::mem::zeroed::<libc::msghdr>() };
mhdr.msg_control = buf.as_ptr().cast_mut().cast();
mhdr.msg_controllen = buf.len() as _;
let cmsghdr = if buf.len() >= size_of::<libc::cmsghdr>() {
unsafe { (libc::CMSG_FIRSTHDR(std::ptr::addr_of!(mhdr))).as_ref() }
} else {
None
};
Self { cmsghdr, mhdr, buf }
}
}
impl<'a> Iterator for CmsgIterator<'a> {
type Item = Result<RawCmsg<'a>, Errno>;
#[expect(clippy::unnecessary_cast)]
fn next(&mut self) -> Option<Self::Item> {
let hdr = self.cmsghdr?;
let cmsg_len0 = usize::try_from(unsafe { libc::CMSG_LEN(0) }).ok()?;
let cmsg_len = hdr.cmsg_len as usize;
let data_len = match cmsg_len.checked_sub(cmsg_len0) {
Some(len) => len,
None => {
self.cmsghdr = None;
return Some(Err(Errno::EINVAL));
}
};
let data_ptr = unsafe { libc::CMSG_DATA(hdr as *const _ as *mut libc::cmsghdr) };
let data_off_isize = unsafe { data_ptr.offset_from(self.buf.as_ptr()) };
let data_off = usize::try_from(data_off_isize).ok()?;
let data_end = match data_off.checked_add(data_len) {
Some(end) => end,
None => {
self.cmsghdr = None;
return Some(Err(Errno::EINVAL));
}
};
if data_end > self.buf.len() {
self.cmsghdr = None;
return Some(Err(Errno::EINVAL));
}
let data = &self.buf[data_off..data_end];
let result = RawCmsg {
level: hdr.cmsg_level,
cmsg_type: hdr.cmsg_type,
data,
};
self.cmsghdr = unsafe {
libc::CMSG_NXTHDR(
std::ptr::addr_of!(self.mhdr),
hdr as *const _ as *mut libc::cmsghdr,
)
.as_ref()
};
Some(Ok(result))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct CmsgIterator32<'a> {
buf: &'a [u8],
offset: usize,
}
impl<'a> CmsgIterator32<'a> {
pub(crate) fn new(buf: &'a [u8]) -> Self {
Self { buf, offset: 0 }
}
}
impl<'a> Iterator for CmsgIterator32<'a> {
type Item = Result<RawCmsg<'a>, Errno>;
fn next(&mut self) -> Option<Self::Item> {
let remaining = self.buf.len().checked_sub(self.offset)?;
if remaining < size_of::<cmsghdr32>() {
return None;
}
let hdr: cmsghdr32 = try_from_bytes(&self.buf[self.offset..]).ok()?;
let cmsg_len0 = cmsg_len_32(0);
let cmsg_len = hdr.cmsg_len as usize;
let data_len = match cmsg_len.checked_sub(cmsg_len0) {
Some(len) => len,
None => {
self.offset = self.buf.len();
return Some(Err(Errno::EINVAL));
}
};
let data_off = match self.offset.checked_add(cmsg_len0) {
Some(off) => off,
None => {
self.offset = self.buf.len();
return Some(Err(Errno::EINVAL));
}
};
let data_end = match data_off.checked_add(data_len) {
Some(end) => end,
None => {
self.offset = self.buf.len();
return Some(Err(Errno::EINVAL));
}
};
if data_end > self.buf.len() {
self.offset = self.buf.len();
return Some(Err(Errno::EINVAL));
}
let result = RawCmsg {
level: hdr.cmsg_level,
cmsg_type: hdr.cmsg_type,
data: &self.buf[data_off..data_end],
};
let data_len_u32 = u32::try_from(data_len).ok()?;
let space = cmsg_space_32(data_len_u32);
self.offset = self.offset.checked_add(space).unwrap_or(self.buf.len());
Some(Ok(result))
}
}
#[inline(always)]
pub(crate) fn recv<Fd: AsFd>(fd: Fd, buf: &mut [u8], flags: MsgFlags) -> Result<usize, Errno> {
let mut iov = [IoSliceMut::new(buf)];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov);
Ok(recvmsg(&fd, &mut hdr, flags)?.bytes)
}
#[expect(clippy::as_underscore)]
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::type_complexity)]
#[inline(always)]
pub(crate) fn recvfrom<Fd: AsFd>(
fd: Fd,
buf: &mut [u8],
flags: MsgFlags,
) -> Result<(usize, Option<(SockaddrStorage, socklen_t)>), Errno> {
let mut iov = [IoSliceMut::new(buf)];
let mut addr = MaybeUninit::<SockaddrStorage>::zeroed();
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov);
hdr.0.msg_name = addr.as_mut_ptr().cast();
hdr.0.msg_namelen = size_of::<SockaddrStorage>() as _;
let n = recvmsg(&fd, &mut hdr, flags)?.bytes;
let addr = if hdr.0.msg_namelen > 0 {
unsafe {
SockaddrStorage::from_raw(hdr.0.msg_name.cast(), Some(hdr.0.msg_namelen as socklen_t))
}
.map(|addr| (addr, hdr.0.msg_namelen as socklen_t))
} else {
None
};
Ok((n, addr))
}
#[inline(always)]
pub(crate) fn send<Fd: AsFd>(fd: Fd, buf: &[u8], flags: MsgFlags) -> Result<usize, Errno> {
let iov = [IoSlice::new(buf)];
sendmsg::<_, SockaddrStorage>(fd, &iov, &[], flags, None)
}
#[inline(always)]
pub(crate) fn sendto<Fd: AsFd, S: SockaddrLike>(
fd: Fd,
buf: &[u8],
addr: &S,
flags: MsgFlags,
) -> Result<usize, Errno> {
let iov = [IoSlice::new(buf)];
sendmsg(fd, &iov, &[], flags, Some(addr))
}
#[inline(always)]
pub(crate) fn sendmsg<Fd: AsFd, S: SockaddrLike>(
fd: Fd,
iov: &[IoSlice<'_>],
cmsgs: &[Cmsg<'_>],
flags: MsgFlags,
addr: Option<&S>,
) -> Result<usize, Errno> {
safe_sendmsg(fd, iov, cmsgs, flags, addr)
}
#[inline(always)]
pub(crate) fn sendmmsg<Fd: AsFd>(
fd: Fd,
msgvec: &mut [MmsgHdr],
flags: MsgFlags,
) -> Result<usize, Errno> {
safe_sendmmsg(fd, msgvec, flags)
}
#[inline(always)]
pub(crate) fn recvmsg<'a, Fd: AsFd>(
fd: Fd,
msghdr: &'a mut MsgHdr,
flags: MsgFlags,
) -> Result<RecvMsg<'a>, Errno> {
safe_recvmsg(fd, msghdr, flags)
}
#[inline(always)]
pub(crate) fn recvmmsg<Fd: AsFd>(
fd: Fd,
msgvec: &mut [MmsgHdr],
flags: MsgFlags,
timeout: Option<&mut TimeSpec64>,
) -> Result<usize, Errno> {
safe_recvmmsg(fd, msgvec, flags, timeout)
}
pub(crate) struct RecvMsg<'a> {
pub(crate) bytes: usize,
pub(crate) flags: c_int,
pub(crate) msghdr: &'a MsgHdr,
}
impl fmt::Debug for RecvMsg<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RecvMsg")
.field("bytes", &self.bytes)
.field("flags", &self.flags)
.finish()
}
}
impl RecvMsg<'_> {
pub(crate) fn cmsgs(&self) -> Result<Vec<CmsgOwned>, Errno> {
const TIMESTAMPING_SIZE: usize = 3 * size_of::<TimeSpec64>();
if self.flags & libc::MSG_CTRUNC != 0 {
return Err(Errno::ENOBUFS);
}
let buf = self.msghdr.cmsg_bytes();
let mut result = Vec::new();
for raw_result in CmsgIterator::new(buf) {
let raw = raw_result?;
let owned = match (raw.level, raw.cmsg_type) {
(libc::SOL_ALG, libc::ALG_SET_AEAD_ASSOCLEN) => {
if raw.data.len() != size_of::<u32>() {
return Err(Errno::EINVAL);
}
let val: u32 = try_from_bytes(raw.data)?;
CmsgOwned::AlgSetAeadAssoclen(val)
}
(libc::SOL_ALG, libc::ALG_SET_IV) => {
if raw.data.len() < size_of::<u32>() {
return Err(Errno::EINVAL);
}
let iv_data = raw.data.get(size_of::<u32>()..).unwrap_or(&[]);
CmsgOwned::AlgSetIv(Zeroizing::new(iv_data.to_vec()))
}
(libc::SOL_ALG, libc::ALG_SET_OP) => {
if raw.data.len() != size_of::<c_int>() {
return Err(Errno::EINVAL);
}
let val: c_int = try_from_bytes(raw.data)?;
CmsgOwned::AlgSetOp(val)
}
(libc::IPPROTO_IP, IP_ORIGDSTADDR) => {
if raw.data.len() != size_of::<libc::sockaddr_in>() {
return Err(Errno::EINVAL);
}
CmsgOwned::Ipv4OrigDstAddr(try_from_bytes(raw.data)?)
}
(libc::IPPROTO_IPV6, IPV6_ORIGDSTADDR) => {
if raw.data.len() != size_of::<libc::sockaddr_in6>() {
return Err(Errno::EINVAL);
}
CmsgOwned::Ipv6OrigDstAddr(try_from_bytes(raw.data)?)
}
(libc::IPPROTO_IP, libc::IP_PKTINFO) => {
if raw.data.len() != size_of::<libc::in_pktinfo>() {
return Err(Errno::EINVAL);
}
let val: libc::in_pktinfo = try_from_bytes(raw.data)?;
CmsgOwned::Ipv4PacketInfo(val)
}
(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => {
if raw.data.len() != size_of::<libc::in6_pktinfo>() {
return Err(Errno::EINVAL);
}
let val: libc::in6_pktinfo = try_from_bytes(raw.data)?;
CmsgOwned::Ipv6PacketInfo(val)
}
(libc::IPPROTO_IP, libc::IP_RECVERR) => CmsgOwned::Ipv4RecvErr(raw.data.to_vec()),
(libc::IPPROTO_IP, libc::IP_RECVOPTS) => CmsgOwned::Ipv4RecvOpts(raw.data.to_vec()),
(libc::IPPROTO_IP, libc::IP_RETOPTS) => {
CmsgOwned::Ipv4ReturnOpts(raw.data.to_vec())
}
(libc::IPPROTO_IP, libc::IP_RECVFRAGSIZE) => {
if raw.data.len() != size_of::<i32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::Ipv4RecvFragsize(try_from_bytes(raw.data)?)
}
(libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => {
CmsgOwned::Ipv6RecvErr(raw.data.to_vec())
}
(libc::IPPROTO_IPV6, libc::IPV6_FLOWINFO) => {
if raw.data.len() != size_of::<u32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::Ipv6FlowInfo(try_from_bytes(raw.data)?)
}
(libc::IPPROTO_IPV6, libc::IPV6_PATHMTU) => {
CmsgOwned::Ipv6PathMtu(raw.data.to_vec())
}
(libc::IPPROTO_IPV6, libc::IPV6_RTHDR)
| (libc::IPPROTO_IPV6, libc::IPV6_2292RTHDR) => {
CmsgOwned::Ipv6RoutingHdr(raw.data.to_vec())
}
(libc::IPPROTO_IPV6, libc::IPV6_RECVFRAGSIZE) => {
if raw.data.len() != size_of::<i32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::Ipv6RecvFragsize(try_from_bytes(raw.data)?)
}
(libc::IPPROTO_IP, libc::IP_TOS) => {
if raw.data.len() != size_of::<u8>() {
return Err(Errno::EINVAL);
}
CmsgOwned::Ipv4Tos(raw.data[0])
}
(libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => {
if raw.data.len() != size_of::<i32>() {
return Err(Errno::EINVAL);
}
let val: i32 = try_from_bytes(raw.data)?;
CmsgOwned::Ipv6TClass(val)
}
(libc::IPPROTO_IP, libc::IP_TTL) => {
if raw.data.len() != size_of::<c_int>() {
return Err(Errno::EINVAL);
}
let val: c_int = try_from_bytes(raw.data)?;
CmsgOwned::Ipv4Ttl(val)
}
(libc::IPPROTO_IPV6, libc::IPV6_HOPLIMIT) => {
if raw.data.len() != size_of::<c_int>() {
return Err(Errno::EINVAL);
}
let val: c_int = try_from_bytes(raw.data)?;
CmsgOwned::Ipv6HopLimit(val)
}
(libc::SOL_SOCKET, libc::SO_RXQ_OVFL) => {
if raw.data.len() != size_of::<u32>() {
return Err(Errno::EINVAL);
}
let val: u32 = try_from_bytes(raw.data)?;
CmsgOwned::RxqOvfl(val)
}
(libc::SOL_SOCKET, libc::SCM_CREDENTIALS) => {
if raw.data.len() != size_of::<libc::ucred>() {
return Err(Errno::EINVAL);
}
let ucred: libc::ucred = try_from_bytes(raw.data)?;
CmsgOwned::ScmCredentials(UnixCredentials::from(ucred))
}
(libc::SOL_SOCKET, SCM_PIDFD) => {
if raw.data.len() != size_of::<RawFd>() {
return Err(Errno::EINVAL);
}
let raw_fd: RawFd = try_from_bytes(raw.data)?;
CmsgOwned::ScmPidFd(unsafe { SafeOwnedFd::from_raw_fd(raw_fd) })
}
(libc::SOL_SOCKET, libc::SCM_RIGHTS) => {
if raw.data.len().checked_rem(size_of::<RawFd>()) != Some(0) {
return Err(Errno::EINVAL);
}
let n = raw
.data
.len()
.checked_div(size_of::<RawFd>())
.ok_or(Errno::EINVAL)?;
let mut fds = Vec::new();
fds.try_reserve(n).or(Err(Errno::ENOMEM))?;
for i in 0..n {
let off = i.checked_mul(size_of::<RawFd>()).ok_or(Errno::EOVERFLOW)?;
let end = off
.checked_add(size_of::<RawFd>())
.ok_or(Errno::EOVERFLOW)?;
let fd_bytes: [u8; size_of::<RawFd>()] = raw
.data
.get(off..end)
.ok_or(Errno::EINVAL)?
.try_into()
.or(Err(Errno::EINVAL))?;
fds.push(unsafe {
SafeOwnedFd::from_raw_fd(RawFd::from_ne_bytes(fd_bytes))
});
}
CmsgOwned::ScmRights(fds)
}
(libc::SOL_SOCKET, SCM_SECURITY) => CmsgOwned::ScmSecurity(raw.data.to_vec()),
(libc::SOL_SOCKET, libc::SCM_TXTIME) => {
if raw.data.len() != size_of::<u64>() {
return Err(Errno::EINVAL);
}
let val: u64 = try_from_bytes(raw.data)?;
CmsgOwned::TxTime(val)
}
(libc::SOL_SOCKET, SO_TIMESTAMP_OLD) => {
if raw.data.len() != size_of::<Timeval64>() {
return Err(Errno::EINVAL);
}
CmsgOwned::ScmTimestamp(try_from_bytes(raw.data)?)
}
(libc::SOL_SOCKET, SO_TIMESTAMP_NEW) => {
if raw.data.len() != size_of::<TimeSpec64>() {
return Err(Errno::EINVAL);
}
CmsgOwned::ScmTimestampNew(try_from_bytes(raw.data)?)
}
(libc::SOL_SOCKET, SO_TIMESTAMPNS_OLD) => {
if raw.data.len() != size_of::<TimeSpec64>() {
return Err(Errno::EINVAL);
}
CmsgOwned::ScmTimestampns(try_from_bytes(raw.data)?)
}
(libc::SOL_SOCKET, SO_TIMESTAMPNS_NEW) => {
if raw.data.len() != size_of::<TimeSpec64>() {
return Err(Errno::EINVAL);
}
CmsgOwned::ScmTimestampnsNew(try_from_bytes(raw.data)?)
}
(libc::SOL_SOCKET, SO_TIMESTAMPING_OLD) => {
if raw.data.len() != TIMESTAMPING_SIZE {
return Err(Errno::EINVAL);
}
let ts0: TimeSpec64 = try_from_bytes(&raw.data[0..16])?;
let ts1: TimeSpec64 = try_from_bytes(&raw.data[16..32])?;
let ts2: TimeSpec64 = try_from_bytes(&raw.data[32..48])?;
CmsgOwned::ScmTimestamping([ts0, ts1, ts2])
}
(libc::SOL_SOCKET, SO_TIMESTAMPING_NEW) => {
if raw.data.len() != TIMESTAMPING_SIZE {
return Err(Errno::EINVAL);
}
let ts0: TimeSpec64 = try_from_bytes(&raw.data[0..16])?;
let ts1: TimeSpec64 = try_from_bytes(&raw.data[16..32])?;
let ts2: TimeSpec64 = try_from_bytes(&raw.data[32..48])?;
CmsgOwned::ScmTimestampingNew([ts0, ts1, ts2])
}
(libc::SOL_SOCKET, libc::SO_MARK) => {
if raw.data.len() != size_of::<u32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::SoMark(try_from_bytes(raw.data)?)
}
(libc::SOL_SOCKET, libc::SO_PRIORITY) => {
if raw.data.len() != size_of::<u32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::SoPriority(try_from_bytes(raw.data)?)
}
(libc::SOL_SOCKET, libc::SCM_WIFI_STATUS) => {
if raw.data.len() != size_of::<u32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::WifiStatus(try_from_bytes(raw.data)?)
}
(libc::SOL_TCP, libc::TCP_CM_INQ) => {
if raw.data.len() != size_of::<i32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::TcpInq(try_from_bytes(raw.data)?)
}
(libc::SOL_UDP, UDP_GRO) => {
if raw.data.len() != size_of::<i32>() {
return Err(Errno::EINVAL);
}
CmsgOwned::UdpGroSegments(try_from_bytes(raw.data)?)
}
(libc::SOL_UDP, libc::UDP_SEGMENT) => {
if raw.data.len() != size_of::<u16>() {
return Err(Errno::EINVAL);
}
let val: u16 = try_from_bytes(raw.data)?;
CmsgOwned::UdpGsoSegments(val)
}
_ => CmsgOwned::Unknown(RawCmsgOwned {
level: raw.level,
cmsg_type: raw.cmsg_type,
data: raw.data.to_vec(),
}),
};
result.try_reserve(1).or(Err(Errno::ENOMEM))?;
result.push(owned);
}
Ok(result)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct XattrArgs {
pub value: u64,
pub size: u32,
pub flags: u32,
}
impl XattrArgs {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Errno> {
if bytes.len() != size_of::<Self>() {
return Err(Errno::EINVAL);
}
Ok(Self {
value: u64::from_ne_bytes(bytes[0..8].try_into().or(Err(Errno::EINVAL))?),
size: u32::from_ne_bytes(bytes[8..12].try_into().or(Err(Errno::EINVAL))?),
flags: u32::from_ne_bytes(bytes[12..16].try_into().or(Err(Errno::EINVAL))?),
})
}
}
pub fn fgetxattr<Fd: AsFd>(
fd: Fd,
name: &CStr,
mut value: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let fd = fd.as_fd().as_raw_fd();
let (val, len) = match value.as_mut() {
Some(v) => (v.as_mut_ptr().cast::<c_void>(), v.capacity()),
None => (std::ptr::null_mut(), 0),
};
#[expect(clippy::cast_sign_loss)]
let n = Errno::result(unsafe { libc::fgetxattr(fd, name.as_ptr(), val, len) })
.map(|r| r as usize)?;
if let Some(value) = value {
unsafe { value.set_len(n) };
}
Ok(n)
}
pub fn lgetxattr<P: ?Sized + NixPath>(
path: &P,
name: &CStr,
mut value: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let (val, len) = match value.as_mut() {
Some(v) => (v.as_mut_ptr().cast::<c_void>(), v.capacity()),
None => (std::ptr::null_mut(), 0),
};
let n = path.with_nix_path(|c_path| {
#[expect(clippy::cast_sign_loss)]
Errno::result(unsafe { libc::lgetxattr(c_path.as_ptr(), name.as_ptr(), val, len) })
.map(|r| r as usize)
})??;
if let Some(value) = value {
unsafe { value.set_len(n) };
}
Ok(n)
}
pub fn flistxattr<Fd: AsFd>(fd: Fd, mut list: Option<&mut Vec<u8>>) -> Result<usize, Errno> {
let (ptr, cap) = match list.as_mut() {
Some(b) => (b.as_mut_ptr().cast::<libc::c_char>(), b.capacity()),
None => (std::ptr::null_mut(), 0),
};
#[expect(clippy::cast_sign_loss)]
let n = Errno::result(unsafe { libc::flistxattr(fd.as_fd().as_raw_fd(), ptr, cap) })
.map(|r| r as usize)?;
if let Some(b) = list {
unsafe { b.set_len(n) };
}
Ok(n)
}
pub fn llistxattr<P: ?Sized + NixPath>(
path: &P,
mut list: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let (ptr, cap) = match list.as_mut() {
Some(b) => (b.as_mut_ptr().cast::<libc::c_char>(), b.capacity()),
None => (std::ptr::null_mut(), 0),
};
let n = path.with_nix_path(|c_path| {
#[expect(clippy::cast_sign_loss)]
Errno::result(unsafe { libc::llistxattr(c_path.as_ptr(), ptr, cap) }).map(|r| r as usize)
})??;
if let Some(b) = list {
unsafe { b.set_len(n) };
}
Ok(n)
}
static SYS_GETXATTRAT: LazyLock<Option<libc::c_long>> =
LazyLock::new(|| resolve_syscall("getxattrat"));
pub fn getxattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
name: &CStr,
flags: AtFlags,
mut value: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let sysno = SYS_GETXATTRAT.ok_or(Errno::ENOSYS)?;
let (val, len) = match value.as_mut() {
Some(v) => (v.as_mut_ptr(), v.capacity()),
None => (std::ptr::null_mut(), 0),
};
#[expect(clippy::cast_possible_truncation)]
let mut args = XattrArgs {
value: val as u64,
size: len as u32,
flags: 0,
};
let res = path.with_nix_path(|c_path| {
unsafe {
libc::syscall(
sysno,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(),
name.as_ptr(),
&raw mut args,
size_of::<XattrArgs>(),
)
}
})?;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let n = Errno::result(res).map(|r| r as usize)?;
if let Some(value) = value {
unsafe { value.set_len(n) };
}
Ok(n)
}
static SYS_SETXATTRAT: LazyLock<Option<libc::c_long>> =
LazyLock::new(|| resolve_syscall("setxattrat"));
pub fn setxattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
name: &CStr,
args: &XattrArgs,
flags: AtFlags,
) -> Result<(), Errno> {
let sysno = SYS_SETXATTRAT.ok_or(Errno::ENOSYS)?;
let res = path.with_nix_path(|c_path| {
unsafe {
libc::syscall(
sysno,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(), name.as_ptr(),
args as *const XattrArgs,
size_of::<XattrArgs>(),
)
}
})?;
Errno::result(res).map(drop)
}
pub(crate) static SYS_LISTXATTRAT: LazyLock<Option<libc::c_long>> =
LazyLock::new(|| resolve_syscall("listxattrat"));
pub fn listxattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
flags: AtFlags,
mut list: Option<&mut Vec<u8>>,
) -> Result<usize, Errno> {
let sysno = SYS_LISTXATTRAT.ok_or(Errno::ENOSYS)?;
let (ptr, cap) = match list.as_mut() {
Some(b) => (b.as_mut_ptr().cast::<libc::c_char>(), b.capacity()),
None => (std::ptr::null_mut(), 0),
};
let res = path.with_nix_path(|c_path| {
unsafe {
libc::syscall(
sysno,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(),
ptr,
cap,
)
}
})?;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
let n = Errno::result(res).map(|r| r as usize)?;
if let Some(list) = list {
unsafe { list.set_len(n) };
}
Ok(n)
}
static SYS_REMOVEXATTRAT: LazyLock<Option<libc::c_long>> =
LazyLock::new(|| resolve_syscall("removexattrat"));
pub fn removexattrat<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
name: &CStr,
flags: AtFlags,
) -> Result<(), Errno> {
let sysno = SYS_REMOVEXATTRAT.ok_or(Errno::ENOSYS)?;
let res = path.with_nix_path(|c_path| {
unsafe {
libc::syscall(
sysno,
dirfd.as_fd().as_raw_fd(),
c_path.as_ptr(),
flags.bits(), name.as_ptr(),
)
}
})?;
Errno::result(res).map(drop)
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct FileStatxTimestamp {
pub tv_sec: i64,
pub tv_nsec: u32,
pub(crate) __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 std::hash::Hash for FileStatxTimestamp {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.tv_sec.hash(state);
self.tv_nsec.hash(state);
}
}
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, Eq, PartialEq, Hash)]
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, Eq, PartialEq, Hash)]
pub struct DirIter {
buffer: NonNull<u8>,
bufsiz: usize,
memsiz: usize,
offset: usize,
}
const DIRENT_ALIGN: usize = 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 buf = unsafe { std::slice::from_raw_parts_mut(self.buffer.as_ptr(), bufsiz) };
let retsiz = safe_getdents64(fd, buf)?;
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> {
const DIRENT64_HEADER_SIZE: usize = offset_of!(libc::dirent64, d_name);
if self.offset >= self.bufsiz {
return None;
}
unsafe {
let rem = self.bufsiz.saturating_sub(self.offset);
if rem < DIRENT64_HEADER_SIZE {
return None;
}
let ptr = self.buffer.as_ptr().add(self.offset);
let d_reclen = std::ptr::read_unaligned(
ptr.add(offset_of!(libc::dirent64, d_reclen)) as *const u16
) as usize;
if d_reclen < DIRENT64_HEADER_SIZE || d_reclen > rem {
return None;
}
let d_name = ptr.add(DIRENT64_HEADER_SIZE) as *const libc::c_char;
let namelen = libc::strlen(d_name);
let dirent = std::slice::from_raw_parts(ptr, 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 fmt::Debug for DirIter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DirIter")
.field("bufsiz", &self.bufsiz)
.field("memsiz", &self.memsiz)
.field("offset", &self.offset)
.finish()
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct DirEntry<'a> {
dirent: &'a [u8],
namelen: usize,
}
impl fmt::Debug for DirEntry<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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_byte_slice(&self) -> &[u8] {
self.dirent
}
pub fn is_dot(&self) -> bool {
self.is_dir() && self.as_xpath().is_equal(b".")
}
pub fn is_dotdot(&self) -> bool {
self.is_dir() && self.as_xpath().is_equal(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 d_type = unsafe { *self.dirent.as_ptr().add(offset_of!(libc::dirent64, d_type)) };
FileType::from(d_type)
}
pub fn ino(&self) -> u64 {
unsafe { std::ptr::read_unaligned(self.dirent.as_ptr() as *const u64) }
}
pub fn size(&self) -> usize {
self.dirent.len()
}
pub fn name_bytes(&self) -> &[u8] {
unsafe {
let d_name = self.dirent.as_ptr().add(offset_of!(libc::dirent64, d_name));
std::slice::from_raw_parts(d_name, self.namelen)
}
}
pub fn as_bytes(&self) -> &[u8] {
self.dirent
}
}
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 ptr: *mut DirIter = if let Ok(mut borrow) = cell.try_borrow_mut() {
if borrow.is_none() {
*borrow = Some(DirIter::new(DIRENT_BUF_SIZE)?);
}
let ptr = unsafe { borrow.as_mut().unwrap_unchecked() as *mut DirIter };
std::mem::forget(borrow);
ptr
} else {
let opt: *mut Option<DirIter> = cell.as_ptr();
unsafe { (*opt).as_mut() }.ok_or(Errno::EFAULT)? as *mut DirIter
};
Ok::<_, Errno>(unsafe { &mut *ptr })
})?;
iter.readdir(fd, bufsiz)?;
Ok(iter)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum WaitStatus {
Exited(Pid, i32),
Signaled(Pid, i32, bool),
Stopped(Pid, i32),
PtraceEvent(Pid, i32, c_int),
PtraceSyscall(Pid),
Continued(Pid),
StillAlive,
}
impl From<NixWaitStatus> for WaitStatus {
fn from(status: NixWaitStatus) -> Self {
match status {
NixWaitStatus::Exited(pid, code) => Self::Exited(pid, code),
NixWaitStatus::Signaled(pid, signal, core_dump) => {
Self::Signaled(pid, signal as i32, core_dump)
}
NixWaitStatus::Stopped(pid, signal) => Self::Stopped(pid, signal as i32),
NixWaitStatus::PtraceEvent(pid, signal, event) => {
WaitStatus::PtraceEvent(pid, signal as i32, event)
}
NixWaitStatus::PtraceSyscall(pid) => Self::PtraceSyscall(pid),
NixWaitStatus::Continued(pid) => Self::Continued(pid),
NixWaitStatus::StillAlive => Self::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) -> c_int {
(status >> 16) as 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 c_int)
}
}
_ => return Err(Errno::EINVAL),
};
Ok(status)
}
pub(crate) fn pipe2_raw(flags: OFlag) -> Result<(RawFd, RawFd), Errno> {
let mut fds = 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))
}
pub(crate) fn sigwaitinfo(set: &libc::sigset_t, info: Option<&mut libc::siginfo_t>) -> c_int {
let info_ptr = info
.map(|i| i as *mut libc::siginfo_t)
.unwrap_or(std::ptr::null_mut());
unsafe { libc::sigtimedwait(set, info_ptr, std::ptr::null()) }
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
pub(crate) unsafe fn timer_create(
clockid: libc::clockid_t,
sevp: *mut libc::sigevent,
timerid: *mut libc::timer_t,
) -> c_int {
let mut kernel_timer_id: c_int = 0;
let res = libc::syscall(libc::SYS_timer_create, clockid, sevp, &mut kernel_timer_id);
if res == 0 {
*timerid = kernel_timer_id as usize as libc::timer_t;
}
res as c_int
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) unsafe fn timer_settime(
timerid: libc::timer_t,
flags: c_int,
new_value: *const libc::itimerspec,
old_value: *mut libc::itimerspec,
) -> c_int {
libc::syscall(
libc::SYS_timer_settime,
timerid,
flags,
new_value,
old_value,
) as c_int
}
#[expect(clippy::cast_possible_truncation)]
pub(crate) unsafe fn timer_delete(timerid: libc::timer_t) -> c_int {
libc::syscall(libc::SYS_timer_delete, timerid) as c_int
}
#[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;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(i32)]
pub enum SockType {
Stream = libc::SOCK_STREAM,
Datagram = libc::SOCK_DGRAM,
Raw = libc::SOCK_RAW,
Rdm = libc::SOCK_RDM,
SeqPacket = libc::SOCK_SEQPACKET,
Dccp = libc::SOCK_DCCP,
Packet = 10,
}
pub(crate) const SOCK_TYPE_MASK: c_int = 0xf;
pub(crate) const AF_MAX: c_int = 46;
pub(crate) const SOCK_MAX: c_int = 11;
impl TryFrom<c_int> for SockType {
type Error = Errno;
fn try_from(raw: c_int) -> Result<Self, Self::Error> {
match raw & SOCK_TYPE_MASK {
libc::SOCK_STREAM => Ok(Self::Stream),
libc::SOCK_DGRAM => Ok(Self::Datagram),
libc::SOCK_RAW => Ok(Self::Raw),
libc::SOCK_RDM => Ok(Self::Rdm),
libc::SOCK_SEQPACKET => Ok(Self::SeqPacket),
libc::SOCK_DCCP => Ok(Self::Dccp),
10 => Ok(Self::Packet),
typ if typ >= SOCK_MAX => Err(Errno::EINVAL),
_ => Err(Errno::ESOCKTNOSUPPORT),
}
}
}
impl SockType {
pub fn as_raw(self) -> c_int {
self as c_int
}
pub fn is_unsafe(self) -> bool {
matches!(self, Self::Raw | Self::Packet)
}
}
#[expect(clippy::disallowed_types)]
impl TryFrom<SockType> for nix::sys::socket::SockType {
type Error = Errno;
fn try_from(stype: SockType) -> Result<Self, Self::Error> {
match stype {
SockType::Stream => Ok(nix::sys::socket::SockType::Stream),
SockType::Datagram => Ok(nix::sys::socket::SockType::Datagram),
SockType::Raw => Ok(nix::sys::socket::SockType::Raw),
SockType::SeqPacket => Ok(nix::sys::socket::SockType::SeqPacket),
_ => Err(Errno::EINVAL),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct AddressFamily(c_int);
#[allow(non_upper_case_globals)]
impl AddressFamily {
pub const Unspec: Self = Self(libc::AF_UNSPEC);
pub const Unix: Self = Self(libc::AF_UNIX);
pub const Inet: Self = Self(libc::AF_INET);
pub const Inet6: Self = Self(libc::AF_INET6);
pub const Netlink: Self = Self(libc::AF_NETLINK);
pub const Packet: Self = Self(libc::AF_PACKET);
pub const Alg: Self = Self(libc::AF_ALG);
pub const Tipc: Self = Self(libc::AF_TIPC);
pub const fn from_raw(raw: c_int) -> Self {
Self(raw)
}
pub const fn as_raw(self) -> c_int {
self.0
}
}
impl From<libc::sa_family_t> for AddressFamily {
fn from(family: libc::sa_family_t) -> Self {
Self(c_int::from(family))
}
}
pub(crate) fn sockaddr_family<T: SockaddrLike>(addr: &T) -> AddressFamily {
AddressFamily::from(addr_family(addr))
}
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: c_int = 0x0000;
pub const AT_STATX_FORCE_SYNC: c_int = 0x2000;
pub const AT_STATX_DONT_SYNC: c_int = 0x4000;
pub fn statx<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
pathname: &P,
flags: c_int,
mask: libc::c_uint,
) -> Result<FileStatx, Errno> {
let dirfd = dirfd.as_fd().as_raw_fd();
let mut dst = MaybeUninit::zeroed();
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 = MaybeUninit::zeroed();
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;
#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
pub(crate) use stat64 as FileStat;
#[cfg(not(any(target_arch = "mips64", target_arch = "mips64r6")))]
pub(crate) use FileStat64 as FileStat;
impl From<FileStatx> for FileStat64 {
fn from(stx: FileStatx) -> FileStat64 {
FileStat64::from(&stx)
}
}
impl From<&FileStatx> for FileStat64 {
#[expect(clippy::as_underscore)]
#[expect(clippy::cast_lossless)]
#[expect(clippy::cast_possible_wrap)]
fn from(stx: &FileStatx) -> FileStat64 {
let mut st: FileStat64 = unsafe { std::mem::zeroed() };
st.st_ino = stx.stx_ino;
st.st_nlink = stx.stx_nlink.into();
st.st_mode = stx.stx_mode.into();
st.st_uid = stx.stx_uid;
st.st_gid = stx.stx_gid;
st.st_size = stx.stx_size as i64;
st.st_blksize = stx.stx_blksize as _;
st.st_blocks = stx.stx_blocks as i64;
st.st_atime = stx.stx_atime.tv_sec as _;
st.st_atime_nsec = stx.stx_atime.tv_nsec as _;
st.st_mtime = stx.stx_mtime.tv_sec as _;
st.st_mtime_nsec = stx.stx_mtime.tv_nsec as _;
st.st_ctime = stx.stx_ctime.tv_sec as _;
st.st_ctime_nsec = stx.stx_ctime.tv_nsec as _;
st.st_dev = makedev(stx.stx_dev_major.into(), stx.stx_dev_minor.into());
st.st_rdev = makedev(stx.stx_rdev_major.into(), stx.stx_rdev_minor.into());
st
}
}
pub(crate) fn fstatat64<Fd: AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
pathname: &P,
flags: c_int,
) -> Result<FileStat64, Errno> {
const FLAGS: c_int = libc::AT_EMPTY_PATH
| libc::AT_NO_AUTOMOUNT
| libc::AT_SYMLINK_NOFOLLOW
| AT_STATX_FORCE_SYNC
| AT_STATX_DONT_SYNC;
if flags & !FLAGS != 0 {
return Err(Errno::EINVAL);
}
Ok(statx(dirfd, pathname, flags, STATX_BASIC_STATS)?.into())
}
#[cfg(target_os = "freebsd")]
pub(crate) type fs_type_t = u32;
#[cfg(target_os = "android")]
pub(crate) type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_arch = "s390x", not(target_env = "musl")))]
pub(crate) type fs_type_t = libc::c_uint;
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub(crate) type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "ohos"))]
pub(crate) type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "uclibc"))]
pub(crate) type fs_type_t = c_int;
#[cfg(all(
target_os = "linux",
not(any(
target_arch = "s390x",
target_env = "musl",
target_env = "ohos",
target_env = "uclibc"
))
))]
pub(crate) type fs_type_t = libc::__fsword_t;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FsType(fs_type_t);
impl FsType {
pub fn get<Fd: AsFd>(fd: Fd) -> Result<Self, Errno> {
retry_on_eintr(|| fstatfs64(&fd)).map(|result| Self(result.0.f_type as fs_type_t))
}
pub fn from_name(name: &str) -> Result<Vec<Self>, Errno> {
let mut fs_types = Vec::new();
if let Ok(fs_type) = str2u64(name.as_bytes()) {
#[expect(clippy::cast_possible_wrap)]
fs_types.push(Self(fs_type as fs_type_t));
} else {
for (fs_name, fs_type) in FS_MAGIC {
if is_equal(name.as_bytes(), fs_name.as_bytes()) {
fs_types.push(fs_type.into());
}
}
}
if fs_types.is_empty() {
return Err(Errno::EINVAL);
}
Ok(fs_types)
}
pub(crate) fn has_broken_devid(self) -> bool {
self.is_overlayfs() || self.is_btrfs()
}
pub fn is_huge_file(self) -> bool {
self.0 == HUGETLBFS_MAGIC
}
pub fn is_proc(self) -> bool {
self.0 == PROC_SUPER_MAGIC
}
pub fn is_overlayfs(self) -> bool {
self.0 == OVERLAYFS_SUPER_MAGIC
}
pub fn is_btrfs(self) -> bool {
self.0 == BTRFS_SUPER_MAGIC
}
pub fn is_zfs(self) -> bool {
self.0 == ZFS_SUPER_MAGIC
}
pub fn is_secretmem(self) -> bool {
self.0 == SECRETMEM_SUPER_MAGIC
}
pub fn is_anon_inode(self) -> bool {
self.0 == ANON_INODE_FS_MAGIC
}
}
impl fmt::Display for FsType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let magic = self.0;
if let Some((name, _)) = FS_MAGIC.iter().find(|(_, m)| *m == magic) {
f.write_str(name)
} else {
write!(f, "{magic:#x}")
}
}
}
impl Serialize for FsType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let magic = self.0;
if let Some((name, _)) = FS_MAGIC.iter().find(|(_, m)| *m == magic) {
serializer.serialize_str(name)
} else {
#[expect(clippy::cast_sign_loss)]
serializer.serialize_u64(magic as u64)
}
}
}
impl From<fs_type_t> for FsType {
fn from(fs_type: fs_type_t) -> Self {
FsType(fs_type)
}
}
impl From<FsType> for fs_type_t {
fn from(fs_type: FsType) -> Self {
fs_type.0
}
}
impl From<&fs_type_t> for FsType {
fn from(fs_type: &fs_type_t) -> Self {
FsType(*fs_type)
}
}
impl From<&FsType> for fs_type_t {
fn from(fs_type: &FsType) -> Self {
fs_type.0
}
}
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;
const ZFS_SUPER_MAGIC: fs_type_t = 0x2fc12fc1i64 as fs_type_t;
const SECRETMEM_SUPER_MAGIC: fs_type_t = 0x5345434di64 as fs_type_t;
const ANON_INODE_FS_MAGIC: fs_type_t = 0x09041934i64 as fs_type_t;
pub(crate) fn fstatfs64<Fd: AsFd>(fd: Fd) -> Result<Statfs, Errno> {
let mut dst = MaybeUninit::<libc::statfs64>::uninit();
#[cfg(not(any(target_env = "musl", target_env = "ohos", target_os = "emscripten")))]
Errno::result(
unsafe { libc::fstatfs64(fd.as_fd().as_raw_fd(), dst.as_mut_ptr()) },
)?;
#[cfg(any(target_env = "musl", target_env = "ohos", target_os = "emscripten"))]
Errno::result(
unsafe { libc::fstatfs(fd.as_fd().as_raw_fd(), dst.as_mut_ptr().cast()) },
)?;
Ok(Statfs(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 c_int,
fd,
&raw mut event,
)
}),
Errno::EEXIST,
)
} else {
(
Errno::result(unsafe {
libc::epoll_ctl(
epoll.as_fd().as_raw_fd(),
EpollOp::EpollCtlDel as 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 c_int,
fd,
&raw mut event,
)
})
.map(drop)
}
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
)))]
const EPIOCSPARAMS: u64 = 0x40088a01;
#[cfg(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
))]
const EPIOCSPARAMS: u64 = 0x80088a01;
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
)))]
const EPIOCGPARAMS: u64 = 0x80088a02;
#[cfg(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
))]
const EPIOCGPARAMS: u64 = 0x40088a02;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct EpollParams {
pub busy_poll_usecs: u32,
pub busy_poll_budget: u16,
pub prefer_busy_poll: u8,
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 = 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<c_int, Errno> {
#[expect(clippy::cast_possible_truncation)]
let mut len = size_of::<c_int>() as socklen_t;
let mut fml: 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(Copy, Clone, Debug, Eq, PartialEq, Hash)]
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 fmt::Display for LsmId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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;
#[expect(clippy::cast_possible_wrap)]
const MSG_CMSG_COMPAT = 0x80000000_u32 as i32;
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_64KB = libc::MFD_HUGE_64KB;
const MFD_HUGE_512KB = libc::MFD_HUGE_512KB;
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())
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct SecretMemFlags: libc::c_uint {
const SM_CLOEXEC = libc::O_CLOEXEC as libc::c_uint;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct ResolveFlag: u64 {
const RESOLVE_BENEATH = 0x08;
const RESOLVE_IN_ROOT = 0x10;
const RESOLVE_NO_MAGICLINKS = 0x02;
const RESOLVE_NO_SYMLINKS = 0x04;
const RESOLVE_NO_XDEV = 0x01;
const RESOLVE_CACHED = 0x20;
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[non_exhaustive]
#[repr(C)]
pub struct OpenHow {
pub flags: u64,
pub mode: u64,
pub resolve: u64,
}
impl OpenHow {
pub fn new() -> Self {
Self::default()
}
pub fn flags(mut self, flags: OFlag) -> Self {
#[expect(clippy::cast_sign_loss)]
let flags = flags.bits() as u64;
self.flags = flags;
self
}
pub fn mode(mut self, mode: Mode) -> Self {
let mode = mode.bits().into();
self.mode = mode;
self
}
pub fn resolve(mut self, resolve: ResolveFlag) -> Self {
let resolve = resolve.bits();
self.resolve = resolve;
self
}
}
static SYS_OPENAT2: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
ScmpSyscall::from_name("openat2")
.map(i32::from)
.map(libc::c_long::from)
.ok()
});
pub fn openat2<P: ?Sized + NixPath, Fd: AsFd>(
dirfd: Fd,
path: &P,
mut how: OpenHow,
) -> Result<SafeOwnedFd, Errno> {
let sys_openat2 = SYS_OPENAT2.ok_or(Errno::ENOSYS)?;
#[expect(clippy::cast_possible_truncation)]
let fd = path.with_nix_path(|cstr| unsafe {
libc::syscall(
sys_openat2,
dirfd.as_fd().as_raw_fd(),
cstr.as_ptr(),
&raw mut how,
std::mem::size_of::<OpenHow>(),
)
})? as RawFd;
Errno::result(fd)?;
Ok(unsafe { SafeOwnedFd::from_raw_fd(fd) })
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct MaskFlags: u64 {
const FAN_ACCESS = 0x00000001;
const FAN_MODIFY = 0x00000002;
const FAN_ATTRIB = 0x00000004;
const FAN_CLOSE_WRITE = 0x00000008;
const FAN_CLOSE_NOWRITE = 0x00000010;
const FAN_OPEN = 0x00000020;
const FAN_MOVED_FROM = 0x00000040;
const FAN_MOVED_TO = 0x00000080;
const FAN_CREATE = 0x00000100;
const FAN_DELETE = 0x00000200;
const FAN_DELETE_SELF = 0x00000400;
const FAN_MOVE_SELF = 0x00000800;
const FAN_OPEN_EXEC = 0x00001000;
const FAN_FS_ERROR = 0x00008000;
const FAN_OPEN_PERM = 0x00010000;
const FAN_ACCESS_PERM = 0x00020000;
const FAN_OPEN_EXEC_PERM = 0x00040000;
const FAN_PRE_ACCESS = 0x00100000;
const FAN_MNT_ATTACH = 0x01000000;
const FAN_MNT_DETACH = 0x02000000;
const FAN_EVENT_ON_CHILD = 0x08000000;
const FAN_RENAME = 0x10000000;
const FAN_ONDIR = 0x40000000;
const FAN_CLOSE = Self::FAN_CLOSE_WRITE.bits() | Self::FAN_CLOSE_NOWRITE.bits();
const FAN_MOVE = Self::FAN_MOVED_FROM.bits() | Self::FAN_MOVED_TO.bits();
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct MarkFlags: libc::c_uint {
const FAN_MARK_ADD = 0x00000001;
const FAN_MARK_REMOVE = 0x00000002;
const FAN_MARK_DONT_FOLLOW = 0x00000004;
const FAN_MARK_ONLYDIR = 0x00000008;
const FAN_MARK_IGNORED_MASK = 0x00000020;
const FAN_MARK_IGNORED_SURV_MODIFY = 0x00000040;
const FAN_MARK_FLUSH = 0x00000080;
const FAN_MARK_EVICTABLE = 0x00000200;
const FAN_MARK_IGNORE = 0x00000400;
const FAN_MARK_MOUNT = 0x00000010;
const FAN_MARK_FILESYSTEM = 0x00000100;
const FAN_MARK_MNTNS = 0x00000110;
const FAN_MARK_IGNORE_SURV = Self::FAN_MARK_IGNORE.bits() | Self::FAN_MARK_IGNORED_SURV_MODIFY.bits();
}
}
#[derive(Debug)]
pub struct Fanotify {
fd: SafeOwnedFd,
}
static SYS_FANOTIFY_MARK: LazyLock<Option<libc::c_long>> = LazyLock::new(|| {
ScmpSyscall::from_name("fanotify_mark")
.map(i32::from)
.map(libc::c_long::from)
.ok()
});
impl Fanotify {
pub fn mark<Fd: AsFd, P: ?Sized + NixPath>(
&self,
flags: MarkFlags,
mask: MaskFlags,
dirfd: Fd,
path: Option<&P>,
) -> Result<(), Errno> {
let sys_fanotify_mark = SYS_FANOTIFY_MARK.ok_or(Errno::ENOSYS)?;
let res = with_opt_nix_path(path, |p| unsafe {
syscall_ll!(
sys_fanotify_mark,
self.fd.as_raw_fd(),
flags.bits(),
@ll(mask.bits()),
dirfd.as_fd().as_raw_fd(),
p
)
})?;
Errno::result(res).map(|_| ())
}
}
impl From<SafeOwnedFd> for Fanotify {
fn from(fd: SafeOwnedFd) -> Self {
Self { fd }
}
}
impl FromRawFd for Fanotify {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Fanotify {
fd: unsafe { SafeOwnedFd::from_raw_fd(fd) },
}
}
}
impl AsFd for Fanotify {
fn as_fd(&'_ self) -> BorrowedFd<'_> {
self.fd.as_fd()
}
}
impl AsRawFd for Fanotify {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
impl From<Fanotify> for SafeOwnedFd {
fn from(value: Fanotify) -> Self {
value.fd
}
}
impl Fanotify {
pub unsafe fn from_owned_fd(fd: SafeOwnedFd) -> Self {
Self { fd }
}
}
pub(crate) fn with_opt_nix_path<P, T, F>(path: Option<&P>, f: F) -> Result<T, Errno>
where
P: ?Sized + NixPath,
F: FnOnce(*const libc::c_char) -> T,
{
match path {
Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
None => Ok(f(std::ptr::null())),
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct FallocateFlags: c_int {
const FALLOC_FL_KEEP_SIZE = 0x01;
const FALLOC_FL_PUNCH_HOLE = 0x02;
const FALLOC_FL_COLLAPSE_RANGE = 0x8;
const FALLOC_FL_ZERO_RANGE = 0x10;
const FALLOC_FL_INSERT_RANGE = 0x20;
const FALLOC_FL_UNSHARE_RANGE = 0x40;
const FALLOC_FL_WRITE_ZEROES = 0x80;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Persona: u32 {
const ADDR_COMPAT_LAYOUT = 0x0200000;
const ADDR_NO_RANDOMIZE = 0x0040000;
const ADDR_LIMIT_32BIT = 0x0800000;
const ADDR_LIMIT_3GB = 0x8000000;
const FDPIC_FUNCPTRS = 0x0080000;
const MMAP_PAGE_ZERO = 0x0100000;
const READ_IMPLIES_EXEC = 0x0400000;
const SHORT_INODE = 0x1000000;
const STICKY_TIMEOUTS = 0x4000000;
const UNAME26 = 0x0020000;
const WHOLE_SECONDS = 0x2000000;
}
}
const fn makedev(major: u64, minor: u64) -> libc::dev_t {
((major & 0xffff_f000) << 32)
| ((major & 0x0000_0fff) << 8)
| ((minor & 0xffff_ff00) << 12)
| (minor & 0x0000_00ff)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct seccomp_notif {
pub(crate) id: u64,
pub(crate) pid: u32,
pub(crate) flags: u32,
pub(crate) data: seccomp_data,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct seccomp_notif_resp {
pub(crate) id: u64,
pub(crate) val: i64,
pub(crate) error: i32,
pub(crate) flags: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct seccomp_notif_addfd {
pub(crate) id: u64,
pub(crate) flags: u32,
pub(crate) srcfd: u32,
pub(crate) newfd: u32,
pub(crate) newfd_flags: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct seccomp_data {
pub(crate) nr: c_int,
pub(crate) arch: u32,
pub(crate) instruction_pointer: u64,
pub(crate) args: [u64; 6],
}
pub fn get_pdeathsig() -> Result<Option<Signal>, Errno> {
let mut sig: c_int = 0;
let res = unsafe { libc::prctl(libc::PR_GET_PDEATHSIG, &mut sig, 0, 0, 0) };
match Errno::result(res) {
Ok(_) => Ok(match sig {
0 => None,
_ => Some(Signal::try_from(sig)?),
}),
Err(e) => Err(e),
}
}
pub fn set_pdeathsig<T: Into<Option<Signal>>>(signal: T) -> Result<(), Errno> {
let sig = match signal.into() {
Some(s) => s as c_int,
None => 0,
};
let res = unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, sig, 0, 0, 0) };
Errno::result(res).map(drop)
}
pub fn set_dumpable(attribute: bool) -> Result<(), Errno> {
prctl_set_bool(libc::PR_SET_DUMPABLE, attribute)
}
pub fn set_child_subreaper(attribute: bool) -> Result<(), Errno> {
prctl_set_bool(libc::PR_SET_CHILD_SUBREAPER, attribute)
}
pub fn get_name() -> Result<CString, Errno> {
let buf = [0u8; 16];
let res = unsafe { libc::prctl(libc::PR_GET_NAME, &buf, 0, 0, 0) };
Errno::result(res).and_then(|_| {
CStr::from_bytes_until_nul(&buf)
.map(CStr::to_owned)
.map_err(|_| Errno::EINVAL)
})
}
pub fn set_name(name: &CStr) -> Result<(), Errno> {
let res = unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr(), 0, 0, 0) };
Errno::result(res).map(drop)
}
pub fn set_no_new_privs() -> Result<(), Errno> {
prctl_set_bool(libc::PR_SET_NO_NEW_PRIVS, true) }
pub fn get_no_new_privs() -> Result<bool, Errno> {
prctl_get_bool(libc::PR_GET_NO_NEW_PRIVS)
}
fn prctl_set_bool(option: c_int, status: bool) -> Result<(), Errno> {
let res = unsafe { libc::prctl(option, libc::c_ulong::from(status), 0, 0, 0) };
Errno::result(res).map(drop)
}
fn prctl_get_bool(option: c_int) -> Result<bool, Errno> {
let res = unsafe { libc::prctl(option, 0, 0, 0, 0) };
Errno::result(res).map(|res| res != 0)
}
pub fn dup3(oldfd: RawFd, newfd: RawFd, flags: c_int) -> Result<SafeOwnedFd, Errno> {
#[expect(clippy::cast_possible_truncation)]
retry_on_eintr(|| {
Errno::result(unsafe { libc::syscall(libc::SYS_dup3, oldfd, newfd, flags) }).map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
})
}
pub fn set_vma_anon_name(
_addr: NonNull<libc::c_void>,
_length: NonZeroUsize,
_name: Option<&CStr>,
) -> Result<(), Errno> {
#[cfg(not(debug_assertions))]
{
return Ok(());
}
#[cfg(debug_assertions)]
{
let nameref = match _name {
Some(n) => n.as_ptr(),
_ => std::ptr::null(),
};
let res = unsafe {
libc::prctl(
libc::PR_SET_VMA,
libc::PR_SET_VMA_ANON_NAME,
_addr.as_ptr(),
_length,
nameref,
)
};
Errno::result(res).map(drop)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SeccompBpfExport {
#[cfg(libseccomp_v2_5)]
Mem,
Fd,
}
#[cfg(libseccomp_v2_5)]
pub const SECCOMP_BPF_EXPORT_DEFAULT: SeccompBpfExport = SeccompBpfExport::Mem;
#[cfg(not(libseccomp_v2_5))]
pub const SECCOMP_BPF_EXPORT_DEFAULT: SeccompBpfExport = SeccompBpfExport::Fd;
#[expect(clippy::arithmetic_side_effects)]
pub fn seccomp_load_listener(ctx: &libseccomp::ScmpFilterContext) -> Result<SafeOwnedFd, Errno> {
let want_nnp = ctx
.get_ctl_nnp()
.map_err(|e| scmp2no(&e).unwrap_or(Errno::EINVAL))?;
let mut buf = seccomp_export_bpf_mem(ctx, SECCOMP_BPF_EXPORT_DEFAULT)?;
let elem = size_of::<libc::sock_filter>();
if buf.is_empty() || buf.len() % elem != 0 {
return Err(Errno::EINVAL);
}
let n_instr = buf.len() / elem;
if n_instr > u16::MAX as usize {
return Err(Errno::EINVAL);
}
if want_nnp {
set_no_new_privs()?;
}
let flags: libc::c_ulong =
libc::SECCOMP_FILTER_FLAG_NEW_LISTENER | libc::SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV;
let prog = libc::sock_fprog {
#[expect(clippy::cast_possible_truncation)]
len: n_instr as u16,
filter: buf.as_mut_ptr().cast(),
};
let ret = unsafe {
libc::syscall(
libc::SYS_seccomp,
libc::c_ulong::from(libc::SECCOMP_SET_MODE_FILTER),
flags,
std::ptr::addr_of!(prog),
)
};
let ret = Errno::result(ret)?;
drop(buf);
#[expect(clippy::cast_possible_truncation)]
let fd = unsafe { SafeOwnedFd::from_raw_fd(ret as RawFd) };
Ok(fd)
}
pub fn seccomp_export_bpf_mem(
ctx: &libseccomp::ScmpFilterContext,
method: SeccompBpfExport,
) -> Result<Vec<u8>, Errno> {
match method {
#[cfg(libseccomp_v2_5)]
SeccompBpfExport::Mem => {
let ctx = ctx.as_ptr();
let mut len: usize = 0;
let rc = unsafe {
libseccomp_sys::seccomp_export_bpf_mem(ctx, std::ptr::null_mut(), &raw mut len)
};
if rc < 0 {
#[expect(clippy::arithmetic_side_effects)]
return Err(Errno::from_raw(-rc));
}
let mut buf: Vec<u8> = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
buf.resize(len, 0);
let mut len = buf.len();
let rc = unsafe {
libseccomp_sys::seccomp_export_bpf_mem(ctx, buf.as_mut_ptr().cast(), &raw mut len)
};
if rc < 0 {
#[expect(clippy::arithmetic_side_effects)]
return Err(Errno::from_raw(-rc));
}
if len != buf.len() {
return Err(Errno::EINVAL);
}
Ok(buf)
}
SeccompBpfExport::Fd => {
let anonfd = safe_memfd_create(c"syd-bpf", MFdFlags::MFD_CLOEXEC)
.or_else(|_| shm_open_anon(OFlag::O_RDWR, Mode::S_IRUSR | Mode::S_IWUSR))?;
ctx.export_bpf(anonfd.as_fd())
.map_err(|e| scmp2no(&e).unwrap_or(Errno::EIO))?;
#[expect(clippy::disallowed_types)]
let mut file = std::fs::File::from(anonfd);
let len: usize = file
.seek(SeekFrom::End(0))
.map_err(|err| err2no(&err))?
.try_into()
.or(Err(Errno::EOVERFLOW))?;
file.seek(SeekFrom::Start(0)).map_err(|err| err2no(&err))?;
let mut buf = Vec::new();
buf.try_reserve(len).or(Err(Errno::ENOMEM))?;
buf.resize(len, 0);
file.read_exact(&mut buf).map_err(|err| err2no(&err))?;
Ok(buf)
}
}
}
#[expect(clippy::disallowed_types)]
type NixTimeSpec = nix::sys::time::TimeSpec;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Expiration {
OneShot(NixTimeSpec),
IntervalDelayed(NixTimeSpec, NixTimeSpec),
Interval(NixTimeSpec),
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct TimerSetTimeFlags: c_int {
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
const TFD_TIMER_CANCEL_ON_SET = libc::TFD_TIMER_CANCEL_ON_SET;
}
}
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;
pub(crate) const MAP_FIXED_NOREPLACE: c_int = 0x100000;
pub(crate) const SHM_EXEC: c_int = 0o100000;
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "sparc",
target_arch = "sparc64",
)))]
pub(crate) const TIOCEXCL: Ioctl = 0x540C;
#[cfg(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6"
))]
pub(crate) const TIOCEXCL: Ioctl = 0x740d;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const TIOCEXCL: Ioctl = 0x2000740d;
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "sparc",
target_arch = "sparc64",
)))]
pub(crate) const TIOCNXCL: Ioctl = 0x540D;
#[cfg(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6"
))]
pub(crate) const TIOCNXCL: Ioctl = 0x740e;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const TIOCNXCL: Ioctl = 0x2000740e;
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
)))]
pub(crate) const TIOCGEXCL: Ioctl = 0x80045440;
#[cfg(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
))]
pub(crate) const TIOCGEXCL: Ioctl = 0x40045440;
#[cfg(not(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "sparc64",
)))]
pub(crate) const TIOCGPTPEER: Ioctl = 0x5441;
#[cfg(any(
target_arch = "mips",
target_arch = "mips64",
target_arch = "mips32r6",
target_arch = "mips64r6"
))]
pub(crate) const TIOCGPTPEER: Ioctl = 0x20005441;
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
pub(crate) const TIOCGPTPEER: Ioctl = 0x20005441;
#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
pub(crate) const TIOCGPTPEER: Ioctl = 0x20007489;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct PidfdInfo {
mask: u64,
cgroupid: u64,
pid: u32,
tgid: u32,
ppid: u32,
ruid: u32,
rgid: u32,
euid: u32,
egid: u32,
suid: u32,
sgid: u32,
fsuid: u32,
fsgid: u32,
exit_code: i32,
coredump_mask: u32,
coredump_signal: u32,
supported_mask: u64,
}
pub(crate) const PIDFD_GET_INFO: Ioctl = 0xc050ff0b;
pub(crate) const PIDFD_INFO_PID: u64 = 1 << 0;
pub fn pidfd_get_tgid<Fd: AsFd>(pidfd: Fd) -> Result<Pid, Errno> {
let mut info = PidfdInfo {
mask: PIDFD_INFO_PID,
..Default::default()
};
Errno::result(unsafe {
libc::syscall(
libc::SYS_ioctl,
pidfd.as_fd().as_raw_fd(),
PIDFD_GET_INFO,
&mut info,
SYSCOOKIE_POOL.get(CookieIdx::PidfdGetInfoArg3),
SYSCOOKIE_POOL.get(CookieIdx::PidfdGetInfoArg4),
SYSCOOKIE_POOL.get(CookieIdx::PidfdGetInfoArg5),
)
})?;
if info.mask & PIDFD_INFO_PID != 0 {
info.tgid
.try_into()
.or(Err(Errno::EOVERFLOW))
.map(Pid::from_raw)
} else {
Err(Errno::ENODATA)
}
}
pub fn pread64<Fd: AsFd>(fd: Fd, buf: &mut [u8], offset: libc::off64_t) -> Result<usize, Errno> {
let res = unsafe {
libc::pread64(
fd.as_fd().as_raw_fd(),
buf.as_mut_ptr().cast(),
buf.len() as libc::size_t,
offset,
)
};
#[expect(clippy::cast_sign_loss)]
Errno::result(res).map(|r| r as usize)
}
pub fn pwrite64<Fd: AsFd>(fd: Fd, buf: &[u8], offset: libc::off64_t) -> Result<usize, Errno> {
let res = unsafe {
libc::pwrite64(
fd.as_fd().as_raw_fd(),
buf.as_ptr().cast(),
buf.len() as libc::size_t,
offset,
)
};
#[expect(clippy::cast_sign_loss)]
Errno::result(res).map(|r| r as usize)
}
pub fn setgroups_none() -> Result<(), Errno> {
Errno::result(unsafe { libc::setgroups(0, std::ptr::null()) }).map(drop)
}
#[cfg(test)]
mod tests {
use std::{
io::{IoSlice, IoSliceMut},
mem::size_of,
net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket},
};
use libc::sock_filter;
#[cfg(libseccomp_v2_5)]
use libc::EPERM;
use libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall};
use nix::{
fcntl::OFlag,
sys::socket::{SockFlag, SockaddrIn, SockaddrStorage, UnixAddr},
unistd::{getgid, getpid, getuid, pipe2, read, write},
};
use super::*;
use crate::cookie::safe_socketpair;
#[test]
fn test_msg_flags_1() {
let f = MsgFlags::empty();
assert_eq!(f.bits(), 0);
}
#[test]
fn test_msg_flags_2() {
assert_eq!(MsgFlags::MSG_EOR.bits(), 0x80);
}
#[test]
fn test_msg_flags_3() {
let f = MsgFlags::MSG_OOB | MsgFlags::MSG_PEEK;
let bits = f.bits();
let f2 = MsgFlags::from_bits(bits).unwrap();
assert_eq!(f, f2);
}
#[test]
fn test_msg_flags_4() {
let f = MsgFlags::MSG_DONTWAIT;
assert_eq!(f.bits(), 0x40);
}
#[test]
fn test_rename_flags_1() {
assert_eq!(RenameFlags::RENAME_NOREPLACE.bits(), 1);
}
#[test]
fn test_rename_flags_2() {
assert_eq!(RenameFlags::RENAME_EXCHANGE.bits(), 2);
}
#[test]
fn test_rename_flags_3() {
let f = RenameFlags::RENAME_NOREPLACE | RenameFlags::RENAME_EXCHANGE;
assert_eq!(f.bits(), 3);
}
#[test]
fn test_file_statx_1() {
let stx: FileStatx = unsafe { std::mem::zeroed() };
assert_eq!(stx.stx_mask, 0);
assert_eq!(stx.stx_nlink, 0);
assert_eq!(stx.stx_uid, 0);
assert_eq!(stx.stx_gid, 0);
}
#[test]
fn test_file_statx_timestamp_1() {
let ts: FileStatxTimestamp = unsafe { std::mem::zeroed() };
assert_eq!(ts.tv_sec, 0);
assert_eq!(ts.tv_nsec, 0);
}
#[test]
fn test_sock_type_1() {
let st = SockType::try_from(libc::SOCK_STREAM).unwrap();
assert!(matches!(st, SockType::Stream));
}
#[test]
fn test_sock_type_2() {
let st = SockType::try_from(libc::SOCK_DGRAM).unwrap();
assert!(matches!(st, SockType::Datagram));
}
#[test]
fn test_sock_type_3() {
let result = SockType::try_from(7);
assert!(matches!(result, Err(Errno::ESOCKTNOSUPPORT)));
}
#[test]
fn test_sock_type_4() {
assert!(SockType::Raw.is_unsafe());
assert!(SockType::Packet.is_unsafe());
assert!(!SockType::Stream.is_unsafe());
}
#[test]
fn test_lsm_id_1() {
let id = LsmId::from(0u64);
assert!(matches!(id, LsmId::Undef));
}
#[test]
fn test_lsm_id_2() {
let id = LsmId::from(104u64);
assert!(matches!(id, LsmId::AppArmor));
assert_eq!(format!("{id}"), "apparmor");
}
#[test]
fn test_lsm_id_3() {
let id = LsmId::from(9999u64);
assert!(matches!(id, LsmId::Unknown(9999)));
assert_eq!(format!("{id}"), "unknown(9999)");
}
#[test]
fn test_fs_type_1() {
let ft = FsType::from(0x9123683E as fs_type_t);
let val: fs_type_t = ft.into();
assert_eq!(val, 0x9123683E as fs_type_t);
}
#[test]
fn test_fs_type_2() {
let ft = FsType::from(&(libc::PROC_SUPER_MAGIC as fs_type_t));
let display = format!("{ft}");
assert_eq!(display, "proc");
}
#[test]
fn test_fallocate_flags_1() {
let f = FallocateFlags::empty();
assert_eq!(f.bits(), 0);
}
#[test]
fn test_fallocate_flags_2() {
assert_eq!(FallocateFlags::FALLOC_FL_KEEP_SIZE.bits(), 0x01);
assert_eq!(FallocateFlags::FALLOC_FL_PUNCH_HOLE.bits(), 0x02);
}
#[test]
fn test_cmsg_align_1() {
assert_eq!(cmsg_align(0), 0);
}
#[test]
fn test_cmsg_align_2() {
assert_eq!(cmsg_align(1), size_of::<usize>());
}
#[test]
fn test_cmsg_align_3() {
assert_eq!(cmsg_align(size_of::<usize>()), size_of::<usize>());
}
#[test]
fn test_cmsg_align_4() {
assert_eq!(cmsg_align(size_of::<usize>() + 1), 2 * size_of::<usize>());
}
#[test]
fn test_cmsg_align_5() {
assert_eq!(cmsg_align(2 * size_of::<usize>()), 2 * size_of::<usize>());
}
#[test]
fn test_cmsg_align_32_1() {
assert_eq!(cmsg_align_32(0), 0);
}
#[test]
fn test_cmsg_align_32_2() {
assert_eq!(cmsg_align_32(1), 4);
}
#[test]
fn test_cmsg_align_32_3() {
assert_eq!(cmsg_align_32(4), 4);
}
#[test]
fn test_cmsg_align_32_4() {
assert_eq!(cmsg_align_32(5), 8);
}
#[test]
fn test_cmsg_align_32_5() {
assert_eq!(cmsg_align_32(8), 8);
}
#[test]
fn test_cmsg_space_32_1() {
let space = cmsg_space_32(0);
assert!(space >= size_of::<cmsghdr32>());
assert_eq!(space % 4, 0);
}
#[test]
fn test_cmsg_space_32_2() {
let space = cmsg_space_32(4);
assert!(space > size_of::<cmsghdr32>());
assert_eq!(space % 4, 0);
}
#[test]
fn test_cmsg_space_32_3() {
let fds = [unsafe { BorrowedFd::borrow_raw(1) }];
let cm = Cmsg::ScmRights(&fds);
assert_eq!(cm.cmsg_space_32(), cmsg_space_32(size_of::<RawFd>() as u32));
}
#[test]
fn test_cmsg_len_32_1() {
let len = cmsg_len_32(0);
assert_eq!(len, cmsg_align_32(size_of::<cmsghdr32>()));
}
#[test]
fn test_cmsg_len_32_2() {
let len = cmsg_len_32(4);
assert_eq!(len, cmsg_align_32(size_of::<cmsghdr32>()) + 4);
}
#[test]
fn test_cmsg_len_32_3() {
let fds = [unsafe { BorrowedFd::borrow_raw(1) }];
let cm = Cmsg::ScmRights(&fds);
assert_eq!(cm.cmsg_len_32(), cmsg_len_32(size_of::<RawFd>() as u32));
}
#[test]
fn test_cmsg_len_32_4() {
let tos: u8 = 0;
let cm = Cmsg::Ipv4Tos(&tos);
assert_eq!(cm.cmsg_len_32(), cmsg_len_32(size_of::<u8>() as u32));
}
#[test]
fn test_makedev_1() {
assert_eq!(makedev(0, 0), 0);
}
#[test]
fn test_makedev_2() {
let dev = makedev(1, 3);
assert_ne!(dev, 0);
}
#[test]
fn test_makedev_3() {
let dev = makedev(5, 0);
assert_ne!(dev, makedev(0, 5));
}
#[test]
fn test_makedev_4() {
let dev = makedev(8, 0);
assert_ne!(dev, 0);
let dev2 = makedev(8, 1);
assert_ne!(dev, dev2);
}
#[test]
fn test_makedev_5() {
let dev = makedev(0, 0);
assert_eq!(dev, 0);
}
#[test]
fn test_makedev_6() {
let dev = makedev(1, 3);
assert_ne!(dev, 0);
}
#[test]
fn test_exited_1() {
assert!(exited(0));
}
#[test]
fn test_exited_2() {
assert!(exited(0x0100));
}
#[test]
fn test_exited_3() {
assert!(!exited(libc::SIGKILL));
}
#[test]
fn test_exit_status_1() {
assert_eq!(exit_status(0), 0);
}
#[test]
fn test_exit_status_2() {
let status = 1 << 8;
assert_eq!(exit_status(status), 1);
}
#[test]
fn test_exit_status_3() {
assert_eq!(exit_status(42 << 8), 42);
}
#[test]
fn test_signaled_1() {
assert!(!signaled(0));
}
#[test]
fn test_signaled_2() {
let status = libc::SIGKILL;
assert!(signaled(status));
}
#[test]
fn test_signaled_3() {
assert!(signaled(libc::SIGSEGV));
}
#[test]
fn test_signaled_4() {
assert!(!signaled(0x0000));
}
#[test]
fn test_term_signal_1() {
let status = libc::SIGKILL;
assert_eq!(term_signal(status), libc::SIGKILL);
}
#[test]
fn test_term_signal_2() {
assert_eq!(term_signal(libc::SIGSEGV), libc::SIGSEGV);
}
#[test]
fn test_stopped_1() {
assert!(!stopped(0));
}
#[test]
fn test_stopped_2() {
let status = (libc::SIGSTOP << 8) | 0x7f;
assert!(stopped(status));
}
#[test]
fn test_stop_signal_1() {
let status = (libc::SIGSTOP << 8) | 0x7f;
assert_eq!(stop_signal(status), libc::SIGSTOP);
}
#[test]
fn test_syscall_stop_1() {
let status = ((libc::SIGTRAP | 0x80) << 8) | 0x7f;
assert!(syscall_stop(status));
}
#[test]
fn test_syscall_stop_2() {
assert!(!syscall_stop(0));
}
#[test]
fn test_syscall_stop_3() {
let status = (libc::SIGSTOP << 8) | 0x7f;
assert!(!syscall_stop(status));
}
#[test]
fn test_stop_additional_1() {
assert_eq!(stop_additional(0), 0);
}
#[test]
fn test_stop_additional_2() {
let status = 0x0005_0000;
assert_eq!(stop_additional(status), 5);
}
#[test]
fn test_continued_1() {
assert!(!continued(0));
}
#[test]
fn test_continued_2() {
assert!(continued(0xffff));
}
#[test]
fn test_dumped_core_1() {
assert!(!dumped_core(0));
}
#[test]
fn test_dumped_core_2() {
let status = libc::SIGSEGV | 0x80;
assert!(dumped_core(status));
}
#[test]
fn test_dumped_core_3() {
assert!(!dumped_core(libc::SIGKILL));
}
#[test]
fn test_waitstatus_from_raw_1() {
let status = WaitStatus::from_raw(Pid::from_raw(1), 0);
assert!(matches!(status, WaitStatus::Exited(_, 0)));
}
#[test]
fn test_waitstatus_from_raw_2() {
let status = WaitStatus::from_raw(Pid::from_raw(1), libc::SIGKILL);
assert!(matches!(status, WaitStatus::Signaled(_, _, _)));
}
#[test]
fn test_waitstatus_from_raw_3() {
let raw = (libc::SIGSTOP << 8) | 0x7f;
let status = WaitStatus::from_raw(Pid::from_raw(1), raw);
assert!(matches!(status, WaitStatus::Stopped(_, _)));
}
#[test]
fn test_waitstatus_from_raw_4() {
let status = WaitStatus::from_raw(Pid::from_raw(1), 0xffff);
assert!(matches!(status, WaitStatus::Continued(_)));
}
#[test]
fn test_cmsghdr32_1() {
let hdr32 = cmsghdr32 {
cmsg_len: 16,
cmsg_level: libc::SOL_SOCKET,
cmsg_type: libc::SCM_RIGHTS,
};
let hdr: cmsghdr = hdr32.into();
assert_eq!(hdr.cmsg_len, 16);
assert_eq!(hdr.cmsg_level, libc::SOL_SOCKET);
assert_eq!(hdr.cmsg_type, libc::SCM_RIGHTS);
}
#[test]
fn test_cmsghdr32_2() {
let hdr32 = cmsghdr32 {
cmsg_len: 0,
cmsg_level: 0,
cmsg_type: 0,
};
let hdr: cmsghdr = hdr32.into();
assert_eq!(hdr.cmsg_len, 0);
assert_eq!(hdr.cmsg_level, 0);
assert_eq!(hdr.cmsg_type, 0);
}
#[test]
fn test_iovec32_1() {
let iv32 = iovec32 {
iov_base: 0x1000,
iov_len: 256,
};
let iv: libc::iovec = iv32.into();
assert_eq!(iv.iov_base as usize, 0x1000);
assert_eq!(iv.iov_len, 256);
}
#[test]
fn test_iovec32_2() {
let iv32 = iovec32 {
iov_base: 0,
iov_len: 0,
};
let iv: libc::iovec = iv32.into();
assert!(iv.iov_base.is_null());
assert_eq!(iv.iov_len, 0);
}
#[test]
fn test_iovec32_3() {
let iv32 = iovec32 {
iov_base: 0xFFFF_FFFF,
iov_len: 0xFFFF_FFFF,
};
let iv: libc::iovec = iv32.into();
assert_eq!(iv.iov_base as usize, 0xFFFF_FFFF);
assert_eq!(iv.iov_len, 0xFFFF_FFFF);
}
#[test]
fn test_msghdr_1() {
let m32 = msghdr32 {
msg_name: 0,
msg_namelen: 0,
msg_iov: 0,
msg_iovlen: 0,
msg_control: 0,
msg_controllen: 0,
msg_flags: 0,
};
let m: msghdr = m32.into();
assert!(m.msg_name.is_null());
assert_eq!(m.msg_namelen, 0);
assert!(m.msg_iov.is_null());
assert_eq!(m.msg_iovlen, 0);
assert!(m.msg_control.is_null());
assert_eq!(m.msg_controllen, 0);
assert_eq!(m.msg_flags, 0);
}
#[test]
fn test_msghdr_2() {
let m = msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 16,
msg_iov: std::ptr::null_mut(),
msg_iovlen: 2,
msg_control: std::ptr::null_mut(),
msg_controllen: 64,
msg_flags: 0x100,
};
let m32: msghdr32 = m.try_into().unwrap();
assert_eq!(m32.msg_name, 0);
assert_eq!(m32.msg_namelen, 16);
assert_eq!(m32.msg_iov, 0);
assert_eq!(m32.msg_iovlen, 2);
assert_eq!(m32.msg_control, 0);
assert_eq!(m32.msg_controllen, 64);
assert_eq!(m32.msg_flags, 0x100);
}
#[test]
fn test_msghdr_3() {
let hdr = MsgHdr::default().into_inner();
assert!(hdr.msg_name.is_null());
assert_eq!(hdr.msg_namelen, 0);
assert!(hdr.msg_iov.is_null());
assert_eq!(hdr.msg_iovlen, 0);
assert!(hdr.msg_control.is_null());
assert_eq!(hdr.msg_controllen, 0);
assert_eq!(hdr.msg_flags, 0);
}
#[test]
fn test_msghdr_4() {
let hdr = MsgHdr::default();
assert!(hdr.cmsg_bytes().is_empty());
}
#[test]
fn test_msghdr_5() {
let mut buf = [1u8, 2, 3, 4];
let mut hdr = MsgHdr::default();
hdr.set_control(&mut buf);
let bytes = hdr.cmsg_bytes();
assert_eq!(bytes.len(), 4);
assert_eq!(bytes, &[1, 2, 3, 4]);
}
#[test]
fn test_msghdr_6() {
let m32 = msghdr32 {
msg_name: 0x1000,
msg_namelen: 16,
msg_iov: 0x2000,
msg_iovlen: 3,
msg_control: 0x3000,
msg_controllen: 128,
msg_flags: 0x40,
};
let m: msghdr = m32.into();
assert_eq!(m.msg_name as usize, 0x1000);
assert_eq!(m.msg_namelen, 16);
assert_eq!(m.msg_iov as usize, 0x2000);
assert_eq!(m.msg_iovlen, 3);
assert_eq!(m.msg_control as usize, 0x3000);
assert_eq!(m.msg_controllen, 128);
assert_eq!(m.msg_flags, 0x40);
}
#[test]
fn test_msghdr_7() {
let m = msghdr {
msg_name: 0xABCD_0000usize as *mut libc::c_void,
msg_namelen: 32,
msg_iov: 0xDEAD_0000usize as *mut libc::iovec,
msg_iovlen: 5,
msg_control: 0xBEEF_0000usize as *mut libc::c_void,
msg_controllen: 256,
msg_flags: 0xFF,
};
let m32: msghdr32 = m.try_into().unwrap();
assert_eq!(m32.msg_name, 0xABCD_0000u32);
assert_eq!(m32.msg_namelen, 32);
assert_eq!(m32.msg_iov, 0xDEAD_0000u32);
assert_eq!(m32.msg_iovlen, 5);
assert_eq!(m32.msg_control, 0xBEEF_0000u32);
assert_eq!(m32.msg_controllen, 256);
assert_eq!(m32.msg_flags, 0xFF);
}
#[test]
fn test_mmsghdr_1() {
let mmhdr = MmsgHdr::default();
let bytes = mmhdr.to_msg_bytes(false).unwrap();
assert_eq!(bytes.len(), size_of::<mmsghdr>());
}
#[test]
fn test_mmsghdr_2() {
let mmhdr = MmsgHdr::default();
let bytes = mmhdr.to_msg_bytes(true).unwrap();
assert_eq!(bytes.len(), size_of::<mmsghdr32>());
}
#[test]
fn test_mmsghdr_3() {
let mmhdr = MmsgHdr::default();
assert!(mmhdr.cmsg_bytes().is_empty());
}
#[test]
fn test_mmsghdr_4() {
let m32 = mmsghdr32 {
msg_hdr: msghdr32 {
msg_name: 0x1000,
msg_namelen: 8,
msg_iov: 0x2000,
msg_iovlen: 1,
msg_control: 0,
msg_controllen: 0,
msg_flags: 0,
},
msg_len: 42,
};
let m: mmsghdr = m32.into();
assert_eq!(m.msg_hdr.msg_name as usize, 0x1000);
assert_eq!(m.msg_hdr.msg_namelen, 8);
assert_eq!(m.msg_len, 42);
}
#[test]
fn test_mmsghdr_5() {
let m = mmsghdr {
msg_hdr: msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: std::ptr::null_mut(),
msg_iovlen: 0,
msg_control: std::ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
},
msg_len: 99,
};
let m32: mmsghdr32 = m.try_into().unwrap();
assert_eq!(m32.msg_hdr.msg_name, 0);
assert_eq!(m32.msg_len, 99);
}
#[test]
fn test_open_how_1() {
let how = OpenHow::new();
assert_eq!(how.flags, 0);
assert_eq!(how.mode, 0);
assert_eq!(how.resolve, 0);
}
#[test]
fn test_open_how_2() {
let how = OpenHow::new().flags(OFlag::O_RDONLY | OFlag::O_CLOEXEC);
let flags = OFlag::from_bits_retain(how.flags as i32);
assert!(flags.contains(OFlag::O_CLOEXEC));
}
#[test]
fn test_open_how_3() {
let how = OpenHow::new().mode(Mode::from_bits_truncate(0o755));
assert_eq!(how.mode, 0o755);
}
#[test]
fn test_open_how_4() {
let how = OpenHow::new().resolve(ResolveFlag::RESOLVE_BENEATH);
let resolve = ResolveFlag::from_bits_retain(how.resolve);
assert!(resolve.contains(ResolveFlag::RESOLVE_BENEATH));
}
#[test]
fn test_open_how_5() {
let how = OpenHow::new()
.flags(OFlag::O_WRONLY | OFlag::O_CREAT)
.mode(Mode::from_bits_truncate(0o600))
.resolve(ResolveFlag::RESOLVE_NO_SYMLINKS);
assert_ne!(how.flags, 0);
assert_eq!(how.mode, 0o600);
let resolve = ResolveFlag::from_bits_retain(how.resolve);
assert!(resolve.contains(ResolveFlag::RESOLVE_NO_SYMLINKS));
}
#[test]
fn test_timeval_1() {
let tv = Timeval64 {
tv_sec: i64::from(i32::MAX) + 1,
tv_usec: 0,
};
let result = Timeval32::try_from(tv);
assert_eq!(result, Err(Errno::EOVERFLOW));
}
#[test]
fn test_timeval_2() {
let tv = Timeval64 {
tv_sec: 0,
tv_usec: i64::from(i32::MAX) + 1,
};
let result = Timeval32::try_from(tv);
assert_eq!(result, Err(Errno::EOVERFLOW));
}
#[test]
fn test_timeval_3() {
let tv = Timeval64 {
tv_sec: 100,
tv_usec: 500,
};
let result = Timeval32::try_from(tv).unwrap();
assert_eq!(result.tv_sec, 100);
assert_eq!(result.tv_usec, 500);
}
#[test]
fn test_timespec_1() {
let ts64 = TimeSpec64 {
tv_sec: 1_000_000,
tv_nsec: 500,
};
assert_eq!(ts64.tv_sec(), 1_000_000);
assert_eq!(ts64.tv_nsec(), 500);
}
#[test]
fn test_timespec_2() {
let tv64 = Timeval64 {
tv_sec: 100,
tv_usec: 500,
};
let ts = TimeSpec64::try_from(tv64).unwrap();
assert_eq!(ts.tv_sec(), 100);
assert_eq!(ts.tv_nsec(), 500_000);
}
#[test]
fn test_timespec_3() {
let tv32 = Timeval32 {
tv_sec: 42,
tv_usec: 1000,
};
let ts = TimeSpec64::try_from(tv32).unwrap();
assert_eq!(ts.tv_sec(), 42);
assert_eq!(ts.tv_nsec(), 1_000_000);
}
#[test]
fn test_timespec_4() {
let ts = TimeSpec64 {
tv_sec: i64::from(i32::MAX) + 1,
tv_nsec: 0,
};
let result = TimeSpec32::try_from(ts);
assert_eq!(result, Err(Errno::EOVERFLOW));
}
#[test]
fn test_timespec_5() {
let ts = TimeSpec64 {
tv_sec: 0,
tv_nsec: i64::from(i32::MAX) + 1,
};
let result = TimeSpec32::try_from(ts);
assert_eq!(result, Err(Errno::EOVERFLOW));
}
#[test]
fn test_timespec_6() {
let ts = TimeSpec64 {
tv_sec: 42,
tv_nsec: 999,
};
let result = TimeSpec32::try_from(ts).unwrap();
assert_eq!(result.tv_sec, 42);
assert_eq!(result.tv_nsec, 999);
}
#[test]
fn test_stat_1() {
let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = 100;
stat.st_nlink = 1;
stat.st_size = 4096;
let result = stat32::try_from(stat);
assert!(result.is_ok());
}
#[test]
fn test_stat_2() {
let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = 1;
stat.st_nlink = 1;
stat.st_size = 0x8000_0000;
let result = stat32::try_from(stat);
assert!(result.is_err());
}
#[test]
fn test_stat32_1() {
let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = u64::from(u32::MAX) + 1;
let result = stat32::try_from(stat);
assert!(result.is_err());
assert!(matches!(result, Err(Errno::EOVERFLOW)));
}
#[test]
fn test_stat32_2() {
let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = 1;
stat.st_nlink = u64::from(u16::MAX) + 1;
let result = stat32::try_from(stat);
assert!(result.is_err());
assert!(matches!(result, Err(Errno::EOVERFLOW)));
}
#[test]
fn test_stat32_3() {
let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = 1;
stat.st_nlink = 1;
stat.st_size = 0x8000_0000;
let result = stat32::try_from(stat);
assert!(result.is_err());
assert!(matches!(result, Err(Errno::EOVERFLOW)));
}
#[test]
fn test_stat32_4() {
let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
stat.st_ino = 1;
stat.st_nlink = 1;
stat.st_size = 0x7fff_ffff;
let result = stat32::try_from(stat);
assert!(result.is_ok());
}
#[test]
fn test_statfs_1() {
let mut inner: libc::statfs64 = unsafe { std::mem::zeroed() };
inner.f_ffree = 0xffff_ffff_ffff_ffff;
inner.f_files = 100;
let st = Statfs(inner);
let result = statfs32::try_from(st);
assert!(result.is_ok());
}
#[test]
fn test_statfs_2() {
let mut inner: libc::statfs64 = unsafe { std::mem::zeroed() };
inner.f_files = 0x2_0000_0000;
inner.f_ffree = 0x3_0000_0000;
let st = Statfs(inner);
let result = statfs32::try_from(st);
assert!(result.is_err());
}
#[test]
fn test_statfs32_1() {
let mut inner: libc::statfs64 = unsafe { std::mem::zeroed() };
inner.f_blocks = 0x1_0000_0000;
let st = Statfs(inner);
let result = statfs32::try_from(st);
assert!(result.is_err());
assert!(matches!(result, Err(Errno::EOVERFLOW)));
}
#[test]
fn test_statfs32_2() {
let mut inner: libc::statfs64 = unsafe { std::mem::zeroed() };
inner.f_files = 0xffff_ffff_ffff_ffff;
inner.f_ffree = 0xffff_ffff_ffff_ffff;
let st = Statfs(inner);
let result = statfs32::try_from(st);
assert!(result.is_ok());
}
#[test]
fn test_statfs32_3() {
let mut inner: libc::statfs64 = unsafe { std::mem::zeroed() };
inner.f_files = 0x1_0000_0000;
let st = Statfs(inner);
let result = statfs32::try_from(st);
assert!(result.is_err());
assert!(matches!(result, Err(Errno::EOVERFLOW)));
}
#[test]
fn test_statfs32_4() {
let mut inner: libc::statfs64 = unsafe { std::mem::zeroed() };
inner.f_ffree = 0x1_0000_0000;
let st = Statfs(inner);
let result = statfs32::try_from(st);
assert!(result.is_err());
assert!(matches!(result, Err(Errno::EOVERFLOW)));
}
#[test]
fn test_cmsg_len_1() {
let fds: &[BorrowedFd<'_>] = &[];
let cm = Cmsg::ScmRights(fds);
assert_eq!(cm.len(), 0);
}
#[test]
fn test_cmsg_len_2() {
let fds = [
unsafe { BorrowedFd::borrow_raw(1) },
unsafe { BorrowedFd::borrow_raw(2) },
unsafe { BorrowedFd::borrow_raw(3) },
];
let cm = Cmsg::ScmRights(&fds);
assert_eq!(cm.len(), 3 * size_of::<RawFd>());
}
#[test]
fn test_cmsg_len_3() {
let cred = UnixCredentials::from(libc::ucred {
pid: 1,
uid: 0,
gid: 0,
});
let cm = Cmsg::ScmCredentials(&cred);
assert_eq!(cm.len(), size_of::<libc::ucred>());
}
#[test]
fn test_cmsg_len_4() {
let iv: &[u8] = &[1, 2, 3, 4];
let cm = Cmsg::AlgSetIv(iv);
assert_eq!(cm.len(), size_of::<u32>() + 4);
}
#[test]
fn test_cmsg_len_5() {
let op: c_int = 0;
let cm = Cmsg::AlgSetOp(&op);
assert_eq!(cm.len(), size_of::<c_int>());
}
#[test]
fn test_cmsg_len_6() {
let data = vec![0u8; 10];
let raw = RawCmsg {
level: 0,
cmsg_type: 0,
data: &data,
};
let cm = Cmsg::Unknown(raw);
assert_eq!(cm.len(), 10);
}
#[test]
fn test_cmsg_len_7() {
let tos: u8 = 0x10;
let cm = Cmsg::Ipv4Tos(&tos);
assert_eq!(cm.len(), size_of::<u8>());
}
#[test]
fn test_cmsg_len_8() {
let seg: u16 = 1400;
let cm = Cmsg::UdpGsoSegments(&seg);
assert_eq!(cm.len(), size_of::<u16>());
}
#[test]
fn test_cmsg_space_1() {
let fds = [unsafe { BorrowedFd::borrow_raw(1) }];
let cm = Cmsg::ScmRights(&fds);
let expected = unsafe { libc::CMSG_SPACE(size_of::<RawFd>() as libc::c_uint) } as usize;
assert_eq!(cm.cmsg_space(), expected);
}
#[test]
fn test_cmsg_space_2() {
let cred = UnixCredentials::from(libc::ucred {
pid: 1,
uid: 0,
gid: 0,
});
let cm = Cmsg::ScmCredentials(&cred);
let expected =
unsafe { libc::CMSG_SPACE(size_of::<libc::ucred>() as libc::c_uint) } as usize;
assert_eq!(cm.cmsg_space(), expected);
}
#[test]
fn test_cmsg_space_3() {
let tos: u8 = 0;
let cm = Cmsg::Ipv4Tos(&tos);
let expected = unsafe { libc::CMSG_SPACE(size_of::<u8>() as libc::c_uint) } as usize;
assert_eq!(cm.cmsg_space(), expected);
}
#[test]
fn test_cmsg_space_4() {
assert_eq!(u8::cmsg_space(), unsafe { libc::CMSG_SPACE(1) } as usize);
}
#[test]
fn test_cmsg_space_5() {
assert_eq!(RawFd::cmsg_space(), unsafe { libc::CMSG_SPACE(4) } as usize);
}
#[test]
fn test_cmsg_space_6() {
assert_eq!(libc::ucred::cmsg_space(), unsafe {
libc::CMSG_SPACE(size_of::<libc::ucred>() as libc::c_uint)
} as usize);
}
#[test]
fn test_cmsg_level_1() {
let fds: &[BorrowedFd<'_>] = &[];
assert_eq!(Cmsg::ScmRights(fds).cmsg_level(), libc::SOL_SOCKET);
}
#[test]
fn test_cmsg_level_2() {
let cred = UnixCredentials::from(libc::ucred {
pid: 1,
uid: 0,
gid: 0,
});
assert_eq!(Cmsg::ScmCredentials(&cred).cmsg_level(), libc::SOL_SOCKET);
}
#[test]
fn test_cmsg_level_3() {
let tos: u8 = 0;
assert_eq!(Cmsg::Ipv4Tos(&tos).cmsg_level(), libc::IPPROTO_IP);
}
#[test]
fn test_cmsg_level_4() {
let ttl: c_int = 64;
assert_eq!(Cmsg::Ipv4Ttl(&ttl).cmsg_level(), libc::IPPROTO_IP);
}
#[test]
fn test_cmsg_level_5() {
let tc: i32 = 0;
assert_eq!(Cmsg::Ipv6TClass(&tc).cmsg_level(), libc::IPPROTO_IPV6);
}
#[test]
fn test_cmsg_level_6() {
let iv: &[u8] = &[];
assert_eq!(Cmsg::AlgSetIv(iv).cmsg_level(), libc::SOL_ALG);
}
#[test]
fn test_cmsg_level_7() {
let op: c_int = 0;
assert_eq!(Cmsg::AlgSetOp(&op).cmsg_level(), libc::SOL_ALG);
}
#[test]
fn test_cmsg_level_8() {
let raw = RawCmsg {
level: 42,
cmsg_type: 99,
data: &[],
};
assert_eq!(Cmsg::Unknown(raw).cmsg_level(), 42);
}
#[test]
fn test_cmsg_type_1() {
let fds: &[BorrowedFd<'_>] = &[];
assert_eq!(Cmsg::ScmRights(fds).cmsg_type(), libc::SCM_RIGHTS);
}
#[test]
fn test_cmsg_type_2() {
let cred = UnixCredentials::from(libc::ucred {
pid: 1,
uid: 0,
gid: 0,
});
assert_eq!(
Cmsg::ScmCredentials(&cred).cmsg_type(),
libc::SCM_CREDENTIALS
);
}
#[test]
fn test_cmsg_type_3() {
let iv: &[u8] = &[];
assert_eq!(Cmsg::AlgSetIv(iv).cmsg_type(), libc::ALG_SET_IV);
}
#[test]
fn test_cmsg_type_4() {
let tos: u8 = 0;
assert_eq!(Cmsg::Ipv4Tos(&tos).cmsg_type(), libc::IP_TOS);
}
#[test]
fn test_cmsg_type_5() {
let raw = RawCmsg {
level: 1,
cmsg_type: 99,
data: &[],
};
assert_eq!(Cmsg::Unknown(raw).cmsg_type(), 99);
}
#[test]
fn test_try_from_bytes_1() {
let val: u32 = try_from_bytes(&[1, 0, 0, 0]).unwrap();
if cfg!(target_endian = "little") {
assert_eq!(val, 1);
} else {
assert_eq!(val, 1u32 << 24);
}
}
#[test]
fn test_try_from_bytes_2() {
let val: i32 = try_from_bytes(&[0xff, 0xff, 0xff, 0xff]).unwrap();
assert_eq!(val, -1);
}
#[test]
fn test_try_from_bytes_3() {
assert!(try_from_bytes::<u32>(&[1, 2]).is_err());
}
#[test]
fn test_try_from_bytes_4() {
assert!(try_from_bytes::<u32>(&[]).is_err());
}
#[test]
fn test_to_byte_array_1() {
assert_eq!(42u32.to_byte_array::<4>(), 42u32.to_ne_bytes());
}
#[test]
fn test_to_byte_array_2() {
assert_eq!((-1i32).to_byte_array::<4>(), (-1i32).to_ne_bytes());
}
#[test]
fn test_raw_cmsg_1() {
let data = [1u8, 2, 3];
let raw = RawCmsg {
level: libc::SOL_SOCKET,
cmsg_type: libc::SCM_RIGHTS,
data: &data,
};
assert_eq!(raw.level, libc::SOL_SOCKET);
assert_eq!(raw.cmsg_type, libc::SCM_RIGHTS);
assert_eq!(raw.data, &[1, 2, 3]);
}
#[test]
fn test_raw_cmsg_2() {
let raw = RawCmsg {
level: 0,
cmsg_type: 0,
data: &[],
};
assert_eq!(raw.data.len(), 0);
}
#[test]
fn test_cmsg_owned_1() {
let tv = Timeval64 {
tv_sec: 1_700_000_000,
tv_usec: 123_456,
};
let owned = CmsgOwned::ScmTimestamp(tv);
let cmsg = Cmsg::from(&owned);
assert_eq!(cmsg.cmsg_level(), libc::SOL_SOCKET);
assert_eq!(cmsg.cmsg_type(), SO_TIMESTAMP_OLD);
assert_eq!(cmsg.len(), size_of::<Timeval64>());
}
#[test]
fn test_cmsg_owned_2() {
let ts = [
TimeSpec64 {
tv_sec: 100,
tv_nsec: 200,
},
TimeSpec64 {
tv_sec: 300,
tv_nsec: 400,
},
TimeSpec64 {
tv_sec: 500,
tv_nsec: 600,
},
];
let owned = CmsgOwned::ScmTimestamping(ts);
let cmsg = Cmsg::from(&owned);
assert_eq!(cmsg.cmsg_level(), libc::SOL_SOCKET);
assert_eq!(cmsg.cmsg_type(), SO_TIMESTAMPING_OLD);
assert_eq!(cmsg.len(), 3 * size_of::<TimeSpec64>());
}
#[test]
fn test_cmsg_owned_3() {
let fds = vec![
unsafe { SafeOwnedFd::from_raw_fd(0) },
unsafe { SafeOwnedFd::from_raw_fd(1) },
unsafe { SafeOwnedFd::from_raw_fd(2) },
];
let owned = CmsgOwned::ScmRights(fds);
let cm = Cmsg::from(&owned);
assert_eq!(cm.cmsg_level(), libc::SOL_SOCKET);
assert_eq!(cm.cmsg_type(), libc::SCM_RIGHTS);
assert_eq!(cm.len(), 3 * size_of::<BorrowedFd<'_>>());
if let CmsgOwned::ScmRights(fds) = owned {
for fd in fds {
std::mem::forget(fd);
}
}
}
#[test]
fn test_cmsg_owned_4() {
let data = vec![0xDE, 0xAD];
let owned = CmsgOwned::Unknown(RawCmsgOwned {
level: 77,
cmsg_type: 88,
data,
});
let cm = Cmsg::from(&owned);
assert_eq!(cm.cmsg_level(), 77);
assert_eq!(cm.cmsg_type(), 88);
assert_eq!(cm.len(), 2);
}
#[test]
fn test_cmsg_owned_5() {
let owned = CmsgOwned::Ipv4Tos(0x10);
let cm = Cmsg::from(&owned);
assert_eq!(cm.cmsg_level(), libc::IPPROTO_IP);
assert_eq!(cm.cmsg_type(), libc::IP_TOS);
assert_eq!(cm.len(), 1);
}
#[test]
fn test_cmsg_iterator_1() {
let buf: &[u8] = &[];
let items: Vec<_> = CmsgIterator::new(buf).collect();
assert!(items.is_empty());
}
#[test]
fn test_cmsg_iterator_2() {
let buf = [0u8; 4];
let items: Vec<_> = CmsgIterator::new(&buf).collect();
assert!(items.is_empty());
}
#[test]
fn test_cmsg_iterator_3() {
let mut buf = vec![0u8; unsafe { libc::CMSG_SPACE(4) } as usize];
unsafe {
let mut mhdr = std::mem::zeroed::<libc::msghdr>();
mhdr.msg_control = buf.as_mut_ptr().cast();
mhdr.msg_controllen = buf.len() as _;
let cmsg = libc::CMSG_FIRSTHDR(&mhdr);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(4) as _;
let data = libc::CMSG_DATA(cmsg);
*(data as *mut i32) = 42;
}
let items: Vec<_> = CmsgIterator::new(&buf)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].level, libc::SOL_SOCKET);
assert_eq!(items[0].cmsg_type, libc::SCM_RIGHTS);
assert_eq!(items[0].data.len(), 4);
let fd = i32::from_ne_bytes(items[0].data.try_into().unwrap());
assert_eq!(fd, 42);
}
#[test]
fn test_cmsg_iterator_4() {
let result: Vec<_> = CmsgIterator::new(&[]).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_cmsg_iterator_5() {
let buf = [0u8; 1];
let result: Vec<_> = CmsgIterator::new(&buf).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_cmsg_iterator_6() {
let buf = vec![0u8; size_of::<libc::cmsghdr>() - 1];
let result: Vec<_> = CmsgIterator::new(&buf).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_cmsg_iterator_7() {
let buf = [0u8; 64];
let mut it = CmsgIterator::new(&buf);
assert_eq!(it.next(), Some(Err(Errno::EINVAL)));
assert!(it.next().is_none());
}
#[test]
fn test_cmsg_iterator_8() {
let mut buf = vec![0u8; 64];
let short = (unsafe { libc::CMSG_LEN(0) } as usize) - 1;
buf[..size_of::<libc::socklen_t>()]
.copy_from_slice(&(short as libc::socklen_t).to_ne_bytes());
let mut it = CmsgIterator::new(&buf);
assert_eq!(it.next(), Some(Err(Errno::EINVAL)));
assert!(it.next().is_none());
}
#[test]
fn test_cmsg_iterator_9() {
let mut buf = vec![0u8; unsafe { libc::CMSG_SPACE(4) } as usize];
unsafe {
let mut mhdr = std::mem::zeroed::<libc::msghdr>();
mhdr.msg_control = buf.as_mut_ptr().cast();
mhdr.msg_controllen = buf.len() as _;
let cmsg = libc::CMSG_FIRSTHDR(&mhdr);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = (buf.len() + 64) as _;
}
let mut it = CmsgIterator::new(&buf);
assert_eq!(it.next(), Some(Err(Errno::EINVAL)));
assert!(it.next().is_none());
}
#[test]
fn test_cmsg_iterator32_1() {
let buf: &[u8] = &[];
let items: Vec<_> = CmsgIterator32::new(buf).collect();
assert!(items.is_empty());
}
#[test]
fn test_cmsg_iterator32_2() {
let buf = [0u8; 4];
let items: Vec<_> = CmsgIterator32::new(&buf).collect();
assert!(items.is_empty());
}
#[test]
fn test_cmsg_iterator32_3() {
let hdr = cmsghdr32 {
cmsg_len: cmsg_len_32(4) as u32,
cmsg_level: libc::SOL_SOCKET,
cmsg_type: libc::SCM_RIGHTS,
};
let hdr_buf: [u8; 12] = unsafe { std::mem::transmute(hdr) };
let mut buf = vec![0u8; cmsg_space_32(4)];
buf[..12].copy_from_slice(&hdr_buf);
buf[12..16].copy_from_slice(&42i32.to_ne_bytes());
let items: Vec<_> = CmsgIterator32::new(&buf)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].level, libc::SOL_SOCKET);
assert_eq!(items[0].cmsg_type, libc::SCM_RIGHTS);
assert_eq!(items[0].data.len(), 4);
let fd = i32::from_ne_bytes(items[0].data.try_into().unwrap());
assert_eq!(fd, 42);
}
#[test]
fn test_cmsg_iterator32_4() {
let result: Vec<_> = CmsgIterator32::new(&[]).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_cmsg_iterator32_5() {
let buf = [0u8; 1];
let result: Vec<_> = CmsgIterator32::new(&buf).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_cmsg_iterator32_6() {
let buf = vec![0u8; size_of::<cmsghdr32>() - 1];
let result: Vec<_> = CmsgIterator32::new(&buf).collect();
assert_eq!(result.len(), 0);
}
#[test]
fn test_cmsg_iterator32_7() {
let buf = vec![0u8; 64];
let mut it = CmsgIterator32::new(&buf);
assert_eq!(it.next(), Some(Err(Errno::EINVAL)));
assert!(it.next().is_none());
}
#[test]
fn test_cmsg_iterator32_8() {
let mut buf = vec![0u8; 64];
let short = cmsg_len_32(0) - 1;
buf[..size_of::<u32>()].copy_from_slice(&(short as u32).to_ne_bytes());
let mut it = CmsgIterator32::new(&buf);
assert_eq!(it.next(), Some(Err(Errno::EINVAL)));
assert!(it.next().is_none());
}
#[test]
fn test_cmsg_iterator32_9() {
let mut buf = vec![0u8; cmsg_space_32(4)];
let too_big = (buf.len() + 64) as u32;
buf[..size_of::<u32>()].copy_from_slice(&too_big.to_ne_bytes());
let mut it = CmsgIterator32::new(&buf);
assert_eq!(it.next(), Some(Err(Errno::EINVAL)));
assert!(it.next().is_none());
}
#[test]
fn test_addr_family_1() {
let addr = UnixAddr::new("/tmp/test.sock").unwrap();
assert_eq!(addr_family(&addr), libc::AF_UNIX as libc::sa_family_t);
}
#[test]
fn test_addr_family_2() {
let addr = SockaddrIn::from(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8080));
assert_eq!(addr_family(&addr), libc::AF_INET as libc::sa_family_t);
}
#[test]
fn test_sockaddr_as_bytes_1() {
let std_addr = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80);
let storage = SockaddrStorage::from(SocketAddr::V4(std_addr));
let bytes = unsafe {
std::slice::from_raw_parts(storage.as_ptr().cast::<u8>(), storage.len() as usize)
};
assert!(!bytes.is_empty());
assert_eq!(bytes.len(), storage.len() as usize);
}
#[test]
fn test_with_opt_nix_path_1() {
let result = with_opt_nix_path(None::<&std::path::Path>, |p| p.is_null());
assert!(result.unwrap());
}
#[test]
fn test_with_opt_nix_path_2() {
let path = std::path::Path::new("/tmp");
let result = with_opt_nix_path(Some(path), |p| {
assert!(!p.is_null());
let cs = unsafe { CStr::from_ptr(p) };
cs.to_str().unwrap().to_string()
});
assert_eq!(result.unwrap(), "/tmp");
}
#[test]
fn test_recv_msg() {
let msghdr = MsgHdr::default();
let r = RecvMsg {
bytes: 0,
flags: 0,
msghdr: &msghdr,
};
let cmsgs = r.cmsgs().unwrap();
assert!(cmsgs.is_empty());
}
#[test]
fn test_pack_cmsg_buf_1() {
let buf = pack_cmsg_buf(&[]).unwrap();
assert!(buf.is_empty());
}
#[test]
fn test_pack_cmsg_buf_2() {
let fds = [unsafe { BorrowedFd::borrow_raw(7) }];
let cmsgs = [Cmsg::ScmRights(&fds)];
let buf = pack_cmsg_buf(&cmsgs).unwrap();
assert!(!buf.is_empty());
let items: Vec<_> = CmsgIterator::new(&buf)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].level, libc::SOL_SOCKET);
assert_eq!(items[0].cmsg_type, libc::SCM_RIGHTS);
let fd = i32::from_ne_bytes(items[0].data.try_into().unwrap());
assert_eq!(fd, 7);
}
#[test]
fn test_pack_cmsg_buf_3() {
let tos: u8 = 0x10;
let cmsgs = [Cmsg::Ipv4Tos(&tos)];
let buf = pack_cmsg_buf(&cmsgs).unwrap();
let items: Vec<_> = CmsgIterator::new(&buf)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].level, libc::IPPROTO_IP);
assert_eq!(items[0].cmsg_type, libc::IP_TOS);
assert_eq!(items[0].data[0], 0x10);
}
#[test]
fn test_pack_cmsg_buf_4() {
let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
let raw = RawCmsg {
level: 123,
cmsg_type: 456,
data: &data,
};
let cmsgs = [Cmsg::Unknown(raw)];
let buf = pack_cmsg_buf(&cmsgs).unwrap();
let items: Vec<_> = CmsgIterator::new(&buf)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].level, 123);
assert_eq!(items[0].cmsg_type, 456);
assert_eq!(items[0].data, &[0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn test_pack_cmsg_buf_32_1() {
let buf = pack_cmsg_buf_32(&[]).unwrap();
assert!(buf.is_empty());
}
#[test]
fn test_pack_cmsg_buf_32_2() {
let fds = [unsafe { BorrowedFd::borrow_raw(7) }];
let cmsgs = [Cmsg::ScmRights(&fds)];
let buf = pack_cmsg_buf_32(&cmsgs).unwrap();
assert!(!buf.is_empty());
let items: Vec<_> = CmsgIterator32::new(&buf)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].level, libc::SOL_SOCKET);
assert_eq!(items[0].cmsg_type, libc::SCM_RIGHTS);
let fd = i32::from_ne_bytes(items[0].data.try_into().unwrap());
assert_eq!(fd, 7);
}
#[test]
fn test_sendmsg_1() {
let (fd1, fd2) =
safe_socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty()).unwrap();
let (r, w) = pipe2(OFlag::O_CLOEXEC).unwrap();
let iov = [IoSlice::new(b"hello")];
let fds = [r.as_fd()];
let cmsg = Cmsg::ScmRights(&fds);
let sent = sendmsg(&fd1, &iov, &[cmsg], MsgFlags::empty(), None::<&SockaddrIn>).unwrap();
assert_eq!(sent, 5);
let mut buf = [0u8; 5];
let mut iov2 = [IoSliceMut::new(&mut buf)];
let cmsg_space = unsafe { libc::CMSG_SPACE(size_of::<RawFd>() as libc::c_uint) } as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov2);
hdr.set_control(&mut cmsg_buf);
let result = recvmsg(&fd2, &mut hdr, MsgFlags::empty()).unwrap();
assert_eq!(result.bytes, 5);
assert_eq!(&buf, b"hello");
let mut received_fd: Option<SafeOwnedFd> = None;
for cmsg in result.cmsgs().unwrap() {
if let CmsgOwned::ScmRights(mut fds) = cmsg {
assert_eq!(fds.len(), 1);
received_fd = Some(fds.remove(0));
}
}
let received_fd = received_fd.expect("did not receive fd");
write(&w, b"world").unwrap();
let mut read_buf = [0u8; 5];
read(received_fd.as_fd(), &mut read_buf).unwrap();
assert_eq!(&read_buf, b"world");
}
#[test]
fn test_sendmsg_2() {
let (fd1, fd2) =
safe_socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty()).unwrap();
let iov = [IoSlice::new(b"test data")];
let sent = sendmsg(&fd1, &iov, &[], MsgFlags::empty(), None::<&SockaddrIn>).unwrap();
assert_eq!(sent, 9);
let mut buf = [0u8; 32];
let mut iov2 = [IoSliceMut::new(&mut buf)];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov2);
let result = recvmsg(&fd2, &mut hdr, MsgFlags::empty()).unwrap();
assert_eq!(result.bytes, 9);
assert_eq!(&buf[..9], b"test data");
}
#[test]
fn test_sendmsg_3() {
let rsock = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = match rsock.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
let ssock = UdpSocket::bind("127.0.0.1:0").unwrap();
let dst = SockaddrIn::from(addr);
let mark_data = 42u32.to_ne_bytes();
let raw = RawCmsg {
level: libc::SOL_SOCKET,
cmsg_type: libc::SO_MARK,
data: &mark_data,
};
let iov = [IoSlice::new(b"x")];
let result = sendmsg(
&ssock,
&iov,
&[Cmsg::Unknown(raw)],
MsgFlags::MSG_DONTWAIT,
Some(&dst),
);
match result {
Ok(_) => {}
Err(errno) => assert_eq!(errno, Errno::EPERM),
}
}
#[test]
fn test_sendmsg_4() {
let rsock = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = match rsock.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
let ssock = UdpSocket::bind("127.0.0.1:0").unwrap();
let tos: u8 = 0x10;
let cmsg = Cmsg::Ipv4Tos(&tos);
let iov = [IoSlice::new(b"tos")];
let dst = SockaddrIn::from(addr);
let result = sendmsg(&ssock, &iov, &[cmsg], MsgFlags::empty(), Some(&dst));
assert!(result.is_ok());
}
#[expect(clippy::disallowed_methods)]
#[expect(clippy::disallowed_types)]
#[test]
fn test_sendmsg_5() {
let (fd1, fd2) =
safe_socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty()).unwrap();
unsafe {
let one: c_int = 1;
libc::setsockopt(
fd1.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PASSCRED,
std::ptr::addr_of!(one).cast(),
size_of::<c_int>() as socklen_t,
);
libc::setsockopt(
fd2.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PASSCRED,
std::ptr::addr_of!(one).cast(),
size_of::<c_int>() as socklen_t,
);
}
let cred = UnixCredentials::from(libc::ucred {
pid: getpid().as_raw(),
uid: getuid().as_raw(),
gid: getgid().as_raw(),
});
let cmsg = Cmsg::ScmCredentials(&cred);
let iov = [IoSlice::new(b"cred")];
let sent = sendmsg(&fd1, &iov, &[cmsg], MsgFlags::empty(), None::<&SockaddrIn>).unwrap();
assert_eq!(sent, 4);
let mut buf = [0u8; 16];
let mut iov2 = [IoSliceMut::new(&mut buf)];
let cmsg_space =
unsafe { libc::CMSG_SPACE(size_of::<libc::ucred>() as libc::c_uint) } as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov2);
hdr.set_control(&mut cmsg_buf);
let result = recvmsg(&fd2, &mut hdr, MsgFlags::empty()).unwrap();
assert_eq!(result.bytes, 4);
assert_eq!(&buf[..4], b"cred");
let mut got_creds = false;
for cmsg in result.cmsgs().unwrap() {
if let CmsgOwned::ScmCredentials(ucred) = cmsg {
assert_eq!(ucred.pid(), std::process::id() as libc::pid_t);
assert_eq!(ucred.uid(), unsafe { libc::getuid() });
assert_eq!(ucred.gid(), unsafe { libc::getgid() });
got_creds = true;
}
}
assert!(got_creds);
}
#[test]
fn test_recvmsg_1() {
let rsock = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = match rsock.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
let ssock = UdpSocket::bind("127.0.0.1:0").unwrap();
let iov = [IoSlice::new(b"udp test")];
let dst = SockaddrIn::from(addr);
sendmsg(&ssock, &iov, &[], MsgFlags::empty(), Some(&dst)).unwrap();
let mut buf = [0u8; 64];
let mut iov2 = [IoSliceMut::new(&mut buf)];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov2);
let result = recvmsg(&rsock, &mut hdr, MsgFlags::empty()).unwrap();
assert_eq!(result.bytes, 8);
assert_eq!(&buf[..8], b"udp test");
}
#[test]
fn test_recvmsg_2() {
let rsock = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = match rsock.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
let ssock = UdpSocket::bind("127.0.0.1:0").unwrap();
unsafe {
let one: c_int = 1;
libc::setsockopt(
rsock.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_TIMESTAMP,
std::ptr::addr_of!(one).cast(),
size_of::<c_int>() as socklen_t,
);
}
let iov = [IoSlice::new(b"ts")];
let dst = SockaddrIn::from(addr);
sendmsg(&ssock, &iov, &[], MsgFlags::empty(), Some(&dst)).unwrap();
let mut buf = [0u8; 64];
let mut iov2 = [IoSliceMut::new(&mut buf)];
let cmsg_space =
unsafe { libc::CMSG_SPACE(size_of::<libc::timeval>() as libc::c_uint) } as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov2);
hdr.set_control(&mut cmsg_buf);
let result = recvmsg(&rsock, &mut hdr, MsgFlags::empty()).unwrap();
assert_eq!(result.bytes, 2);
assert!(hdr.msg_flags() & libc::MSG_CTRUNC == 0);
}
#[test]
fn test_recvmsg_3() {
let mut buf = [0u8; 8];
let mut iov = [IoSliceMut::new(&mut buf)];
let bad_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(999) };
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov);
let result = recvmsg(bad_fd, &mut hdr, MsgFlags::empty());
assert_eq!(result.unwrap_err(), Errno::EBADF);
}
#[test]
fn test_recvmsg_4() {
let (fd1, fd2) =
safe_socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty()).unwrap();
let fds = [fd1.as_fd()];
let cmsg = Cmsg::ScmRights(&fds);
let iov = [IoSlice::new(b"x")];
sendmsg(&fd1, &iov, &[cmsg], MsgFlags::empty(), None::<&SockaddrIn>).unwrap();
let mut buf = [0u8; 4];
let mut iov2 = [IoSliceMut::new(&mut buf)];
let cmsg_space = unsafe { libc::CMSG_SPACE(size_of::<RawFd>() as libc::c_uint) } as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov2);
hdr.set_control(&mut cmsg_buf);
let result = recvmsg(&fd2, &mut hdr, MsgFlags::empty()).unwrap();
assert_eq!(result.bytes, 1);
let mut got_rights = false;
for cmsg in result.cmsgs().unwrap() {
if let CmsgOwned::ScmRights(_) = cmsg {
got_rights = true;
}
}
assert!(got_rights);
}
#[test]
fn test_sendmmsg_1() {
let rsock = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = rsock.local_addr().unwrap();
let ssock = UdpSocket::bind("127.0.0.1:0").unwrap();
ssock.connect(addr).unwrap();
let msg1 = b"msg one";
let msg2 = b"msg two";
let iov1 = [IoSlice::new(msg1)];
let iov2 = [IoSlice::new(msg2)];
let mut hdrs = [MmsgHdr::default(), MmsgHdr::default()];
hdrs[0].as_inner_mut().msg_hdr.msg_iov = iov1.as_ptr().cast_mut().cast();
hdrs[0].as_inner_mut().msg_hdr.msg_iovlen = 1;
hdrs[1].as_inner_mut().msg_hdr.msg_iov = iov2.as_ptr().cast_mut().cast();
hdrs[1].as_inner_mut().msg_hdr.msg_iovlen = 1;
let sent = sendmmsg(&ssock, &mut hdrs, MsgFlags::empty()).unwrap();
assert_eq!(sent, 2);
let mut buf1 = [0u8; 32];
let mut buf2 = [0u8; 32];
let mut riov1 = [IoSliceMut::new(&mut buf1)];
let mut riov2 = [IoSliceMut::new(&mut buf2)];
let mut hdr1 = MsgHdr::default();
hdr1.set_iov_mut(&mut riov1);
let r1 = recvmsg(&rsock, &mut hdr1, MsgFlags::empty()).unwrap();
let mut hdr2 = MsgHdr::default();
hdr2.set_iov_mut(&mut riov2);
let r2 = recvmsg(&rsock, &mut hdr2, MsgFlags::empty()).unwrap();
assert_eq!(r1.bytes, 7);
assert_eq!(r2.bytes, 7);
let mut received = [&buf1[..7], &buf2[..7]];
received.sort();
let mut expected: [&[u8]; 2] = [b"msg one", b"msg two"];
expected.sort();
assert_eq!(received, expected);
}
#[test]
fn test_recvmmsg_1() {
let rsock = UdpSocket::bind("127.0.0.1:0").unwrap();
let addr = rsock.local_addr().unwrap();
let ssock = UdpSocket::bind("127.0.0.1:0").unwrap();
ssock.connect(addr).unwrap();
let msg1 = b"aaa";
let msg2 = b"bbb";
let iov1 = [IoSlice::new(msg1)];
let iov2 = [IoSlice::new(msg2)];
let mut shdrs = [MmsgHdr::default(), MmsgHdr::default()];
shdrs[0].as_inner_mut().msg_hdr.msg_iov = iov1.as_ptr().cast_mut().cast();
shdrs[0].as_inner_mut().msg_hdr.msg_iovlen = 1;
shdrs[1].as_inner_mut().msg_hdr.msg_iov = iov2.as_ptr().cast_mut().cast();
shdrs[1].as_inner_mut().msg_hdr.msg_iovlen = 1;
let sent = sendmmsg(&ssock, &mut shdrs, MsgFlags::empty()).unwrap();
assert_eq!(sent, 2);
let mut buf1 = [0u8; 32];
let mut buf2 = [0u8; 32];
let mut riov1 = [libc::iovec {
iov_base: buf1.as_mut_ptr().cast(),
iov_len: buf1.len(),
}];
let mut riov2 = [libc::iovec {
iov_base: buf2.as_mut_ptr().cast(),
iov_len: buf2.len(),
}];
let mut rhdrs = [MmsgHdr::default(), MmsgHdr::default()];
rhdrs[0].as_inner_mut().msg_hdr.msg_iov = riov1.as_mut_ptr();
rhdrs[0].as_inner_mut().msg_hdr.msg_iovlen = 1;
rhdrs[1].as_inner_mut().msg_hdr.msg_iov = riov2.as_mut_ptr();
rhdrs[1].as_inner_mut().msg_hdr.msg_iovlen = 1;
let mut timeout = TimeSpec64::new(5, 0);
let recvd = recvmmsg(
&rsock,
&mut rhdrs,
MsgFlags::MSG_WAITFORONE,
Some(&mut timeout),
)
.unwrap();
assert_eq!(recvd, 2);
assert_eq!(rhdrs[0].msg_len(), 3);
assert_eq!(rhdrs[1].msg_len(), 3);
let mut received = [&buf1[..3], &buf2[..3]];
received.sort();
assert_eq!(received, [b"aaa".as_slice(), b"bbb".as_slice()]);
}
#[test]
fn test_time32_1() {
assert_eq!(size_of::<time32_t>(), 4);
}
#[test]
fn test_time32_2() {
assert_eq!(time32_t::MIN, i32::MIN);
assert_eq!(time32_t::MAX, i32::MAX);
}
#[test]
fn test_time32_3() {
assert_eq!(time32_t::try_from(i32::MAX as i64).unwrap(), i32::MAX);
}
#[test]
fn test_time32_4() {
assert_eq!(time32_t::try_from(i32::MIN as i64).unwrap(), i32::MIN);
}
#[test]
fn test_time32_5() {
assert!(time32_t::try_from(2_147_483_647i64).is_ok());
assert!(time32_t::try_from(2_147_483_648i64).is_err());
}
#[test]
fn test_time64_1() {
assert_eq!(size_of::<time64_t>(), 8);
}
#[test]
fn test_time64_2() {
assert_eq!(time64_t::MIN, i64::MIN);
assert_eq!(time64_t::MAX, i64::MAX);
}
#[test]
fn test_time64_3() {
assert_eq!(time64_t::from(i32::MAX), i32::MAX as i64);
}
#[test]
fn test_utime_1() {
assert_eq!(UTIME_NOW, 0x3FFF_FFFF);
}
#[test]
fn test_utime_2() {
assert_eq!(UTIME_OMIT, 0x3FFF_FFFE);
}
#[test]
fn test_utime_3() {
assert_ne!(UTIME_NOW, UTIME_OMIT);
}
#[test]
fn test_timespec64_1() {
let ts = TimeSpec64::new(3, 5);
assert_eq!(ts.tv_sec(), 3);
assert_eq!(ts.tv_nsec(), 5);
}
#[test]
fn test_timespec64_2() {
assert_eq!(TimeSpec64::UTIME_NOW.tv_nsec, UTIME_NOW);
assert_eq!(TimeSpec64::UTIME_OMIT.tv_nsec, UTIME_OMIT);
assert_eq!(TimeSpec64::UTIME_NOW.tv_sec, 0);
assert_eq!(TimeSpec64::UTIME_OMIT.tv_sec, 0);
}
#[test]
fn test_timespec64_3() {
let a = TimeSpec64::new(3, 5);
let b = TimeSpec64::new(3, 6);
let c = TimeSpec64::new(4, 0);
assert!(a < b);
assert!(b < c);
}
#[test]
fn test_timespec32_1() {
let ts = TimeSpec32::new(3, 5);
assert_eq!(ts.tv_sec(), 3);
assert_eq!(ts.tv_nsec(), 5);
}
#[test]
fn test_timespec32_2() {
assert_eq!(TimeSpec32::UTIME_NOW.tv_nsec, UTIME_NOW as i32);
assert_eq!(TimeSpec32::UTIME_OMIT.tv_nsec, UTIME_OMIT as i32);
}
#[test]
fn test_timespec32_3() {
let a = TimeSpec32::new(3, 5);
let b = TimeSpec32::new(3, 6);
assert!(a < b);
}
#[test]
fn test_try_from_time_1() {
let ts = TimeSpec64 {
tv_sec: 42,
tv_nsec: 999,
};
let ts32 = TimeSpec32::try_from(ts).unwrap();
assert_eq!(ts32.tv_sec, 42);
assert_eq!(ts32.tv_nsec, 999);
}
#[test]
fn test_try_from_time_2() {
let ts = TimeSpec64 {
tv_sec: i64::from(i32::MAX) + 1,
tv_nsec: 0,
};
assert_eq!(TimeSpec32::try_from(ts), Err(Errno::EOVERFLOW));
}
#[test]
fn test_try_from_time_3() {
let ts = TimeSpec64 {
tv_sec: 0,
tv_nsec: i64::from(i32::MAX) + 1,
};
assert_eq!(TimeSpec32::try_from(ts), Err(Errno::EOVERFLOW));
}
#[test]
fn test_try_from_time_4() {
let ts32 = TimeSpec32 {
tv_sec: i32::MAX,
tv_nsec: i32::MIN,
};
let ts64 = TimeSpec64::from(ts32);
assert_eq!(ts64.tv_sec, i32::MAX as i64);
assert_eq!(ts64.tv_nsec, i32::MIN as i64);
}
#[test]
fn test_try_from_time_5() {
let ts = TimeSpec64::try_from(Timeval64 {
tv_sec: 100,
tv_usec: 500,
})
.unwrap();
assert_eq!(ts.tv_sec, 100);
assert_eq!(ts.tv_nsec, 500_000);
}
#[test]
fn test_try_from_time_6() {
let tv = Timeval64 {
tv_sec: 0,
tv_usec: i64::MAX,
};
assert_eq!(TimeSpec64::try_from(tv), Err(Errno::EOVERFLOW));
}
#[test]
fn test_try_from_time_7() {
let ts = TimeSpec64::try_from(Timeval32 {
tv_sec: 42,
tv_usec: 1000,
})
.unwrap();
assert_eq!(ts.tv_sec, 42);
assert_eq!(ts.tv_nsec, 1_000_000);
}
#[test]
fn test_try_from_time_8() {
let [a, m] = <[TimeSpec64; 2]>::from(Utimbuf64 {
actime: 1,
modtime: 2,
});
assert_eq!(a, TimeSpec64::new(1, 0));
assert_eq!(m, TimeSpec64::new(2, 0));
}
#[test]
fn test_try_from_time_9() {
let [a, m] = <[TimeSpec64; 2]>::from(Utimbuf32 {
actime: i32::MAX,
modtime: i32::MIN,
});
assert_eq!(a, TimeSpec64::new(i32::MAX as i64, 0));
assert_eq!(m, TimeSpec64::new(i32::MIN as i64, 0));
}
#[test]
fn test_seccomp_export_bpf_mem_1() {
let ctx = ScmpFilterContext::new(ScmpAction::Allow).unwrap();
let buf = seccomp_export_bpf_mem(&ctx, SeccompBpfExport::Fd).unwrap();
assert!(!buf.is_empty());
assert_eq!(buf.len() % size_of::<sock_filter>(), 0);
}
#[test]
fn test_seccomp_export_bpf_mem_2() {
let a = ScmpFilterContext::new(ScmpAction::Allow).unwrap();
let mut b = ScmpFilterContext::new(ScmpAction::Allow).unwrap();
b.add_rule(
ScmpAction::KillProcess,
ScmpSyscall::from_name("read").unwrap(),
)
.unwrap();
assert_ne!(
seccomp_export_bpf_mem(&a, SeccompBpfExport::Fd).unwrap(),
seccomp_export_bpf_mem(&b, SeccompBpfExport::Fd).unwrap()
);
}
#[test]
fn test_seccomp_export_bpf_mem_3() {
let ctx = ScmpFilterContext::new(ScmpAction::Allow).unwrap();
let buf = seccomp_export_bpf_mem(&ctx, SECCOMP_BPF_EXPORT_DEFAULT).unwrap();
assert!(!buf.is_empty());
assert_eq!(buf.len() % size_of::<sock_filter>(), 0);
}
#[cfg(libseccomp_v2_5)]
#[test]
fn test_seccomp_export_bpf_mem_4() {
let ctx = ScmpFilterContext::new(ScmpAction::Allow).unwrap();
let buf = seccomp_export_bpf_mem(&ctx, SeccompBpfExport::Mem).unwrap();
assert!(!buf.is_empty());
assert_eq!(buf.len() % size_of::<sock_filter>(), 0);
}
#[cfg(libseccomp_v2_5)]
#[test]
fn test_seccomp_export_bpf_mem_5() {
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow).unwrap();
ctx.add_rule(
ScmpAction::Errno(EPERM),
ScmpSyscall::from_name("write").unwrap(),
)
.unwrap();
assert_eq!(
seccomp_export_bpf_mem(&ctx, SeccompBpfExport::Mem).unwrap(),
seccomp_export_bpf_mem(&ctx, SeccompBpfExport::Fd).unwrap()
);
}
}