use std::{
convert::Infallible,
fmt,
fs::{Metadata, Permissions},
io,
mem::ManuallyDrop,
os::{
fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd},
unix::{fs::FileExt, net::UnixStream},
},
sync::OnceLock,
};
use btoi::btoi;
use libc::{
c_int, c_long, c_uint, c_ulong, syscall, SYS_ioctl, SYS_kcmp, SYS_pidfd_getfd, SYS_pidfd_open,
SYS_pidfd_send_signal, EBADF, O_NONBLOCK,
};
use nix::{
errno::Errno,
fcntl::{fcntl, AtFlags, FcntlArg, FdFlag, OFlag, SealFlag},
sched::CloneFlags,
sys::{
socket::{
getsockopt,
sockopt::{PeerCredentials, ReceiveTimeout, SendTimeout},
SockFlag, SockaddrStorage, UnixCredentials,
},
stat::Mode,
},
unistd::{lseek64, read, write, AccessFlags, Pid, Whence},
};
use crate::{
compat::{
fstatx, getdents64, openat2, pread64, pwrite64, recvmsg, sendmsg, statx, AddressFamily,
Cmsg, CmsgOwned, CmsgSpace, FsType, MsgFlags, MsgHdr, ResolveFlag, SockType,
STATX_BASIC_STATS, STATX_INO, STATX_MNT_ID, STATX_MNT_ID_UNIQUE, STATX_MODE, STATX_SIZE,
TIOCEXCL, TIOCGEXCL, TIOCNXCL,
},
config::{
DIRENT_BUF_SIZE, HAVE_AT_EXECVE_CHECK, HAVE_PIDFD_THREAD, HAVE_PROC_PID_FD_STAT_SIZE,
HAVE_STATX_MNT_ID_UNIQUE,
},
cookie::{safe_close, safe_close_range, safe_execve_check, safe_faccess, safe_socket},
fs::{oflag_accmode, readlinkat},
hash::SydHashSet,
info,
lookup::safe_open_how,
path::{XPath, XPathBuf},
proc::proc_tgid,
retry::retry_on_eintr,
rng::duprand,
};
pub const AT_BADFD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(-EBADF) };
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct SafeOwnedFd {
fd: RawFd,
}
impl Drop for SafeOwnedFd {
#[inline(always)]
fn drop(&mut self) {
let _ = close(self.fd);
}
}
impl fmt::Debug for SafeOwnedFd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SafeOwnedFd").field("fd", &self.fd).finish()
}
}
impl AsRawFd for SafeOwnedFd {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl AsFd for SafeOwnedFd {
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.fd) }
}
}
impl SafeOwnedFd {
pub fn as_borrowed_slice(fds: &[SafeOwnedFd]) -> &[BorrowedFd<'_>] {
unsafe { std::slice::from_raw_parts(fds.as_ptr().cast(), fds.len()) }
}
}
impl IntoRawFd for SafeOwnedFd {
fn into_raw_fd(self) -> RawFd {
let fd = self.fd;
std::mem::forget(self);
fd
}
}
impl FromRawFd for SafeOwnedFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
assert!(
fd >= 0,
"SafeOwnedFd::from_raw_fd: fd must be non-negative, got {fd}"
);
let fd_tmp = BorrowedFd::borrow_raw(fd);
#[expect(clippy::disallowed_methods)]
set_cloexec(fd_tmp, true).expect("set_cloexec");
SafeOwnedFd { fd }
}
}
#[expect(clippy::disallowed_types)]
impl From<std::os::fd::OwnedFd> for SafeOwnedFd {
#[inline]
fn from(owned: std::os::fd::OwnedFd) -> Self {
SafeOwnedFd {
fd: owned.into_raw_fd(),
}
}
}
#[expect(clippy::disallowed_types)]
impl From<SafeOwnedFd> for std::os::fd::OwnedFd {
fn from(safe: SafeOwnedFd) -> Self {
unsafe { std::os::fd::OwnedFd::from_raw_fd(safe.into_raw_fd()) }
}
}
#[expect(clippy::disallowed_types)]
impl From<std::fs::File> for SafeOwnedFd {
fn from(file: std::fs::File) -> Self {
SafeOwnedFd::from(std::os::fd::OwnedFd::from(file))
}
}
#[expect(clippy::disallowed_types)]
impl From<SafeOwnedFd> for std::fs::File {
fn from(safe: SafeOwnedFd) -> Self {
std::fs::File::from(std::os::fd::OwnedFd::from(safe))
}
}
#[expect(clippy::disallowed_types)]
impl From<UnixStream> for SafeOwnedFd {
fn from(stream: UnixStream) -> Self {
SafeOwnedFd::from(std::os::fd::OwnedFd::from(stream))
}
}
#[expect(clippy::disallowed_types)]
impl From<SafeOwnedFd> for UnixStream {
fn from(safe: SafeOwnedFd) -> Self {
UnixStream::from(std::os::fd::OwnedFd::from(safe))
}
}
impl io::Read for SafeOwnedFd {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
nix::unistd::read(self.as_fd(), buf).map_err(io::Error::from)
}
}
impl io::Write for SafeOwnedFd {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
nix::unistd::write(self, buf).map_err(io::Error::from)
}
fn flush(&mut self) -> io::Result<()> {
Ok(()) }
}
impl io::Seek for SafeOwnedFd {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
#[expect(clippy::cast_possible_wrap)]
let (offset, whence) = match pos {
io::SeekFrom::Start(n) => (n as libc::off64_t, Whence::SeekSet),
io::SeekFrom::End(n) => (n as libc::off64_t, Whence::SeekEnd),
io::SeekFrom::Current(n) => (n as libc::off64_t, Whence::SeekCur),
};
#[expect(clippy::cast_sign_loss)]
lseek64(self.as_fd(), offset, whence)
.map(|r| r as u64)
.map_err(io::Error::from)
}
}
impl FileExt for SafeOwnedFd {
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
let offset = offset
.try_into()
.or(Err(Errno::EOVERFLOW))
.map_err(io::Error::from)?;
pread64(self.as_fd(), buf, offset).map_err(io::Error::from)
}
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
let offset = offset
.try_into()
.or(Err(Errno::EOVERFLOW))
.map_err(io::Error::from)?;
pwrite64(self.as_fd(), buf, offset).map_err(io::Error::from)
}
}
impl SafeOwnedFd {
pub fn try_clone(&self) -> Result<Self, Errno> {
let fd = fcntl(self.as_fd(), FcntlArg::F_DUPFD_CLOEXEC(3))?;
Ok(Self { fd })
}
pub fn metadata(&self) -> io::Result<Metadata> {
#[expect(clippy::disallowed_types)]
let file = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(self.as_raw_fd()) });
file.metadata()
}
pub fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
#[expect(clippy::disallowed_types)]
let file = ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(self.as_raw_fd()) });
file.set_permissions(perm)
}
}
pub fn set_append<Fd: AsFd>(fd: Fd, state: bool) -> Result<(), Errno> {
let flags = fcntl(&fd, FcntlArg::F_GETFL)?;
let mut new_flags = flags;
if state {
new_flags |= OFlag::O_APPEND.bits();
} else {
new_flags &= !OFlag::O_APPEND.bits();
}
fcntl(&fd, FcntlArg::F_SETFL(OFlag::from_bits_truncate(new_flags))).map(drop)
}
pub fn get_nonblock<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
fcntl(fd, FcntlArg::F_GETFL).map(|flags| flags & O_NONBLOCK != 0)
}
pub fn set_nonblock<Fd: AsFd>(fd: Fd, state: bool) -> Result<(), Errno> {
let flags = fcntl(&fd, FcntlArg::F_GETFL)?;
let mut new_flags = flags;
if state {
new_flags |= OFlag::O_NONBLOCK.bits();
} else {
new_flags &= !OFlag::O_NONBLOCK.bits();
}
fcntl(&fd, FcntlArg::F_SETFL(OFlag::from_bits_truncate(new_flags))).map(drop)
}
pub fn set_cloexec<Fd: AsFd>(fd: Fd, state: bool) -> Result<(), Errno> {
let flags = fcntl(&fd, FcntlArg::F_GETFD)?;
let mut new_flags = flags;
if state {
new_flags |= FdFlag::FD_CLOEXEC.bits();
} else {
new_flags &= !FdFlag::FD_CLOEXEC.bits();
}
fcntl(
&fd,
FcntlArg::F_SETFD(FdFlag::from_bits_truncate(new_flags)),
)
.map(drop)
}
#[inline(always)]
pub fn close<Fd: IntoRawFd>(fd: Fd) -> Result<(), Errno> {
let fd = fd.into_raw_fd();
match safe_close(fd) {
Ok(_) => Ok(()),
Err(Errno::EBADF) => panic!("BUG: Attempt to close bad fd:{fd}, report a bug!"),
Err(errno) => Err(errno),
}
}
#[inline(always)]
pub fn close_range(first: c_uint, last: c_uint, flags: c_uint) -> Result<(), Errno> {
safe_close_range(first, last, flags)
}
pub fn closefrom(fd: c_uint) -> Result<(), Errno> {
close_range(fd, RawFd::MAX as c_uint, 0)
}
pub fn closeall(closefds: &[c_uint]) -> Result<(), Errno> {
if closefds.is_empty() {
return Ok(());
}
if closefds.windows(2).any(|w| w[0] >= w[1]) {
return Err(Errno::EINVAL);
}
let mut first = closefds[0];
let mut last = first;
#[expect(clippy::arithmetic_side_effects)]
for &fd in &closefds[1..] {
if fd != last + 1 {
close_range(first, last, 0)?;
first = fd;
}
last = fd;
}
close_range(first, last, 0)
}
pub fn closeexcept(exceptions: &[c_uint]) -> Result<(), Errno> {
if exceptions.windows(2).any(|w| w[0] >= w[1]) {
return Err(Errno::EINVAL);
}
if exceptions.is_empty() {
return closefrom(0);
}
let mut next: u64 = 0;
for &ex_fd in exceptions {
let ex_fd = u64::from(ex_fd);
if next < ex_fd {
let first = c_uint::try_from(next).or(Err(Errno::EOVERFLOW))?;
let last = c_uint::try_from(ex_fd.checked_sub(1).ok_or(Errno::EOVERFLOW)?)
.or(Err(Errno::EOVERFLOW))?;
close_range(first, last, 0)?;
}
next = ex_fd.saturating_add(1);
}
if next <= RawFd::MAX as u64 {
let first = c_uint::try_from(next).or(Err(Errno::EOVERFLOW))?;
closefrom(first)?;
}
Ok(())
}
const KCMP_FILE: c_long = 0;
pub fn is_open_fd(pid: Pid, fd: RawFd) -> Result<bool, Errno> {
#[expect(clippy::cast_lossless)]
#[expect(clippy::cast_possible_wrap)]
#[expect(clippy::cast_sign_loss)]
match Errno::result(unsafe {
syscall(
SYS_kcmp,
pid.as_raw() as c_long,
pid.as_raw() as c_long,
KCMP_FILE,
fd as c_ulong as c_long,
fd as c_ulong as c_long,
)
}) {
Ok(_) => Ok(true),
Err(Errno::EBADF) => Ok(false),
Err(errno) => Err(errno),
}
}
pub fn is_same_fd(pid1: Pid, pid2: Pid, fd1: RawFd, fd2: RawFd) -> Result<bool, Errno> {
if pid1 == pid2 && fd1 == fd2 {
return Ok(true);
}
#[expect(clippy::cast_lossless)]
#[expect(clippy::cast_possible_wrap)]
#[expect(clippy::cast_sign_loss)]
Ok(Errno::result(unsafe {
syscall(
SYS_kcmp,
pid1.as_raw() as c_long,
pid2.as_raw() as c_long,
KCMP_FILE,
fd1 as c_ulong as c_long,
fd2 as c_ulong as c_long,
)
})? == 0)
}
pub fn is_huge_file<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
FsType::get(fd).map(|fs_type| fs_type.is_huge_file())
}
pub fn is_proc<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
FsType::get(fd).map(|fs_type| fs_type.is_proc())
}
pub fn is_secretmem<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
FsType::get(fd).map(|fs_type| fs_type.is_secretmem())
}
pub fn is_dev_null<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
const NULL_MAJOR: u32 = 1;
const NULL_MINOR: u32 = 3;
is_char_dev(fd, NULL_MAJOR, NULL_MINOR)
}
pub fn is_dev_kfd<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
const KFD_MAJOR: u32 = 238;
const KFD_MINOR: u32 = 0;
is_char_dev(fd, KFD_MAJOR, KFD_MINOR)
}
pub fn is_dev_ptmx<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
const PTMX_MAJOR: u32 = 5;
const PTMX_MINOR: u32 = 2;
is_char_dev(fd, PTMX_MAJOR, PTMX_MINOR)
}
pub fn is_char_dev<Fd: AsFd>(fd: Fd, major: u32, minor: u32) -> Result<bool, Errno> {
#[expect(clippy::cast_possible_truncation)]
const S_IFCHR: u16 = libc::S_IFCHR as u16;
#[expect(clippy::cast_possible_truncation)]
const S_IFMT: u16 = libc::S_IFMT as u16;
let statx = fstatx(fd, STATX_BASIC_STATS)?;
Ok(statx.stx_mode & S_IFMT == S_IFCHR
&& statx.stx_rdev_major == major
&& statx.stx_rdev_minor == minor)
}
pub fn is_file<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
#[expect(clippy::cast_possible_truncation)]
const S_IFREG: u16 = libc::S_IFREG as u16;
#[expect(clippy::cast_possible_truncation)]
const S_IFMT: u16 = libc::S_IFMT as u16;
let statx = fstatx(&fd, STATX_BASIC_STATS)?;
Ok(statx.stx_mode & S_IFMT == S_IFREG)
}
pub fn is_empty_file<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
#[expect(clippy::cast_possible_truncation)]
const S_IFREG: u16 = libc::S_IFREG as u16;
#[expect(clippy::cast_possible_truncation)]
const S_IFMT: u16 = libc::S_IFMT as u16;
let statx = fstatx(&fd, STATX_BASIC_STATS)?;
Ok(statx.stx_size == 0 && statx.stx_mode & S_IFMT == S_IFREG)
}
pub fn is_memfd<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
let pfd = XPathBuf::from_self_fd(fd.as_fd().as_raw_fd())?;
let lnk = readlinkat(PROC_FILE(), &pfd)?;
Ok(lnk.starts_with(b"/memfd:") && lnk.ends_with(b" (deleted)"))
}
pub fn parse_fd(path: &XPath) -> Result<RawFd, Errno> {
btoi::<RawFd>(path.as_bytes()).or(Err(Errno::EBADF))
}
pub fn seal_memfd_all<Fd: AsFd>(fd: Fd) -> Result<(), Errno> {
seal_memfd(
fd,
SealFlag::F_SEAL_SEAL
| SealFlag::F_SEAL_WRITE
| SealFlag::F_SEAL_SHRINK
| SealFlag::F_SEAL_GROW,
)
}
pub fn seal_memfd<Fd: AsFd>(fd: Fd, flags: SealFlag) -> Result<(), Errno> {
if flags.is_empty() {
return Err(Errno::EINVAL);
}
fcntl(fd, FcntlArg::F_ADD_SEALS(flags)).map(drop)
}
pub fn set_pipemax<Fd: AsFd>(fd: Fd, size: c_int) -> Result<usize, Errno> {
#[expect(clippy::cast_sign_loss)]
fcntl(fd, FcntlArg::F_SETPIPE_SZ(size)).map(|r| r as usize)
}
pub fn get_exclusive<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
let mut set: c_int = 0;
let fd = fd.as_fd().as_raw_fd();
Errno::result(unsafe { syscall(SYS_ioctl, fd, TIOCGEXCL, std::ptr::addr_of_mut!(set)) })
.map(|_| set != 0)
}
pub fn set_exclusive<Fd: AsFd>(fd: Fd, enable: bool) -> Result<(), Errno> {
let fd = fd.as_fd().as_raw_fd();
let req = if enable { TIOCEXCL } else { TIOCNXCL };
Errno::result(unsafe { syscall(SYS_ioctl, fd, req) }).map(drop)
}
pub fn has_send_timeout<F: AsFd>(fd: &F) -> Result<bool, Errno> {
let tv = getsockopt(fd, SendTimeout)?;
Ok(tv.tv_sec() != 0 || tv.tv_usec() != 0)
}
pub fn has_recv_timeout<F: AsFd>(fd: &F) -> Result<bool, Errno> {
let tv = getsockopt(fd, ReceiveTimeout)?;
Ok(tv.tv_sec() != 0 || tv.tv_usec() != 0)
}
pub fn fd_inode<Fd: AsFd>(fd: Fd) -> Result<u64, Errno> {
retry_on_eintr(|| fstatx(&fd, STATX_INO)).map(|statx| statx.stx_ino)
}
pub fn fd_mode<Fd: AsFd>(fd: Fd) -> Result<Mode, Errno> {
retry_on_eintr(|| fstatx(&fd, STATX_MODE))
.map(|statx| statx.stx_mode)
.map(u32::from)
.map(Mode::from_bits_retain)
}
pub fn is_active_fd<Fd: AsFd>(fd: Fd) -> bool {
fcntl(fd, FcntlArg::F_GETFD).is_ok()
}
pub fn is_valid_fd(fd: u64) -> bool {
to_valid_fd(fd).map(|fd| fd >= 0).unwrap_or(false)
}
#[expect(clippy::cast_possible_truncation)]
pub fn to_valid_fd(fd: u64) -> Result<RawFd, Errno> {
let fd = fd as RawFd;
if fd == libc::AT_FDCWD || fd >= 0 {
Ok(fd)
} else {
Err(Errno::EBADF)
}
}
#[expect(clippy::cast_possible_truncation)]
pub fn to_fd(fd: u64) -> Result<RawFd, Errno> {
let fd = fd as RawFd;
if fd >= 0 {
Ok(fd)
} else {
Err(Errno::EBADF)
}
}
pub fn fd_status_flags<Fd: AsFd>(fd: Fd) -> Result<OFlag, Errno> {
fcntl(fd, FcntlArg::F_GETFL).map(OFlag::from_bits_truncate)
}
pub fn is_writable_fd<Fd: AsFd>(fd: Fd) -> Result<bool, Errno> {
fd_status_flags(fd)
.map(oflag_accmode)
.map(|mode| !mode.is_empty())
}
pub fn fd_count(pid: Option<Pid>) -> Result<u64, Errno> {
let mut pfd = XPathBuf::from("/proc");
if let Some(pid) = pid {
pfd.push_pid(pid);
} else {
pfd.push(b"thread-self");
}
pfd.push(b"fd");
if *HAVE_PROC_PID_FD_STAT_SIZE {
let stx = statx(AT_BADFD, &pfd, 0, STATX_SIZE)?;
return Ok(stx.stx_size);
}
#[expect(clippy::disallowed_methods)]
let fd = nix::fcntl::openat(
AT_BADFD,
&pfd,
OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_CLOEXEC,
Mode::empty(),
)?;
let mut nfds: u64 = 0;
loop {
match getdents64(&fd, DIRENT_BUF_SIZE) {
Ok(entries) => {
nfds = nfds
.checked_add(entries.count() as u64)
.ok_or(Errno::ERANGE)?
}
Err(Errno::ECANCELED) => break, Err(errno) => return Err(errno),
};
}
Ok(nfds.saturating_sub(2))
}
pub(crate) const AT_EXECVE_CHECK: AtFlags = AtFlags::from_bits_retain(0x10000);
pub fn is_executable<Fd: AsFd>(file: Fd) -> bool {
check_executable(file).is_ok()
}
pub fn check_executable<Fd: AsFd>(file: Fd) -> Result<(), Errno> {
if *HAVE_AT_EXECVE_CHECK {
safe_execve_check(file)
} else {
safe_faccess(file, AccessFlags::X_OK, crate::compat::AT_EACCESS)
}
}
#[expect(clippy::cast_sign_loss)]
pub const PIDFD_THREAD: u32 = OFlag::O_EXCL.bits() as u32;
pub const PIDFD_NONBLOCK: u32 = libc::O_NONBLOCK as u32;
pub fn pidfd_open(pid: Pid, mut flags: u32) -> Result<SafeOwnedFd, Errno> {
let pid = if *HAVE_PIDFD_THREAD || flags & PIDFD_THREAD == 0 {
pid
} else {
flags &= !PIDFD_THREAD;
proc_tgid(pid)?
};
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe { syscall(SYS_pidfd_open, pid.as_raw(), flags) }).map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
}
pub fn pidfd_getfd<Fd: AsFd>(pid_fd: Fd, remote_fd: RawFd) -> Result<SafeOwnedFd, Errno> {
#[expect(clippy::cast_possible_truncation)]
Errno::result(unsafe { syscall(SYS_pidfd_getfd, pid_fd.as_fd().as_raw_fd(), remote_fd, 0) })
.map(|fd| {
unsafe { SafeOwnedFd::from_raw_fd(fd as RawFd) }
})
}
pub fn pidfd_send_signal<Fd: AsFd>(pid_fd: Fd, sig: i32) -> Result<(), Errno> {
Errno::result(unsafe { syscall(SYS_pidfd_send_signal, pid_fd.as_fd().as_raw_fd(), sig, 0, 0) })
.map(drop)
}
pub fn pidfd_is_alive<Fd: AsFd>(pid_fd: Fd) -> Result<(), Errno> {
pidfd_send_signal(pid_fd, 0)
}
#[expect(unreachable_code)]
pub fn fdclone<F: FnOnce() -> Infallible>(
func: F,
flags: CloneFlags,
signal: Option<c_int>,
) -> Result<(SafeOwnedFd, Pid), Errno> {
if flags.contains(CloneFlags::CLONE_VM) {
return Err(Errno::EINVAL);
}
let mut pid_fd: libc::c_int = -1;
let clone_flags = flags.bits() | signal.unwrap_or(0) | libc::CLONE_PIDFD;
let child = Errno::result(unsafe {
#[cfg(target_arch = "s390x")]
{
syscall(
libc::SYS_clone,
0,
clone_flags,
std::ptr::addr_of_mut!(pid_fd) as c_long,
0,
0,
)
}
#[cfg(not(target_arch = "s390x"))]
{
syscall(
libc::SYS_clone,
clone_flags,
0,
std::ptr::addr_of_mut!(pid_fd) as c_long,
0,
0,
)
}
})?;
if child == 0 {
func(); }
#[expect(clippy::cast_possible_truncation)]
Ok((
unsafe { SafeOwnedFd::from_raw_fd(pid_fd) },
Pid::from_raw(child as libc::pid_t),
))
}
pub fn send_with_fd<Fd: AsFd>(sock: Fd, bytes: &[u8], fds: &[RawFd]) -> Result<usize, Errno> {
let iov = [io::IoSlice::new(bytes)];
let borrowed: &[BorrowedFd<'_>] =
unsafe { std::slice::from_raw_parts(fds.as_ptr().cast(), fds.len()) };
let cmsgs: &[Cmsg<'_>] = if fds.is_empty() {
&[]
} else {
&[Cmsg::ScmRights(borrowed)]
};
sendmsg::<_, SockaddrStorage>(&sock, &iov, cmsgs, MsgFlags::empty(), None)
}
pub fn recv_with_fd<Fd: AsFd>(
sock: Fd,
bytes: &mut [u8],
fds: &mut [RawFd],
) -> Result<(usize, usize), Errno> {
let mut iov = [io::IoSliceMut::new(bytes)];
let cmsg_siz = RawFd::cmsg_space()
.checked_mul(fds.len())
.ok_or(Errno::EOVERFLOW)?;
let mut cmsg_buf = vec![0u8; cmsg_siz];
let mut hdr = MsgHdr::default();
hdr.set_iov_mut(&mut iov);
if !fds.is_empty() {
hdr.set_control(&mut cmsg_buf);
}
let msg = recvmsg(&sock, &mut hdr, MsgFlags::empty())?;
let mut fd_count = 0;
if let Ok(cmsgs) = msg.cmsgs() {
for cmsg in cmsgs {
if let CmsgOwned::ScmRights(recv_fds) = cmsg {
for fd in recv_fds {
if fd_count < fds.len() {
fds[fd_count] = fd.into_raw_fd();
fd_count = fd_count.checked_add(1).ok_or(Errno::EOVERFLOW)?;
}
}
}
}
}
Ok((msg.bytes, fd_count))
}
pub fn peer_creds<Fd: AsFd>(fd: Fd) -> Result<UnixCredentials, Errno> {
getsockopt(&fd, PeerCredentials)
}
#[expect(clippy::arithmetic_side_effects)]
pub fn nlmsg_align(v: usize) -> usize {
(v + 3) & !3usize
}
#[expect(clippy::arithmetic_side_effects)]
pub fn nla_align(v: usize) -> usize {
(v + 3) & !3usize
}
const SOCK_DIAG_BY_FAMILY: u16 = 20;
#[expect(clippy::cast_possible_truncation)]
const NLMSG_DONE: u16 = libc::NLMSG_DONE as u16;
#[expect(clippy::cast_possible_truncation)]
const NLMSG_ERROR: u16 = libc::NLMSG_ERROR as u16;
const NL_HDR_LEN: usize = 16;
const UD_REQ_LEN: usize = 24;
#[expect(clippy::cast_possible_truncation)]
const NL_MSG_LEN: u32 = (NL_HDR_LEN + UD_REQ_LEN) as u32;
const UNIX_DIAG_VFS: u16 = 1;
const UNIX_DIAG_PEER: u16 = 2;
const UDIAG_SHOW_VFS: u32 = 0x0000_0002;
const UDIAG_SHOW_PEER: u32 = 0x0000_0004;
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn peer_inode(inode: u64) -> Result<u64, Errno> {
let local_ino = inode;
let local_ino32 = (local_ino & 0xffff_ffff) as u32;
let nl = safe_socket(
AddressFamily::Netlink,
SockType::Datagram,
SockFlag::SOCK_CLOEXEC,
libc::NETLINK_SOCK_DIAG,
)?;
let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];
let mut p = 0usize;
req[p..p + 4].copy_from_slice(&NL_MSG_LEN.to_ne_bytes()); p += 4;
req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes()); p += 2;
let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_ROOT | libc::NLM_F_MATCH) as u16;
req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes()); p += 2;
req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes()); p += 4;
req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes()); p += 4;
req[p] = libc::AF_UNIX as u8;
p += 1; req[p] = 0u8;
p += 1; req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
p += 2; req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&local_ino32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&UDIAG_SHOW_PEER.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; assert_eq!(p, req.len());
let mut sent_total = 0usize;
while sent_total < req.len() {
let slice = &req[sent_total..];
let sent = retry_on_eintr(|| write(&nl, slice))?;
if sent == 0 {
return Err(Errno::EIO);
}
sent_total = sent_total.saturating_add(sent);
}
let mut rbuf = [0u8; 0x8000];
loop {
let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
if n == 0 {
return Err(Errno::EIO);
}
let mut off = 0usize;
while off + NL_HDR_LEN <= n {
let nlmsg_len = {
let b: [u8; 4] = rbuf[off..off + 4].try_into().or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b) as usize
};
if nlmsg_len == 0 || off + nlmsg_len > n {
return Err(Errno::EIO);
}
let nlmsg_type = {
let b: [u8; 2] = rbuf[off + 4..off + 6]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nlmsg_type == NLMSG_DONE {
return Ok(local_ino);
} else if nlmsg_type == NLMSG_ERROR {
if nlmsg_len >= NL_HDR_LEN + 4 {
let err_b: [u8; 4] = rbuf[off + NL_HDR_LEN..off + NL_HDR_LEN + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
let nl_err = i32::from_ne_bytes(err_b);
return Err(Errno::from_raw(-nl_err));
} else {
return Err(Errno::EIO);
}
} else if nlmsg_type == SOCK_DIAG_BY_FAMILY {
let payload_off = off + NL_HDR_LEN;
let ud_min = 16usize;
if payload_off + ud_min > off + nlmsg_len {
return Err(Errno::EIO);
}
let found_ino32 = {
let b: [u8; 4] = rbuf[payload_off + 4..payload_off + 8]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u64::from(u32::from_ne_bytes(b))
};
if (found_ino32 & 0xffff_ffff) != (local_ino & 0xffff_ffff) {
off = nlmsg_align(off + nlmsg_len);
continue;
}
let mut attr_off = payload_off + ud_min;
while attr_off + 4 <= off + nlmsg_len {
let nla_len = {
let b: [u8; 2] = rbuf[attr_off..attr_off + 2]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b) as usize
};
let nla_type = {
let b: [u8; 2] = rbuf[attr_off + 2..attr_off + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nla_len < 4 {
break;
}
let payload_start = attr_off + 4;
let payload_len = nla_len - 4;
if payload_start + payload_len > off + nlmsg_len {
break;
}
if nla_type == UNIX_DIAG_PEER && payload_len >= 4 {
let peer_b: [u8; 4] = rbuf[payload_start..payload_start + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
let peer_ino = u64::from(u32::from_ne_bytes(peer_b));
return Ok(peer_ino);
}
attr_off = attr_off.saturating_add(nla_align(nla_len));
}
}
off = nlmsg_align(off + nlmsg_len);
}
}
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn unix_vfs_id(inode: u64) -> Result<(u32, u32), Errno> {
let local_ino32 = (inode & 0xffff_ffff) as u32;
let nl = safe_socket(
AddressFamily::Netlink,
SockType::Datagram,
SockFlag::SOCK_CLOEXEC,
libc::NETLINK_SOCK_DIAG,
)?;
let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];
let mut p = 0usize;
req[p..p + 4].copy_from_slice(&NL_MSG_LEN.to_ne_bytes());
p += 4;
req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes());
p += 2;
let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_ROOT | libc::NLM_F_MATCH) as u16;
req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes());
p += 2;
req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes());
p += 4;
req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4;
req[p] = libc::AF_UNIX as u8;
p += 1;
req[p] = 0u8;
p += 1;
req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
p += 2;
req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
p += 4;
req[p..p + 4].copy_from_slice(&local_ino32.to_ne_bytes());
p += 4;
req[p..p + 4].copy_from_slice(&UDIAG_SHOW_VFS.to_ne_bytes());
p += 4;
req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4;
req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4;
assert_eq!(p, req.len());
let mut sent_total = 0usize;
while sent_total < req.len() {
let sent = retry_on_eintr(|| write(&nl, &req[sent_total..]))?;
if sent == 0 {
return Err(Errno::EIO);
}
sent_total = sent_total.saturating_add(sent);
}
let mut rbuf = [0u8; 0x8000];
loop {
let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
if n == 0 {
return Err(Errno::EIO);
}
let mut off = 0usize;
while off + NL_HDR_LEN <= n {
let nlmsg_len = {
let b: [u8; 4] = rbuf[off..off + 4].try_into().or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b) as usize
};
if nlmsg_len == 0 || off + nlmsg_len > n {
return Err(Errno::EIO);
}
let nlmsg_type = {
let b: [u8; 2] = rbuf[off + 4..off + 6]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nlmsg_type == NLMSG_DONE {
return Err(Errno::ENODATA);
} else if nlmsg_type == NLMSG_ERROR {
if nlmsg_len >= NL_HDR_LEN + 4 {
let err_b: [u8; 4] = rbuf[off + NL_HDR_LEN..off + NL_HDR_LEN + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
return Err(Errno::from_raw(-i32::from_ne_bytes(err_b)));
}
return Err(Errno::EIO);
} else if nlmsg_type == SOCK_DIAG_BY_FAMILY {
let payload_off = off + NL_HDR_LEN;
let ud_min = 16usize;
if payload_off + ud_min > off + nlmsg_len {
return Err(Errno::EIO);
}
let found_ino32 = {
let b: [u8; 4] = rbuf[payload_off + 4..payload_off + 8]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u64::from(u32::from_ne_bytes(b))
};
if (found_ino32 & 0xffff_ffff) != (inode & 0xffff_ffff) {
off = nlmsg_align(off + nlmsg_len);
continue;
}
let mut attr_off = payload_off + ud_min;
while attr_off + 4 <= off + nlmsg_len {
let nla_len = {
let b: [u8; 2] = rbuf[attr_off..attr_off + 2]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b) as usize
};
let nla_type = {
let b: [u8; 2] = rbuf[attr_off + 2..attr_off + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nla_len < 4 {
break;
}
let payload_start = attr_off + 4;
let payload_len = nla_len - 4;
if payload_start + payload_len > off + nlmsg_len {
break;
}
if nla_type == UNIX_DIAG_VFS && payload_len >= 8 {
let vfs_ino = {
let b: [u8; 4] = rbuf[payload_start..payload_start + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b)
};
let vfs_dev = {
let b: [u8; 4] = rbuf[payload_start + 4..payload_start + 8]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b)
};
return Ok((vfs_dev, vfs_ino));
}
attr_off = attr_off.saturating_add(nla_align(nla_len));
}
}
off = nlmsg_align(off + nlmsg_len);
}
}
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn unix_inodes() -> Result<SydHashSet<u64>, Errno> {
let nl = safe_socket(
AddressFamily::Netlink,
SockType::Datagram,
SockFlag::SOCK_CLOEXEC,
libc::NETLINK_SOCK_DIAG,
)?;
let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];
let mut p = 0usize;
req[p..p + 4].copy_from_slice(&NL_MSG_LEN.to_ne_bytes()); p += 4;
req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes()); p += 2;
let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_ROOT | libc::NLM_F_MATCH) as u16;
req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes()); p += 2;
req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes()); p += 4;
req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes()); p += 4;
req[p] = libc::AF_UNIX as u8;
p += 1; req[p] = 0u8;
p += 1; req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
p += 2; req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&UDIAG_SHOW_VFS.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; assert_eq!(p, req.len());
let mut sent_total = 0usize;
while sent_total < req.len() {
let slice = &req[sent_total..];
let sent = retry_on_eintr(|| write(&nl, slice))?;
if sent == 0 {
return Err(Errno::EIO);
}
sent_total = sent_total.saturating_add(sent);
}
let mut rbuf = [0u8; 0x8000];
let mut iset = SydHashSet::default();
'recv: loop {
let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
if n == 0 {
return Err(Errno::EIO);
}
let mut off = 0usize;
while off + NL_HDR_LEN <= n {
let nlmsg_len = {
let b: [u8; 4] = rbuf[off..off + 4].try_into().or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b) as usize
};
if nlmsg_len == 0 || off + nlmsg_len > n {
return Err(Errno::EIO);
}
let nlmsg_type = {
let b: [u8; 2] = rbuf[off + 4..off + 6]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nlmsg_type == NLMSG_DONE {
break 'recv;
} else if nlmsg_type == NLMSG_ERROR {
if nlmsg_len >= NL_HDR_LEN + 4 {
let err_b: [u8; 4] = rbuf[off + NL_HDR_LEN..off + NL_HDR_LEN + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
let nl_err = i32::from_ne_bytes(err_b);
return Err(Errno::from_raw(-nl_err));
} else {
return Err(Errno::EIO);
}
} else if nlmsg_type == SOCK_DIAG_BY_FAMILY {
let payload_off = off + NL_HDR_LEN;
let ud_min = 16usize;
if payload_off + ud_min > off + nlmsg_len {
return Err(Errno::EIO);
}
let ino32 = {
let b: [u8; 4] = rbuf[payload_off + 4..payload_off + 8]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b)
};
let mut has_vfs = false;
let mut attr_off = payload_off + ud_min;
let attrs_end = off + nlmsg_len;
while attr_off + 4 <= attrs_end {
let nla_len = {
let b: [u8; 2] = rbuf[attr_off..attr_off + 2]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b) as usize
};
let nla_type = {
let b: [u8; 2] = rbuf[attr_off + 2..attr_off + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nla_len < 4 {
break;
}
let payload_start = attr_off + 4;
let payload_len = nla_len - 4;
if payload_start > attrs_end || payload_start + payload_len > attrs_end {
break;
}
if nla_type == UNIX_DIAG_VFS {
has_vfs = true;
break;
}
let next = attr_off.saturating_add(nla_align(nla_len));
if next <= attr_off {
break;
} attr_off = next;
}
if has_vfs {
iset.try_reserve(1).or(Err(Errno::ENOMEM))?;
let _ = iset.insert(ino32.into());
}
}
off = nlmsg_align(off + nlmsg_len);
}
}
Ok(iset)
}
pub fn open_static_files() -> Result<(), Errno> {
open_static_root()?;
open_static_proc()?;
open_static_null()
}
pub fn close_static_files() {
close_static_root();
close_static_proc();
close_static_null();
}
#[expect(clippy::disallowed_methods)]
pub fn open_static_root() -> Result<(), Errno> {
if ROOT_FD_OK() {
return Ok(());
}
let mut mask = STATX_MODE;
mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
STATX_MNT_ID_UNIQUE
} else {
STATX_MNT_ID
};
let how = safe_open_how(OFlag::O_PATH | OFlag::O_DIRECTORY, ResolveFlag::empty())
.resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS);
let fd_root = retry_on_eintr(|| openat2(AT_BADFD, "/", how))?;
#[expect(clippy::cast_possible_truncation)]
let (f_mode_root, mnt_id_root) = fstatx(&fd_root, mask)
.map(|stx| (stx.stx_mode & !(libc::S_IFMT as u16), stx.stx_mnt_id))?;
let fd_root = duprand(fd_root.as_raw_fd(), OFlag::O_CLOEXEC)?.into_raw_fd();
info!("ctx": "run", "op": "opendir_root",
"msg": "opened root directory",
"fd": fd_root,
"f_mode": f_mode_root,
"mnt_id": mnt_id_root);
_ROOT_FD.set(fd_root).or(Err(Errno::EAGAIN))?;
_ROOT_F_MODE.set(f_mode_root).or(Err(Errno::EAGAIN))?;
_ROOT_MNT_ID.set(mnt_id_root).or(Err(Errno::EAGAIN))?;
Ok(())
}
#[expect(clippy::disallowed_methods)]
pub fn open_static_proc() -> Result<(), Errno> {
if PROC_FD_OK() {
return Ok(());
}
let mut mask = STATX_MODE;
mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
STATX_MNT_ID_UNIQUE
} else {
STATX_MNT_ID
};
let fd_proc = if ROOT_FD_OK() {
let how = safe_open_how(OFlag::O_RDONLY | OFlag::O_DIRECTORY, ResolveFlag::empty());
retry_on_eintr(|| openat2(ROOT_FILE(), c"proc", how))
} else {
let how = safe_open_how(OFlag::O_RDONLY | OFlag::O_DIRECTORY, ResolveFlag::empty())
.resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS);
retry_on_eintr(|| openat2(AT_BADFD, c"/proc", how))
}?;
if !is_proc(&fd_proc).unwrap_or(false) {
return Err(Errno::ENODEV);
}
#[expect(clippy::cast_possible_truncation)]
let (f_mode_proc, mnt_id_proc) = fstatx(&fd_proc, mask)
.map(|stx| (stx.stx_mode & !(libc::S_IFMT as u16), stx.stx_mnt_id))?;
let fd_proc = duprand(fd_proc.as_raw_fd(), OFlag::O_CLOEXEC)?.into_raw_fd();
info!("ctx": "run", "op": "opendir_proc",
"msg": "opened /proc directory",
"fd": fd_proc,
"f_mode": f_mode_proc,
"mnt_id": mnt_id_proc);
_PROC_FD.set(fd_proc).or(Err(Errno::EAGAIN))?;
_PROC_F_MODE.set(f_mode_proc).or(Err(Errno::EAGAIN))?;
_PROC_MNT_ID.set(mnt_id_proc).or(Err(Errno::EAGAIN))?;
Ok(())
}
#[expect(clippy::disallowed_methods)]
pub fn open_static_null() -> Result<(), Errno> {
if NULL_FD_OK() {
return Ok(());
}
let mut mask = STATX_MODE;
mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
STATX_MNT_ID_UNIQUE
} else {
STATX_MNT_ID
};
let fd_null = if ROOT_FD_OK() {
let how = safe_open_how(OFlag::O_PATH, ResolveFlag::empty());
retry_on_eintr(|| openat2(ROOT_FILE(), c"dev/null", how))
} else {
let how = safe_open_how(OFlag::O_PATH, ResolveFlag::empty())
.resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS);
retry_on_eintr(|| openat2(AT_BADFD, c"/dev/null", how))
}?;
if !is_dev_null(&fd_null).unwrap_or(false) {
return Err(Errno::ENODEV);
}
#[expect(clippy::cast_possible_truncation)]
let (f_mode_null, mnt_id_null) = fstatx(&fd_null, mask)
.map(|stx| (stx.stx_mode & !(libc::S_IFMT as u16), stx.stx_mnt_id))?;
let fd_null = duprand(fd_null.as_raw_fd(), OFlag::O_CLOEXEC)?.into_raw_fd();
info!("ctx": "run", "op": "opendev_null",
"msg": "opened /dev/null",
"fd": fd_null,
"f_mode": f_mode_null,
"mnt_id": mnt_id_null);
_NULL_FD.set(fd_null).or(Err(Errno::EAGAIN))?;
_NULL_F_MODE.set(f_mode_null).or(Err(Errno::EAGAIN))?;
_NULL_MNT_ID.set(mnt_id_null).or(Err(Errno::EAGAIN))?;
Ok(())
}
pub fn close_static_root() {
if let Some(fd) = _ROOT_FD.get() {
let _ = close(*fd);
}
}
pub fn close_static_proc() {
if let Some(fd) = _PROC_FD.get() {
let _ = close(*fd);
}
}
pub fn close_static_null() {
if let Some(fd) = _NULL_FD.get() {
let _ = close(*fd);
}
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn ROOT_FD() -> RawFd {
*_ROOT_FD.get().unwrap()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn ROOT_F_MODE() -> u16 {
*_ROOT_F_MODE.get().unwrap()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn ROOT_MNT_ID() -> u64 {
*_ROOT_MNT_ID.get().unwrap()
}
#[expect(non_snake_case)]
#[inline(always)]
pub fn ROOT_FILE() -> BorrowedFd<'static> {
unsafe { BorrowedFd::borrow_raw(ROOT_FD()) }
}
#[expect(non_snake_case)]
#[inline(always)]
pub fn ROOT_FD_OK() -> bool {
_ROOT_FD.get().is_some()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn PROC_FD() -> RawFd {
*_PROC_FD.get().unwrap()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn PROC_F_MODE() -> u16 {
*_PROC_F_MODE.get().unwrap()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn PROC_MNT_ID() -> u64 {
*_PROC_MNT_ID.get().unwrap()
}
#[expect(non_snake_case)]
#[inline(always)]
pub fn PROC_FILE() -> BorrowedFd<'static> {
unsafe { BorrowedFd::borrow_raw(PROC_FD()) }
}
#[expect(non_snake_case)]
#[inline(always)]
pub fn PROC_FD_OK() -> bool {
_PROC_FD.get().is_some()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn NULL_FD() -> RawFd {
*_NULL_FD.get().unwrap()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn NULL_F_MODE() -> u16 {
*_NULL_F_MODE.get().unwrap()
}
#[expect(clippy::disallowed_methods)]
#[expect(non_snake_case)]
#[inline(always)]
pub fn NULL_MNT_ID() -> u64 {
*_NULL_MNT_ID.get().unwrap()
}
#[expect(non_snake_case)]
#[inline(always)]
pub fn NULL_FILE() -> BorrowedFd<'static> {
unsafe { BorrowedFd::borrow_raw(NULL_FD()) }
}
#[expect(non_snake_case)]
#[inline(always)]
pub fn NULL_FD_OK() -> bool {
_NULL_FD.get().is_some()
}
static _ROOT_FD: OnceLock<RawFd> = OnceLock::new();
static _ROOT_F_MODE: OnceLock<u16> = OnceLock::new();
static _ROOT_MNT_ID: OnceLock<u64> = OnceLock::new();
static _PROC_FD: OnceLock<RawFd> = OnceLock::new();
static _PROC_F_MODE: OnceLock<u16> = OnceLock::new();
static _PROC_MNT_ID: OnceLock<u64> = OnceLock::new();
static _NULL_FD: OnceLock<RawFd> = OnceLock::new();
static _NULL_F_MODE: OnceLock<u16> = OnceLock::new();
static _NULL_MNT_ID: OnceLock<u64> = OnceLock::new();
#[cfg(test)]
mod tests {
use std::{
fs::{File, OpenOptions},
io::ErrorKind,
os::unix::{
ffi::OsStrExt,
fs::OpenOptionsExt,
net::{UnixListener, UnixStream},
},
sync::mpsc,
thread,
time::Duration,
};
use libc::c_uint;
use nix::{
fcntl::{open, AT_FDCWD},
sys::socket::{accept, bind, connect, listen, Backlog, SockFlag, UnixAddr},
unistd::{dup, pipe, read, write},
};
use tempfile::NamedTempFile;
use super::*;
use crate::{
compat::SockType,
confine::check_unix_diag,
cookie::{safe_socket, safe_socketpair},
};
fn tempdir() -> Result<XPathBuf, Box<dyn std::error::Error>> {
let tmp = tempfile::Builder::new()
.disable_cleanup(true)
.tempdir_in(".")?;
let _ = OpenOptions::new()
.write(true)
.create(true)
.mode(0o600)
.open(tmp.path().join("test"))?;
Ok(tmp
.path()
.to_path_buf()
.file_name()
.unwrap()
.as_bytes()
.into())
}
#[test]
fn test_nlmsg_align_1() {
assert_eq!(nlmsg_align(0), 0);
}
#[test]
fn test_nlmsg_align_2() {
assert_eq!(nlmsg_align(1), 4);
}
#[test]
fn test_nlmsg_align_3() {
assert_eq!(nlmsg_align(4), 4);
}
#[test]
fn test_nlmsg_align_4() {
assert_eq!(nlmsg_align(5), 8);
}
#[test]
fn test_nla_align_1() {
assert_eq!(nla_align(0), 0);
}
#[test]
fn test_nla_align_2() {
assert_eq!(nla_align(1), 4);
}
#[test]
fn test_nla_align_3() {
assert_eq!(nla_align(4), 4);
}
#[test]
fn test_nla_align_4() {
assert_eq!(nla_align(5), 8);
}
#[test]
fn test_to_fd_1() {
assert_eq!(to_fd(0), Ok(0));
}
#[test]
fn test_to_fd_2() {
assert_eq!(to_fd(5), Ok(5));
}
#[test]
fn test_to_fd_3() {
let at_fdcwd = libc::AT_FDCWD as u64;
assert_eq!(to_fd(at_fdcwd), Err(Errno::EBADF));
}
#[test]
fn test_to_fd_4() {
let neg = (-2i32) as u64;
assert_eq!(to_fd(neg), Err(Errno::EBADF));
}
#[test]
fn test_to_valid_fd_1() {
assert!(is_valid_fd(0));
}
#[test]
fn test_to_valid_fd_2() {
assert!(is_valid_fd(42));
}
#[test]
fn test_to_valid_fd_3() {
assert!(!is_valid_fd(u64::MAX));
}
#[test]
fn test_to_valid_fd_4() {
let at_fdcwd = libc::AT_FDCWD as u64;
assert!(!is_valid_fd(at_fdcwd));
}
#[test]
fn test_to_valid_fd_5() {
assert_eq!(to_valid_fd(0), Ok(0));
}
#[test]
fn test_to_valid_fd_6() {
assert_eq!(to_valid_fd(3), Ok(3));
}
#[test]
fn test_to_valid_fd_7() {
let at_fdcwd = libc::AT_FDCWD as u64;
assert_eq!(to_valid_fd(at_fdcwd), Ok(libc::AT_FDCWD));
}
#[test]
fn test_to_valid_fd_8() {
let neg = (-1i32) as u64;
assert_eq!(to_valid_fd(neg), Err(Errno::EBADF));
}
#[test]
fn test_parse_fd_1() {
let path = XPath::from_bytes(b"0");
assert_eq!(parse_fd(path).unwrap(), 0);
}
#[test]
fn test_parse_fd_2() {
let path = XPath::from_bytes(b"42");
assert_eq!(parse_fd(path).unwrap(), 42);
}
#[test]
fn test_parse_fd_3() {
let path = XPath::from_bytes(b"2147483647");
assert_eq!(parse_fd(path).unwrap(), i32::MAX);
}
#[test]
fn test_parse_fd_4() {
let path = XPath::from_bytes(b"not_a_number");
assert_eq!(parse_fd(path).unwrap_err(), Errno::EBADF);
}
#[test]
fn test_parse_fd_5() {
let path = XPath::from_bytes(b"");
assert_eq!(parse_fd(path).unwrap_err(), Errno::EBADF);
}
#[test]
fn test_parse_fd_6() {
let path = XPath::from_bytes(b"/dev/null");
assert_eq!(parse_fd(path).unwrap_err(), Errno::EBADF);
}
#[test]
fn test_parse_fd_7() {
let path = XPath::from_bytes(b"-1");
assert_eq!(parse_fd(path).unwrap(), -1);
}
#[test]
fn test_is_dev_null_1() {
let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
assert!(is_dev_null(&file).unwrap());
}
#[test]
fn test_is_dev_null_2() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
assert!(!is_dev_null(&file).unwrap());
}
#[test]
fn test_is_file_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
assert!(is_file(&file).unwrap());
}
#[test]
fn test_is_file_2() {
let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
assert!(!is_file(&file).unwrap());
}
#[test]
fn test_is_empty_file_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
assert!(is_empty_file(&file).unwrap());
}
#[test]
fn test_is_empty_file_2() {
use std::io::Write;
let mut temp = NamedTempFile::new().unwrap();
temp.write_all(b"data").unwrap();
temp.flush().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
assert!(!is_empty_file(&file).unwrap());
}
#[test]
fn test_is_empty_file_3() {
let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
assert!(!is_empty_file(&file).unwrap());
}
#[test]
fn test_set_cloexec_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
set_cloexec(&file, true).unwrap();
let flags = fcntl(&file, FcntlArg::F_GETFD).unwrap();
assert!(flags & FdFlag::FD_CLOEXEC.bits() != 0);
}
#[test]
fn test_set_cloexec_2() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
set_cloexec(&file, true).unwrap();
set_cloexec(&file, false).unwrap();
let flags = fcntl(&file, FcntlArg::F_GETFD).unwrap();
assert!(flags & FdFlag::FD_CLOEXEC.bits() == 0);
}
#[test]
fn test_get_nonblock() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
assert!(!get_nonblock(&file).unwrap());
}
#[test]
fn test_set_nonblock_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
set_nonblock(&file, true).unwrap();
assert!(get_nonblock(&file).unwrap());
}
#[test]
fn test_set_nonblock_2() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
set_nonblock(&file, true).unwrap();
set_nonblock(&file, false).unwrap();
assert!(!get_nonblock(&file).unwrap());
}
#[test]
fn test_set_append_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
set_append(&file, true).unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_APPEND));
}
#[test]
fn test_set_append_2() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
set_append(&file, true).unwrap();
set_append(&file, false).unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(!flags.contains(OFlag::O_APPEND));
}
#[test]
fn test_fd_status_flags_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_2() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_3() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_RDWR));
assert!(!flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_4() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let owned_fd = unsafe { SafeOwnedFd::from_raw_fd(file.as_raw_fd()) };
std::mem::forget(file);
let flags = fd_status_flags(&owned_fd).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_5() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let owned_fd = unsafe { SafeOwnedFd::from_raw_fd(file.as_raw_fd()) };
std::mem::forget(file);
let flags = fd_status_flags(&owned_fd).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_6() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let owned_fd = unsafe { SafeOwnedFd::from_raw_fd(file.as_raw_fd()) };
std::mem::forget(file);
let flags = fd_status_flags(&owned_fd).unwrap();
assert!(flags.contains(OFlag::O_RDWR));
assert!(!flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_7() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let borrowed_fd = file.as_fd();
let flags = fd_status_flags(borrowed_fd).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_8() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let borrowed_fd = file.as_fd();
let flags = fd_status_flags(borrowed_fd).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_9() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let borrowed_fd = file.as_fd();
let flags = fd_status_flags(borrowed_fd).unwrap();
assert!(flags.contains(OFlag::O_RDWR));
assert!(!flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_10() {
let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_11() {
let file = OpenOptions::new().write(true).open("/dev/null").unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_12() {
let file = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/null")
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_RDWR));
assert!(!flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_13() {
let (read_fd, _) = pipe().unwrap();
let flags = fd_status_flags(&read_fd).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_14() {
let (_, write_fd) = pipe().unwrap();
let flags = fd_status_flags(&write_fd).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_15() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.write(true)
.append(true)
.open(temp.path())
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(flags.contains(OFlag::O_APPEND));
}
#[test]
fn test_fd_status_flags_16() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.write(true)
.create(true)
.open(temp.path())
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_17() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.write(true)
.truncate(true)
.open(temp.path())
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_18() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.append(true)
.open(temp.path())
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_RDWR));
assert!(flags.contains(OFlag::O_APPEND));
}
#[test]
fn test_fd_status_flags_19() {
let temp = NamedTempFile::new().unwrap();
std::fs::remove_file(temp.path()).unwrap();
let file = OpenOptions::new()
.write(true)
.create_new(true)
.open(temp.path())
.unwrap();
let flags = fd_status_flags(&file).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_20() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let file_ref = &file;
let flags = fd_status_flags(file_ref).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_21() {
let temp = NamedTempFile::new().unwrap();
let mut file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let file_ref = &mut file;
let flags = fd_status_flags(file_ref).unwrap();
assert!(flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_22() {
let temp = NamedTempFile::new().unwrap();
let file = Box::new(OpenOptions::new().read(true).open(temp.path()).unwrap());
let flags = fd_status_flags(&file).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_23() {
use std::sync::Arc;
let temp = NamedTempFile::new().unwrap();
let file = Arc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());
let flags = fd_status_flags(&file).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_24() {
use std::rc::Rc;
let temp = NamedTempFile::new().unwrap();
let file = Rc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());
let flags = fd_status_flags(&file).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_fd_status_flags_25() {
let result = fd_status_flags(AT_BADFD);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Errno::EBADF);
}
#[test]
fn test_fd_status_flags_26() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let flags1 = fd_status_flags(&file).unwrap();
let flags2 = fd_status_flags(&file).unwrap();
let flags3 = fd_status_flags(&file).unwrap();
assert_eq!(flags1, flags2);
assert_eq!(flags2, flags3);
}
#[test]
fn test_fd_status_flags_27() {
let temp = NamedTempFile::new().unwrap();
let file1 = OpenOptions::new().write(true).open(temp.path()).unwrap();
let file2 = OpenOptions::new().write(true).open("/dev/null").unwrap();
let flags1 = fd_status_flags(&file1).unwrap();
let flags2 = fd_status_flags(&file2).unwrap();
assert!(flags1.contains(OFlag::O_WRONLY));
assert!(flags2.contains(OFlag::O_WRONLY));
}
#[test]
fn test_fd_status_flags_28() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let duped_fd = dup(&file).unwrap();
let flags = fd_status_flags(&duped_fd).unwrap();
assert!(!flags.contains(OFlag::O_WRONLY));
assert!(!flags.contains(OFlag::O_RDWR));
}
#[test]
fn test_is_writable_fd_1() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_2() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_3() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_4() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let owned_fd = unsafe { SafeOwnedFd::from_raw_fd(file.as_raw_fd()) };
std::mem::forget(file);
let result = is_writable_fd(&owned_fd).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_5() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let owned_fd = unsafe { SafeOwnedFd::from_raw_fd(file.as_raw_fd()) };
std::mem::forget(file);
let result = is_writable_fd(&owned_fd).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_6() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let owned_fd = unsafe { SafeOwnedFd::from_raw_fd(file.as_raw_fd()) };
std::mem::forget(file);
let result = is_writable_fd(&owned_fd).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_7() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let borrowed_fd = file.as_fd();
let result = is_writable_fd(borrowed_fd).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_8() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let borrowed_fd = file.as_fd();
let result = is_writable_fd(borrowed_fd).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_9() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let borrowed_fd = file.as_fd();
let result = is_writable_fd(borrowed_fd).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_10() {
let file = OpenOptions::new().read(true).open("/dev/null").unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_11() {
let file = OpenOptions::new().write(true).open("/dev/null").unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_12() {
let file = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/null")
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_13() {
let (read_fd, _) = pipe().unwrap();
let result = is_writable_fd(&read_fd).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_14() {
let (_, write_fd) = pipe().unwrap();
let result = is_writable_fd(&write_fd).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_15() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.write(true)
.append(true)
.open(temp.path())
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_16() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.write(true)
.create(true)
.open(temp.path())
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_17() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.write(true)
.truncate(true)
.open(temp.path())
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_18() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.append(true)
.open(temp.path())
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_19() {
let temp = NamedTempFile::new().unwrap();
std::fs::remove_file(temp.path()).unwrap();
let file = OpenOptions::new()
.write(true)
.create_new(true)
.open(temp.path())
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_20() {
let temp = NamedTempFile::new().unwrap();
let file = open(
temp.path(),
OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
Mode::empty(),
)
.map(File::from)
.unwrap();
let result = is_writable_fd(&file).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_21() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let file_ref = &file;
let result = is_writable_fd(file_ref).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_22() {
let temp = NamedTempFile::new().unwrap();
let mut file = OpenOptions::new().write(true).open(temp.path()).unwrap();
let file_ref = &mut file;
let result = is_writable_fd(file_ref).unwrap();
assert!(result);
}
#[test]
fn test_is_writable_fd_23() {
let temp = NamedTempFile::new().unwrap();
let file = Box::new(OpenOptions::new().read(true).open(temp.path()).unwrap());
let result = is_writable_fd(&file).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_24() {
use std::sync::Arc;
let temp = NamedTempFile::new().unwrap();
let file = Arc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());
let result = is_writable_fd(&file).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_25() {
use std::rc::Rc;
let temp = NamedTempFile::new().unwrap();
let file = Rc::new(OpenOptions::new().read(true).open(temp.path()).unwrap());
let result = is_writable_fd(&file).unwrap();
assert!(!result);
}
#[test]
fn test_is_writable_fd_26() {
let result = is_writable_fd(AT_BADFD);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Errno::EBADF);
}
#[test]
fn test_is_writable_fd_27() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(temp.path())
.unwrap();
let result1 = is_writable_fd(&file).unwrap();
let result2 = is_writable_fd(&file).unwrap();
let result3 = is_writable_fd(&file).unwrap();
assert_eq!(result1, result2);
assert_eq!(result2, result3);
}
#[test]
fn test_is_writable_fd_28() {
let temp = NamedTempFile::new().unwrap();
let file1 = OpenOptions::new().write(true).open(temp.path()).unwrap();
let file2 = OpenOptions::new().write(true).open("/dev/null").unwrap();
let result1 = is_writable_fd(&file1).unwrap();
let result2 = is_writable_fd(&file2).unwrap();
assert!(result1);
assert!(result2);
}
#[test]
fn test_is_writable_fd_29() {
let temp = NamedTempFile::new().unwrap();
let file = OpenOptions::new().read(true).open(temp.path()).unwrap();
let duped_fd = dup(&file).unwrap();
let result = is_writable_fd(&duped_fd).unwrap();
assert!(!result);
}
#[test]
fn test_closeall_1() {
let (r1, w1) = pipe().unwrap();
let (r2, w2) = pipe().unwrap();
let (r3, w3) = pipe().unwrap();
let fds = vec![
r1.as_raw_fd() as c_uint,
w1.as_raw_fd() as c_uint,
r2.as_raw_fd() as c_uint,
w2.as_raw_fd() as c_uint,
r3.as_raw_fd() as c_uint,
w3.as_raw_fd() as c_uint,
];
std::mem::forget(r1);
std::mem::forget(w1);
std::mem::forget(r2);
std::mem::forget(w2);
std::mem::forget(r3);
std::mem::forget(w3);
let mut sorted_fds = fds.clone();
sorted_fds.sort();
assert!(closeall(&sorted_fds).is_ok());
}
#[test]
fn test_closeall_2() {
let (r, w) = pipe().unwrap();
let r_fd = r.as_raw_fd() as c_uint;
let w_fd = w.as_raw_fd() as c_uint;
let mut unsorted = vec![w_fd, r_fd];
if unsorted[0] < unsorted[1] {
unsorted.swap(0, 1);
}
assert_eq!(closeall(&unsorted), Err(Errno::EINVAL));
let dup = vec![r_fd, r_fd];
assert_eq!(closeall(&dup), Err(Errno::EINVAL));
}
#[test]
fn test_send_recv_with_fd_1() {
let (l, r) = UnixStream::pair().unwrap();
let (read_fd, _write_fd) = pipe().unwrap();
let sent_bytes = b"hello";
let sent_fds = [read_fd.as_raw_fd()];
let n = send_with_fd(&l, sent_bytes, &sent_fds).unwrap();
assert_eq!(n, sent_bytes.len());
let mut recv_bytes = [0u8; 64];
let mut recv_fds = [0i32; 4];
let (nbytes, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
assert_eq!(nbytes, sent_bytes.len());
assert_eq!(nfds, 1);
assert_eq!(&recv_bytes[..nbytes], sent_bytes);
assert_ne!(recv_fds[0], sent_fds[0]);
}
#[test]
fn test_send_recv_with_fd_2() {
let (l, r) = UnixStream::pair().unwrap();
let (r1, w1) = pipe().unwrap();
let (r2, w2) = pipe().unwrap();
let sent_bytes = b"multi";
let sent_fds = [
r1.as_raw_fd(),
w1.as_raw_fd(),
r2.as_raw_fd(),
w2.as_raw_fd(),
];
let n = send_with_fd(&l, sent_bytes, &sent_fds).unwrap();
assert_eq!(n, sent_bytes.len());
let mut recv_bytes = [0u8; 64];
let mut recv_fds = [0i32; 8];
let (nbytes, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
assert_eq!(nbytes, sent_bytes.len());
assert_eq!(nfds, 4);
assert_eq!(&recv_bytes[..nbytes], sent_bytes);
}
#[test]
fn test_send_recv_with_fd_3() {
let (l, r) = UnixStream::pair().unwrap();
let sent_bytes = b"data only";
let n = send_with_fd(&l, sent_bytes, &[]).unwrap();
assert_eq!(n, sent_bytes.len());
let mut recv_bytes = [0u8; 64];
let mut recv_fds = [0i32; 4];
let (nbytes, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
assert_eq!(nbytes, sent_bytes.len());
assert_eq!(nfds, 0);
assert_eq!(&recv_bytes[..nbytes], sent_bytes);
}
#[test]
fn test_send_recv_with_fd_4() {
let (l, _r) = UnixStream::pair().unwrap();
let sent_bytes = b"bad";
let bad_fds = [RawFd::MAX];
let result = send_with_fd(&l, sent_bytes, &bad_fds);
assert!(result.is_err());
}
#[test]
fn test_send_recv_with_fd_5() {
let (l, r) = UnixStream::pair().unwrap();
let (pipe_r, pipe_w) = pipe().unwrap();
let sent_bytes = b"x";
let sent_fds = [pipe_w.as_raw_fd()];
send_with_fd(&l, sent_bytes, &sent_fds).unwrap();
let mut recv_bytes = [0u8; 4];
let mut recv_fds = [0i32; 2];
let (_, nfds) = recv_with_fd(&r, &mut recv_bytes, &mut recv_fds).unwrap();
assert_eq!(nfds, 1);
let recv_pipe_w = unsafe { SafeOwnedFd::from_raw_fd(recv_fds[0]) };
write(&recv_pipe_w, b"hello").unwrap();
drop(recv_pipe_w);
drop(pipe_w);
let mut buf = [0u8; 16];
let n = read(pipe_r, &mut buf).unwrap();
assert_eq!(&buf[..n], b"hello");
}
#[test]
fn test_peer_inode_1() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let (a_fd, b_fd) = safe_socketpair(
AddressFamily::Unix,
SockType::Stream,
0,
SockFlag::SOCK_CLOEXEC,
)
.unwrap();
let b_ino = fd_inode(&b_fd).unwrap();
let expected = (b_ino & 0xffff_ffff) as u64;
let got = fd_inode(&a_fd).and_then(peer_inode).unwrap();
assert_eq!(got, expected);
}
#[test]
fn test_peer_inode_2() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let td = tempdir().unwrap();
let sock_path = td.as_path().join("peer_inode.sock");
let (tx_ready, rx_ready) = mpsc::channel::<()>();
let (tx_peer, rx_peer) = mpsc::channel::<u64>();
let sock_path_clone = sock_path.clone();
let server = thread::spawn(move || {
let listener = UnixListener::bind(&sock_path_clone).unwrap();
tx_ready.send(()).unwrap();
let (accepted, _addr) = listener.accept().unwrap();
let peer = fd_inode(&accepted).and_then(peer_inode).unwrap();
tx_peer.send(peer).unwrap();
});
rx_ready.recv_timeout(Duration::from_secs(10)).unwrap();
let client = loop {
match UnixStream::connect(&sock_path) {
Ok(s) => break s,
Err(e) => {
if e.kind() == ErrorKind::NotFound || e.kind() == ErrorKind::ConnectionRefused {
thread::sleep(Duration::from_millis(10));
continue;
} else {
panic!("connect failed: {e:?}");
}
}
}
};
let client_ino = fd_inode(&client).unwrap();
let expected = (client_ino & 0xffff_ffff) as u64;
let got = rx_peer.recv_timeout(Duration::from_secs(10)).unwrap();
assert_eq!(got, expected);
server.join().unwrap();
}
#[test]
fn test_peer_inode_3() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let name = b"peer_inode_test_abstract_12345";
let srv_fd = safe_socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
0,
)
.unwrap();
let sockaddr = UnixAddr::new_abstract(name).unwrap();
bind(srv_fd.as_raw_fd(), &sockaddr).unwrap();
listen(&srv_fd, Backlog::new(1).unwrap()).unwrap();
let cli_fd = safe_socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
0,
)
.unwrap();
connect(cli_fd.as_raw_fd(), &sockaddr).unwrap();
let acc_fd = accept(srv_fd.as_raw_fd()).unwrap();
let acc_fd = unsafe { SafeOwnedFd::from_raw_fd(acc_fd) };
let cli_ino = fd_inode(&cli_fd).unwrap();
let expected = (cli_ino & 0xffff_ffff) as u64;
let got = fd_inode(&acc_fd).and_then(peer_inode).unwrap();
assert_eq!(got, expected);
}
#[test]
fn test_peer_inode_4() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let (a_fd, b_fd) = safe_socketpair(
AddressFamily::Unix,
SockType::Stream,
0,
SockFlag::SOCK_CLOEXEC,
)
.unwrap();
let a_ino = fd_inode(&a_fd).unwrap();
let b_ino = fd_inode(&b_fd).unwrap();
let expected_a = (a_ino & 0xffff_ffff) as u64;
let expected_b = (b_ino & 0xffff_ffff) as u64;
let got_from_a = peer_inode(a_ino).unwrap();
let got_from_b = peer_inode(b_ino).unwrap();
assert_eq!(got_from_a, expected_b);
assert_eq!(got_from_b, expected_a);
}
#[test]
fn test_unix_vfs_id_1() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let td = tempdir().unwrap();
let sock_path = td.as_path().join("vfs_test.sock");
let srv_fd = safe_socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
0,
)
.unwrap();
let sockaddr = UnixAddr::new(&sock_path).unwrap();
bind(srv_fd.as_raw_fd(), &sockaddr).unwrap();
listen(&srv_fd, Backlog::new(1).unwrap()).unwrap();
let sockfs_ino = fd_inode(&srv_fd).unwrap();
let (vfs_dev, vfs_ino) = unix_vfs_id(sockfs_ino).unwrap();
let stx = statx(AT_FDCWD, sock_path.as_path(), 0, STATX_INO).unwrap();
let expected_ino = stx.stx_ino as u32;
let stat_major = stx.stx_dev_major;
let stat_minor = stx.stx_dev_minor;
let vfs_major = vfs_dev >> 20;
let vfs_minor = vfs_dev & 0xfffff;
assert_eq!(vfs_ino, expected_ino, "VFS inode mismatch");
assert_eq!(vfs_major, stat_major, "VFS device major mismatch");
assert_eq!(vfs_minor, stat_minor, "VFS device minor mismatch");
}
#[test]
fn test_unix_vfs_id_2() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let (fd_a, _fd_b) = safe_socketpair(
AddressFamily::Unix,
SockType::Stream,
0,
SockFlag::SOCK_CLOEXEC,
)
.unwrap();
let ino_a = fd_inode(&fd_a).unwrap();
assert_eq!(unix_vfs_id(ino_a), Err(Errno::ENODATA));
}
#[test]
fn test_unix_vfs_id_3() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let name = b"unix_vfs_id_test_abstract_12345";
let srv_fd = safe_socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
0,
)
.unwrap();
let sockaddr = UnixAddr::new_abstract(name).unwrap();
bind(srv_fd.as_raw_fd(), &sockaddr).unwrap();
listen(&srv_fd, Backlog::new(1).unwrap()).unwrap();
let srv_ino = fd_inode(&srv_fd).unwrap();
assert_eq!(unix_vfs_id(srv_ino), Err(Errno::ENODATA));
}
#[test]
fn test_unix_vfs_id_4() {
if !check_unix_diag().unwrap_or(false) {
eprintln!("UNIX socket diagnostics are not supported, skipping!");
return;
}
let td_a = tempdir().unwrap();
let td_b = tempdir().unwrap();
let path_a = td_a.as_path().join("socket");
let path_b = td_b.as_path().join("socket");
let sock_a = safe_socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
0,
)
.unwrap();
let sock_b = safe_socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
0,
)
.unwrap();
let addr_a = UnixAddr::new(&path_a).unwrap();
let addr_b = UnixAddr::new(&path_b).unwrap();
bind(sock_a.as_raw_fd(), &addr_a).unwrap();
bind(sock_b.as_raw_fd(), &addr_b).unwrap();
listen(&sock_a, Backlog::new(1).unwrap()).unwrap();
listen(&sock_b, Backlog::new(1).unwrap()).unwrap();
let ino_a = fd_inode(&sock_a).unwrap();
let ino_b = fd_inode(&sock_b).unwrap();
let vfs_a = unix_vfs_id(ino_a).unwrap();
let vfs_b = unix_vfs_id(ino_b).unwrap();
assert_ne!(vfs_a, vfs_b);
}
}