summaryrefslogtreecommitdiff
path: root/src/backend/libc
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/libc')
-rw-r--r--src/backend/libc/c.rs468
-rw-r--r--src/backend/libc/conv.rs255
-rw-r--r--src/backend/libc/event/epoll.rs506
-rw-r--r--src/backend/libc/event/mod.rs9
-rw-r--r--src/backend/libc/event/poll_fd.rs142
-rw-r--r--src/backend/libc/event/syscalls.rs193
-rw-r--r--src/backend/libc/event/types.rs37
-rw-r--r--src/backend/libc/event/windows_syscalls.rs16
-rw-r--r--src/backend/libc/fs/dir.rs423
-rw-r--r--src/backend/libc/fs/inotify.rs131
-rw-r--r--src/backend/libc/fs/makedev.rs138
-rw-r--r--src/backend/libc/fs/mod.rs23
-rw-r--r--src/backend/libc/fs/syscalls.rs2533
-rw-r--r--src/backend/libc/fs/types.rs1167
-rw-r--r--src/backend/libc/io/errno.rs1052
-rw-r--r--src/backend/libc/io/mod.rs6
-rw-r--r--src/backend/libc/io/syscalls.rs340
-rw-r--r--src/backend/libc/io/types.rs65
-rw-r--r--src/backend/libc/io/windows_syscalls.rs30
-rw-r--r--src/backend/libc/io_uring/mod.rs1
-rw-r--r--src/backend/libc/io_uring/syscalls.rs70
-rw-r--r--src/backend/libc/mm/mod.rs2
-rw-r--r--src/backend/libc/mm/syscalls.rs244
-rw-r--r--src/backend/libc/mm/types.rs477
-rw-r--r--src/backend/libc/mod.rs215
-rw-r--r--src/backend/libc/mount/mod.rs2
-rw-r--r--src/backend/libc/mount/syscalls.rs272
-rw-r--r--src/backend/libc/mount/types.rs340
-rw-r--r--src/backend/libc/net/addr.rs245
-rw-r--r--src/backend/libc/net/ext.rs135
-rw-r--r--src/backend/libc/net/mod.rs18
-rw-r--r--src/backend/libc/net/msghdr.rs160
-rw-r--r--src/backend/libc/net/netdevice.rs52
-rw-r--r--src/backend/libc/net/read_sockaddr.rs332
-rw-r--r--src/backend/libc/net/send_recv.rs103
-rw-r--r--src/backend/libc/net/sockopt.rs1233
-rw-r--r--src/backend/libc/net/syscalls.rs621
-rw-r--r--src/backend/libc/net/write_sockaddr.rs125
-rw-r--r--src/backend/libc/param/auxv.rs54
-rw-r--r--src/backend/libc/param/mod.rs1
-rw-r--r--src/backend/libc/pid/mod.rs1
-rw-r--r--src/backend/libc/pid/syscalls.rs14
-rw-r--r--src/backend/libc/pipe/mod.rs2
-rw-r--r--src/backend/libc/pipe/syscalls.rs125
-rw-r--r--src/backend/libc/pipe/types.rs103
-rw-r--r--src/backend/libc/prctl/mod.rs1
-rw-r--r--src/backend/libc/prctl/syscalls.rs14
-rw-r--r--src/backend/libc/process/cpu_set.rs50
-rw-r--r--src/backend/libc/process/mod.rs7
-rw-r--r--src/backend/libc/process/syscalls.rs715
-rw-r--r--src/backend/libc/process/types.rs172
-rw-r--r--src/backend/libc/process/wait.rs9
-rw-r--r--src/backend/libc/pty/mod.rs1
-rw-r--r--src/backend/libc/pty/syscalls.rs108
-rw-r--r--src/backend/libc/rand/mod.rs2
-rw-r--r--src/backend/libc/rand/syscalls.rs18
-rw-r--r--src/backend/libc/rand/types.rs24
-rw-r--r--src/backend/libc/shm/mod.rs2
-rw-r--r--src/backend/libc/shm/syscalls.rs25
-rw-r--r--src/backend/libc/shm/types.rs30
-rw-r--r--src/backend/libc/system/mod.rs3
-rw-r--r--src/backend/libc/system/syscalls.rs67
-rw-r--r--src/backend/libc/system/types.rs8
-rw-r--r--src/backend/libc/termios/mod.rs1
-rw-r--r--src/backend/libc/termios/syscalls.rs403
-rw-r--r--src/backend/libc/thread/futex.rs43
-rw-r--r--src/backend/libc/thread/mod.rs4
-rw-r--r--src/backend/libc/thread/syscalls.rs523
-rw-r--r--src/backend/libc/time/mod.rs3
-rw-r--r--src/backend/libc/time/syscalls.rs452
-rw-r--r--src/backend/libc/time/types.rs177
-rw-r--r--src/backend/libc/ugid/mod.rs1
-rw-r--r--src/backend/libc/ugid/syscalls.rs42
-rw-r--r--src/backend/libc/winsock_c.rs59
74 files changed, 15445 insertions, 0 deletions
diff --git a/src/backend/libc/c.rs b/src/backend/libc/c.rs
new file mode 100644
index 0000000..5af9157
--- /dev/null
+++ b/src/backend/libc/c.rs
@@ -0,0 +1,468 @@
+//! Libc and supplemental types and constants.
+
+#![allow(unused_imports)]
+
+// Import everything from libc, but we'll add some stuff and override some
+// things below.
+pub(crate) use libc::*;
+
+/// `PROC_SUPER_MAGIC`—The magic number for the procfs filesystem.
+#[cfg(all(linux_kernel, target_env = "musl"))]
+pub(crate) const PROC_SUPER_MAGIC: u32 = 0x0000_9fa0;
+
+/// `NFS_SUPER_MAGIC`—The magic number for the NFS filesystem.
+#[cfg(all(linux_kernel, target_env = "musl"))]
+pub(crate) const NFS_SUPER_MAGIC: u32 = 0x0000_6969;
+
+#[cfg(feature = "process")]
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) const EXIT_SIGNALED_SIGABRT: c_int = 128 + SIGABRT as c_int;
+
+// TODO: Upstream these.
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_TSN: c_int = linux_raw_sys::if_ether::ETH_P_TSN as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_ERSPAN2: c_int = linux_raw_sys::if_ether::ETH_P_ERSPAN2 as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_ERSPAN: c_int = linux_raw_sys::if_ether::ETH_P_ERSPAN as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_PROFINET: c_int = linux_raw_sys::if_ether::ETH_P_PROFINET as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_REALTEK: c_int = linux_raw_sys::if_ether::ETH_P_REALTEK as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_ETHERCAT: c_int = linux_raw_sys::if_ether::ETH_P_ETHERCAT as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_PREAUTH: c_int = linux_raw_sys::if_ether::ETH_P_PREAUTH as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_LLDP: c_int = linux_raw_sys::if_ether::ETH_P_LLDP as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_MRP: c_int = linux_raw_sys::if_ether::ETH_P_MRP as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_NCSI: c_int = linux_raw_sys::if_ether::ETH_P_NCSI as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_CFM: c_int = linux_raw_sys::if_ether::ETH_P_CFM as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_IBOE: c_int = linux_raw_sys::if_ether::ETH_P_IBOE as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_HSR: c_int = linux_raw_sys::if_ether::ETH_P_HSR as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_NSH: c_int = linux_raw_sys::if_ether::ETH_P_NSH as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_DSA_8021Q: c_int = linux_raw_sys::if_ether::ETH_P_DSA_8021Q as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_DSA_A5PSW: c_int = linux_raw_sys::if_ether::ETH_P_DSA_A5PSW as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_IFE: c_int = linux_raw_sys::if_ether::ETH_P_IFE as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_CAN: c_int = linux_raw_sys::if_ether::ETH_P_CAN as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_CANXL: c_int = linux_raw_sys::if_ether::ETH_P_CANXL as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_XDSA: c_int = linux_raw_sys::if_ether::ETH_P_XDSA as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_MAP: c_int = linux_raw_sys::if_ether::ETH_P_MAP as _;
+#[cfg(all(linux_kernel, feature = "net"))]
+pub(crate) const ETH_P_MCTP: c_int = linux_raw_sys::if_ether::ETH_P_MCTP as _;
+
+#[cfg(all(
+ linux_kernel,
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ )
+))]
+pub(crate) const SIGEMT: c_int = linux_raw_sys::general::SIGEMT as _;
+
+// TODO: Upstream these.
+#[cfg(all(linux_kernel, feature = "termios"))]
+pub(crate) const IUCLC: tcflag_t = linux_raw_sys::general::IUCLC as _;
+#[cfg(all(linux_kernel, feature = "termios"))]
+pub(crate) const XCASE: tcflag_t = linux_raw_sys::general::XCASE as _;
+
+#[cfg(target_os = "aix")]
+pub(crate) const MSG_DONTWAIT: c_int = libc::MSG_NONBLOCK;
+
+// TODO: Remove once https://github.com/rust-lang/libc/pull/3377 is merged and released.
+#[cfg(target_os = "netbsd")]
+#[cfg(feature = "net")]
+pub(crate) const SO_NOSIGPIPE: c_int = 0x0800;
+
+// On PowerPC, the regular `termios` has the `termios2` fields and there is no
+// `termios2`. linux-raw-sys has aliases `termios2` to `termios` to cover this
+// difference, but we still need to manually import it since `libc` doesn't
+// have this.
+#[cfg(all(
+ linux_kernel,
+ feature = "termios",
+ any(target_arch = "powerpc", target_arch = "powerpc64")
+))]
+pub(crate) use {
+ linux_raw_sys::general::{termios2, CIBAUD},
+ linux_raw_sys::ioctl::{TCGETS2, TCSETS2, TCSETSF2, TCSETSW2},
+};
+
+// Automatically enable “large file” support (LFS) features.
+
+#[cfg(target_os = "vxworks")]
+pub(super) use libc::_Vx_ticks64_t as _Vx_ticks_t;
+#[cfg(linux_kernel)]
+pub(super) use libc::fallocate64 as fallocate;
+#[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+#[cfg(any(linux_like, target_os = "aix"))]
+pub(super) use libc::open64 as open;
+#[cfg(any(
+ linux_kernel,
+ target_os = "aix",
+ target_os = "hurd",
+ target_os = "l4re"
+))]
+pub(super) use libc::posix_fallocate64 as posix_fallocate;
+#[cfg(any(all(linux_like, not(target_os = "android")), target_os = "aix"))]
+pub(super) use libc::{blkcnt64_t as blkcnt_t, rlim64_t as rlim_t};
+// TODO: AIX has `stat64x`, `fstat64x`, `lstat64x`, and `stat64xat`; add them
+// to the upstream libc crate and implement rustix's `statat` etc. with them.
+#[cfg(target_os = "aix")]
+pub(super) use libc::{
+ blksize64_t as blksize_t, fstat64 as fstat, fstatfs64 as fstatfs, fstatvfs64 as fstatvfs,
+ ftruncate64 as ftruncate, getrlimit64 as getrlimit, ino_t, lseek64 as lseek, mmap,
+ off64_t as off_t, openat, posix_fadvise64 as posix_fadvise, preadv, pwritev,
+ rlimit64 as rlimit, setrlimit64 as setrlimit, stat64at as fstatat, statfs64 as statfs,
+ statvfs64 as statvfs, RLIM_INFINITY,
+};
+#[cfg(any(linux_like, target_os = "hurd"))]
+pub(super) use libc::{
+ fstat64 as fstat, fstatat64 as fstatat, fstatfs64 as fstatfs, fstatvfs64 as fstatvfs,
+ ftruncate64 as ftruncate, getrlimit64 as getrlimit, ino64_t as ino_t, lseek64 as lseek,
+ mmap64 as mmap, off64_t as off_t, openat64 as openat, posix_fadvise64 as posix_fadvise,
+ rlimit64 as rlimit, setrlimit64 as setrlimit, statfs64 as statfs, statvfs64 as statvfs,
+ RLIM64_INFINITY as RLIM_INFINITY,
+};
+#[cfg(apple)]
+pub(super) use libc::{
+ host_info64_t as host_info_t, host_statistics64 as host_statistics,
+ vm_statistics64_t as vm_statistics_t,
+};
+#[cfg(not(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+)))]
+#[cfg(any(linux_like, target_os = "aix", target_os = "hurd"))]
+pub(super) use libc::{lstat64 as lstat, stat64 as stat};
+#[cfg(any(
+ linux_kernel,
+ target_os = "aix",
+ target_os = "hurd",
+ target_os = "emscripten"
+))]
+pub(super) use libc::{pread64 as pread, pwrite64 as pwrite};
+#[cfg(any(target_os = "linux", target_os = "hurd", target_os = "emscripten"))]
+pub(super) use libc::{preadv64 as preadv, pwritev64 as pwritev};
+
+#[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]
+pub(super) unsafe fn prlimit(
+ pid: libc::pid_t,
+ resource: libc::__rlimit_resource_t,
+ new_limit: *const libc::rlimit64,
+ old_limit: *mut libc::rlimit64,
+) -> libc::c_int {
+ // `prlimit64` wasn't supported in glibc until 2.13.
+ weak_or_syscall! {
+ fn prlimit64(
+ pid: libc::pid_t,
+ resource: libc::__rlimit_resource_t,
+ new_limit: *const libc::rlimit64,
+ old_limit: *mut libc::rlimit64
+ ) via SYS_prlimit64 -> libc::c_int
+ }
+
+ prlimit64(pid, resource, new_limit, old_limit)
+}
+
+#[cfg(all(target_os = "linux", target_env = "musl"))]
+pub(super) unsafe fn prlimit(
+ pid: libc::pid_t,
+ resource: libc::c_int,
+ new_limit: *const libc::rlimit64,
+ old_limit: *mut libc::rlimit64,
+) -> libc::c_int {
+ weak_or_syscall! {
+ fn prlimit64(
+ pid: libc::pid_t,
+ resource: libc::c_int,
+ new_limit: *const libc::rlimit64,
+ old_limit: *mut libc::rlimit64
+ ) via SYS_prlimit64 -> libc::c_int
+ }
+
+ prlimit64(pid, resource, new_limit, old_limit)
+}
+
+#[cfg(target_os = "android")]
+pub(super) unsafe fn prlimit(
+ pid: libc::pid_t,
+ resource: libc::c_int,
+ new_limit: *const libc::rlimit64,
+ old_limit: *mut libc::rlimit64,
+) -> libc::c_int {
+ weak_or_syscall! {
+ fn prlimit64(
+ pid: libc::pid_t,
+ resource: libc::c_int,
+ new_limit: *const libc::rlimit64,
+ old_limit: *mut libc::rlimit64
+ ) via SYS_prlimit64 -> libc::c_int
+ }
+
+ prlimit64(pid, resource, new_limit, old_limit)
+}
+
+#[cfg(target_os = "android")]
+mod readwrite_pv64 {
+ use super::*;
+
+ pub(in super::super) unsafe fn preadv64(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off64_t,
+ ) -> libc::ssize_t {
+ // Older Android libc lacks `preadv64`, so use the `weak!` mechanism to
+ // test for it, and call back to `libc::syscall`. We don't use
+ // `weak_or_syscall` here because we need to pass the 64-bit offset
+ // specially.
+ weak! {
+ fn preadv64(libc::c_int, *const libc::iovec, libc::c_int, libc::off64_t) -> libc::ssize_t
+ }
+ if let Some(fun) = preadv64.get() {
+ fun(fd, iov, iovcnt, offset)
+ } else {
+ // Unlike the plain "p" functions, the "pv" functions pass their
+ // offset in an endian-independent way, and always in two registers.
+ syscall! {
+ fn preadv(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset_lo: usize,
+ offset_hi: usize
+ ) via SYS_preadv -> libc::ssize_t
+ }
+ preadv(fd, iov, iovcnt, offset as usize, (offset >> 32) as usize)
+ }
+ }
+ pub(in super::super) unsafe fn pwritev64(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off64_t,
+ ) -> libc::ssize_t {
+ // See the comments in `preadv64`.
+ weak! {
+ fn pwritev64(libc::c_int, *const libc::iovec, libc::c_int, libc::off64_t) -> libc::ssize_t
+ }
+ if let Some(fun) = pwritev64.get() {
+ fun(fd, iov, iovcnt, offset)
+ } else {
+ // Unlike the plain "p" functions, the "pv" functions pass their
+ // offset in an endian-independent way, and always in two registers.
+ syscall! {
+ fn pwritev(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset_lo: usize,
+ offset_hi: usize
+ ) via SYS_pwritev -> libc::ssize_t
+ }
+ pwritev(fd, iov, iovcnt, offset as usize, (offset >> 32) as usize)
+ }
+ }
+}
+#[cfg(target_os = "android")]
+pub(super) use readwrite_pv64::{preadv64 as preadv, pwritev64 as pwritev};
+
+// macOS added `preadv` and `pwritev` in version 11.0.
+#[cfg(apple)]
+mod readwrite_pv {
+ weakcall! {
+ pub(in super::super) fn preadv(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off_t
+ ) -> libc::ssize_t
+ }
+ weakcall! {
+ pub(in super::super) fn pwritev(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int, offset: libc::off_t
+ ) -> libc::ssize_t
+ }
+}
+#[cfg(apple)]
+pub(super) use readwrite_pv::{preadv, pwritev};
+
+// glibc added `preadv64v2` and `pwritev64v2` in version 2.26.
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+mod readwrite_pv64v2 {
+ use super::*;
+
+ pub(in super::super) unsafe fn preadv64v2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off64_t,
+ flags: libc::c_int,
+ ) -> libc::ssize_t {
+ // Older glibc lacks `preadv64v2`, so use the `weak!` mechanism to
+ // test for it, and call back to `libc::syscall`. We don't use
+ // `weak_or_syscall` here because we need to pass the 64-bit offset
+ // specially.
+ weak! {
+ fn preadv64v2(libc::c_int, *const libc::iovec, libc::c_int, libc::off64_t, libc::c_int) -> libc::ssize_t
+ }
+ if let Some(fun) = preadv64v2.get() {
+ fun(fd, iov, iovcnt, offset, flags)
+ } else {
+ // Unlike the plain "p" functions, the "pv" functions pass their
+ // offset in an endian-independent way, and always in two registers.
+ syscall! {
+ fn preadv2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset_lo: usize,
+ offset_hi: usize,
+ flags: libc::c_int
+ ) via SYS_preadv2 -> libc::ssize_t
+ }
+ preadv2(
+ fd,
+ iov,
+ iovcnt,
+ offset as usize,
+ (offset >> 32) as usize,
+ flags,
+ )
+ }
+ }
+ pub(in super::super) unsafe fn pwritev64v2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off64_t,
+ flags: libc::c_int,
+ ) -> libc::ssize_t {
+ // See the comments in `preadv64v2`.
+ weak! {
+ fn pwritev64v2(libc::c_int, *const libc::iovec, libc::c_int, libc::off64_t, libc::c_int) -> libc::ssize_t
+ }
+ if let Some(fun) = pwritev64v2.get() {
+ fun(fd, iov, iovcnt, offset, flags)
+ } else {
+ // Unlike the plain "p" functions, the "pv" functions pass their
+ // offset in an endian-independent way, and always in two registers.
+ syscall! {
+ fn pwritev2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovec: libc::c_int,
+ offset_lo: usize,
+ offset_hi: usize,
+ flags: libc::c_int
+ ) via SYS_pwritev2 -> libc::ssize_t
+ }
+ pwritev2(
+ fd,
+ iov,
+ iovcnt,
+ offset as usize,
+ (offset >> 32) as usize,
+ flags,
+ )
+ }
+ }
+}
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+pub(super) use readwrite_pv64v2::{preadv64v2 as preadv2, pwritev64v2 as pwritev2};
+
+// On non-glibc, assume we don't have `pwritev2`/`preadv2` in libc and use
+// `c::syscall` instead.
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+mod readwrite_pv64v2 {
+ use super::*;
+
+ pub(in super::super) unsafe fn preadv64v2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off64_t,
+ flags: libc::c_int,
+ ) -> libc::ssize_t {
+ // Unlike the plain "p" functions, the "pv" functions pass their offset
+ // in an endian-independent way, and always in two registers.
+ syscall! {
+ fn preadv2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset_lo: usize,
+ offset_hi: usize,
+ flags: libc::c_int
+ ) via SYS_preadv2 -> libc::ssize_t
+ }
+ preadv2(
+ fd,
+ iov,
+ iovcnt,
+ offset as usize,
+ (offset >> 32) as usize,
+ flags,
+ )
+ }
+ pub(in super::super) unsafe fn pwritev64v2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset: libc::off64_t,
+ flags: libc::c_int,
+ ) -> libc::ssize_t {
+ // Unlike the plain "p" functions, the "pv" functions pass their offset
+ // in an endian-independent way, and always in two registers.
+ syscall! {
+ fn pwritev2(
+ fd: libc::c_int,
+ iov: *const libc::iovec,
+ iovcnt: libc::c_int,
+ offset_lo: usize,
+ offset_hi: usize,
+ flags: libc::c_int
+ ) via SYS_pwritev2 -> libc::ssize_t
+ }
+ pwritev2(
+ fd,
+ iov,
+ iovcnt,
+ offset as usize,
+ (offset >> 32) as usize,
+ flags,
+ )
+ }
+}
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+pub(super) use readwrite_pv64v2::{preadv64v2 as preadv2, pwritev64v2 as pwritev2};
diff --git a/src/backend/libc/conv.rs b/src/backend/libc/conv.rs
new file mode 100644
index 0000000..171fc10
--- /dev/null
+++ b/src/backend/libc/conv.rs
@@ -0,0 +1,255 @@
+//! Libc call arguments and return values are often things like `c_int`,
+//! `c_uint`, or libc-specific pointer types. This module provides functions
+//! for converting between rustix's types and libc types.
+
+use super::c;
+#[cfg(all(feature = "alloc", not(any(windows, target_os = "espidf"))))]
+use super::fd::IntoRawFd;
+use super::fd::{AsRawFd, BorrowedFd, FromRawFd, LibcFd, OwnedFd, RawFd};
+#[cfg(not(windows))]
+use crate::ffi::CStr;
+use crate::io;
+
+#[cfg(not(windows))]
+#[inline]
+pub(super) fn c_str(c: &CStr) -> *const c::c_char {
+ c.as_ptr()
+}
+
+#[cfg(not(any(windows, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(super) fn no_fd() -> LibcFd {
+ -1
+}
+
+#[inline]
+pub(super) fn borrowed_fd(fd: BorrowedFd<'_>) -> LibcFd {
+ fd.as_raw_fd() as LibcFd
+}
+
+#[cfg(all(
+ feature = "alloc",
+ not(any(windows, target_os = "espidf", target_os = "redox"))
+))]
+#[inline]
+pub(super) fn owned_fd(fd: OwnedFd) -> LibcFd {
+ fd.into_raw_fd() as LibcFd
+}
+
+#[inline]
+pub(super) fn ret(raw: c::c_int) -> io::Result<()> {
+ if raw == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno::last_os_error())
+ }
+}
+
+#[cfg(apple)]
+#[inline]
+pub(super) fn nonnegative_ret(raw: c::c_int) -> io::Result<()> {
+ if raw >= 0 {
+ Ok(())
+ } else {
+ Err(io::Errno::last_os_error())
+ }
+}
+
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[inline]
+pub(super) unsafe fn ret_infallible(raw: c::c_int) {
+ debug_assert_eq!(raw, 0, "unexpected error: {:?}", io::Errno::last_os_error());
+}
+
+#[inline]
+pub(super) fn ret_c_int(raw: c::c_int) -> io::Result<c::c_int> {
+ if raw == -1 {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(raw)
+ }
+}
+
+#[cfg(any(linux_kernel, all(target_os = "redox", feature = "event")))]
+#[inline]
+pub(super) fn ret_u32(raw: c::c_int) -> io::Result<u32> {
+ if raw == -1 {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(raw as u32)
+ }
+}
+
+#[inline]
+pub(super) fn ret_usize(raw: c::ssize_t) -> io::Result<usize> {
+ if raw == -1 {
+ Err(io::Errno::last_os_error())
+ } else {
+ debug_assert!(raw >= 0);
+ Ok(raw as usize)
+ }
+}
+
+#[cfg(not(windows))]
+#[cfg(feature = "fs")]
+#[inline]
+pub(super) fn ret_off_t(raw: c::off_t) -> io::Result<c::off_t> {
+ if raw == -1 {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(raw)
+ }
+}
+
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[inline]
+pub(super) fn ret_pid_t(raw: c::pid_t) -> io::Result<c::pid_t> {
+ if raw == -1 {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(raw)
+ }
+}
+
+/// Convert a `c_int` returned from a libc function to an `OwnedFd`, if valid.
+///
+/// # Safety
+///
+/// The caller must ensure that this is the return value of a libc function
+/// which returns an owned file descriptor.
+#[inline]
+pub(super) unsafe fn ret_owned_fd(raw: LibcFd) -> io::Result<OwnedFd> {
+ if raw == !0 {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(OwnedFd::from_raw_fd(raw as RawFd))
+ }
+}
+
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[inline]
+pub(super) fn ret_discarded_fd(raw: LibcFd) -> io::Result<()> {
+ if raw == !0 {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+#[cfg(all(feature = "alloc", not(any(windows, target_os = "wasi"))))]
+#[inline]
+pub(super) fn ret_discarded_char_ptr(raw: *mut c::c_char) -> io::Result<()> {
+ if raw.is_null() {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+/// Convert the buffer-length argument value of a `send` or `recv` call.
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub(super) fn send_recv_len(len: usize) -> usize {
+ len
+}
+
+/// Convert the buffer-length argument value of a `send` or `recv` call.
+#[cfg(windows)]
+#[inline]
+pub(super) fn send_recv_len(len: usize) -> i32 {
+ // On Windows, the length argument has type `i32`; saturate the length,
+ // since `send` and `recv` are allowed to send and recv less data than
+ // requested.
+ len.try_into().unwrap_or(i32::MAX)
+}
+
+/// Convert the return value of a `send` or `recv` call.
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub(super) fn ret_send_recv(len: isize) -> io::Result<usize> {
+ ret_usize(len)
+}
+
+/// Convert the return value of a `send` or `recv` call.
+#[cfg(windows)]
+#[inline]
+pub(super) fn ret_send_recv(len: i32) -> io::Result<usize> {
+ ret_usize(len as isize)
+}
+
+/// Convert the value to the `msg_iovlen` field of a `msghdr` struct.
+#[cfg(all(
+ not(any(windows, target_os = "espidf", target_os = "redox", target_os = "wasi")),
+ any(
+ target_os = "android",
+ all(
+ target_os = "linux",
+ not(target_env = "musl"),
+ not(all(target_env = "uclibc", any(target_arch = "arm", target_arch = "mips")))
+ )
+ )
+))]
+#[inline]
+pub(super) fn msg_iov_len(len: usize) -> c::size_t {
+ len
+}
+
+/// Convert the value to the `msg_iovlen` field of a `msghdr` struct.
+#[cfg(all(
+ not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+ )),
+ not(any(
+ target_os = "android",
+ all(
+ target_os = "linux",
+ not(target_env = "musl"),
+ not(all(target_env = "uclibc", any(target_arch = "arm", target_arch = "mips")))
+ )
+ ))
+))]
+#[inline]
+pub(crate) fn msg_iov_len(len: usize) -> c::c_int {
+ len.try_into().unwrap_or(c::c_int::MAX)
+}
+
+/// Convert the value to a `socklen_t`.
+#[cfg(any(
+ bsd,
+ solarish,
+ target_env = "musl",
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+))]
+#[inline]
+pub(crate) fn msg_control_len(len: usize) -> c::socklen_t {
+ len.try_into().unwrap_or(c::socklen_t::MAX)
+}
+
+/// Convert the value to a `size_t`.
+#[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_env = "musl",
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+#[inline]
+pub(crate) fn msg_control_len(len: usize) -> c::size_t {
+ len
+}
diff --git a/src/backend/libc/event/epoll.rs b/src/backend/libc/event/epoll.rs
new file mode 100644
index 0000000..6e2ba3c
--- /dev/null
+++ b/src/backend/libc/event/epoll.rs
@@ -0,0 +1,506 @@
+//! Linux `epoll` support.
+//!
+//! # Examples
+//!
+//! ```no_run
+//! # #[cfg(feature = "net")]
+//! # fn main() -> std::io::Result<()> {
+//! use rustix::event::epoll;
+//! use rustix::fd::AsFd;
+//! use rustix::io::{ioctl_fionbio, read, write};
+//! use rustix::net::{
+//! accept, bind_v4, listen, socket, AddressFamily, Ipv4Addr, SocketAddrV4, SocketType,
+//! };
+//! use std::collections::HashMap;
+//! use std::os::unix::io::AsRawFd;
+//!
+//! // Create a socket and listen on it.
+//! let listen_sock = socket(AddressFamily::INET, SocketType::STREAM, None)?;
+//! bind_v4(&listen_sock, &SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))?;
+//! listen(&listen_sock, 1)?;
+//!
+//! // Create an epoll object. Using `Owning` here means the epoll object will
+//! // take ownership of the file descriptors registered with it.
+//! let epoll = epoll::create(epoll::CreateFlags::CLOEXEC)?;
+//!
+//! // Register the socket with the epoll object.
+//! epoll::add(
+//! &epoll,
+//! &listen_sock,
+//! epoll::EventData::new_u64(1),
+//! epoll::EventFlags::IN,
+//! )?;
+//!
+//! // Keep track of the sockets we've opened.
+//! let mut next_id = epoll::EventData::new_u64(2);
+//! let mut sockets = HashMap::new();
+//!
+//! // Process events.
+//! let mut event_list = epoll::EventVec::with_capacity(4);
+//! loop {
+//! epoll::wait(&epoll, &mut event_list, -1)?;
+//! for event in &event_list {
+//! let target = event.data;
+//! if target.u64() == 1 {
+//! // Accept a new connection, set it to non-blocking, and
+//! // register to be notified when it's ready to write to.
+//! let conn_sock = accept(&listen_sock)?;
+//! ioctl_fionbio(&conn_sock, true)?;
+//! epoll::add(
+//! &epoll,
+//! &conn_sock,
+//! next_id,
+//! epoll::EventFlags::OUT | epoll::EventFlags::ET,
+//! )?;
+//!
+//! // Keep track of the socket.
+//! sockets.insert(next_id, conn_sock);
+//! next_id = epoll::EventData::new_u64(next_id.u64() + 1);
+//! } else {
+//! // Write a message to the stream and then unregister it.
+//! let target = sockets.remove(&target).unwrap();
+//! write(&target, b"hello\n")?;
+//! let _ = epoll::delete(&epoll, &target)?;
+//! }
+//! }
+//! }
+//! # }
+//! # #[cfg(not(feature = "net"))]
+//! # fn main() {}
+//! ```
+
+use crate::backend::c;
+#[cfg(feature = "alloc")]
+use crate::backend::conv::ret_u32;
+use crate::backend::conv::{ret, ret_owned_fd};
+use crate::fd::{AsFd, AsRawFd, OwnedFd};
+use crate::io;
+use crate::utils::as_mut_ptr;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+use bitflags::bitflags;
+use core::ffi::c_void;
+use core::hash::{Hash, Hasher};
+use core::ptr::null_mut;
+use core::slice;
+
+bitflags! {
+ /// `EPOLL_*` for use with [`new`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CreateFlags: u32 {
+ /// `EPOLL_CLOEXEC`
+ const CLOEXEC = bitcast!(c::EPOLL_CLOEXEC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `EPOLL*` for use with [`add`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct EventFlags: u32 {
+ /// `EPOLLIN`
+ const IN = bitcast!(c::EPOLLIN);
+
+ /// `EPOLLOUT`
+ const OUT = bitcast!(c::EPOLLOUT);
+
+ /// `EPOLLPRI`
+ const PRI = bitcast!(c::EPOLLPRI);
+
+ /// `EPOLLERR`
+ const ERR = bitcast!(c::EPOLLERR);
+
+ /// `EPOLLHUP`
+ const HUP = bitcast!(c::EPOLLHUP);
+
+ /// `EPOLLRDNORM`
+ const RDNORM = bitcast!(c::EPOLLRDNORM);
+
+ /// `EPOLLRDBAND`
+ const RDBAND = bitcast!(c::EPOLLRDBAND);
+
+ /// `EPOLLWRNORM`
+ const WRNORM = bitcast!(c::EPOLLWRNORM);
+
+ /// `EPOLLWRBAND`
+ const WRBAND = bitcast!(c::EPOLLWRBAND);
+
+ /// `EPOLLMSG`
+ const MSG = bitcast!(c::EPOLLMSG);
+
+ /// `EPOLLRDHUP`
+ const RDHUP = bitcast!(c::EPOLLRDHUP);
+
+ /// `EPOLLET`
+ const ET = bitcast!(c::EPOLLET);
+
+ /// `EPOLLONESHOT`
+ const ONESHOT = bitcast!(c::EPOLLONESHOT);
+
+ /// `EPOLLWAKEUP`
+ const WAKEUP = bitcast!(c::EPOLLWAKEUP);
+
+ /// `EPOLLEXCLUSIVE`
+ #[cfg(not(target_os = "android"))]
+ const EXCLUSIVE = bitcast!(c::EPOLLEXCLUSIVE);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `epoll_create1(flags)`—Creates a new epoll object.
+///
+/// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file
+/// descriptor from being implicitly passed across `exec` boundaries.
+#[inline]
+#[doc(alias = "epoll_create1")]
+pub fn create(flags: CreateFlags) -> io::Result<OwnedFd> {
+ // SAFETY: We're calling `epoll_create1` via FFI and we know how it
+ // behaves.
+ unsafe { ret_owned_fd(c::epoll_create1(bitflags_bits!(flags))) }
+}
+
+/// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an epoll
+/// object.
+///
+/// This registers interest in any of the events set in `events` occurring on
+/// the file descriptor associated with `data`.
+///
+/// If [`delete`] is not called on the I/O source passed into this function
+/// before the I/O source is `close`d, then the `epoll` will act as if the I/O
+/// source is still registered with it. This can lead to spurious events being
+/// returned from [`wait`]. If a file descriptor is an
+/// `Arc<dyn SystemResource>`, then `epoll` can be thought to maintain a
+/// `Weak<dyn SystemResource>` to the file descriptor.
+#[doc(alias = "epoll_ctl")]
+pub fn add(
+ epoll: impl AsFd,
+ source: impl AsFd,
+ data: EventData,
+ event_flags: EventFlags,
+) -> io::Result<()> {
+ // SAFETY: We're calling `epoll_ctl` via FFI and we know how it
+ // behaves. We use our own `Event` struct instead of libc's because
+ // ours preserves pointer provenance instead of just using a `u64`,
+ // and we have tests elsewhere for layout equivalence.
+ unsafe {
+ let raw_fd = source.as_fd().as_raw_fd();
+ ret(c::epoll_ctl(
+ epoll.as_fd().as_raw_fd(),
+ c::EPOLL_CTL_ADD,
+ raw_fd,
+ as_mut_ptr(&mut Event {
+ flags: event_flags,
+ data,
+ #[cfg(target_os = "redox")]
+ _pad: 0,
+ })
+ .cast(),
+ ))
+ }
+}
+
+/// `epoll_ctl(self, EPOLL_CTL_MOD, target, event)`—Modifies an element in a
+/// given epoll object.
+///
+/// This sets the events of interest with `target` to `events`.
+#[doc(alias = "epoll_ctl")]
+pub fn modify(
+ epoll: impl AsFd,
+ source: impl AsFd,
+ data: EventData,
+ event_flags: EventFlags,
+) -> io::Result<()> {
+ let raw_fd = source.as_fd().as_raw_fd();
+
+ // SAFETY: We're calling `epoll_ctl` via FFI and we know how it
+ // behaves. We use our own `Event` struct instead of libc's because
+ // ours preserves pointer provenance instead of just using a `u64`,
+ // and we have tests elsewhere for layout equivalence.
+ unsafe {
+ ret(c::epoll_ctl(
+ epoll.as_fd().as_raw_fd(),
+ c::EPOLL_CTL_MOD,
+ raw_fd,
+ as_mut_ptr(&mut Event {
+ flags: event_flags,
+ data,
+ #[cfg(target_os = "redox")]
+ _pad: 0,
+ })
+ .cast(),
+ ))
+ }
+}
+
+/// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in a
+/// given epoll object.
+#[doc(alias = "epoll_ctl")]
+pub fn delete(epoll: impl AsFd, source: impl AsFd) -> io::Result<()> {
+ // SAFETY: We're calling `epoll_ctl` via FFI and we know how it
+ // behaves.
+ unsafe {
+ let raw_fd = source.as_fd().as_raw_fd();
+ ret(c::epoll_ctl(
+ epoll.as_fd().as_raw_fd(),
+ c::EPOLL_CTL_DEL,
+ raw_fd,
+ null_mut(),
+ ))
+ }
+}
+
+/// `epoll_wait(self, events, timeout)`—Waits for registered events of
+/// interest.
+///
+/// For each event of interest, an element is written to `events`. On
+/// success, this returns the number of written elements.
+#[cfg(feature = "alloc")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
+pub fn wait(epoll: impl AsFd, event_list: &mut EventVec, timeout: c::c_int) -> io::Result<()> {
+ // SAFETY: We're calling `epoll_wait` via FFI and we know how it
+ // behaves.
+ unsafe {
+ event_list.events.set_len(0);
+ let nfds = ret_u32(c::epoll_wait(
+ epoll.as_fd().as_raw_fd(),
+ event_list.events.as_mut_ptr().cast::<c::epoll_event>(),
+ event_list.events.capacity().try_into().unwrap_or(i32::MAX),
+ timeout,
+ ))?;
+ event_list.events.set_len(nfds as usize);
+ }
+
+ Ok(())
+}
+
+/// An iterator over the `Event`s in an `EventVec`.
+pub struct Iter<'a> {
+ /// Use `Copied` to copy the struct, since `Event` is `packed` on some
+ /// platforms, and it's common for users to directly destructure it, which
+ /// would lead to errors about forming references to packed fields.
+ iter: core::iter::Copied<slice::Iter<'a, Event>>,
+}
+
+impl<'a> Iterator for Iter<'a> {
+ type Item = Event;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next()
+ }
+}
+
+/// A record of an event that occurred.
+#[repr(C)]
+#[cfg_attr(
+ all(
+ linux_kernel,
+ any(
+ all(
+ target_arch = "x86",
+ not(target_env = "musl"),
+ not(target_os = "android"),
+ ),
+ target_arch = "x86_64",
+ )
+ ),
+ repr(packed)
+)]
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Event {
+ /// Which specific event(s) occurred.
+ pub flags: EventFlags,
+ /// User data.
+ pub data: EventData,
+
+ #[cfg(target_os = "redox")]
+ _pad: u64,
+}
+
+/// Data associated with an [`Event`]. This can either be a 64-bit integer
+/// value or a pointer which preserves pointer provenance.
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union EventData {
+ /// A 64-bit integer value.
+ as_u64: u64,
+
+ /// A `*mut c_void` which preserves pointer provenance, extended to be
+ /// 64-bit so that if we read the value as a `u64` union field, we don't
+ /// get uninitialized memory.
+ sixty_four_bit_pointer: SixtyFourBitPointer,
+}
+
+impl EventData {
+ /// Construct a new value containing a `u64`.
+ #[inline]
+ pub const fn new_u64(value: u64) -> Self {
+ Self { as_u64: value }
+ }
+
+ /// Construct a new value containing a `*mut c_void`.
+ #[inline]
+ pub const fn new_ptr(value: *mut c_void) -> Self {
+ Self {
+ sixty_four_bit_pointer: SixtyFourBitPointer {
+ pointer: value,
+ #[cfg(target_pointer_width = "32")]
+ _padding: 0,
+ },
+ }
+ }
+
+ /// Return the value as a `u64`.
+ ///
+ /// If the stored value was a pointer, the pointer is zero-extended to a
+ /// `u64`.
+ #[inline]
+ pub fn u64(self) -> u64 {
+ unsafe { self.as_u64 }
+ }
+
+ /// Return the value as a `*mut c_void`.
+ ///
+ /// If the stored value was a `u64`, the least-significant bits of the
+ /// `u64` are returned as a pointer value.
+ #[inline]
+ pub fn ptr(self) -> *mut c_void {
+ unsafe { self.sixty_four_bit_pointer.pointer }
+ }
+}
+
+impl PartialEq for EventData {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ self.u64() == other.u64()
+ }
+}
+
+impl Eq for EventData {}
+
+impl Hash for EventData {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.u64().hash(state)
+ }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct SixtyFourBitPointer {
+ #[cfg(target_endian = "big")]
+ #[cfg(target_pointer_width = "32")]
+ _padding: u32,
+
+ pointer: *mut c_void,
+
+ #[cfg(target_endian = "little")]
+ #[cfg(target_pointer_width = "32")]
+ _padding: u32,
+}
+
+/// A vector of `Event`s, plus context for interpreting them.
+#[cfg(feature = "alloc")]
+pub struct EventVec {
+ events: Vec<Event>,
+}
+
+#[cfg(feature = "alloc")]
+impl EventVec {
+ /// Constructs an `EventVec` from raw pointer, length, and capacity.
+ ///
+ /// # Safety
+ ///
+ /// This function calls [`Vec::from_raw_parts`] with its arguments.
+ ///
+ /// [`Vec::from_raw_parts`]: https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.from_raw_parts
+ #[inline]
+ pub unsafe fn from_raw_parts(ptr: *mut Event, len: usize, capacity: usize) -> Self {
+ Self {
+ events: Vec::from_raw_parts(ptr, len, capacity),
+ }
+ }
+
+ /// Constructs an `EventVec` with memory for `capacity` `Event`s.
+ #[inline]
+ pub fn with_capacity(capacity: usize) -> Self {
+ Self {
+ events: Vec::with_capacity(capacity),
+ }
+ }
+
+ /// Returns the current `Event` capacity of this `EventVec`.
+ #[inline]
+ pub fn capacity(&self) -> usize {
+ self.events.capacity()
+ }
+
+ /// Reserves enough memory for at least `additional` more `Event`s.
+ #[inline]
+ pub fn reserve(&mut self, additional: usize) {
+ self.events.reserve(additional);
+ }
+
+ /// Reserves enough memory for exactly `additional` more `Event`s.
+ #[inline]
+ pub fn reserve_exact(&mut self, additional: usize) {
+ self.events.reserve_exact(additional);
+ }
+
+ /// Clears all the `Events` out of this `EventVec`.
+ #[inline]
+ pub fn clear(&mut self) {
+ self.events.clear();
+ }
+
+ /// Shrinks the capacity of this `EventVec` as much as possible.
+ #[inline]
+ pub fn shrink_to_fit(&mut self) {
+ self.events.shrink_to_fit();
+ }
+
+ /// Returns an iterator over the `Event`s in this `EventVec`.
+ #[inline]
+ pub fn iter(&self) -> Iter<'_> {
+ Iter {
+ iter: self.events.iter().copied(),
+ }
+ }
+
+ /// Returns the number of `Event`s logically contained in this `EventVec`.
+ #[inline]
+ pub fn len(&mut self) -> usize {
+ self.events.len()
+ }
+
+ /// Tests whether this `EventVec` is logically empty.
+ #[inline]
+ pub fn is_empty(&mut self) -> bool {
+ self.events.is_empty()
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl<'a> IntoIterator for &'a EventVec {
+ type IntoIter = Iter<'a>;
+ type Item = Event;
+
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+
+#[test]
+fn test_epoll_layouts() {
+ check_renamed_type!(Event, epoll_event);
+ check_renamed_type!(Event, epoll_event);
+ check_renamed_struct_renamed_field!(Event, epoll_event, flags, events);
+ check_renamed_struct_renamed_field!(Event, epoll_event, data, u64);
+}
diff --git a/src/backend/libc/event/mod.rs b/src/backend/libc/event/mod.rs
new file mode 100644
index 0000000..a07d06b
--- /dev/null
+++ b/src/backend/libc/event/mod.rs
@@ -0,0 +1,9 @@
+pub(crate) mod poll_fd;
+#[cfg(not(windows))]
+pub(crate) mod types;
+
+#[cfg_attr(windows, path = "windows_syscalls.rs")]
+pub(crate) mod syscalls;
+
+#[cfg(any(linux_kernel, target_os = "redox"))]
+pub mod epoll;
diff --git a/src/backend/libc/event/poll_fd.rs b/src/backend/libc/event/poll_fd.rs
new file mode 100644
index 0000000..32fd83d
--- /dev/null
+++ b/src/backend/libc/event/poll_fd.rs
@@ -0,0 +1,142 @@
+use crate::backend::c;
+use crate::backend::conv::borrowed_fd;
+use crate::backend::fd::{AsFd, AsRawFd, BorrowedFd, LibcFd};
+use bitflags::bitflags;
+use core::marker::PhantomData;
+#[cfg(windows)]
+use {
+ crate::backend::fd::{AsSocket, RawFd},
+ core::fmt,
+};
+
+bitflags! {
+ /// `POLL*` flags for use with [`poll`].
+ ///
+ /// [`poll`]: crate::event::poll
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PollFlags: c::c_short {
+ /// `POLLIN`
+ const IN = c::POLLIN;
+ /// `POLLPRI`
+ #[cfg(not(target_os = "wasi"))]
+ const PRI = c::POLLPRI;
+ /// `POLLOUT`
+ const OUT = c::POLLOUT;
+ /// `POLLRDNORM`
+ const RDNORM = c::POLLRDNORM;
+ /// `POLLWRNORM`
+ #[cfg(not(target_os = "l4re"))]
+ const WRNORM = c::POLLWRNORM;
+ /// `POLLRDBAND`
+ #[cfg(not(any(target_os = "l4re", target_os = "wasi")))]
+ const RDBAND = c::POLLRDBAND;
+ /// `POLLWRBAND`
+ #[cfg(not(any(target_os = "l4re", target_os = "wasi")))]
+ const WRBAND = c::POLLWRBAND;
+ /// `POLLERR`
+ const ERR = c::POLLERR;
+ /// `POLLHUP`
+ const HUP = c::POLLHUP;
+ /// `POLLNVAL`
+ #[cfg(not(target_os = "espidf"))]
+ const NVAL = c::POLLNVAL;
+ /// `POLLRDHUP`
+ #[cfg(all(
+ linux_kernel,
+ not(any(target_arch = "sparc", target_arch = "sparc64"))),
+ )]
+ const RDHUP = c::POLLRDHUP;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `struct pollfd`—File descriptor and flags for use with [`poll`].
+///
+/// [`poll`]: crate::event::poll
+#[doc(alias = "pollfd")]
+#[derive(Clone)]
+#[cfg_attr(not(windows), derive(Debug))]
+#[repr(transparent)]
+pub struct PollFd<'fd> {
+ pollfd: c::pollfd,
+ _phantom: PhantomData<BorrowedFd<'fd>>,
+}
+
+#[cfg(windows)]
+impl<'fd> fmt::Debug for PollFd<'fd> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt.debug_struct("pollfd")
+ .field("fd", &self.pollfd.fd)
+ .field("events", &self.pollfd.events)
+ .field("revents", &self.pollfd.revents)
+ .finish()
+ }
+}
+
+impl<'fd> PollFd<'fd> {
+ /// Constructs a new `PollFd` holding `fd` and `events`.
+ #[inline]
+ pub fn new<Fd: AsFd>(fd: &'fd Fd, events: PollFlags) -> Self {
+ Self::from_borrowed_fd(fd.as_fd(), events)
+ }
+
+ /// Sets the contained file descriptor to `fd`.
+ #[inline]
+ pub fn set_fd<Fd: AsFd>(&mut self, fd: &'fd Fd) {
+ self.pollfd.fd = fd.as_fd().as_raw_fd() as LibcFd;
+ }
+
+ /// Clears the ready events.
+ #[inline]
+ pub fn clear_revents(&mut self) {
+ self.pollfd.revents = 0;
+ }
+
+ /// Constructs a new `PollFd` holding `fd` and `events`.
+ ///
+ /// This is the same as `new`, but can be used to avoid borrowing the
+ /// `BorrowedFd`, which can be tricky in situations where the `BorrowedFd`
+ /// is a temporary.
+ #[inline]
+ pub fn from_borrowed_fd(fd: BorrowedFd<'fd>, events: PollFlags) -> Self {
+ Self {
+ pollfd: c::pollfd {
+ fd: borrowed_fd(fd),
+ events: events.bits(),
+ revents: 0,
+ },
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Returns the ready events.
+ #[inline]
+ pub fn revents(&self) -> PollFlags {
+ // Use `.unwrap()` here because in theory we know we know all the bits
+ // the OS might set here, but OS's have added extensions in the past.
+ PollFlags::from_bits(self.pollfd.revents).unwrap()
+ }
+}
+
+#[cfg(not(windows))]
+impl<'fd> AsFd for PollFd<'fd> {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ // SAFETY: Our constructors and `set_fd` require `pollfd.fd` to be
+ // valid for the `'fd` lifetime.
+ unsafe { BorrowedFd::borrow_raw(self.pollfd.fd) }
+ }
+}
+
+#[cfg(windows)]
+impl<'fd> AsSocket for PollFd<'fd> {
+ #[inline]
+ fn as_socket(&self) -> BorrowedFd<'_> {
+ // SAFETY: Our constructors and `set_fd` require `pollfd.fd` to be
+ // valid for the `'fd` lifetime.
+ unsafe { BorrowedFd::borrow_raw(self.pollfd.fd as RawFd) }
+ }
+}
diff --git a/src/backend/libc/event/syscalls.rs b/src/backend/libc/event/syscalls.rs
new file mode 100644
index 0000000..c3c5245
--- /dev/null
+++ b/src/backend/libc/event/syscalls.rs
@@ -0,0 +1,193 @@
+//! libc syscalls supporting `rustix::event`.
+
+use crate::backend::c;
+use crate::backend::conv::ret_c_int;
+#[cfg(any(apple, netbsdlike, target_os = "dragonfly", target_os = "solaris"))]
+use crate::backend::conv::ret_owned_fd;
+use crate::event::PollFd;
+#[cfg(any(linux_kernel, bsd, solarish, target_os = "espidf"))]
+use crate::fd::OwnedFd;
+use crate::io;
+#[cfg(any(bsd, solarish))]
+use {crate::backend::conv::borrowed_fd, crate::fd::BorrowedFd, core::mem::MaybeUninit};
+#[cfg(solarish)]
+use {
+ crate::backend::conv::ret, crate::event::port::Event, crate::utils::as_mut_ptr,
+ core::ptr::null_mut,
+};
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "espidf"
+))]
+use {crate::backend::conv::ret_owned_fd, crate::event::EventfdFlags};
+#[cfg(all(feature = "alloc", bsd))]
+use {crate::event::kqueue::Event, crate::utils::as_ptr, core::ptr::null};
+
+use errno as libc_errno;
+
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "espidf"
+))]
+pub(crate) fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result<OwnedFd> {
+ #[cfg(linux_kernel)]
+ unsafe {
+ syscall! {
+ fn eventfd2(
+ initval: c::c_uint,
+ flags: c::c_int
+ ) via SYS_eventfd2 -> c::c_int
+ }
+ ret_owned_fd(eventfd2(initval, bitflags_bits!(flags)))
+ }
+
+ // `eventfd` was added in FreeBSD 13, so it isn't available on FreeBSD 12.
+ #[cfg(target_os = "freebsd")]
+ unsafe {
+ weakcall! {
+ fn eventfd(
+ initval: c::c_uint,
+ flags: c::c_int
+ ) -> c::c_int
+ }
+ ret_owned_fd(eventfd(initval, bitflags_bits!(flags)))
+ }
+
+ #[cfg(any(target_os = "illumos", target_os = "espidf"))]
+ unsafe {
+ ret_owned_fd(c::eventfd(initval, bitflags_bits!(flags)))
+ }
+}
+
+#[cfg(all(feature = "alloc", bsd))]
+pub(crate) fn kqueue() -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::kqueue()) }
+}
+
+#[cfg(all(feature = "alloc", bsd))]
+pub(crate) unsafe fn kevent(
+ kq: BorrowedFd<'_>,
+ changelist: &[Event],
+ eventlist: &mut [MaybeUninit<Event>],
+ timeout: Option<&c::timespec>,
+) -> io::Result<c::c_int> {
+ ret_c_int(c::kevent(
+ borrowed_fd(kq),
+ changelist.as_ptr().cast(),
+ changelist
+ .len()
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ eventlist.as_mut_ptr().cast(),
+ eventlist
+ .len()
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ timeout.map_or(null(), as_ptr),
+ ))
+}
+
+#[inline]
+pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
+ let nfds = fds
+ .len()
+ .try_into()
+ .map_err(|_convert_err| io::Errno::INVAL)?;
+
+ ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
+ .map(|nready| nready as usize)
+}
+
+#[cfg(solarish)]
+pub(crate) fn port_create() -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::port_create()) }
+}
+
+#[cfg(solarish)]
+pub(crate) unsafe fn port_associate(
+ port: BorrowedFd<'_>,
+ source: c::c_int,
+ object: c::uintptr_t,
+ events: c::c_int,
+ user: *mut c::c_void,
+) -> io::Result<()> {
+ ret(c::port_associate(
+ borrowed_fd(port),
+ source,
+ object,
+ events,
+ user,
+ ))
+}
+
+#[cfg(solarish)]
+pub(crate) unsafe fn port_dissociate(
+ port: BorrowedFd<'_>,
+ source: c::c_int,
+ object: c::uintptr_t,
+) -> io::Result<()> {
+ ret(c::port_dissociate(borrowed_fd(port), source, object))
+}
+
+#[cfg(solarish)]
+pub(crate) fn port_get(
+ port: BorrowedFd<'_>,
+ timeout: Option<&mut c::timespec>,
+) -> io::Result<Event> {
+ let mut event = MaybeUninit::<c::port_event>::uninit();
+ let timeout = timeout.map_or(null_mut(), as_mut_ptr);
+
+ unsafe {
+ ret(c::port_get(borrowed_fd(port), event.as_mut_ptr(), timeout))?;
+ }
+
+ // If we're done, initialize the event and return it.
+ Ok(Event(unsafe { event.assume_init() }))
+}
+
+#[cfg(all(feature = "alloc", solarish))]
+pub(crate) fn port_getn(
+ port: BorrowedFd<'_>,
+ timeout: Option<&mut c::timespec>,
+ events: &mut Vec<Event>,
+ mut nget: u32,
+) -> io::Result<()> {
+ let timeout = timeout.map_or(null_mut(), as_mut_ptr);
+ unsafe {
+ ret(c::port_getn(
+ borrowed_fd(port),
+ events.as_mut_ptr().cast(),
+ events.len().try_into().unwrap(),
+ &mut nget,
+ timeout,
+ ))?;
+ }
+
+ // Update the vector length.
+ unsafe {
+ events.set_len(nget.try_into().unwrap());
+ }
+
+ Ok(())
+}
+
+#[cfg(solarish)]
+pub(crate) fn port_send(
+ port: BorrowedFd<'_>,
+ events: c::c_int,
+ userdata: *mut c::c_void,
+) -> io::Result<()> {
+ unsafe { ret(c::port_send(borrowed_fd(port), events, userdata)) }
+}
+
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+pub(crate) fn pause() {
+ let r = unsafe { libc::pause() };
+ let errno = libc_errno::errno().0;
+ debug_assert_eq!(r, -1);
+ debug_assert_eq!(errno, libc::EINTR);
+}
diff --git a/src/backend/libc/event/types.rs b/src/backend/libc/event/types.rs
new file mode 100644
index 0000000..a04d7e6
--- /dev/null
+++ b/src/backend/libc/event/types.rs
@@ -0,0 +1,37 @@
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "illumos"))]
+use crate::backend::c;
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "espidf"
+))]
+use bitflags::bitflags;
+
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "espidf"
+))]
+bitflags! {
+ /// `EFD_*` flags for use with [`eventfd`].
+ ///
+ /// [`eventfd`]: crate::event::eventfd
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct EventfdFlags: u32 {
+ /// `EFD_CLOEXEC`
+ #[cfg(not(target_os = "espidf"))]
+ const CLOEXEC = bitcast!(c::EFD_CLOEXEC);
+ /// `EFD_NONBLOCK`
+ #[cfg(not(target_os = "espidf"))]
+ const NONBLOCK = bitcast!(c::EFD_NONBLOCK);
+ /// `EFD_SEMAPHORE`
+ #[cfg(not(target_os = "espidf"))]
+ const SEMAPHORE = bitcast!(c::EFD_SEMAPHORE);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/libc/event/windows_syscalls.rs b/src/backend/libc/event/windows_syscalls.rs
new file mode 100644
index 0000000..8ccad47
--- /dev/null
+++ b/src/backend/libc/event/windows_syscalls.rs
@@ -0,0 +1,16 @@
+//! Windows system calls in the `event` module.
+
+use crate::backend::c;
+use crate::backend::conv::ret_c_int;
+use crate::event::PollFd;
+use crate::io;
+
+pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
+ let nfds = fds
+ .len()
+ .try_into()
+ .map_err(|_convert_err| io::Errno::INVAL)?;
+
+ ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
+ .map(|nready| nready as usize)
+}
diff --git a/src/backend/libc/fs/dir.rs b/src/backend/libc/fs/dir.rs
new file mode 100644
index 0000000..e3d4dfd
--- /dev/null
+++ b/src/backend/libc/fs/dir.rs
@@ -0,0 +1,423 @@
+#[cfg(not(any(solarish, target_os = "haiku", target_os = "nto", target_os = "vita")))]
+use super::types::FileType;
+use crate::backend::c;
+use crate::backend::conv::owned_fd;
+use crate::fd::{AsFd, BorrowedFd, OwnedFd};
+use crate::ffi::{CStr, CString};
+use crate::fs::{fcntl_getfl, openat, Mode, OFlags};
+#[cfg(not(target_os = "vita"))]
+use crate::fs::{fstat, Stat};
+#[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+use crate::fs::{fstatfs, StatFs};
+#[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::fs::{fstatvfs, StatVfs};
+use crate::io;
+#[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+#[cfg(feature = "process")]
+use crate::process::fchdir;
+use alloc::borrow::ToOwned;
+#[cfg(not(any(linux_like, target_os = "hurd")))]
+use c::readdir as libc_readdir;
+#[cfg(any(linux_like, target_os = "hurd"))]
+use c::readdir64 as libc_readdir;
+use core::fmt;
+use core::ptr::NonNull;
+use errno::{errno, set_errno, Errno};
+
+/// `DIR*`
+pub struct Dir {
+ /// The `libc` `DIR` pointer.
+ libc_dir: NonNull<c::DIR>,
+
+ /// Have we seen any errors in this iteration?
+ any_errors: bool,
+}
+
+impl Dir {
+ /// Take ownership of `fd` and construct a `Dir` that reads entries from
+ /// the given directory file descriptor.
+ #[inline]
+ pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
+ Self::_new(fd.into())
+ }
+
+ #[inline]
+ fn _new(fd: OwnedFd) -> io::Result<Self> {
+ let raw = owned_fd(fd);
+ unsafe {
+ let libc_dir = c::fdopendir(raw);
+
+ if let Some(libc_dir) = NonNull::new(libc_dir) {
+ Ok(Self {
+ libc_dir,
+ any_errors: false,
+ })
+ } else {
+ let err = io::Errno::last_os_error();
+ let _ = c::close(raw);
+ Err(err)
+ }
+ }
+ }
+
+ /// Borrow `fd` and construct a `Dir` that reads entries from the given
+ /// directory file descriptor.
+ #[inline]
+ pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
+ Self::_read_from(fd.as_fd())
+ }
+
+ #[inline]
+ #[allow(unused_mut)]
+ fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
+ let mut any_errors = false;
+
+ // Given an arbitrary `OwnedFd`, it's impossible to know whether the
+ // user holds a `dup`'d copy which could continue to modify the
+ // file description state, which would cause Undefined Behavior after
+ // our call to `fdopendir`. To prevent this, we obtain an independent
+ // `OwnedFd`.
+ let flags = fcntl_getfl(fd)?;
+ let fd_for_dir = match openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty()) {
+ Ok(fd) => fd,
+ #[cfg(not(target_os = "wasi"))]
+ Err(io::Errno::NOENT) => {
+ // If "." doesn't exist, it means the directory was removed.
+ // We treat that as iterating through a directory with no
+ // entries.
+ any_errors = true;
+ crate::io::dup(fd)?
+ }
+ Err(err) => return Err(err),
+ };
+
+ let raw = owned_fd(fd_for_dir);
+ unsafe {
+ let libc_dir = c::fdopendir(raw);
+
+ if let Some(libc_dir) = NonNull::new(libc_dir) {
+ Ok(Self {
+ libc_dir,
+ any_errors,
+ })
+ } else {
+ let err = io::Errno::last_os_error();
+ let _ = c::close(raw);
+ Err(err)
+ }
+ }
+ }
+
+ /// `rewinddir(self)`
+ #[inline]
+ pub fn rewind(&mut self) {
+ self.any_errors = false;
+ unsafe { c::rewinddir(self.libc_dir.as_ptr()) }
+ }
+
+ /// `readdir(self)`, where `None` means the end of the directory.
+ pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
+ // If we've seen errors, don't continue to try to read anything further.
+ if self.any_errors {
+ return None;
+ }
+
+ set_errno(Errno(0));
+ let dirent_ptr = unsafe { libc_readdir(self.libc_dir.as_ptr()) };
+ if dirent_ptr.is_null() {
+ let curr_errno = errno().0;
+ if curr_errno == 0 {
+ // We successfully reached the end of the stream.
+ None
+ } else {
+ // `errno` is unknown or non-zero, so an error occurred.
+ self.any_errors = true;
+ Some(Err(io::Errno(curr_errno)))
+ }
+ } else {
+ // We successfully read an entry.
+ unsafe {
+ let dirent = &*dirent_ptr;
+
+ // We have our own copy of OpenBSD's dirent; check that the
+ // layout minimally matches libc's.
+ #[cfg(target_os = "openbsd")]
+ check_dirent_layout(dirent);
+
+ let result = DirEntry {
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ d_type: dirent.d_type,
+
+ #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
+ d_ino: dirent.d_ino,
+
+ #[cfg(any(freebsdlike, netbsdlike))]
+ d_fileno: dirent.d_fileno,
+
+ name: CStr::from_ptr(dirent.d_name.as_ptr()).to_owned(),
+ };
+
+ Some(Ok(result))
+ }
+ }
+ }
+
+ /// `fstat(self)`
+ #[cfg(not(target_os = "vita"))]
+ #[inline]
+ pub fn stat(&self) -> io::Result<Stat> {
+ fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+
+ /// `fstatfs(self)`
+ #[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ #[inline]
+ pub fn statfs(&self) -> io::Result<StatFs> {
+ fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+
+ /// `fstatvfs(self)`
+ #[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ #[inline]
+ pub fn statvfs(&self) -> io::Result<StatVfs> {
+ fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+
+ /// `fchdir(self)`
+ #[cfg(feature = "process")]
+ #[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+ #[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
+ #[inline]
+ pub fn chdir(&self) -> io::Result<()> {
+ fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+}
+
+/// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is
+/// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they
+/// need `Sync`, which is effectively what'd need to do to implement `Sync`
+/// ourselves.
+unsafe impl Send for Dir {}
+
+impl Drop for Dir {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe { c::closedir(self.libc_dir.as_ptr()) };
+ }
+}
+
+impl Iterator for Dir {
+ type Item = io::Result<DirEntry>;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ Self::read(self)
+ }
+}
+
+impl fmt::Debug for Dir {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut s = f.debug_struct("Dir");
+ #[cfg(not(target_os = "vita"))]
+ s.field("fd", unsafe { &c::dirfd(self.libc_dir.as_ptr()) });
+ s.finish()
+ }
+}
+
+/// `struct dirent`
+#[derive(Debug)]
+pub struct DirEntry {
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ d_type: u8,
+
+ #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
+ d_ino: c::ino_t,
+
+ #[cfg(any(freebsdlike, netbsdlike))]
+ d_fileno: c::ino_t,
+
+ name: CString,
+}
+
+impl DirEntry {
+ /// Returns the file name of this directory entry.
+ #[inline]
+ pub fn file_name(&self) -> &CStr {
+ &self.name
+ }
+
+ /// Returns the type of this directory entry.
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ #[inline]
+ pub fn file_type(&self) -> FileType {
+ FileType::from_dirent_d_type(self.d_type)
+ }
+
+ /// Return the inode number of this directory entry.
+ #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ self.d_ino as u64
+ }
+
+ /// Return the inode number of this directory entry.
+ #[cfg(any(freebsdlike, netbsdlike))]
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ #[allow(clippy::useless_conversion)]
+ self.d_fileno.into()
+ }
+}
+
+/// libc's OpenBSD `dirent` has a private field so we can't construct it
+/// directly, so we declare it ourselves to make all fields accessible.
+#[cfg(target_os = "openbsd")]
+#[repr(C)]
+#[derive(Debug)]
+struct libc_dirent {
+ d_fileno: c::ino_t,
+ d_off: c::off_t,
+ d_reclen: u16,
+ d_type: u8,
+ d_namlen: u8,
+ __d_padding: [u8; 4],
+ d_name: [c::c_char; 256],
+}
+
+/// We have our own copy of OpenBSD's dirent; check that the layout
+/// minimally matches libc's.
+#[cfg(target_os = "openbsd")]
+fn check_dirent_layout(dirent: &c::dirent) {
+ use crate::utils::as_ptr;
+
+ // Check that the basic layouts match.
+ #[cfg(test)]
+ {
+ assert_eq_size!(libc_dirent, c::dirent);
+ assert_eq_size!(libc_dirent, c::dirent);
+ }
+
+ // Check that the field offsets match.
+ assert_eq!(
+ {
+ let z = libc_dirent {
+ d_fileno: 0_u64,
+ d_off: 0_i64,
+ d_reclen: 0_u16,
+ d_type: 0_u8,
+ d_namlen: 0_u8,
+ __d_padding: [0_u8; 4],
+ d_name: [0 as c::c_char; 256],
+ };
+ let base = as_ptr(&z) as usize;
+ (
+ (as_ptr(&z.d_fileno) as usize) - base,
+ (as_ptr(&z.d_off) as usize) - base,
+ (as_ptr(&z.d_reclen) as usize) - base,
+ (as_ptr(&z.d_type) as usize) - base,
+ (as_ptr(&z.d_namlen) as usize) - base,
+ (as_ptr(&z.d_name) as usize) - base,
+ )
+ },
+ {
+ let z = dirent;
+ let base = as_ptr(z) as usize;
+ (
+ (as_ptr(&z.d_fileno) as usize) - base,
+ (as_ptr(&z.d_off) as usize) - base,
+ (as_ptr(&z.d_reclen) as usize) - base,
+ (as_ptr(&z.d_type) as usize) - base,
+ (as_ptr(&z.d_namlen) as usize) - base,
+ (as_ptr(&z.d_name) as usize) - base,
+ )
+ }
+ );
+}
+
+#[test]
+fn dir_iterator_handles_io_errors() {
+ // create a dir, keep the FD, then delete the dir
+ let tmp = tempfile::tempdir().unwrap();
+ let fd = crate::fs::openat(
+ crate::fs::CWD,
+ tmp.path(),
+ crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
+ crate::fs::Mode::empty(),
+ )
+ .unwrap();
+
+ let file_fd = crate::fs::openat(
+ &fd,
+ tmp.path().join("test.txt"),
+ crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
+ crate::fs::Mode::RWXU,
+ )
+ .unwrap();
+
+ let mut dir = Dir::read_from(&fd).unwrap();
+
+ // Reach inside the `Dir` and replace its directory with a file, which
+ // will cause the subsequent `readdir` to fail.
+ unsafe {
+ let raw_fd = c::dirfd(dir.libc_dir.as_ptr());
+ let mut owned_fd: crate::fd::OwnedFd = crate::fd::FromRawFd::from_raw_fd(raw_fd);
+ crate::io::dup2(&file_fd, &mut owned_fd).unwrap();
+ core::mem::forget(owned_fd);
+ }
+
+ // FreeBSD and macOS seem to read some directory entries before we call
+ // `.next()`.
+ #[cfg(any(apple, freebsdlike))]
+ {
+ dir.rewind();
+ }
+
+ assert!(matches!(dir.next(), Some(Err(_))));
+ assert!(dir.next().is_none());
+}
diff --git a/src/backend/libc/fs/inotify.rs b/src/backend/libc/fs/inotify.rs
new file mode 100644
index 0000000..2044bd9
--- /dev/null
+++ b/src/backend/libc/fs/inotify.rs
@@ -0,0 +1,131 @@
+//! inotify support for working with inotifies
+
+use crate::backend::c;
+use crate::backend::conv::{borrowed_fd, c_str, ret, ret_c_int, ret_owned_fd};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `IN_*` for use with [`inotify_init`].
+ ///
+ /// [`inotify_init`]: crate::fs::inotify::inotify_init
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CreateFlags: u32 {
+ /// `IN_CLOEXEC`
+ const CLOEXEC = bitcast!(c::IN_CLOEXEC);
+ /// `IN_NONBLOCK`
+ const NONBLOCK = bitcast!(c::IN_NONBLOCK);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `IN*` for use with [`inotify_add_watch`].
+ ///
+ /// [`inotify_add_watch`]: crate::fs::inotify::inotify_add_watch
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct WatchFlags: u32 {
+ /// `IN_ACCESS`
+ const ACCESS = c::IN_ACCESS;
+ /// `IN_ATTRIB`
+ const ATTRIB = c::IN_ATTRIB;
+ /// `IN_CLOSE_NOWRITE`
+ const CLOSE_NOWRITE = c::IN_CLOSE_NOWRITE;
+ /// `IN_CLOSE_WRITE`
+ const CLOSE_WRITE = c::IN_CLOSE_WRITE;
+ /// `IN_CREATE`
+ const CREATE = c::IN_CREATE;
+ /// `IN_DELETE`
+ const DELETE = c::IN_DELETE;
+ /// `IN_DELETE_SELF`
+ const DELETE_SELF = c::IN_DELETE_SELF;
+ /// `IN_MODIFY`
+ const MODIFY = c::IN_MODIFY;
+ /// `IN_MOVE_SELF`
+ const MOVE_SELF = c::IN_MOVE_SELF;
+ /// `IN_MOVED_FROM`
+ const MOVED_FROM = c::IN_MOVED_FROM;
+ /// `IN_MOVED_TO`
+ const MOVED_TO = c::IN_MOVED_TO;
+ /// `IN_OPEN`
+ const OPEN = c::IN_OPEN;
+
+ /// `IN_CLOSE`
+ const CLOSE = c::IN_CLOSE;
+ /// `IN_MOVE`
+ const MOVE = c::IN_MOVE;
+ /// `IN_ALL_EVENTS`
+ const ALL_EVENTS = c::IN_ALL_EVENTS;
+
+ /// `IN_DONT_FOLLOW`
+ const DONT_FOLLOW = c::IN_DONT_FOLLOW;
+ /// `IN_EXCL_UNLINK`
+ const EXCL_UNLINK = 1;
+ /// `IN_MASK_ADD`
+ const MASK_ADD = 1;
+ /// `IN_MASK_CREATE`
+ const MASK_CREATE = 1;
+ /// `IN_ONESHOT`
+ const ONESHOT = c::IN_ONESHOT;
+ /// `IN_ONLYDIR`
+ const ONLYDIR = c::IN_ONLYDIR;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `inotify_init1(flags)`—Creates a new inotify object.
+///
+/// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file
+/// descriptor from being implicitly passed across `exec` boundaries.
+#[doc(alias = "inotify_init1")]
+pub fn inotify_init(flags: CreateFlags) -> io::Result<OwnedFd> {
+ // SAFETY: `inotify_init1` has no safety preconditions.
+ unsafe { ret_owned_fd(c::inotify_init1(bitflags_bits!(flags))) }
+}
+
+/// `inotify_add_watch(self, path, flags)`—Adds a watch to inotify.
+///
+/// This registers or updates a watch for the filesystem path `path` and
+/// returns a watch descriptor corresponding to this watch.
+///
+/// Note: Due to the existence of hardlinks, providing two different paths to
+/// this method may result in it returning the same watch descriptor. An
+/// application should keep track of this externally to avoid logic errors.
+pub fn inotify_add_watch<P: crate::path::Arg>(
+ inot: BorrowedFd<'_>,
+ path: P,
+ flags: WatchFlags,
+) -> io::Result<i32> {
+ path.into_with_c_str(|path| {
+ // SAFETY: The fd and path we are passing is guaranteed valid by the
+ // type system.
+ unsafe {
+ ret_c_int(c::inotify_add_watch(
+ borrowed_fd(inot),
+ c_str(path),
+ flags.bits(),
+ ))
+ }
+ })
+}
+
+/// `inotify_rm_watch(self, wd)`—Removes a watch from this inotify.
+///
+/// The watch descriptor provided should have previously been returned by
+/// [`inotify_add_watch`] and not previously have been removed.
+#[doc(alias = "inotify_rm_watch")]
+pub fn inotify_remove_watch(inot: BorrowedFd<'_>, wd: i32) -> io::Result<()> {
+ // Android's `inotify_rm_watch` takes `u32` despite that
+ // `inotify_add_watch` expects a `i32`.
+ #[cfg(target_os = "android")]
+ let wd = wd as u32;
+ // SAFETY: The fd is valid and closing an arbitrary wd is valid.
+ unsafe { ret(c::inotify_rm_watch(borrowed_fd(inot), wd)) }
+}
diff --git a/src/backend/libc/fs/makedev.rs b/src/backend/libc/fs/makedev.rs
new file mode 100644
index 0000000..aa12102
--- /dev/null
+++ b/src/backend/libc/fs/makedev.rs
@@ -0,0 +1,138 @@
+#[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+use crate::backend::c;
+use crate::fs::Dev;
+
+#[cfg(not(any(
+ apple,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+)))]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ c::makedev(maj, min)
+}
+
+#[cfg(solarish)]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // SAFETY: Solarish's `makedev` is marked unsafe but it isn't doing
+ // anything unsafe.
+ unsafe { c::makedev(maj, min) }
+}
+
+#[cfg(all(target_os = "android", not(target_pointer_width = "32")))]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ c::makedev(maj, min)
+}
+
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // 32-bit Android's `dev_t` is 32-bit, but its `st_dev` is 64-bit, so we do
+ // it ourselves.
+ ((u64::from(maj) & 0xffff_f000_u64) << 32)
+ | ((u64::from(maj) & 0x0000_0fff_u64) << 8)
+ | ((u64::from(min) & 0xffff_ff00_u64) << 12)
+ | (u64::from(min) & 0x0000_00ff_u64)
+}
+
+#[cfg(target_os = "emscripten")]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // Emscripten's `makedev` has a 32-bit return value.
+ Dev::from(c::makedev(maj, min))
+}
+
+#[cfg(apple)]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // Apple's `makedev` oddly has signed argument types and is `unsafe`.
+ unsafe { c::makedev(maj as i32, min as i32) }
+}
+
+#[cfg(target_os = "aix")]
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ // AIX's `makedev` oddly is `unsafe`.
+ unsafe { c::makedev(maj, min) }
+}
+
+#[cfg(not(any(
+ apple,
+ freebsdlike,
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "netbsd"
+)))]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ unsafe { c::major(dev) }
+}
+
+#[cfg(any(
+ apple,
+ freebsdlike,
+ target_os = "netbsd",
+ all(target_os = "android", not(target_pointer_width = "32")),
+))]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ // On some platforms `major` oddly has signed return types.
+ (unsafe { c::major(dev) }) as u32
+}
+
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ // 32-bit Android's `dev_t` is 32-bit, but its `st_dev` is 64-bit, so we do
+ // it ourselves.
+ (((dev >> 31 >> 1) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff)) as u32
+}
+
+#[cfg(target_os = "emscripten")]
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ // Emscripten's `major` has a 32-bit argument value.
+ unsafe { c::major(dev as u32) }
+}
+
+#[cfg(not(any(
+ apple,
+ freebsdlike,
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "netbsd"
+)))]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ unsafe { c::minor(dev) }
+}
+
+#[cfg(any(
+ apple,
+ freebsdlike,
+ target_os = "netbsd",
+ all(target_os = "android", not(target_pointer_width = "32"))
+))]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ // On some platforms, `minor` oddly has signed return types.
+ (unsafe { c::minor(dev) }) as u32
+}
+
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ // 32-bit Android's `dev_t` is 32-bit, but its `st_dev` is 64-bit, so we do
+ // it ourselves.
+ (((dev >> 12) & 0xffff_ff00) | (dev & 0x0000_00ff)) as u32
+}
+
+#[cfg(target_os = "emscripten")]
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ // Emscripten's `minor` has a 32-bit argument value.
+ unsafe { c::minor(dev as u32) }
+}
diff --git a/src/backend/libc/fs/mod.rs b/src/backend/libc/fs/mod.rs
new file mode 100644
index 0000000..d342f3a
--- /dev/null
+++ b/src/backend/libc/fs/mod.rs
@@ -0,0 +1,23 @@
+#[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))]
+pub(crate) mod dir;
+#[cfg(linux_kernel)]
+pub mod inotify;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) mod makedev;
+#[cfg(not(windows))]
+pub(crate) mod syscalls;
+pub(crate) mod types;
+
+// TODO: Fix linux-raw-sys to define ioctl codes for sparc.
+#[cfg(all(feature = "linux-raw-sys", any(target_arch = "sparc", target_arch = "sparc64")))]
+pub(crate) const EXT4_IOC_RESIZE_FS: crate::ioctl::RawOpcode = 0x8008_6610;
+
+#[cfg(all(feature = "linux-raw-sys", not(any(target_arch = "sparc", target_arch = "sparc64"))))]
+pub(crate) const EXT4_IOC_RESIZE_FS: crate::ioctl::RawOpcode =
+ linux_raw_sys::ioctl::EXT4_IOC_RESIZE_FS as crate::ioctl::RawOpcode;
diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs
new file mode 100644
index 0000000..4b3bff1
--- /dev/null
+++ b/src/backend/libc/fs/syscalls.rs
@@ -0,0 +1,2533 @@
+//! libc syscalls supporting `rustix::fs`.
+
+use crate::backend::c;
+#[cfg(any(
+ not(target_os = "redox"),
+ feature = "alloc",
+ all(linux_kernel, feature = "procfs")
+))]
+use crate::backend::conv::ret_usize;
+use crate::backend::conv::{borrowed_fd, c_str, ret, ret_c_int, ret_off_t, ret_owned_fd};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::ffi::CStr;
+#[cfg(all(apple, feature = "alloc"))]
+use crate::ffi::CString;
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+use crate::fs::Access;
+#[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+use crate::fs::Advice;
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+use crate::fs::AtFlags;
+#[cfg(not(any(
+ netbsdlike,
+ solarish,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+use crate::fs::FallocateFlags;
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+use crate::fs::FlockOperation;
+#[cfg(any(linux_kernel, target_os = "freebsd"))]
+use crate::fs::MemfdFlags;
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+use crate::fs::SealFlags;
+#[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+use crate::fs::StatFs;
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+use crate::fs::Timestamps;
+#[cfg(not(any(
+ apple,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::fs::{Dev, FileType};
+use crate::fs::{Mode, OFlags, SeekFrom, Stat};
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+use crate::fs::{StatVfs, StatVfsMountFlags};
+use crate::io;
+#[cfg(all(target_env = "gnu", fix_y2038))]
+use crate::timespec::LibcTimespec;
+#[cfg(not(target_os = "wasi"))]
+use crate::ugid::{Gid, Uid};
+#[cfg(all(apple, feature = "alloc"))]
+use alloc::vec;
+use core::mem::MaybeUninit;
+#[cfg(apple)]
+use {
+ crate::backend::conv::nonnegative_ret,
+ crate::fs::{copyfile_state_t, CloneFlags, CopyfileFlags},
+};
+#[cfg(any(apple, linux_kernel))]
+use {crate::fs::XattrFlags, core::mem::size_of, core::ptr::null_mut};
+#[cfg(linux_kernel)]
+use {
+ crate::fs::{RenameFlags, ResolveFlags, Statx, StatxFlags, CWD},
+ core::ptr::null,
+};
+
+use errno as libc_errno;
+
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __utimensat64(c::c_int, *const c::c_char, *const LibcTimespec, c::c_int) -> c::c_int);
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __futimens64(c::c_int, *const LibcTimespec) -> c::c_int);
+
+/// Use a direct syscall (via libc) for `open`.
+///
+/// This is only currently necessary as a workaround for old glibc; see below.
+#[cfg(all(unix, target_env = "gnu"))]
+fn open_via_syscall(path: &CStr, oflags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
+ // Linux on aarch64, loongarch64 and riscv64 has no `open` syscall so use
+ // `openat`.
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "riscv32",
+ target_arch = "riscv64",
+ target_arch = "csky",
+ target_arch = "loongarch64"
+ ))]
+ {
+ openat_via_syscall(CWD, path, oflags, mode)
+ }
+
+ // Use the `open` syscall.
+ #[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "riscv32",
+ target_arch = "riscv64",
+ target_arch = "csky",
+ target_arch = "loongarch64"
+ )))]
+ unsafe {
+ syscall! {
+ fn open(
+ pathname: *const c::c_char,
+ oflags: c::c_int,
+ mode: c::mode_t
+ ) via SYS_open -> c::c_int
+ }
+
+ ret_owned_fd(open(
+ c_str(path),
+ bitflags_bits!(oflags),
+ bitflags_bits!(mode),
+ ))
+ }
+}
+
+pub(crate) fn open(path: &CStr, oflags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
+ // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>.
+ // glibc versions before 2.25 don't handle `O_TMPFILE` correctly.
+ #[cfg(all(unix, target_env = "gnu", not(target_os = "hurd")))]
+ if oflags.contains(OFlags::TMPFILE) && crate::backend::if_glibc_is_less_than_2_25() {
+ return open_via_syscall(path, oflags, mode);
+ }
+
+ // On these platforms, `mode_t` is `u16` and can't be passed directly to a
+ // variadic function.
+ #[cfg(any(
+ apple,
+ freebsdlike,
+ all(target_os = "android", target_pointer_width = "32")
+ ))]
+ let mode: c::c_uint = mode.bits().into();
+
+ // Otherwise, cast to `mode_t` as that's what `open` is documented to take.
+ #[cfg(not(any(
+ apple,
+ freebsdlike,
+ all(target_os = "android", target_pointer_width = "32")
+ )))]
+ let mode: c::mode_t = mode.bits() as _;
+
+ unsafe { ret_owned_fd(c::open(c_str(path), bitflags_bits!(oflags), mode)) }
+}
+
+/// Use a direct syscall (via libc) for `openat`.
+///
+/// This is only currently necessary as a workaround for old glibc; see below.
+#[cfg(all(unix, target_env = "gnu", not(target_os = "hurd")))]
+fn openat_via_syscall(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ oflags: OFlags,
+ mode: Mode,
+) -> io::Result<OwnedFd> {
+ syscall! {
+ fn openat(
+ base_dirfd: c::c_int,
+ pathname: *const c::c_char,
+ oflags: c::c_int,
+ mode: c::mode_t
+ ) via SYS_openat -> c::c_int
+ }
+
+ unsafe {
+ ret_owned_fd(openat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ bitflags_bits!(oflags),
+ bitflags_bits!(mode),
+ ))
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn openat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ oflags: OFlags,
+ mode: Mode,
+) -> io::Result<OwnedFd> {
+ // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>.
+ // glibc versions before 2.25 don't handle `O_TMPFILE` correctly.
+ #[cfg(all(unix, target_env = "gnu", not(target_os = "hurd")))]
+ if oflags.contains(OFlags::TMPFILE) && crate::backend::if_glibc_is_less_than_2_25() {
+ return openat_via_syscall(dirfd, path, oflags, mode);
+ }
+
+ // On these platforms, `mode_t` is `u16` and can't be passed directly to a
+ // variadic function.
+ #[cfg(any(
+ apple,
+ freebsdlike,
+ all(target_os = "android", target_pointer_width = "32")
+ ))]
+ let mode: c::c_uint = mode.bits().into();
+
+ // Otherwise, cast to `mode_t` as that's what `open` is documented to take.
+ #[cfg(not(any(
+ apple,
+ freebsdlike,
+ all(target_os = "android", target_pointer_width = "32")
+ )))]
+ let mode: c::mode_t = mode.bits() as _;
+
+ unsafe {
+ ret_owned_fd(c::openat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ bitflags_bits!(oflags),
+ mode,
+ ))
+ }
+}
+
+#[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+#[inline]
+pub(crate) fn statfs(filename: &CStr) -> io::Result<StatFs> {
+ unsafe {
+ let mut result = MaybeUninit::<StatFs>::uninit();
+ ret(c::statfs(c_str(filename), result.as_mut_ptr()))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub(crate) fn statvfs(filename: &CStr) -> io::Result<StatVfs> {
+ unsafe {
+ let mut result = MaybeUninit::<c::statvfs>::uninit();
+ ret(c::statvfs(c_str(filename), result.as_mut_ptr()))?;
+ Ok(libc_statvfs_to_statvfs(result.assume_init()))
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[inline]
+pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ ret_usize(
+ c::readlink(c_str(path), buf.as_mut_ptr().cast::<c::c_char>(), buf.len()) as isize,
+ )
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+#[inline]
+pub(crate) fn readlinkat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ buf: &mut [MaybeUninit<u8>],
+) -> io::Result<usize> {
+ unsafe {
+ ret_usize(c::readlinkat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ buf.as_mut_ptr().cast::<c::c_char>(),
+ buf.len(),
+ ) as isize)
+ }
+}
+
+pub(crate) fn mkdir(path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe { ret(c::mkdir(c_str(path), mode.bits() as c::mode_t)) }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn mkdirat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe {
+ ret(c::mkdirat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ mode.bits() as c::mode_t,
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn getdents_uninit(
+ fd: BorrowedFd<'_>,
+ buf: &mut [MaybeUninit<u8>],
+) -> io::Result<usize> {
+ syscall! {
+ fn getdents64(
+ fd: c::c_int,
+ dirp: *mut c::c_void,
+ count: usize
+ ) via SYS_getdents64 -> c::ssize_t
+ }
+ unsafe {
+ ret_usize(getdents64(
+ borrowed_fd(fd),
+ buf.as_mut_ptr().cast::<c::c_void>(),
+ buf.len(),
+ ))
+ }
+}
+
+pub(crate) fn link(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::link(c_str(old_path), c_str(new_path))) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+pub(crate) fn linkat(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // macOS <= 10.9 lacks `linkat`.
+ #[cfg(target_os = "macos")]
+ unsafe {
+ weak! {
+ fn linkat(
+ c::c_int,
+ *const c::c_char,
+ c::c_int,
+ *const c::c_char,
+ c::c_int
+ ) -> c::c_int
+ }
+ // If we have `linkat`, use it.
+ if let Some(libc_linkat) = linkat.get() {
+ return ret(libc_linkat(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ bitflags_bits!(flags),
+ ));
+ }
+ // Otherwise, see if we can emulate the `AT_FDCWD` case.
+ if borrowed_fd(old_dirfd) != c::AT_FDCWD || borrowed_fd(new_dirfd) != c::AT_FDCWD {
+ return Err(io::Errno::NOSYS);
+ }
+ if flags.intersects(!AtFlags::SYMLINK_FOLLOW) {
+ return Err(io::Errno::INVAL);
+ }
+ if !flags.is_empty() {
+ return Err(io::Errno::OPNOTSUPP);
+ }
+ ret(c::link(c_str(old_path), c_str(new_path)))
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ unsafe {
+ ret(c::linkat(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+pub(crate) fn rmdir(path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::rmdir(c_str(path))) }
+}
+
+pub(crate) fn unlink(path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::unlink(c_str(path))) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
+ // macOS <= 10.9 lacks `unlinkat`.
+ #[cfg(target_os = "macos")]
+ unsafe {
+ weak! {
+ fn unlinkat(
+ c::c_int,
+ *const c::c_char,
+ c::c_int
+ ) -> c::c_int
+ }
+ // If we have `unlinkat`, use it.
+ if let Some(libc_unlinkat) = unlinkat.get() {
+ return ret(libc_unlinkat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ bitflags_bits!(flags),
+ ));
+ }
+ // Otherwise, see if we can emulate the `AT_FDCWD` case.
+ if borrowed_fd(dirfd) != c::AT_FDCWD {
+ return Err(io::Errno::NOSYS);
+ }
+ if flags.intersects(!AtFlags::REMOVEDIR) {
+ return Err(io::Errno::INVAL);
+ }
+ if flags.contains(AtFlags::REMOVEDIR) {
+ ret(c::rmdir(c_str(path)))
+ } else {
+ ret(c::unlink(c_str(path)))
+ }
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ unsafe {
+ ret(c::unlinkat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+pub(crate) fn rename(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::rename(c_str(old_path), c_str(new_path))) }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn renameat(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+) -> io::Result<()> {
+ // macOS <= 10.9 lacks `renameat`.
+ #[cfg(target_os = "macos")]
+ unsafe {
+ weak! {
+ fn renameat(
+ c::c_int,
+ *const c::c_char,
+ c::c_int,
+ *const c::c_char
+ ) -> c::c_int
+ }
+ // If we have `renameat`, use it.
+ if let Some(libc_renameat) = renameat.get() {
+ return ret(libc_renameat(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ ));
+ }
+ // Otherwise, see if we can emulate the `AT_FDCWD` case.
+ if borrowed_fd(old_dirfd) != c::AT_FDCWD || borrowed_fd(new_dirfd) != c::AT_FDCWD {
+ return Err(io::Errno::NOSYS);
+ }
+ ret(c::rename(c_str(old_path), c_str(new_path)))
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ unsafe {
+ ret(c::renameat(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ ))
+ }
+}
+
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+pub(crate) fn renameat2(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: RenameFlags,
+) -> io::Result<()> {
+ // `renameat2` wasn't supported in glibc until 2.28.
+ weak_or_syscall! {
+ fn renameat2(
+ olddirfd: c::c_int,
+ oldpath: *const c::c_char,
+ newdirfd: c::c_int,
+ newpath: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_renameat2 -> c::c_int
+ }
+
+ unsafe {
+ ret(renameat2(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+#[inline]
+pub(crate) fn renameat2(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: RenameFlags,
+) -> io::Result<()> {
+ // At present, `libc` only has `renameat2` defined for glibc. If we have
+ // no flags, we can use plain `renameat`, but otherwise we use `syscall!`.
+ // to call `renameat2` ourselves.
+ if flags.is_empty() {
+ renameat(old_dirfd, old_path, new_dirfd, new_path)
+ } else {
+ syscall! {
+ fn renameat2(
+ olddirfd: c::c_int,
+ oldpath: *const c::c_char,
+ newdirfd: c::c_int,
+ newpath: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_renameat2 -> c::c_int
+ }
+
+ unsafe {
+ ret(renameat2(
+ borrowed_fd(old_dirfd),
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ flags.bits(),
+ ))
+ }
+ }
+}
+
+pub(crate) fn symlink(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::symlink(c_str(old_path), c_str(new_path))) }
+}
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn symlinkat(
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::symlinkat(
+ c_str(old_path),
+ borrowed_fd(new_dirfd),
+ c_str(new_path),
+ ))
+ }
+}
+
+pub(crate) fn stat(path: &CStr) -> io::Result<Stat> {
+ // See the comments in `fstat` about using `crate::fs::statx` here.
+ #[cfg(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ ))]
+ {
+ match crate::fs::statx(
+ crate::fs::CWD,
+ path,
+ AtFlags::empty(),
+ StatxFlags::BASIC_STATS,
+ ) {
+ Ok(x) => statx_to_stat(x),
+ Err(io::Errno::NOSYS) => statat_old(crate::fs::CWD, path, AtFlags::empty()),
+ Err(err) => Err(err),
+ }
+ }
+
+ // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
+ // there's nothing practical we can do.
+ #[cfg(not(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ )))]
+ unsafe {
+ let mut stat = MaybeUninit::<Stat>::uninit();
+ ret(c::stat(c_str(path), stat.as_mut_ptr()))?;
+ Ok(stat.assume_init())
+ }
+}
+
+pub(crate) fn lstat(path: &CStr) -> io::Result<Stat> {
+ // See the comments in `fstat` about using `crate::fs::statx` here.
+ #[cfg(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ ))]
+ {
+ match crate::fs::statx(
+ crate::fs::CWD,
+ path,
+ AtFlags::SYMLINK_NOFOLLOW,
+ StatxFlags::BASIC_STATS,
+ ) {
+ Ok(x) => statx_to_stat(x),
+ Err(io::Errno::NOSYS) => statat_old(crate::fs::CWD, path, AtFlags::SYMLINK_NOFOLLOW),
+ Err(err) => Err(err),
+ }
+ }
+
+ // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
+ // there's nothing practical we can do.
+ #[cfg(not(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ )))]
+ unsafe {
+ let mut stat = MaybeUninit::<Stat>::uninit();
+ ret(c::lstat(c_str(path), stat.as_mut_ptr()))?;
+ Ok(stat.assume_init())
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
+ // See the comments in `fstat` about using `crate::fs::statx` here.
+ #[cfg(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ ))]
+ {
+ match crate::fs::statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
+ Ok(x) => statx_to_stat(x),
+ Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags),
+ Err(err) => Err(err),
+ }
+ }
+
+ // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
+ // there's nothing practical we can do.
+ #[cfg(not(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ )))]
+ unsafe {
+ let mut stat = MaybeUninit::<Stat>::uninit();
+ ret(c::fstatat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ stat.as_mut_ptr(),
+ bitflags_bits!(flags),
+ ))?;
+ Ok(stat.assume_init())
+ }
+}
+
+#[cfg(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+))]
+fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
+ unsafe {
+ let mut result = MaybeUninit::<c::stat64>::uninit();
+ ret(c::fstatat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ result.as_mut_ptr(),
+ bitflags_bits!(flags),
+ ))?;
+ stat64_to_stat(result.assume_init())
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "emscripten", target_os = "vita")))]
+pub(crate) fn access(path: &CStr, access: Access) -> io::Result<()> {
+ unsafe { ret(c::access(c_str(path), access.bits())) }
+}
+
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita"
+)))]
+pub(crate) fn accessat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ access: Access,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // macOS <= 10.9 lacks `faccessat`.
+ #[cfg(target_os = "macos")]
+ unsafe {
+ weak! {
+ fn faccessat(
+ c::c_int,
+ *const c::c_char,
+ c::c_int,
+ c::c_int
+ ) -> c::c_int
+ }
+ // If we have `faccessat`, use it.
+ if let Some(libc_faccessat) = faccessat.get() {
+ return ret(libc_faccessat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ bitflags_bits!(access),
+ bitflags_bits!(flags),
+ ));
+ }
+ // Otherwise, see if we can emulate the `AT_FDCWD` case.
+ if borrowed_fd(dirfd) != c::AT_FDCWD {
+ return Err(io::Errno::NOSYS);
+ }
+ if flags.intersects(!(AtFlags::EACCESS | AtFlags::SYMLINK_NOFOLLOW)) {
+ return Err(io::Errno::INVAL);
+ }
+ if !flags.is_empty() {
+ return Err(io::Errno::OPNOTSUPP);
+ }
+ ret(c::access(c_str(path), bitflags_bits!(access)))
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ unsafe {
+ ret(c::faccessat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ bitflags_bits!(access),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(target_os = "emscripten")]
+pub(crate) fn access(_path: &CStr, _access: Access) -> io::Result<()> {
+ Ok(())
+}
+
+#[cfg(target_os = "emscripten")]
+pub(crate) fn accessat(
+ _dirfd: BorrowedFd<'_>,
+ _path: &CStr,
+ _access: Access,
+ _flags: AtFlags,
+) -> io::Result<()> {
+ Ok(())
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
+pub(crate) fn utimensat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // Old 32-bit version: libc has `utimensat` but it is not y2038 safe by
+ // default. But there may be a `__utimensat64` we can use.
+ #[cfg(all(fix_y2038, not(apple)))]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_utimensat) = __utimensat64.get() {
+ let libc_times: [LibcTimespec; 2] = [
+ times.last_access.clone().into(),
+ times.last_modification.clone().into(),
+ ];
+
+ unsafe {
+ return ret(libc_utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ libc_times.as_ptr(),
+ bitflags_bits!(flags),
+ ));
+ }
+ }
+
+ utimensat_old(dirfd, path, times, flags)
+ }
+
+ // Main version: libc is y2038 safe and has `utimensat`. Or, the platform
+ // is not y2038 safe and there's nothing practical we can do.
+ #[cfg(not(any(apple, fix_y2038)))]
+ unsafe {
+ use crate::utils::as_ptr;
+
+ ret(c::utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ as_ptr(times).cast(),
+ bitflags_bits!(flags),
+ ))
+ }
+
+ // Apple version: `utimensat` was introduced in macOS 10.13.
+ #[cfg(apple)]
+ unsafe {
+ use crate::utils::as_ptr;
+
+ // ABI details
+ weak! {
+ fn utimensat(
+ c::c_int,
+ *const c::c_char,
+ *const c::timespec,
+ c::c_int
+ ) -> c::c_int
+ }
+ extern "C" {
+ fn setattrlist(
+ path: *const c::c_char,
+ attr_list: *const Attrlist,
+ attr_buf: *const c::c_void,
+ attr_buf_size: c::size_t,
+ options: c::c_ulong,
+ ) -> c::c_int;
+ }
+ const FSOPT_NOFOLLOW: c::c_ulong = 0x0000_0001;
+
+ // If we have `utimensat`, use it.
+ if let Some(have_utimensat) = utimensat.get() {
+ return ret(have_utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ as_ptr(times).cast(),
+ bitflags_bits!(flags),
+ ));
+ }
+
+ // Convert `times`. We only need this in the child, but do it before
+ // calling `fork` because it might fail.
+ let (attrbuf_size, times, attrs) = times_to_attrlist(times)?;
+
+ // `setattrlistat` was introduced in 10.13 along with `utimensat`, so
+ // if we don't have `utimensat`, we don't have `setattrlistat` either.
+ // Emulate it using `fork`, and `fchdir` and [`setattrlist`].
+ //
+ // [`setattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setattrlist.2.html
+ match c::fork() {
+ -1 => Err(io::Errno::IO),
+ 0 => {
+ if c::fchdir(borrowed_fd(dirfd)) != 0 {
+ let code = match libc_errno::errno().0 {
+ c::EACCES => 2,
+ c::ENOTDIR => 3,
+ _ => 1,
+ };
+ c::_exit(code);
+ }
+
+ let mut flags_arg = 0;
+ if flags.contains(AtFlags::SYMLINK_NOFOLLOW) {
+ flags_arg |= FSOPT_NOFOLLOW;
+ }
+
+ if setattrlist(
+ c_str(path),
+ &attrs,
+ as_ptr(&times).cast(),
+ attrbuf_size,
+ flags_arg,
+ ) != 0
+ {
+ // Translate expected `errno` codes into ad-hoc integer
+ // values suitable for exit statuses.
+ let code = match libc_errno::errno().0 {
+ c::EACCES => 2,
+ c::ENOTDIR => 3,
+ c::EPERM => 4,
+ c::EROFS => 5,
+ c::ELOOP => 6,
+ c::ENOENT => 7,
+ c::ENAMETOOLONG => 8,
+ c::EINVAL => 9,
+ c::ESRCH => 10,
+ c::ENOTSUP => 11,
+ _ => 1,
+ };
+ c::_exit(code);
+ }
+
+ c::_exit(0);
+ }
+ child_pid => {
+ let mut wstatus = 0;
+ let _ = ret_c_int(c::waitpid(child_pid, &mut wstatus, 0))?;
+ if c::WIFEXITED(wstatus) {
+ // Translate our ad-hoc exit statuses back to `errno`
+ // codes.
+ match c::WEXITSTATUS(wstatus) {
+ 0 => Ok(()),
+ 2 => Err(io::Errno::ACCESS),
+ 3 => Err(io::Errno::NOTDIR),
+ 4 => Err(io::Errno::PERM),
+ 5 => Err(io::Errno::ROFS),
+ 6 => Err(io::Errno::LOOP),
+ 7 => Err(io::Errno::NOENT),
+ 8 => Err(io::Errno::NAMETOOLONG),
+ 9 => Err(io::Errno::INVAL),
+ 10 => Err(io::Errno::SRCH),
+ 11 => Err(io::Errno::NOTSUP),
+ _ => Err(io::Errno::IO),
+ }
+ } else {
+ Err(io::Errno::IO)
+ }
+ }
+ }
+ }
+}
+
+#[cfg(all(fix_y2038, not(apple)))]
+fn utimensat_old(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ let old_times = [
+ c::timespec {
+ tv_sec: times
+ .last_access
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times
+ .last_access
+ .tv_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ },
+ c::timespec {
+ tv_sec: times
+ .last_modification
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times
+ .last_modification
+ .tv_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ },
+ ];
+ unsafe {
+ ret(c::utimensat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ old_times.as_ptr(),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn chmod(path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe { ret(c::chmod(c_str(path), mode.bits() as c::mode_t)) }
+}
+
+#[cfg(not(any(
+ linux_kernel,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "wasi"
+)))]
+pub(crate) fn chmodat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ mode: Mode,
+ flags: AtFlags,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::fchmodat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ mode.bits() as c::mode_t,
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn chmodat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ mode: Mode,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // Linux's `fchmodat` does not have a flags argument.
+ //
+ // Use `c::syscall` rather than `c::fchmodat` because some libc
+ // implementations, such as musl, add extra logic to `fchmod` to emulate
+ // support for `AT_SYMLINK_NOFOLLOW`, which uses `/proc` outside our
+ // control.
+ syscall! {
+ fn fchmodat(
+ base_dirfd: c::c_int,
+ pathname: *const c::c_char,
+ mode: c::mode_t
+ ) via SYS_fchmodat -> c::c_int
+ }
+ if flags == AtFlags::SYMLINK_NOFOLLOW {
+ return Err(io::Errno::OPNOTSUPP);
+ }
+ if !flags.is_empty() {
+ return Err(io::Errno::INVAL);
+ }
+ unsafe {
+ ret(fchmodat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ mode.bits() as c::mode_t,
+ ))
+ }
+}
+
+#[cfg(apple)]
+pub(crate) fn fclonefileat(
+ srcfd: BorrowedFd<'_>,
+ dst_dirfd: BorrowedFd<'_>,
+ dst: &CStr,
+ flags: CloneFlags,
+) -> io::Result<()> {
+ syscall! {
+ fn fclonefileat(
+ srcfd: BorrowedFd<'_>,
+ dst_dirfd: BorrowedFd<'_>,
+ dst: *const c::c_char,
+ flags: c::c_int
+ ) via SYS_fclonefileat -> c::c_int
+ }
+
+ unsafe {
+ ret(fclonefileat(
+ srcfd,
+ dst_dirfd,
+ c_str(dst),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
+pub(crate) fn chownat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ owner: Option<Uid>,
+ group: Option<Gid>,
+ flags: AtFlags,
+) -> io::Result<()> {
+ unsafe {
+ let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
+ ret(c::fchownat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ ow,
+ gr,
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(not(any(
+ apple,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn mknodat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ file_type: FileType,
+ mode: Mode,
+ dev: Dev,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::mknodat(
+ borrowed_fd(dirfd),
+ c_str(path),
+ (mode.bits() | file_type.as_raw_mode()) as c::mode_t,
+ dev.try_into().map_err(|_e| io::Errno::PERM)?,
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn copy_file_range(
+ fd_in: BorrowedFd<'_>,
+ off_in: Option<&mut u64>,
+ fd_out: BorrowedFd<'_>,
+ off_out: Option<&mut u64>,
+ len: usize,
+) -> io::Result<usize> {
+ syscall! {
+ fn copy_file_range(
+ fd_in: c::c_int,
+ off_in: *mut c::loff_t,
+ fd_out: c::c_int,
+ off_out: *mut c::loff_t,
+ len: usize,
+ flags: c::c_uint
+ ) via SYS_copy_file_range -> c::ssize_t
+ }
+
+ let mut off_in_val: c::loff_t = 0;
+ let mut off_out_val: c::loff_t = 0;
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let off_in_ptr = if let Some(off_in) = &off_in {
+ off_in_val = **off_in as i64;
+ &mut off_in_val
+ } else {
+ null_mut()
+ };
+ let off_out_ptr = if let Some(off_out) = &off_out {
+ off_out_val = **off_out as i64;
+ &mut off_out_val
+ } else {
+ null_mut()
+ };
+ let copied = unsafe {
+ ret_usize(copy_file_range(
+ borrowed_fd(fd_in),
+ off_in_ptr,
+ borrowed_fd(fd_out),
+ off_out_ptr,
+ len,
+ 0, // no flags are defined yet
+ ))?
+ };
+ if let Some(off_in) = off_in {
+ *off_in = off_in_val as u64;
+ }
+ if let Some(off_out) = off_out {
+ *off_out = off_out_val as u64;
+ }
+ Ok(copied)
+}
+
+#[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+pub(crate) fn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
+ let offset = offset as i64;
+ let len = len as i64;
+
+ // FreeBSD returns `EINVAL` on invalid offsets; emulate the POSIX behavior.
+ #[cfg(target_os = "freebsd")]
+ let offset = if (offset as i64) < 0 {
+ i64::MAX
+ } else {
+ offset
+ };
+
+ // FreeBSD returns `EINVAL` on overflow; emulate the POSIX behavior.
+ #[cfg(target_os = "freebsd")]
+ let len = if len > 0 && offset.checked_add(len).is_none() {
+ i64::MAX - offset
+ } else {
+ len
+ };
+
+ let err = unsafe { c::posix_fadvise(borrowed_fd(fd), offset, len, advice as c::c_int) };
+
+ // `posix_fadvise` returns its error status rather than using `errno`.
+ if err == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno(err))
+ }
+}
+
+pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> {
+ let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFL))? };
+ Ok(OFlags::from_bits_retain(bitcast!(flags)))
+}
+
+pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFL, flags.bits())) }
+}
+
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> {
+ let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GET_SEALS))? };
+ Ok(SealFlags::from_bits_retain(bitcast!(flags)))
+}
+
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) }
+}
+
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
+ use c::{flock, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, SEEK_SET};
+
+ let (cmd, l_type) = match operation {
+ FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
+ FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
+ FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
+ FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
+ FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
+ FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
+ };
+
+ unsafe {
+ let mut lock: flock = core::mem::zeroed();
+ lock.l_type = l_type as _;
+
+ // When `l_len` is zero, this locks all the bytes from
+ // `l_whence`/`l_start` to the end of the file, even as the
+ // file grows dynamically.
+ lock.l_whence = SEEK_SET as _;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ ret(c::fcntl(borrowed_fd(fd), cmd, &lock))
+ }
+}
+
+pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> {
+ let (whence, offset) = match pos {
+ SeekFrom::Start(pos) => {
+ let pos: u64 = pos;
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ (c::SEEK_SET, pos as i64)
+ }
+ SeekFrom::End(offset) => (c::SEEK_END, offset),
+ SeekFrom::Current(offset) => (c::SEEK_CUR, offset),
+ #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
+ SeekFrom::Data(offset) => (c::SEEK_DATA, offset),
+ #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
+ SeekFrom::Hole(offset) => (c::SEEK_HOLE, offset),
+ };
+
+ // ESP-IDF and Vita don't support 64-bit offsets.
+ #[cfg(any(target_os = "espidf", target_os = "vita"))]
+ let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+
+ let offset = unsafe { ret_off_t(c::lseek(borrowed_fd(fd), offset, whence))? };
+ Ok(offset as u64)
+}
+
+pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> {
+ let offset = unsafe { ret_off_t(c::lseek(borrowed_fd(fd), 0, c::SEEK_CUR))? };
+ Ok(offset as u64)
+}
+
+#[cfg(not(any(linux_kernel, target_os = "wasi")))]
+pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
+ unsafe { ret(c::fchmod(borrowed_fd(fd), bitflags_bits!(mode))) }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
+ // Use `c::syscall` rather than `c::fchmod` because some libc
+ // implementations, such as musl, add extra logic to `fchmod` to emulate
+ // support for `O_PATH`, which uses `/proc` outside our control and
+ // interferes with our own use of `O_PATH`.
+ syscall! {
+ fn fchmod(
+ fd: c::c_int,
+ mode: c::mode_t
+ ) via SYS_fchmod -> c::c_int
+ }
+ unsafe { ret(fchmod(borrowed_fd(fd), mode.bits() as c::mode_t)) }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn chown(path: &CStr, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
+ unsafe {
+ let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
+ ret(c::chown(c_str(path), ow, gr))
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
+ // Use `c::syscall` rather than `c::fchown` because some libc
+ // implementations, such as musl, add extra logic to `fchown` to emulate
+ // support for `O_PATH`, which uses `/proc` outside our control and
+ // interferes with our own use of `O_PATH`.
+ syscall! {
+ fn fchown(
+ fd: c::c_int,
+ owner: c::uid_t,
+ group: c::gid_t
+ ) via SYS_fchown -> c::c_int
+ }
+ unsafe {
+ let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
+ ret(fchown(borrowed_fd(fd), ow, gr))
+ }
+}
+
+#[cfg(not(any(linux_kernel, target_os = "wasi")))]
+pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
+ unsafe {
+ let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
+ ret(c::fchown(borrowed_fd(fd), ow, gr))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "solaris",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
+ unsafe { ret(c::flock(borrowed_fd(fd), operation as c::c_int)) }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn syncfs(fd: BorrowedFd<'_>) -> io::Result<()> {
+ // Some versions of Android libc lack a `syncfs` function.
+ #[cfg(target_os = "android")]
+ syscall! {
+ fn syncfs(fd: c::c_int) via SYS_syncfs -> c::c_int
+ }
+
+ // `syncfs` was added to glibc in 2.20.
+ #[cfg(not(target_os = "android"))]
+ weak_or_syscall! {
+ fn syncfs(fd: c::c_int) via SYS_syncfs -> c::c_int
+ }
+
+ unsafe { ret(syncfs(borrowed_fd(fd))) }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn sync() {
+ unsafe { c::sync() }
+}
+
+pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
+ // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
+ // `statx`.
+ //
+ // And, some old platforms don't support `statx`, and some fail with a
+ // confusing error code, so we call `crate::fs::statx` to handle that. If
+ // `statx` isn't available, fall back to the buggy system call.
+ #[cfg(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ ))]
+ {
+ match crate::fs::statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
+ Ok(x) => statx_to_stat(x),
+ Err(io::Errno::NOSYS) => fstat_old(fd),
+ Err(err) => Err(err),
+ }
+ }
+
+ // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
+ // there's nothing practical we can do.
+ #[cfg(not(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+ )))]
+ unsafe {
+ let mut stat = MaybeUninit::<Stat>::uninit();
+ ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
+ Ok(stat.assume_init())
+ }
+}
+
+#[cfg(all(
+ linux_kernel,
+ any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+))]
+fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {
+ unsafe {
+ let mut result = MaybeUninit::<c::stat64>::uninit();
+ ret(c::fstat(borrowed_fd(fd), result.as_mut_ptr()))?;
+ stat64_to_stat(result.assume_init())
+ }
+}
+
+#[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
+ let mut statfs = MaybeUninit::<StatFs>::uninit();
+ unsafe {
+ ret(c::fstatfs(borrowed_fd(fd), statfs.as_mut_ptr()))?;
+ Ok(statfs.assume_init())
+ }
+}
+
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+pub(crate) fn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> {
+ let mut statvfs = MaybeUninit::<c::statvfs>::uninit();
+ unsafe {
+ ret(c::fstatvfs(borrowed_fd(fd), statvfs.as_mut_ptr()))?;
+ Ok(libc_statvfs_to_statvfs(statvfs.assume_init()))
+ }
+}
+
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+fn libc_statvfs_to_statvfs(from: c::statvfs) -> StatVfs {
+ StatVfs {
+ f_bsize: from.f_bsize as u64,
+ f_frsize: from.f_frsize as u64,
+ f_blocks: from.f_blocks as u64,
+ f_bfree: from.f_bfree as u64,
+ f_bavail: from.f_bavail as u64,
+ f_files: from.f_files as u64,
+ f_ffree: from.f_ffree as u64,
+ f_favail: from.f_ffree as u64,
+ #[cfg(not(target_os = "aix"))]
+ f_fsid: from.f_fsid as u64,
+ #[cfg(target_os = "aix")]
+ f_fsid: ((from.f_fsid.val[0] as u64) << 32) | from.f_fsid.val[1],
+ f_flag: StatVfsMountFlags::from_bits_retain(from.f_flag as u64),
+ f_namemax: from.f_namemax as u64,
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
+ // Old 32-bit version: libc has `futimens` but it is not y2038 safe by
+ // default. But there may be a `__futimens64` we can use.
+ #[cfg(all(fix_y2038, not(apple)))]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_futimens) = __futimens64.get() {
+ let libc_times: [LibcTimespec; 2] = [
+ times.last_access.clone().into(),
+ times.last_modification.clone().into(),
+ ];
+
+ unsafe {
+ return ret(libc_futimens(borrowed_fd(fd), libc_times.as_ptr()));
+ }
+ }
+
+ futimens_old(fd, times)
+ }
+
+ // Main version: libc is y2038 safe and has `futimens`. Or, the platform
+ // is not y2038 safe and there's nothing practical we can do.
+ #[cfg(not(any(apple, fix_y2038)))]
+ unsafe {
+ use crate::utils::as_ptr;
+
+ ret(c::futimens(borrowed_fd(fd), as_ptr(times).cast()))
+ }
+
+ // Apple version: `futimens` was introduced in macOS 10.13.
+ #[cfg(apple)]
+ unsafe {
+ use crate::utils::as_ptr;
+
+ // ABI details.
+ weak! {
+ fn futimens(c::c_int, *const c::timespec) -> c::c_int
+ }
+ extern "C" {
+ fn fsetattrlist(
+ fd: c::c_int,
+ attr_list: *const Attrlist,
+ attr_buf: *const c::c_void,
+ attr_buf_size: c::size_t,
+ options: c::c_ulong,
+ ) -> c::c_int;
+ }
+
+ // If we have `futimens`, use it.
+ if let Some(have_futimens) = futimens.get() {
+ return ret(have_futimens(borrowed_fd(fd), as_ptr(times).cast()));
+ }
+
+ // Otherwise use `fsetattrlist`.
+ let (attrbuf_size, times, attrs) = times_to_attrlist(times)?;
+
+ ret(fsetattrlist(
+ borrowed_fd(fd),
+ &attrs,
+ as_ptr(&times).cast(),
+ attrbuf_size,
+ 0,
+ ))
+ }
+}
+
+#[cfg(all(fix_y2038, not(apple)))]
+fn futimens_old(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
+ let old_times = [
+ c::timespec {
+ tv_sec: times
+ .last_access
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times
+ .last_access
+ .tv_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ },
+ c::timespec {
+ tv_sec: times
+ .last_modification
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times
+ .last_modification
+ .tv_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ },
+ ];
+
+ unsafe { ret(c::futimens(borrowed_fd(fd), old_times.as_ptr())) }
+}
+
+#[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+pub(crate) fn fallocate(
+ fd: BorrowedFd<'_>,
+ mode: FallocateFlags,
+ offset: u64,
+ len: u64,
+) -> io::Result<()> {
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+ let len = len as i64;
+
+ #[cfg(any(linux_kernel, target_os = "fuchsia"))]
+ unsafe {
+ ret(c::fallocate(
+ borrowed_fd(fd),
+ bitflags_bits!(mode),
+ offset,
+ len,
+ ))
+ }
+
+ #[cfg(not(any(linux_kernel, target_os = "fuchsia")))]
+ {
+ assert!(mode.is_empty());
+ let err = unsafe { c::posix_fallocate(borrowed_fd(fd), offset, len) };
+
+ // `posix_fallocate` returns its error status rather than using
+ // `errno`.
+ if err == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno(err))
+ }
+ }
+}
+
+#[cfg(apple)]
+pub(crate) fn fallocate(
+ fd: BorrowedFd<'_>,
+ mode: FallocateFlags,
+ offset: u64,
+ len: u64,
+) -> io::Result<()> {
+ let offset: i64 = offset.try_into().map_err(|_e| io::Errno::INVAL)?;
+ let len = len as i64;
+
+ assert!(mode.is_empty());
+
+ let new_len = offset.checked_add(len).ok_or(io::Errno::FBIG)?;
+ let mut store = c::fstore_t {
+ fst_flags: c::F_ALLOCATECONTIG,
+ fst_posmode: c::F_PEOFPOSMODE,
+ fst_offset: 0,
+ fst_length: new_len,
+ fst_bytesalloc: 0,
+ };
+ unsafe {
+ if c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store) == -1 {
+ // Unable to allocate contiguous disk space; attempt to allocate
+ // non-contiguously.
+ store.fst_flags = c::F_ALLOCATEALL;
+ let _ = ret_c_int(c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store))?;
+ }
+ ret(c::ftruncate(borrowed_fd(fd), new_len))
+ }
+}
+
+pub(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fsync(borrowed_fd(fd))) }
+}
+
+#[cfg(not(any(
+ apple,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fdatasync(borrowed_fd(fd))) }
+}
+
+pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> {
+ let length = length.try_into().map_err(|_overflow_err| io::Errno::FBIG)?;
+ unsafe { ret(c::ftruncate(borrowed_fd(fd), length)) }
+}
+
+#[cfg(any(linux_kernel, target_os = "freebsd"))]
+pub(crate) fn memfd_create(name: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> {
+ #[cfg(target_os = "freebsd")]
+ weakcall! {
+ fn memfd_create(
+ name: *const c::c_char,
+ flags: c::c_uint
+ ) -> c::c_int
+ }
+
+ #[cfg(linux_kernel)]
+ weak_or_syscall! {
+ fn memfd_create(
+ name: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_memfd_create -> c::c_int
+ }
+
+ unsafe { ret_owned_fd(memfd_create(c_str(name), bitflags_bits!(flags))) }
+}
+
+#[cfg(feature = "linux-raw-sys")]
+pub(crate) fn openat2(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ oflags: OFlags,
+ mode: Mode,
+ resolve: ResolveFlags,
+) -> io::Result<OwnedFd> {
+ use linux_raw_sys::general::open_how;
+
+ syscall! {
+ fn openat2(
+ base_dirfd: c::c_int,
+ pathname: *const c::c_char,
+ how: *mut open_how,
+ size: usize
+ ) via SYS_OPENAT2 -> c::c_int
+ }
+
+ let oflags = oflags.bits();
+ let mut open_how = open_how {
+ flags: u64::from(oflags),
+ mode: u64::from(mode.bits()),
+ resolve: resolve.bits(),
+ };
+
+ unsafe {
+ ret_owned_fd(openat2(
+ borrowed_fd(dirfd),
+ c_str(path),
+ &mut open_how,
+ size_of::<open_how>(),
+ ))
+ }
+}
+#[cfg(all(linux_kernel, target_pointer_width = "32"))]
+const SYS_OPENAT2: i32 = 437;
+#[cfg(all(linux_kernel, target_pointer_width = "64"))]
+const SYS_OPENAT2: i64 = 437;
+
+#[cfg(target_os = "linux")]
+pub(crate) fn sendfile(
+ out_fd: BorrowedFd<'_>,
+ in_fd: BorrowedFd<'_>,
+ offset: Option<&mut u64>,
+ count: usize,
+) -> io::Result<usize> {
+ unsafe {
+ ret_usize(c::sendfile64(
+ borrowed_fd(out_fd),
+ borrowed_fd(in_fd),
+ offset.map_or(null_mut(), crate::utils::as_mut_ptr).cast(),
+ count,
+ ))
+ }
+}
+
+/// Convert from a Linux `statx` value to rustix's `Stat`.
+#[cfg(all(linux_kernel, target_pointer_width = "32"))]
+#[allow(deprecated)] // for `st_[amc]time` u64->i64 transition
+fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
+ Ok(Stat {
+ st_dev: crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor).into(),
+ st_mode: x.stx_mode.into(),
+ st_nlink: x.stx_nlink.into(),
+ st_uid: x.stx_uid.into(),
+ st_gid: x.stx_gid.into(),
+ st_rdev: crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor).into(),
+ st_size: x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_blksize: x.stx_blksize.into(),
+ st_blocks: x.stx_blocks.into(),
+ st_atime: bitcast!(i64::from(x.stx_atime.tv_sec)),
+ st_atime_nsec: x.stx_atime.tv_nsec as _,
+ st_mtime: bitcast!(i64::from(x.stx_mtime.tv_sec)),
+ st_mtime_nsec: x.stx_mtime.tv_nsec as _,
+ st_ctime: bitcast!(i64::from(x.stx_ctime.tv_sec)),
+ st_ctime_nsec: x.stx_ctime.tv_nsec as _,
+ st_ino: x.stx_ino.into(),
+ })
+}
+
+/// Convert from a Linux `statx` value to rustix's `Stat`.
+///
+/// mips64' `struct stat64` in libc has private fields, and `stx_blocks`
+#[cfg(all(linux_kernel, any(target_arch = "mips64", target_arch = "mips64r6")))]
+fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
+ let mut result: Stat = unsafe { core::mem::zeroed() };
+
+ result.st_dev = crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor);
+ result.st_mode = x.stx_mode.into();
+ result.st_nlink = x.stx_nlink.into();
+ result.st_uid = x.stx_uid.into();
+ result.st_gid = x.stx_gid.into();
+ result.st_rdev = crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor);
+ result.st_size = x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_blksize = x.stx_blksize.into();
+ result.st_blocks = x.stx_blocks.try_into().map_err(|_e| io::Errno::OVERFLOW)?;
+ result.st_atime = bitcast!(i64::from(x.stx_atime.tv_sec));
+ result.st_atime_nsec = x.stx_atime.tv_nsec as _;
+ result.st_mtime = bitcast!(i64::from(x.stx_mtime.tv_sec));
+ result.st_mtime_nsec = x.stx_mtime.tv_nsec as _;
+ result.st_ctime = bitcast!(i64::from(x.stx_ctime.tv_sec));
+ result.st_ctime_nsec = x.stx_ctime.tv_nsec as _;
+ result.st_ino = x.stx_ino.into();
+
+ Ok(result)
+}
+
+/// Convert from a Linux `stat64` value to rustix's `Stat`.
+#[cfg(all(linux_kernel, target_pointer_width = "32"))]
+#[allow(deprecated)] // for `st_[amc]time` u64->i64 transition
+fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> {
+ Ok(Stat {
+ st_dev: s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_mode: s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_nlink: s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_uid: s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_gid: s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_rdev: s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_atime: bitcast!(i64::from(s64.st_atime)),
+ st_atime_nsec: s64
+ .st_atime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_mtime: bitcast!(i64::from(s64.st_mtime)),
+ st_mtime_nsec: s64
+ .st_mtime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_ctime: bitcast!(i64::from(s64.st_ctime)),
+ st_ctime_nsec: s64
+ .st_ctime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_ino: s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ })
+}
+
+/// Convert from a Linux `stat64` value to rustix's `Stat`.
+///
+/// mips64' `struct stat64` in libc has private fields, and `st_blocks` has
+/// type `i64`.
+#[cfg(all(linux_kernel, any(target_arch = "mips64", target_arch = "mips64r6")))]
+fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> {
+ let mut result: Stat = unsafe { core::mem::zeroed() };
+
+ result.st_dev = s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_mode = s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_nlink = s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_uid = s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_gid = s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_rdev = s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_size = s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_blksize = s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_blocks = s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_atime = i64::from(s64.st_atime) as _;
+ result.st_atime_nsec = s64
+ .st_atime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_mtime = i64::from(s64.st_mtime) as _;
+ result.st_mtime_nsec = s64
+ .st_mtime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_ctime = i64::from(s64.st_ctime) as _;
+ result.st_ctime_nsec = s64
+ .st_ctime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?;
+ result.st_ino = s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+
+ Ok(result)
+}
+
+#[cfg(linux_kernel)]
+#[allow(non_upper_case_globals)]
+mod sys {
+ use super::{c, BorrowedFd, Statx};
+
+ weak_or_syscall! {
+ pub(super) fn statx(
+ dirfd_: BorrowedFd<'_>,
+ path: *const c::c_char,
+ flags: c::c_int,
+ mask: c::c_uint,
+ buf: *mut Statx
+ ) via SYS_statx -> c::c_int
+ }
+}
+
+#[cfg(linux_kernel)]
+#[allow(non_upper_case_globals)]
+pub(crate) fn statx(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: AtFlags,
+ mask: StatxFlags,
+) -> io::Result<Statx> {
+ // If a future Linux kernel adds more fields to `struct statx` and users
+ // passing flags unknown to rustix in `StatxFlags`, we could end up
+ // writing outside of the buffer. To prevent this possibility, we mask off
+ // any flags that we don't know about.
+ //
+ // This includes `STATX__RESERVED`, which has a value that we know, but
+ // which could take on arbitrary new meaning in the future. Linux currently
+ // rejects this flag with `EINVAL`, so we do the same.
+ //
+ // This doesn't rely on `STATX_ALL` because [it's deprecated] and already
+ // doesn't represent all the known flags.
+ //
+ // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/
+ #[cfg(not(any(target_os = "android", target_env = "musl")))]
+ const STATX__RESERVED: u32 = c::STATX__RESERVED as u32;
+ #[cfg(all(
+ any(target_os = "android", target_env = "musl"),
+ feature = "linux-raw-sys",
+ ))]
+ const STATX__RESERVED: u32 = linux_raw_sys::general::STATX__RESERVED;
+ #[cfg(any(
+ not(any(target_os = "android", target_env = "musl")),
+ feature = "linux-raw-sys",
+ ))]
+ if (mask.bits() & STATX__RESERVED) == STATX__RESERVED {
+ return Err(io::Errno::INVAL);
+ }
+ let mask = mask & StatxFlags::all();
+
+ let mut statx_buf = MaybeUninit::<Statx>::uninit();
+ unsafe {
+ ret(sys::statx(
+ dirfd,
+ c_str(path),
+ bitflags_bits!(flags),
+ mask.bits(),
+ statx_buf.as_mut_ptr(),
+ ))?;
+ Ok(statx_buf.assume_init())
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn is_statx_available() -> bool {
+ unsafe {
+ // Call `statx` with null pointers so that if it fails for any reason
+ // other than `EFAULT`, we know it's not supported.
+ matches!(
+ ret(sys::statx(CWD, null(), 0, 0, null_mut())),
+ Err(io::Errno::FAULT)
+ )
+ }
+}
+
+#[cfg(apple)]
+pub(crate) unsafe fn fcopyfile(
+ from: BorrowedFd<'_>,
+ to: BorrowedFd<'_>,
+ state: copyfile_state_t,
+ flags: CopyfileFlags,
+) -> io::Result<()> {
+ extern "C" {
+ fn fcopyfile(
+ from: c::c_int,
+ to: c::c_int,
+ state: copyfile_state_t,
+ flags: c::c_uint,
+ ) -> c::c_int;
+ }
+
+ nonnegative_ret(fcopyfile(
+ borrowed_fd(from),
+ borrowed_fd(to),
+ state,
+ bitflags_bits!(flags),
+ ))
+}
+
+#[cfg(apple)]
+pub(crate) fn copyfile_state_alloc() -> io::Result<copyfile_state_t> {
+ extern "C" {
+ fn copyfile_state_alloc() -> copyfile_state_t;
+ }
+
+ let result = unsafe { copyfile_state_alloc() };
+ if result.0.is_null() {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(result)
+ }
+}
+
+#[cfg(apple)]
+pub(crate) unsafe fn copyfile_state_free(state: copyfile_state_t) -> io::Result<()> {
+ extern "C" {
+ fn copyfile_state_free(state: copyfile_state_t) -> c::c_int;
+ }
+
+ nonnegative_ret(copyfile_state_free(state))
+}
+
+#[cfg(apple)]
+const COPYFILE_STATE_COPIED: u32 = 8;
+
+#[cfg(apple)]
+pub(crate) unsafe fn copyfile_state_get_copied(state: copyfile_state_t) -> io::Result<u64> {
+ let mut copied = MaybeUninit::<u64>::uninit();
+ copyfile_state_get(state, COPYFILE_STATE_COPIED, copied.as_mut_ptr().cast())?;
+ Ok(copied.assume_init())
+}
+
+#[cfg(apple)]
+pub(crate) unsafe fn copyfile_state_get(
+ state: copyfile_state_t,
+ flag: u32,
+ dst: *mut c::c_void,
+) -> io::Result<()> {
+ extern "C" {
+ fn copyfile_state_get(state: copyfile_state_t, flag: u32, dst: *mut c::c_void) -> c::c_int;
+ }
+
+ nonnegative_ret(copyfile_state_get(state, flag, dst))
+}
+
+#[cfg(all(apple, feature = "alloc"))]
+pub(crate) fn getpath(fd: BorrowedFd<'_>) -> io::Result<CString> {
+ // The use of `PATH_MAX` is generally not encouraged, but it
+ // is inevitable in this case because macOS defines `fcntl` with
+ // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
+ // alternatives. If a better method is invented, it should be used
+ // instead.
+ let mut buf = vec![0; c::PATH_MAX as usize];
+
+ // From the [macOS `fcntl` manual page]:
+ // `F_GETPATH` - Get the path of the file descriptor `Fildes`. The argument
+ // must be a buffer of size `MAXPATHLEN` or greater.
+ //
+ // [macOS `fcntl` manual page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+ unsafe {
+ ret(c::fcntl(borrowed_fd(fd), c::F_GETPATH, buf.as_mut_ptr()))?;
+ }
+
+ let l = buf.iter().position(|&c| c == 0).unwrap();
+ buf.truncate(l);
+ buf.shrink_to_fit();
+
+ Ok(CString::new(buf).unwrap())
+}
+
+#[cfg(apple)]
+pub(crate) fn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::Result<()> {
+ // From the [macOS `fcntl` manual page]:
+ // `F_RDADVISE` - Issue an advisory read async with no copy to user.
+ //
+ // The `F_RDADVISE` command operates on the following structure which holds
+ // information passed from the user to the system:
+ //
+ // ```c
+ // struct radvisory {
+ // off_t ra_offset; /* offset into the file */
+ // int ra_count; /* size of the read */
+ // };
+ // ```
+ //
+ // [macOS `fcntl` manual page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+ let ra_offset = match offset.try_into() {
+ Ok(len) => len,
+ // If this conversion fails, the user is providing an offset outside
+ // any possible file extent, so just ignore it.
+ Err(_) => return Ok(()),
+ };
+ let ra_count = match len.try_into() {
+ Ok(len) => len,
+ // If this conversion fails, the user is providing a dubiously large
+ // hint which is unlikely to improve performance.
+ Err(_) => return Ok(()),
+ };
+ unsafe {
+ let radvisory = c::radvisory {
+ ra_offset,
+ ra_count,
+ };
+ ret(c::fcntl(borrowed_fd(fd), c::F_RDADVISE, &radvisory))
+ }
+}
+
+#[cfg(apple)]
+pub(crate) fn fcntl_fullfsync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_FULLFSYNC)) }
+}
+
+#[cfg(apple)]
+pub(crate) fn fcntl_nocache(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_NOCACHE, value as c::c_int)) }
+}
+
+#[cfg(apple)]
+pub(crate) fn fcntl_global_nocache(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ unsafe {
+ ret(c::fcntl(
+ borrowed_fd(fd),
+ c::F_GLOBAL_NOCACHE,
+ value as c::c_int,
+ ))
+ }
+}
+
+/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist`
+/// arguments.
+#[cfg(apple)]
+fn times_to_attrlist(times: &Timestamps) -> io::Result<(c::size_t, [c::timespec; 2], Attrlist)> {
+ // ABI details.
+ const ATTR_CMN_MODTIME: u32 = 0x0000_0400;
+ const ATTR_CMN_ACCTIME: u32 = 0x0000_1000;
+ const ATTR_BIT_MAP_COUNT: u16 = 5;
+
+ let mut times = times.clone();
+
+ // If we have any `UTIME_NOW` elements, replace them with the current time.
+ if times.last_access.tv_nsec == c::UTIME_NOW.into()
+ || times.last_modification.tv_nsec == c::UTIME_NOW.into()
+ {
+ let now = {
+ let mut tv = c::timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ };
+ unsafe {
+ let r = c::gettimeofday(&mut tv, null_mut());
+ assert_eq!(r, 0);
+ }
+ c::timespec {
+ tv_sec: tv.tv_sec,
+ tv_nsec: (tv.tv_usec * 1000) as _,
+ }
+ };
+ if times.last_access.tv_nsec == c::UTIME_NOW.into() {
+ times.last_access = crate::timespec::Timespec {
+ tv_sec: now.tv_sec.into(),
+ tv_nsec: now.tv_nsec as _,
+ };
+ }
+ if times.last_modification.tv_nsec == c::UTIME_NOW.into() {
+ times.last_modification = crate::timespec::Timespec {
+ tv_sec: now.tv_sec.into(),
+ tv_nsec: now.tv_nsec as _,
+ };
+ }
+ }
+
+ // Pack the return values following the rules for [`getattrlist`].
+ //
+ // [`getattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getattrlist.2.html
+ let mut times_size = 0;
+ let mut attrs = Attrlist {
+ bitmapcount: ATTR_BIT_MAP_COUNT,
+ reserved: 0,
+ commonattr: 0,
+ volattr: 0,
+ dirattr: 0,
+ fileattr: 0,
+ forkattr: 0,
+ };
+ let mut return_times = [c::timespec {
+ tv_sec: 0,
+ tv_nsec: 0,
+ }; 2];
+ let mut times_index = 0;
+ if times.last_modification.tv_nsec != c::UTIME_OMIT.into() {
+ attrs.commonattr |= ATTR_CMN_MODTIME;
+ return_times[times_index] = c::timespec {
+ tv_sec: times
+ .last_modification
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times.last_modification.tv_nsec as _,
+ };
+ times_index += 1;
+ times_size += size_of::<c::timespec>();
+ }
+ if times.last_access.tv_nsec != c::UTIME_OMIT.into() {
+ attrs.commonattr |= ATTR_CMN_ACCTIME;
+ return_times[times_index] = c::timespec {
+ tv_sec: times
+ .last_access
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: times.last_access.tv_nsec as _,
+ };
+ times_size += size_of::<c::timespec>();
+ }
+
+ Ok((times_size, return_times, attrs))
+}
+
+/// Support type for `Attrlist`.
+#[cfg(apple)]
+type Attrgroup = u32;
+
+/// Attribute list for use with `setattrlist`.
+#[cfg(apple)]
+#[repr(C)]
+struct Attrlist {
+ bitmapcount: u16,
+ reserved: u16,
+ commonattr: Attrgroup,
+ volattr: Attrgroup,
+ dirattr: Attrgroup,
+ fileattr: Attrgroup,
+ forkattr: Attrgroup,
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn getxattr(path: &CStr, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
+ let value_ptr = value.as_mut_ptr();
+
+ #[cfg(not(apple))]
+ unsafe {
+ ret_usize(c::getxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value_ptr.cast::<c::c_void>(),
+ value.len(),
+ ))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret_usize(c::getxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value_ptr.cast::<c::c_void>(),
+ value.len(),
+ 0,
+ 0,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn lgetxattr(path: &CStr, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
+ let value_ptr = value.as_mut_ptr();
+
+ #[cfg(not(apple))]
+ unsafe {
+ ret_usize(c::lgetxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value_ptr.cast::<c::c_void>(),
+ value.len(),
+ ))
+ }
+
+ #[cfg(apple)]
+ {
+ // Passing an empty to slice to getxattr leads to ERANGE on macOS. Pass null instead.
+ let ptr = if value.is_empty() {
+ core::ptr::null_mut()
+ } else {
+ value_ptr.cast::<c::c_void>()
+ };
+
+ unsafe {
+ ret_usize(c::getxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ ptr,
+ value.len(),
+ 0,
+ c::XATTR_NOFOLLOW,
+ ))
+ }
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn fgetxattr(fd: BorrowedFd<'_>, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
+ let value_ptr = value.as_mut_ptr();
+
+ #[cfg(not(apple))]
+ unsafe {
+ ret_usize(c::fgetxattr(
+ borrowed_fd(fd),
+ name.as_ptr(),
+ value_ptr.cast::<c::c_void>(),
+ value.len(),
+ ))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret_usize(c::fgetxattr(
+ borrowed_fd(fd),
+ name.as_ptr(),
+ value_ptr.cast::<c::c_void>(),
+ value.len(),
+ 0,
+ 0,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn setxattr(
+ path: &CStr,
+ name: &CStr,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret(c::setxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value.as_ptr().cast::<c::c_void>(),
+ value.len(),
+ flags.bits() as i32,
+ ))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret(c::setxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value.as_ptr().cast::<c::c_void>(),
+ value.len(),
+ 0,
+ flags.bits() as i32,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn lsetxattr(
+ path: &CStr,
+ name: &CStr,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret(c::lsetxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value.as_ptr().cast::<c::c_void>(),
+ value.len(),
+ flags.bits() as i32,
+ ))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret(c::setxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value.as_ptr().cast::<c::c_void>(),
+ value.len(),
+ 0,
+ flags.bits() as i32 | c::XATTR_NOFOLLOW,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn fsetxattr(
+ fd: BorrowedFd<'_>,
+ name: &CStr,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret(c::fsetxattr(
+ borrowed_fd(fd),
+ name.as_ptr(),
+ value.as_ptr().cast::<c::c_void>(),
+ value.len(),
+ flags.bits() as i32,
+ ))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret(c::fsetxattr(
+ borrowed_fd(fd),
+ name.as_ptr(),
+ value.as_ptr().cast::<c::c_void>(),
+ value.len(),
+ 0,
+ flags.bits() as i32,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn listxattr(path: &CStr, list: &mut [c::c_char]) -> io::Result<usize> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret_usize(c::listxattr(path.as_ptr(), list.as_mut_ptr(), list.len()))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret_usize(c::listxattr(
+ path.as_ptr(),
+ list.as_mut_ptr(),
+ list.len(),
+ 0,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn llistxattr(path: &CStr, list: &mut [c::c_char]) -> io::Result<usize> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret_usize(c::llistxattr(path.as_ptr(), list.as_mut_ptr(), list.len()))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret_usize(c::listxattr(
+ path.as_ptr(),
+ list.as_mut_ptr(),
+ list.len(),
+ c::XATTR_NOFOLLOW,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn flistxattr(fd: BorrowedFd<'_>, list: &mut [c::c_char]) -> io::Result<usize> {
+ let fd = borrowed_fd(fd);
+
+ #[cfg(not(apple))]
+ unsafe {
+ ret_usize(c::flistxattr(fd, list.as_mut_ptr(), list.len()))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret_usize(c::flistxattr(fd, list.as_mut_ptr(), list.len(), 0))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn removexattr(path: &CStr, name: &CStr) -> io::Result<()> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret(c::removexattr(path.as_ptr(), name.as_ptr()))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret(c::removexattr(path.as_ptr(), name.as_ptr(), 0))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn lremovexattr(path: &CStr, name: &CStr) -> io::Result<()> {
+ #[cfg(not(apple))]
+ unsafe {
+ ret(c::lremovexattr(path.as_ptr(), name.as_ptr()))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret(c::removexattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ c::XATTR_NOFOLLOW,
+ ))
+ }
+}
+
+#[cfg(any(apple, linux_kernel))]
+pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
+ let fd = borrowed_fd(fd);
+
+ #[cfg(not(apple))]
+ unsafe {
+ ret(c::fremovexattr(fd, name.as_ptr()))
+ }
+
+ #[cfg(apple)]
+ unsafe {
+ ret(c::fremovexattr(fd, name.as_ptr(), 0))
+ }
+}
+
+#[test]
+fn test_sizes() {
+ #[cfg(linux_kernel)]
+ assert_eq_size!(c::loff_t, u64);
+
+ // Assert that `Timestamps` has the expected layout. If we're not fixing
+ // y2038, libc's type should match ours. If we are, it's smaller.
+ #[cfg(not(fix_y2038))]
+ assert_eq_size!([c::timespec; 2], Timestamps);
+ #[cfg(fix_y2038)]
+ assert!(core::mem::size_of::<[c::timespec; 2]>() < core::mem::size_of::<Timestamps>());
+}
diff --git a/src/backend/libc/fs/types.rs b/src/backend/libc/fs/types.rs
new file mode 100644
index 0000000..f318f16
--- /dev/null
+++ b/src/backend/libc/fs/types.rs
@@ -0,0 +1,1167 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+bitflags! {
+ /// `*_OK` constants for use with [`accessat`].
+ ///
+ /// [`accessat`]: fn.accessat.html
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct Access: c::c_int {
+ /// `R_OK`
+ const READ_OK = c::R_OK;
+
+ /// `W_OK`
+ const WRITE_OK = c::W_OK;
+
+ /// `X_OK`
+ const EXEC_OK = c::X_OK;
+
+ /// `F_OK`
+ const EXISTS = c::F_OK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+bitflags! {
+ /// `AT_*` constants for use with [`openat`], [`statat`], and other `*at`
+ /// functions.
+ ///
+ /// [`openat`]: crate::fs::openat
+ /// [`statat`]: crate::fs::statat
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct AtFlags: u32 {
+ /// `AT_SYMLINK_NOFOLLOW`
+ const SYMLINK_NOFOLLOW = bitcast!(c::AT_SYMLINK_NOFOLLOW);
+
+ /// `AT_EACCESS`
+ #[cfg(not(any(target_os = "emscripten", target_os = "android")))]
+ const EACCESS = bitcast!(c::AT_EACCESS);
+
+ /// `AT_REMOVEDIR`
+ const REMOVEDIR = bitcast!(c::AT_REMOVEDIR);
+
+ /// `AT_SYMLINK_FOLLOW`
+ const SYMLINK_FOLLOW = bitcast!(c::AT_SYMLINK_FOLLOW);
+
+ /// `AT_NO_AUTOMOUNT`
+ #[cfg(any(linux_like, target_os = "fuchsia"))]
+ const NO_AUTOMOUNT = bitcast!(c::AT_NO_AUTOMOUNT);
+
+ /// `AT_EMPTY_PATH`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ ))]
+ const EMPTY_PATH = bitcast!(c::AT_EMPTY_PATH);
+
+ /// `AT_RESOLVE_BENEATH`
+ #[cfg(target_os = "freebsd")]
+ const RESOLVE_BENEATH = bitcast!(c::AT_RESOLVE_BENEATH);
+
+ /// `AT_STATX_SYNC_AS_STAT`
+ #[cfg(all(target_os = "linux", target_env = "gnu"))]
+ const STATX_SYNC_AS_STAT = bitcast!(c::AT_STATX_SYNC_AS_STAT);
+
+ /// `AT_STATX_FORCE_SYNC`
+ #[cfg(all(target_os = "linux", target_env = "gnu"))]
+ const STATX_FORCE_SYNC = bitcast!(c::AT_STATX_FORCE_SYNC);
+
+ /// `AT_STATX_DONT_SYNC`
+ #[cfg(all(target_os = "linux", target_env = "gnu"))]
+ const STATX_DONT_SYNC = bitcast!(c::AT_STATX_DONT_SYNC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `S_I*` constants for use with [`openat`], [`chmodat`], and [`fchmod`].
+ ///
+ /// [`openat`]: crate::fs::openat
+ /// [`chmodat`]: crate::fs::chmodat
+ /// [`fchmod`]: crate::fs::fchmod
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct Mode: RawMode {
+ /// `S_IRWXU`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const RWXU = c::S_IRWXU as RawMode;
+
+ /// `S_IRUSR`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const RUSR = c::S_IRUSR as RawMode;
+
+ /// `S_IWUSR`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const WUSR = c::S_IWUSR as RawMode;
+
+ /// `S_IXUSR`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const XUSR = c::S_IXUSR as RawMode;
+
+ /// `S_IRWXG`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const RWXG = c::S_IRWXG as RawMode;
+
+ /// `S_IRGRP`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const RGRP = c::S_IRGRP as RawMode;
+
+ /// `S_IWGRP`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const WGRP = c::S_IWGRP as RawMode;
+
+ /// `S_IXGRP`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const XGRP = c::S_IXGRP as RawMode;
+
+ /// `S_IRWXO`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const RWXO = c::S_IRWXO as RawMode;
+
+ /// `S_IROTH`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const ROTH = c::S_IROTH as RawMode;
+
+ /// `S_IWOTH`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const WOTH = c::S_IWOTH as RawMode;
+
+ /// `S_IXOTH`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const XOTH = c::S_IXOTH as RawMode;
+
+ /// `S_ISUID`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const SUID = c::S_ISUID as RawMode;
+
+ /// `S_ISGID`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const SGID = c::S_ISGID as RawMode;
+
+ /// `S_ISVTX`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const SVTX = c::S_ISVTX as RawMode;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(not(target_os = "espidf"))]
+impl Mode {
+ /// Construct a `Mode` from the mode bits of the `st_mode` field of a
+ /// `Mode`.
+ #[inline]
+ pub const fn from_raw_mode(st_mode: RawMode) -> Self {
+ Self::from_bits_truncate(st_mode)
+ }
+
+ /// Construct an `st_mode` value from a `Mode`.
+ #[inline]
+ pub const fn as_raw_mode(self) -> RawMode {
+ self.bits()
+ }
+}
+
+#[cfg(not(target_os = "espidf"))]
+impl From<RawMode> for Mode {
+ /// Support conversions from raw mode values to `Mode`.
+ ///
+ /// ```
+ /// use rustix::fs::{Mode, RawMode};
+ /// assert_eq!(Mode::from(0o700), Mode::RWXU);
+ /// ```
+ #[inline]
+ fn from(st_mode: RawMode) -> Self {
+ Self::from_raw_mode(st_mode)
+ }
+}
+
+#[cfg(not(target_os = "espidf"))]
+impl From<Mode> for RawMode {
+ /// Support conversions from `Mode` to raw mode values.
+ ///
+ /// ```
+ /// use rustix::fs::{Mode, RawMode};
+ /// assert_eq!(RawMode::from(Mode::RWXU), 0o700);
+ /// ```
+ #[inline]
+ fn from(mode: Mode) -> Self {
+ mode.as_raw_mode()
+ }
+}
+
+bitflags! {
+ /// `O_*` constants for use with [`openat`].
+ ///
+ /// [`openat`]: crate::fs::openat
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct OFlags: u32 {
+ /// `O_ACCMODE`
+ const ACCMODE = bitcast!(c::O_ACCMODE);
+
+ /// Similar to `ACCMODE`, but just includes the read/write flags, and
+ /// no other flags.
+ ///
+ /// On some platforms, `PATH` may be included in `ACCMODE`, when
+ /// sometimes we really just want the read/write bits. Caution is
+ /// indicated, as the presence of `PATH` may mean that the read/write
+ /// bits don't have their usual meaning.
+ const RWMODE = bitcast!(c::O_RDONLY | c::O_WRONLY | c::O_RDWR);
+
+ /// `O_APPEND`
+ const APPEND = bitcast!(c::O_APPEND);
+
+ /// `O_CREAT`
+ #[doc(alias = "CREAT")]
+ const CREATE = bitcast!(c::O_CREAT);
+
+ /// `O_DIRECTORY`
+ #[cfg(not(target_os = "espidf"))]
+ const DIRECTORY = bitcast!(c::O_DIRECTORY);
+
+ /// `O_DSYNC`
+ #[cfg(not(any(target_os = "dragonfly", target_os = "espidf", target_os = "l4re", target_os = "redox", target_os = "vita")))]
+ const DSYNC = bitcast!(c::O_DSYNC);
+
+ /// `O_EXCL`
+ const EXCL = bitcast!(c::O_EXCL);
+
+ /// `O_FSYNC`
+ #[cfg(any(
+ bsd,
+ all(target_os = "linux", not(target_env = "musl")),
+ ))]
+ const FSYNC = bitcast!(c::O_FSYNC);
+
+ /// `O_NOFOLLOW`
+ #[cfg(not(target_os = "espidf"))]
+ const NOFOLLOW = bitcast!(c::O_NOFOLLOW);
+
+ /// `O_NONBLOCK`
+ const NONBLOCK = bitcast!(c::O_NONBLOCK);
+
+ /// `O_RDONLY`
+ const RDONLY = bitcast!(c::O_RDONLY);
+
+ /// `O_WRONLY`
+ const WRONLY = bitcast!(c::O_WRONLY);
+
+ /// `O_RDWR`
+ ///
+ /// This is not equal to `RDONLY | WRONLY`. It's a distinct flag.
+ const RDWR = bitcast!(c::O_RDWR);
+
+ /// `O_NOCTTY`
+ #[cfg(not(any(target_os = "espidf", target_os = "l4re", target_os = "redox", target_os = "vita")))]
+ const NOCTTY = bitcast!(c::O_NOCTTY);
+
+ /// `O_RSYNC`
+ #[cfg(any(
+ linux_kernel,
+ netbsdlike,
+ target_os = "emscripten",
+ target_os = "wasi",
+ ))]
+ const RSYNC = bitcast!(c::O_RSYNC);
+
+ /// `O_SYNC`
+ #[cfg(not(any(target_os = "l4re", target_os = "redox")))]
+ const SYNC = bitcast!(c::O_SYNC);
+
+ /// `O_TRUNC`
+ const TRUNC = bitcast!(c::O_TRUNC);
+
+ /// `O_PATH`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "emscripten",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "redox",
+ ))]
+ const PATH = bitcast!(c::O_PATH);
+
+ /// `O_CLOEXEC`
+ const CLOEXEC = bitcast!(c::O_CLOEXEC);
+
+ /// `O_TMPFILE`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ ))]
+ const TMPFILE = bitcast!(c::O_TMPFILE);
+
+ /// `O_NOATIME`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "fuchsia",
+ ))]
+ const NOATIME = bitcast!(c::O_NOATIME);
+
+ /// `O_DIRECT`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "emscripten",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "netbsd",
+ ))]
+ const DIRECT = bitcast!(c::O_DIRECT);
+
+ /// `O_RESOLVE_BENEATH`
+ #[cfg(target_os = "freebsd")]
+ const RESOLVE_BENEATH = bitcast!(c::O_RESOLVE_BENEATH);
+
+ /// `O_EMPTY_PATH`
+ #[cfg(target_os = "freebsd")]
+ const EMPTY_PATH = bitcast!(c::O_EMPTY_PATH);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(apple)]
+bitflags! {
+ /// `CLONE_*` constants for use with [`fclonefileat`].
+ ///
+ /// [`fclonefileat`]: crate::fs::fclonefileat
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CloneFlags: u32 {
+ /// `CLONE_NOFOLLOW`
+ const NOFOLLOW = 1;
+
+ /// `CLONE_NOOWNERCOPY`
+ const NOOWNERCOPY = 2;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(apple)]
+mod copyfile {
+ pub(super) const ACL: u32 = 1 << 0;
+ pub(super) const STAT: u32 = 1 << 1;
+ pub(super) const XATTR: u32 = 1 << 2;
+ pub(super) const DATA: u32 = 1 << 3;
+ pub(super) const SECURITY: u32 = STAT | ACL;
+ pub(super) const METADATA: u32 = SECURITY | XATTR;
+ pub(super) const ALL: u32 = METADATA | DATA;
+}
+
+#[cfg(apple)]
+bitflags! {
+ /// `COPYFILE_*` constants for use with [`fcopyfile`].
+ ///
+ /// [`fcopyfile`]: crate::fs::fcopyfile
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CopyfileFlags: c::c_uint {
+ /// `COPYFILE_ACL`
+ const ACL = copyfile::ACL;
+
+ /// `COPYFILE_STAT`
+ const STAT = copyfile::STAT;
+
+ /// `COPYFILE_XATTR`
+ const XATTR = copyfile::XATTR;
+
+ /// `COPYFILE_DATA`
+ const DATA = copyfile::DATA;
+
+ /// `COPYFILE_SECURITY`
+ const SECURITY = copyfile::SECURITY;
+
+ /// `COPYFILE_METADATA`
+ const METADATA = copyfile::METADATA;
+
+ /// `COPYFILE_ALL`
+ const ALL = copyfile::ALL;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `RESOLVE_*` constants for use with [`openat2`].
+ ///
+ /// [`openat2`]: crate::fs::openat2
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ResolveFlags: u64 {
+ /// `RESOLVE_NO_XDEV`
+ const NO_XDEV = 0x01;
+
+ /// `RESOLVE_NO_MAGICLINKS`
+ const NO_MAGICLINKS = 0x02;
+
+ /// `RESOLVE_NO_SYMLINKS`
+ const NO_SYMLINKS = 0x04;
+
+ /// `RESOLVE_BENEATH`
+ const BENEATH = 0x08;
+
+ /// `RESOLVE_IN_ROOT`
+ const IN_ROOT = 0x10;
+
+ /// `RESOLVE_CACHED` (since Linux 5.12)
+ const CACHED = 0x20;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `RENAME_*` constants for use with [`renameat_with`].
+ ///
+ /// [`renameat_with`]: crate::fs::renameat_with
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct RenameFlags: c::c_uint {
+ /// `RENAME_EXCHANGE`
+ const EXCHANGE = bitcast!(c::RENAME_EXCHANGE);
+
+ /// `RENAME_NOREPLACE`
+ const NOREPLACE = bitcast!(c::RENAME_NOREPLACE);
+
+ /// `RENAME_WHITEOUT`
+ const WHITEOUT = bitcast!(c::RENAME_WHITEOUT);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `S_IF*` constants for use with [`mknodat`] and [`Stat`]'s `st_mode` field.
+///
+/// [`mknodat`]: crate::fs::mknodat
+/// [`Stat`]: crate::fs::Stat
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum FileType {
+ /// `S_IFREG`
+ RegularFile = c::S_IFREG as isize,
+
+ /// `S_IFDIR`
+ Directory = c::S_IFDIR as isize,
+
+ /// `S_IFLNK`
+ Symlink = c::S_IFLNK as isize,
+
+ /// `S_IFIFO`
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFIFO`.
+ #[doc(alias = "IFO")]
+ Fifo = c::S_IFIFO as isize,
+
+ /// `S_IFSOCK`
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFSOCK`.
+ Socket = c::S_IFSOCK as isize,
+
+ /// `S_IFCHR`
+ CharacterDevice = c::S_IFCHR as isize,
+
+ /// `S_IFBLK`
+ BlockDevice = c::S_IFBLK as isize,
+
+ /// An unknown filesystem object.
+ Unknown,
+}
+
+impl FileType {
+ /// Construct a `FileType` from the `S_IFMT` bits of the `st_mode` field of
+ /// a `Stat`.
+ #[inline]
+ pub const fn from_raw_mode(st_mode: RawMode) -> Self {
+ match (st_mode as c::mode_t) & c::S_IFMT {
+ c::S_IFREG => Self::RegularFile,
+ c::S_IFDIR => Self::Directory,
+ c::S_IFLNK => Self::Symlink,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFIFO`.
+ c::S_IFIFO => Self::Fifo,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFSOCK`.
+ c::S_IFSOCK => Self::Socket,
+ c::S_IFCHR => Self::CharacterDevice,
+ c::S_IFBLK => Self::BlockDevice,
+ _ => Self::Unknown,
+ }
+ }
+
+ /// Construct an `st_mode` value from a `FileType`.
+ #[inline]
+ pub const fn as_raw_mode(self) -> RawMode {
+ match self {
+ Self::RegularFile => c::S_IFREG as RawMode,
+ Self::Directory => c::S_IFDIR as RawMode,
+ Self::Symlink => c::S_IFLNK as RawMode,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFIFO`.
+ Self::Fifo => c::S_IFIFO as RawMode,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `S_IFSOCK`.
+ Self::Socket => c::S_IFSOCK as RawMode,
+ Self::CharacterDevice => c::S_IFCHR as RawMode,
+ Self::BlockDevice => c::S_IFBLK as RawMode,
+ Self::Unknown => c::S_IFMT as RawMode,
+ }
+ }
+
+ /// Construct a `FileType` from the `d_type` field of a `c::dirent`.
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita"
+ )))]
+ #[inline]
+ pub(crate) const fn from_dirent_d_type(d_type: u8) -> Self {
+ match d_type {
+ c::DT_REG => Self::RegularFile,
+ c::DT_DIR => Self::Directory,
+ c::DT_LNK => Self::Symlink,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `DT_SOCK`.
+ c::DT_SOCK => Self::Socket,
+ #[cfg(not(target_os = "wasi"))] // TODO: Use WASI's `DT_FIFO`.
+ c::DT_FIFO => Self::Fifo,
+ c::DT_CHR => Self::CharacterDevice,
+ c::DT_BLK => Self::BlockDevice,
+ // c::DT_UNKNOWN |
+ _ => Self::Unknown,
+ }
+ }
+}
+
+/// `POSIX_FADV_*` constants for use with [`fadvise`].
+///
+/// [`fadvise`]: crate::fs::fadvise
+#[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub enum Advice {
+ /// `POSIX_FADV_NORMAL`
+ Normal = c::POSIX_FADV_NORMAL as c::c_uint,
+
+ /// `POSIX_FADV_SEQUENTIAL`
+ Sequential = c::POSIX_FADV_SEQUENTIAL as c::c_uint,
+
+ /// `POSIX_FADV_RANDOM`
+ Random = c::POSIX_FADV_RANDOM as c::c_uint,
+
+ /// `POSIX_FADV_NOREUSE`
+ NoReuse = c::POSIX_FADV_NOREUSE as c::c_uint,
+
+ /// `POSIX_FADV_WILLNEED`
+ WillNeed = c::POSIX_FADV_WILLNEED as c::c_uint,
+
+ /// `POSIX_FADV_DONTNEED`
+ DontNeed = c::POSIX_FADV_DONTNEED as c::c_uint,
+}
+
+#[cfg(any(linux_kernel, target_os = "freebsd"))]
+bitflags! {
+ /// `MFD_*` constants for use with [`memfd_create`].
+ ///
+ /// [`memfd_create`]: crate::fs::memfd_create
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MemfdFlags: c::c_uint {
+ /// `MFD_CLOEXEC`
+ const CLOEXEC = c::MFD_CLOEXEC;
+
+ /// `MFD_ALLOW_SEALING`
+ const ALLOW_SEALING = c::MFD_ALLOW_SEALING;
+
+ /// `MFD_HUGETLB` (since Linux 4.14)
+ const HUGETLB = c::MFD_HUGETLB;
+
+ /// `MFD_HUGE_64KB`
+ const HUGE_64KB = c::MFD_HUGE_64KB;
+ /// `MFD_HUGE_512JB`
+ const HUGE_512KB = c::MFD_HUGE_512KB;
+ /// `MFD_HUGE_1MB`
+ const HUGE_1MB = c::MFD_HUGE_1MB;
+ /// `MFD_HUGE_2MB`
+ const HUGE_2MB = c::MFD_HUGE_2MB;
+ /// `MFD_HUGE_8MB`
+ const HUGE_8MB = c::MFD_HUGE_8MB;
+ /// `MFD_HUGE_16MB`
+ const HUGE_16MB = c::MFD_HUGE_16MB;
+ /// `MFD_HUGE_32MB`
+ const HUGE_32MB = c::MFD_HUGE_32MB;
+ /// `MFD_HUGE_256MB`
+ const HUGE_256MB = c::MFD_HUGE_256MB;
+ /// `MFD_HUGE_512MB`
+ const HUGE_512MB = c::MFD_HUGE_512MB;
+ /// `MFD_HUGE_1GB`
+ const HUGE_1GB = c::MFD_HUGE_1GB;
+ /// `MFD_HUGE_2GB`
+ const HUGE_2GB = c::MFD_HUGE_2GB;
+ /// `MFD_HUGE_16GB`
+ const HUGE_16GB = c::MFD_HUGE_16GB;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+bitflags! {
+ /// `F_SEAL_*` constants for use with [`fcntl_add_seals`] and
+ /// [`fcntl_get_seals`].
+ ///
+ /// [`fcntl_add_seals`]: crate::fs::fcntl_add_seals
+ /// [`fcntl_get_seals`]: crate::fs::fcntl_get_seals
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SealFlags: u32 {
+ /// `F_SEAL_SEAL`
+ const SEAL = bitcast!(c::F_SEAL_SEAL);
+ /// `F_SEAL_SHRINK`
+ const SHRINK = bitcast!(c::F_SEAL_SHRINK);
+ /// `F_SEAL_GROW`
+ const GROW = bitcast!(c::F_SEAL_GROW);
+ /// `F_SEAL_WRITE`
+ const WRITE = bitcast!(c::F_SEAL_WRITE);
+ /// `F_SEAL_FUTURE_WRITE` (since Linux 5.1)
+ #[cfg(linux_kernel)]
+ const FUTURE_WRITE = bitcast!(c::F_SEAL_FUTURE_WRITE);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+bitflags! {
+ /// `STATX_*` constants for use with [`statx`].
+ ///
+ /// [`statx`]: crate::fs::statx
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct StatxFlags: u32 {
+ /// `STATX_TYPE`
+ const TYPE = c::STATX_TYPE;
+
+ /// `STATX_MODE`
+ const MODE = c::STATX_MODE;
+
+ /// `STATX_NLINK`
+ const NLINK = c::STATX_NLINK;
+
+ /// `STATX_UID`
+ const UID = c::STATX_UID;
+
+ /// `STATX_GID`
+ const GID = c::STATX_GID;
+
+ /// `STATX_ATIME`
+ const ATIME = c::STATX_ATIME;
+
+ /// `STATX_MTIME`
+ const MTIME = c::STATX_MTIME;
+
+ /// `STATX_CTIME`
+ const CTIME = c::STATX_CTIME;
+
+ /// `STATX_INO`
+ const INO = c::STATX_INO;
+
+ /// `STATX_SIZE`
+ const SIZE = c::STATX_SIZE;
+
+ /// `STATX_BLOCKS`
+ const BLOCKS = c::STATX_BLOCKS;
+
+ /// `STATX_BASIC_STATS`
+ const BASIC_STATS = c::STATX_BASIC_STATS;
+
+ /// `STATX_BTIME`
+ const BTIME = c::STATX_BTIME;
+
+ /// `STATX_MNT_ID` (since Linux 5.8)
+ const MNT_ID = c::STATX_MNT_ID;
+
+ /// `STATX_DIOALIGN` (since Linux 6.1)
+ const DIOALIGN = c::STATX_DIOALIGN;
+
+ /// `STATX_ALL`
+ const ALL = c::STATX_ALL;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+bitflags! {
+ /// `STATX_*` constants for use with [`statx`].
+ ///
+ /// [`statx`]: crate::fs::statx
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct StatxFlags: u32 {
+ /// `STATX_TYPE`
+ const TYPE = 0x0001;
+
+ /// `STATX_MODE`
+ const MODE = 0x0002;
+
+ /// `STATX_NLINK`
+ const NLINK = 0x0004;
+
+ /// `STATX_UID`
+ const UID = 0x0008;
+
+ /// `STATX_GID`
+ const GID = 0x0010;
+
+ /// `STATX_ATIME`
+ const ATIME = 0x0020;
+
+ /// `STATX_MTIME`
+ const MTIME = 0x0040;
+
+ /// `STATX_CTIME`
+ const CTIME = 0x0080;
+
+ /// `STATX_INO`
+ const INO = 0x0100;
+
+ /// `STATX_SIZE`
+ const SIZE = 0x0200;
+
+ /// `STATX_BLOCKS`
+ const BLOCKS = 0x0400;
+
+ /// `STATX_BASIC_STATS`
+ const BASIC_STATS = 0x07ff;
+
+ /// `STATX_BTIME`
+ const BTIME = 0x800;
+
+ /// `STATX_MNT_ID` (since Linux 5.8)
+ const MNT_ID = 0x1000;
+
+ /// `STATX_ALL`
+ const ALL = 0xfff;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(not(any(
+ netbsdlike,
+ solarish,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita"
+)))]
+bitflags! {
+ /// `FALLOC_FL_*` constants for use with [`fallocate`].
+ ///
+ /// [`fallocate`]: crate::fs::fallocate
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FallocateFlags: u32 {
+ /// `FALLOC_FL_KEEP_SIZE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "wasi",
+ )))]
+ const KEEP_SIZE = bitcast!(c::FALLOC_FL_KEEP_SIZE);
+ /// `FALLOC_FL_PUNCH_HOLE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "wasi",
+ )))]
+ const PUNCH_HOLE = bitcast!(c::FALLOC_FL_PUNCH_HOLE);
+ /// `FALLOC_FL_NO_HIDE_STALE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "linux",
+ target_os = "wasi",
+ )))]
+ const NO_HIDE_STALE = bitcast!(c::FALLOC_FL_NO_HIDE_STALE);
+ /// `FALLOC_FL_COLLAPSE_RANGE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const COLLAPSE_RANGE = bitcast!(c::FALLOC_FL_COLLAPSE_RANGE);
+ /// `FALLOC_FL_ZERO_RANGE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const ZERO_RANGE = bitcast!(c::FALLOC_FL_ZERO_RANGE);
+ /// `FALLOC_FL_INSERT_RANGE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const INSERT_RANGE = bitcast!(c::FALLOC_FL_INSERT_RANGE);
+ /// `FALLOC_FL_UNSHARE_RANGE`
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "emscripten",
+ target_os = "wasi",
+ )))]
+ const UNSHARE_RANGE = bitcast!(c::FALLOC_FL_UNSHARE_RANGE);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+bitflags! {
+ /// `ST_*` constants for use with [`StatVfs`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct StatVfsMountFlags: u64 {
+ /// `ST_MANDLOCK`
+ #[cfg(any(linux_kernel, target_os = "emscripten", target_os = "fuchsia"))]
+ const MANDLOCK = c::ST_MANDLOCK as u64;
+
+ /// `ST_NOATIME`
+ #[cfg(any(linux_kernel, target_os = "emscripten", target_os = "fuchsia"))]
+ const NOATIME = c::ST_NOATIME as u64;
+
+ /// `ST_NODEV`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "fuchsia"
+ ))]
+ const NODEV = c::ST_NODEV as u64;
+
+ /// `ST_NODIRATIME`
+ #[cfg(any(linux_kernel, target_os = "emscripten", target_os = "fuchsia"))]
+ const NODIRATIME = c::ST_NODIRATIME as u64;
+
+ /// `ST_NOEXEC`
+ #[cfg(any(linux_kernel, target_os = "emscripten", target_os = "fuchsia"))]
+ const NOEXEC = c::ST_NOEXEC as u64;
+
+ /// `ST_NOSUID`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const NOSUID = c::ST_NOSUID as u64;
+
+ /// `ST_RDONLY`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ const RDONLY = c::ST_RDONLY as u64;
+
+ /// `ST_RELATIME`
+ #[cfg(any(target_os = "android", all(target_os = "linux", target_env = "gnu")))]
+ const RELATIME = c::ST_RELATIME as u64;
+
+ /// `ST_SYNCHRONOUS`
+ #[cfg(any(linux_kernel, target_os = "emscripten", target_os = "fuchsia"))]
+ const SYNCHRONOUS = c::ST_SYNCHRONOUS as u64;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`].
+///
+/// [`flock`]: crate::fs::flock
+/// [`fcntl_lock`]: crate::fs::fcntl_lock
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum FlockOperation {
+ /// `LOCK_SH`
+ LockShared = bitcast!(c::LOCK_SH),
+ /// `LOCK_EX`
+ LockExclusive = bitcast!(c::LOCK_EX),
+ /// `LOCK_UN`
+ Unlock = bitcast!(c::LOCK_UN),
+ /// `LOCK_SH | LOCK_NB`
+ NonBlockingLockShared = bitcast!(c::LOCK_SH | c::LOCK_NB),
+ /// `LOCK_EX | LOCK_NB`
+ NonBlockingLockExclusive = bitcast!(c::LOCK_EX | c::LOCK_NB),
+ /// `LOCK_UN | LOCK_NB`
+ NonBlockingUnlock = bitcast!(c::LOCK_UN | c::LOCK_NB),
+}
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+#[cfg(not(any(linux_like, target_os = "hurd")))]
+pub type Stat = c::stat;
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+#[cfg(any(
+ all(linux_kernel, target_pointer_width = "64"),
+ target_os = "hurd",
+ target_os = "emscripten",
+ target_os = "l4re",
+))]
+pub type Stat = c::stat64;
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+// On 32-bit, Linux's `struct stat64` has a 32-bit `st_mtime` and friends, so
+// we use our own struct, populated from `statx` where possible, to avoid the
+// y2038 bug.
+#[cfg(all(linux_kernel, target_pointer_width = "32"))]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+#[allow(missing_docs)]
+pub struct Stat {
+ pub st_dev: u64,
+ pub st_mode: u32,
+ pub st_nlink: u32,
+ pub st_uid: u32,
+ pub st_gid: u32,
+ pub st_rdev: u64,
+ pub st_size: i64,
+ pub st_blksize: u32,
+ pub st_blocks: u64,
+ #[deprecated(note = "Use `rustix::fs::StatExt::atime` instead.")]
+ pub st_atime: u64,
+ pub st_atime_nsec: u32,
+ #[deprecated(note = "Use `rustix::fs::StatExt::mtime` instead.")]
+ pub st_mtime: u64,
+ pub st_mtime_nsec: u32,
+ #[deprecated(note = "Use `rustix::fs::StatExt::ctime` instead.")]
+ pub st_ctime: u64,
+ pub st_ctime_nsec: u32,
+ pub st_ino: u64,
+}
+
+/// `struct statfs` for use with [`statfs`] and [`fstatfs`].
+///
+/// [`statfs`]: crate::fs::statfs
+/// [`fstatfs`]: crate::fs::fstatfs
+#[cfg(not(any(
+ linux_like,
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+#[allow(clippy::module_name_repetitions)]
+pub type StatFs = c::statfs;
+
+/// `struct statfs` for use with [`statfs`] and [`fstatfs`].
+///
+/// [`statfs`]: crate::fs::statfs
+/// [`fstatfs`]: crate::fs::fstatfs
+#[cfg(linux_like)]
+pub type StatFs = c::statfs64;
+
+/// `struct statvfs` for use with [`statvfs`] and [`fstatvfs`].
+///
+/// [`statvfs`]: crate::fs::statvfs
+/// [`fstatvfs`]: crate::fs::fstatvfs
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+#[allow(missing_docs)]
+pub struct StatVfs {
+ pub f_bsize: u64,
+ pub f_frsize: u64,
+ pub f_blocks: u64,
+ pub f_bfree: u64,
+ pub f_bavail: u64,
+ pub f_files: u64,
+ pub f_ffree: u64,
+ pub f_favail: u64,
+ pub f_fsid: u64,
+ pub f_flag: StatVfsMountFlags,
+ pub f_namemax: u64,
+}
+
+/// `struct statx` for use with [`statx`].
+///
+/// [`statx`]: crate::fs::statx
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+// Use the glibc `struct statx`.
+pub type Statx = c::statx;
+
+/// `struct statx_timestamp` for use with [`Statx`].
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+// Use the glibc `struct statx_timestamp`.
+pub type StatxTimestamp = c::statx;
+
+/// `struct statx` for use with [`statx`].
+///
+/// [`statx`]: crate::fs::statx
+// Non-glibc ABIs don't currently declare a `struct statx`, so we declare it
+// ourselves.
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+#[repr(C)]
+#[allow(missing_docs)]
+pub struct Statx {
+ pub stx_mask: u32,
+ pub stx_blksize: u32,
+ pub 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,
+ pub stx_blocks: u64,
+ pub stx_attributes_mask: u64,
+ pub stx_atime: StatxTimestamp,
+ pub stx_btime: StatxTimestamp,
+ pub stx_ctime: StatxTimestamp,
+ pub stx_mtime: StatxTimestamp,
+ pub stx_rdev_major: u32,
+ pub stx_rdev_minor: u32,
+ pub stx_dev_major: u32,
+ pub stx_dev_minor: u32,
+ pub stx_mnt_id: u64,
+ __statx_pad2: u64,
+ __statx_pad3: [u64; 12],
+}
+
+/// `struct statx_timestamp` for use with [`Statx`].
+// Non-glibc ABIs don't currently declare a `struct statx_timestamp`, so we
+// declare it ourselves.
+#[cfg(any(
+ target_os = "android",
+ all(target_os = "linux", not(target_env = "gnu")),
+))]
+#[repr(C)]
+#[allow(missing_docs)]
+pub struct StatxTimestamp {
+ pub tv_sec: i64,
+ pub tv_nsec: u32,
+ pub __statx_timestamp_pad1: [i32; 1],
+}
+
+/// `mode_t`
+#[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+pub type RawMode = c::mode_t;
+
+/// `mode_t`
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+pub type RawMode = c::c_uint;
+
+/// `dev_t`
+#[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+pub type Dev = c::dev_t;
+
+/// `dev_t`
+#[cfg(all(target_os = "android", target_pointer_width = "32"))]
+pub type Dev = c::c_ulonglong;
+
+/// `__fsword_t`
+#[cfg(all(
+ target_os = "linux",
+ not(target_env = "musl"),
+ not(target_arch = "s390x"),
+))]
+pub type FsWord = c::__fsword_t;
+
+/// `__fsword_t`
+#[cfg(all(
+ any(target_os = "android", all(target_os = "linux", target_env = "musl")),
+ target_pointer_width = "32",
+))]
+pub type FsWord = u32;
+
+/// `__fsword_t`
+#[cfg(all(
+ any(target_os = "android", all(target_os = "linux", target_env = "musl")),
+ not(target_arch = "s390x"),
+ target_pointer_width = "64",
+))]
+pub type FsWord = u64;
+
+/// `__fsword_t`
+// s390x uses `u32` for `statfs` entries on glibc, even though `__fsword_t` is
+// `u64`.
+#[cfg(all(target_os = "linux", target_arch = "s390x", target_env = "gnu"))]
+pub type FsWord = u32;
+
+/// `__fsword_t`
+// s390x uses `u64` for `statfs` entries on musl.
+#[cfg(all(target_os = "linux", target_arch = "s390x", target_env = "musl"))]
+pub type FsWord = u64;
+
+/// `copyfile_state_t`—State for use with [`fcopyfile`].
+///
+/// [`fcopyfile`]: crate::fs::fcopyfile
+#[cfg(apple)]
+#[allow(non_camel_case_types)]
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct copyfile_state_t(pub(crate) *mut c::c_void);
diff --git a/src/backend/libc/io/errno.rs b/src/backend/libc/io/errno.rs
new file mode 100644
index 0000000..8d3d00a
--- /dev/null
+++ b/src/backend/libc/io/errno.rs
@@ -0,0 +1,1052 @@
+//! The `rustix` `Errno` type.
+//!
+//! This type holds an OS error code, which conceptually corresponds to an
+//! `errno` value.
+
+use crate::backend::c;
+use errno::errno;
+
+/// `errno`—An error code.
+///
+/// The error type for `rustix` APIs. This is similar to [`std::io::Error`],
+/// but only holds an OS error code, and no extra error value.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/errno.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/errno.3.html
+/// [Winsock]: https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?errno
+/// [NetBSD]: https://man.netbsd.org/errno.2
+/// [OpenBSD]: https://man.openbsd.org/errno.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=errno&section=2
+/// [illumos]: https://illumos.org/man/3C/errno
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
+/// [`std::io::Error`]: Result
+#[repr(transparent)]
+#[doc(alias = "errno")]
+#[derive(Eq, PartialEq, Hash, Copy, Clone)]
+pub struct Errno(pub(crate) c::c_int);
+
+impl Errno {
+ /// `EACCES`
+ #[doc(alias = "ACCES")]
+ pub const ACCESS: Self = Self(c::EACCES);
+ /// `EADDRINUSE`
+ pub const ADDRINUSE: Self = Self(c::EADDRINUSE);
+ /// `EADDRNOTAVAIL`
+ pub const ADDRNOTAVAIL: Self = Self(c::EADDRNOTAVAIL);
+ /// `EADV`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const ADV: Self = Self(c::EADV);
+ /// `EAFNOSUPPORT`
+ #[cfg(not(target_os = "l4re"))]
+ pub const AFNOSUPPORT: Self = Self(c::EAFNOSUPPORT);
+ /// `EAGAIN`
+ pub const AGAIN: Self = Self(c::EAGAIN);
+ /// `EALREADY`
+ #[cfg(not(target_os = "l4re"))]
+ pub const ALREADY: Self = Self(c::EALREADY);
+ /// `EAUTH`
+ #[cfg(bsd)]
+ pub const AUTH: Self = Self(c::EAUTH);
+ /// `EBADE`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const BADE: Self = Self(c::EBADE);
+ /// `EBADF`
+ pub const BADF: Self = Self(c::EBADF);
+ /// `EBADFD`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const BADFD: Self = Self(c::EBADFD);
+ /// `EBADMSG`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const BADMSG: Self = Self(c::EBADMSG);
+ /// `EBADR`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const BADR: Self = Self(c::EBADR);
+ /// `EBADRPC`
+ #[cfg(bsd)]
+ pub const BADRPC: Self = Self(c::EBADRPC);
+ /// `EBADRQC`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const BADRQC: Self = Self(c::EBADRQC);
+ /// `EBADSLT`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const BADSLT: Self = Self(c::EBADSLT);
+ /// `EBFONT`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const BFONT: Self = Self(c::EBFONT);
+ /// `EBUSY`
+ #[cfg(not(windows))]
+ pub const BUSY: Self = Self(c::EBUSY);
+ /// `ECANCELED`
+ #[cfg(not(target_os = "l4re"))]
+ pub const CANCELED: Self = Self(c::ECANCELED);
+ /// `ECAPMODE`
+ #[cfg(target_os = "freebsd")]
+ pub const CAPMODE: Self = Self(c::ECAPMODE);
+ /// `ECHILD`
+ #[cfg(not(windows))]
+ pub const CHILD: Self = Self(c::ECHILD);
+ /// `ECHRNG`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const CHRNG: Self = Self(c::ECHRNG);
+ /// `ECOMM`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const COMM: Self = Self(c::ECOMM);
+ /// `ECONNABORTED`
+ pub const CONNABORTED: Self = Self(c::ECONNABORTED);
+ /// `ECONNREFUSED`
+ pub const CONNREFUSED: Self = Self(c::ECONNREFUSED);
+ /// `ECONNRESET`
+ pub const CONNRESET: Self = Self(c::ECONNRESET);
+ /// `EDEADLK`
+ #[cfg(not(windows))]
+ pub const DEADLK: Self = Self(c::EDEADLK);
+ /// `EDEADLOCK`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const DEADLOCK: Self = Self(c::EDEADLOCK);
+ /// `EDESTADDRREQ`
+ #[cfg(not(target_os = "l4re"))]
+ pub const DESTADDRREQ: Self = Self(c::EDESTADDRREQ);
+ /// `EDISCON`
+ #[cfg(windows)]
+ pub const DISCON: Self = Self(c::EDISCON);
+ /// `EDOM`
+ #[cfg(not(windows))]
+ pub const DOM: Self = Self(c::EDOM);
+ /// `EDOOFUS`
+ #[cfg(freebsdlike)]
+ pub const DOOFUS: Self = Self(c::EDOOFUS);
+ /// `EDOTDOT`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const DOTDOT: Self = Self(c::EDOTDOT);
+ /// `EDQUOT`
+ pub const DQUOT: Self = Self(c::EDQUOT);
+ /// `EEXIST`
+ #[cfg(not(windows))]
+ pub const EXIST: Self = Self(c::EEXIST);
+ /// `EFAULT`
+ pub const FAULT: Self = Self(c::EFAULT);
+ /// `EFBIG`
+ #[cfg(not(windows))]
+ pub const FBIG: Self = Self(c::EFBIG);
+ /// `EFTYPE`
+ #[cfg(any(bsd, target_env = "newlib"))]
+ pub const FTYPE: Self = Self(c::EFTYPE);
+ /// `EHOSTDOWN`
+ #[cfg(not(any(target_os = "l4re", target_os = "wasi")))]
+ pub const HOSTDOWN: Self = Self(c::EHOSTDOWN);
+ /// `EHOSTUNREACH`
+ pub const HOSTUNREACH: Self = Self(c::EHOSTUNREACH);
+ /// `EHWPOISON`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const HWPOISON: Self = Self(c::EHWPOISON);
+ /// `EIDRM`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const IDRM: Self = Self(c::EIDRM);
+ /// `EILSEQ`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const ILSEQ: Self = Self(c::EILSEQ);
+ /// `EINPROGRESS`
+ #[cfg(not(target_os = "l4re"))]
+ pub const INPROGRESS: Self = Self(c::EINPROGRESS);
+ /// `EINTR`
+ ///
+ /// For a convenient way to retry system calls that exit with `INTR`, use
+ /// [`retry_on_intr`].
+ ///
+ /// [`retry_on_intr`]: crate::io::retry_on_intr
+ pub const INTR: Self = Self(c::EINTR);
+ /// `EINVAL`
+ pub const INVAL: Self = Self(c::EINVAL);
+ /// `EINVALIDPROCTABLE`
+ #[cfg(windows)]
+ pub const INVALIDPROCTABLE: Self = Self(c::EINVALIDPROCTABLE);
+ /// `EINVALIDPROVIDER`
+ #[cfg(windows)]
+ pub const INVALIDPROVIDER: Self = Self(c::EINVALIDPROVIDER);
+ /// `EIO`
+ #[cfg(not(windows))]
+ pub const IO: Self = Self(c::EIO);
+ /// `EISCONN`
+ #[cfg(not(target_os = "l4re"))]
+ pub const ISCONN: Self = Self(c::EISCONN);
+ /// `EISDIR`
+ #[cfg(not(windows))]
+ pub const ISDIR: Self = Self(c::EISDIR);
+ /// `EISNAM`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const ISNAM: Self = Self(c::EISNAM);
+ /// `EKEYEXPIRED`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const KEYEXPIRED: Self = Self(c::EKEYEXPIRED);
+ /// `EKEYREJECTED`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const KEYREJECTED: Self = Self(c::EKEYREJECTED);
+ /// `EKEYREVOKED`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const KEYREVOKED: Self = Self(c::EKEYREVOKED);
+ /// `EL2HLT`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const L2HLT: Self = Self(c::EL2HLT);
+ /// `EL2NSYNC`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const L2NSYNC: Self = Self(c::EL2NSYNC);
+ /// `EL3HLT`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const L3HLT: Self = Self(c::EL3HLT);
+ /// `EL3RST`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const L3RST: Self = Self(c::EL3RST);
+ /// `ELIBACC`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const LIBACC: Self = Self(c::ELIBACC);
+ /// `ELIBBAD`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const LIBBAD: Self = Self(c::ELIBBAD);
+ /// `ELIBEXEC`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const LIBEXEC: Self = Self(c::ELIBEXEC);
+ /// `ELIBMAX`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const LIBMAX: Self = Self(c::ELIBMAX);
+ /// `ELIBSCN`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const LIBSCN: Self = Self(c::ELIBSCN);
+ /// `ELNRNG`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const LNRNG: Self = Self(c::ELNRNG);
+ /// `ELOOP`
+ pub const LOOP: Self = Self(c::ELOOP);
+ /// `EMEDIUMTYPE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const MEDIUMTYPE: Self = Self(c::EMEDIUMTYPE);
+ /// `EMFILE`
+ pub const MFILE: Self = Self(c::EMFILE);
+ /// `EMLINK`
+ #[cfg(not(windows))]
+ pub const MLINK: Self = Self(c::EMLINK);
+ /// `EMSGSIZE`
+ #[cfg(not(target_os = "l4re"))]
+ pub const MSGSIZE: Self = Self(c::EMSGSIZE);
+ /// `EMULTIHOP`
+ #[cfg(not(any(windows, target_os = "l4re", target_os = "openbsd")))]
+ pub const MULTIHOP: Self = Self(c::EMULTIHOP);
+ /// `ENAMETOOLONG`
+ pub const NAMETOOLONG: Self = Self(c::ENAMETOOLONG);
+ /// `ENAVAIL`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NAVAIL: Self = Self(c::ENAVAIL);
+ /// `ENEEDAUTH`
+ #[cfg(bsd)]
+ pub const NEEDAUTH: Self = Self(c::ENEEDAUTH);
+ /// `ENETDOWN`
+ pub const NETDOWN: Self = Self(c::ENETDOWN);
+ /// `ENETRESET`
+ #[cfg(not(target_os = "l4re"))]
+ pub const NETRESET: Self = Self(c::ENETRESET);
+ /// `ENETUNREACH`
+ pub const NETUNREACH: Self = Self(c::ENETUNREACH);
+ /// `ENFILE`
+ #[cfg(not(windows))]
+ pub const NFILE: Self = Self(c::ENFILE);
+ /// `ENOANO`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NOANO: Self = Self(c::ENOANO);
+ /// `ENOATTR`
+ #[cfg(any(bsd, target_os = "haiku"))]
+ pub const NOATTR: Self = Self(c::ENOATTR);
+ /// `ENOBUFS`
+ #[cfg(not(target_os = "l4re"))]
+ pub const NOBUFS: Self = Self(c::ENOBUFS);
+ /// `ENOCSI`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const NOCSI: Self = Self(c::ENOCSI);
+ /// `ENODATA`
+ #[cfg(not(any(
+ freebsdlike,
+ windows,
+ target_os = "haiku",
+ target_os = "openbsd",
+ target_os = "wasi",
+ )))]
+ pub const NODATA: Self = Self(c::ENODATA);
+ /// `ENODEV`
+ #[cfg(not(windows))]
+ pub const NODEV: Self = Self(c::ENODEV);
+ /// `ENOENT`
+ #[cfg(not(windows))]
+ pub const NOENT: Self = Self(c::ENOENT);
+ /// `ENOEXEC`
+ #[cfg(not(windows))]
+ pub const NOEXEC: Self = Self(c::ENOEXEC);
+ /// `ENOKEY`
+ #[cfg(not(any(
+ solarish,
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NOKEY: Self = Self(c::ENOKEY);
+ /// `ENOLCK`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const NOLCK: Self = Self(c::ENOLCK);
+ /// `ENOLINK`
+ #[cfg(not(any(windows, target_os = "l4re", target_os = "openbsd")))]
+ pub const NOLINK: Self = Self(c::ENOLINK);
+ /// `ENOMEDIUM`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NOMEDIUM: Self = Self(c::ENOMEDIUM);
+ /// `ENOMEM`
+ #[cfg(not(windows))]
+ pub const NOMEM: Self = Self(c::ENOMEM);
+ /// `ENOMORE`
+ #[cfg(windows)]
+ pub const NOMORE: Self = Self(c::ENOMORE);
+ /// `ENOMSG`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const NOMSG: Self = Self(c::ENOMSG);
+ /// `ENONET`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NONET: Self = Self(c::ENONET);
+ /// `ENOPKG`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NOPKG: Self = Self(c::ENOPKG);
+ /// `ENOPROTOOPT`
+ #[cfg(not(target_os = "l4re"))]
+ pub const NOPROTOOPT: Self = Self(c::ENOPROTOOPT);
+ /// `ENOSPC`
+ #[cfg(not(windows))]
+ pub const NOSPC: Self = Self(c::ENOSPC);
+ /// `ENOSR`
+ #[cfg(not(any(
+ freebsdlike,
+ windows,
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "openbsd",
+ target_os = "wasi",
+ )))]
+ pub const NOSR: Self = Self(c::ENOSR);
+ /// `ENOSTR`
+ #[cfg(not(any(
+ freebsdlike,
+ windows,
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "openbsd",
+ target_os = "wasi",
+ )))]
+ pub const NOSTR: Self = Self(c::ENOSTR);
+ /// `ENOSYS`
+ #[cfg(not(windows))]
+ pub const NOSYS: Self = Self(c::ENOSYS);
+ /// `ENOTBLK`
+ #[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const NOTBLK: Self = Self(c::ENOTBLK);
+ /// `ENOTCAPABLE`
+ #[cfg(any(target_os = "freebsd", target_os = "wasi"))]
+ pub const NOTCAPABLE: Self = Self(c::ENOTCAPABLE);
+ /// `ENOTCONN`
+ pub const NOTCONN: Self = Self(c::ENOTCONN);
+ /// `ENOTDIR`
+ #[cfg(not(windows))]
+ pub const NOTDIR: Self = Self(c::ENOTDIR);
+ /// `ENOTEMPTY`
+ pub const NOTEMPTY: Self = Self(c::ENOTEMPTY);
+ /// `ENOTNAM`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NOTNAM: Self = Self(c::ENOTNAM);
+ /// `ENOTRECOVERABLE`
+ #[cfg(not(any(
+ freebsdlike,
+ netbsdlike,
+ windows,
+ target_os = "haiku",
+ target_os = "l4re"
+ )))]
+ pub const NOTRECOVERABLE: Self = Self(c::ENOTRECOVERABLE);
+ /// `ENOTSOCK`
+ #[cfg(not(target_os = "l4re"))]
+ pub const NOTSOCK: Self = Self(c::ENOTSOCK);
+ /// `ENOTSUP`
+ #[cfg(not(any(windows, target_os = "haiku", target_os = "redox")))]
+ pub const NOTSUP: Self = Self(c::ENOTSUP);
+ /// `ENOTTY`
+ #[cfg(not(windows))]
+ pub const NOTTY: Self = Self(c::ENOTTY);
+ /// `ENOTUNIQ`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const NOTUNIQ: Self = Self(c::ENOTUNIQ);
+ /// `ENXIO`
+ #[cfg(not(windows))]
+ pub const NXIO: Self = Self(c::ENXIO);
+ /// `EOPNOTSUPP`
+ pub const OPNOTSUPP: Self = Self(c::EOPNOTSUPP);
+ /// `EOVERFLOW`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const OVERFLOW: Self = Self(c::EOVERFLOW);
+ /// `EOWNERDEAD`
+ #[cfg(not(any(
+ freebsdlike,
+ netbsdlike,
+ windows,
+ target_os = "haiku",
+ target_os = "l4re"
+ )))]
+ pub const OWNERDEAD: Self = Self(c::EOWNERDEAD);
+ /// `EPERM`
+ #[cfg(not(windows))]
+ pub const PERM: Self = Self(c::EPERM);
+ /// `EPFNOSUPPORT`
+ #[cfg(not(any(target_os = "l4re", target_os = "wasi")))]
+ pub const PFNOSUPPORT: Self = Self(c::EPFNOSUPPORT);
+ /// `EPIPE`
+ #[cfg(not(windows))]
+ pub const PIPE: Self = Self(c::EPIPE);
+ /// `EPROCLIM`
+ #[cfg(bsd)]
+ pub const PROCLIM: Self = Self(c::EPROCLIM);
+ /// `EPROCUNAVAIL`
+ #[cfg(bsd)]
+ pub const PROCUNAVAIL: Self = Self(c::EPROCUNAVAIL);
+ /// `EPROGMISMATCH`
+ #[cfg(bsd)]
+ pub const PROGMISMATCH: Self = Self(c::EPROGMISMATCH);
+ /// `EPROGUNAVAIL`
+ #[cfg(bsd)]
+ pub const PROGUNAVAIL: Self = Self(c::EPROGUNAVAIL);
+ /// `EPROTO`
+ #[cfg(not(any(windows, target_os = "l4re")))]
+ pub const PROTO: Self = Self(c::EPROTO);
+ /// `EPROTONOSUPPORT`
+ #[cfg(not(target_os = "l4re"))]
+ pub const PROTONOSUPPORT: Self = Self(c::EPROTONOSUPPORT);
+ /// `EPROTOTYPE`
+ #[cfg(not(target_os = "l4re"))]
+ pub const PROTOTYPE: Self = Self(c::EPROTOTYPE);
+ /// `EPROVIDERFAILEDINIT`
+ #[cfg(windows)]
+ pub const PROVIDERFAILEDINIT: Self = Self(c::EPROVIDERFAILEDINIT);
+ /// `ERANGE`
+ #[cfg(not(windows))]
+ pub const RANGE: Self = Self(c::ERANGE);
+ /// `EREFUSED`
+ #[cfg(windows)]
+ pub const REFUSED: Self = Self(c::EREFUSED);
+ /// `EREMCHG`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const REMCHG: Self = Self(c::EREMCHG);
+ /// `EREMOTE`
+ #[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const REMOTE: Self = Self(c::EREMOTE);
+ /// `EREMOTEIO`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const REMOTEIO: Self = Self(c::EREMOTEIO);
+ /// `ERESTART`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const RESTART: Self = Self(c::ERESTART);
+ /// `ERFKILL`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const RFKILL: Self = Self(c::ERFKILL);
+ /// `EROFS`
+ #[cfg(not(windows))]
+ pub const ROFS: Self = Self(c::EROFS);
+ /// `ERPCMISMATCH`
+ #[cfg(bsd)]
+ pub const RPCMISMATCH: Self = Self(c::ERPCMISMATCH);
+ /// `ESHUTDOWN`
+ #[cfg(not(any(
+ target_os = "espidf",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const SHUTDOWN: Self = Self(c::ESHUTDOWN);
+ /// `ESOCKTNOSUPPORT`
+ #[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const SOCKTNOSUPPORT: Self = Self(c::ESOCKTNOSUPPORT);
+ /// `ESPIPE`
+ #[cfg(not(windows))]
+ pub const SPIPE: Self = Self(c::ESPIPE);
+ /// `ESRCH`
+ #[cfg(not(windows))]
+ pub const SRCH: Self = Self(c::ESRCH);
+ /// `ESRMNT`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const SRMNT: Self = Self(c::ESRMNT);
+ /// `ESTALE`
+ pub const STALE: Self = Self(c::ESTALE);
+ /// `ESTRPIPE`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const STRPIPE: Self = Self(c::ESTRPIPE);
+ /// `ETIME`
+ #[cfg(not(any(
+ freebsdlike,
+ windows,
+ target_os = "l4re",
+ target_os = "openbsd",
+ target_os = "wasi"
+ )))]
+ pub const TIME: Self = Self(c::ETIME);
+ /// `ETIMEDOUT`
+ pub const TIMEDOUT: Self = Self(c::ETIMEDOUT);
+ /// `E2BIG`
+ #[cfg(not(windows))]
+ #[doc(alias = "2BIG")]
+ pub const TOOBIG: Self = Self(c::E2BIG);
+ /// `ETOOMANYREFS`
+ #[cfg(not(any(target_os = "haiku", target_os = "l4re", target_os = "wasi")))]
+ pub const TOOMANYREFS: Self = Self(c::ETOOMANYREFS);
+ /// `ETXTBSY`
+ #[cfg(not(windows))]
+ pub const TXTBSY: Self = Self(c::ETXTBSY);
+ /// `EUCLEAN`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const UCLEAN: Self = Self(c::EUCLEAN);
+ /// `EUNATCH`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const UNATCH: Self = Self(c::EUNATCH);
+ /// `EUSERS`
+ #[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ pub const USERS: Self = Self(c::EUSERS);
+ /// `EWOULDBLOCK`
+ pub const WOULDBLOCK: Self = Self(c::EWOULDBLOCK);
+ /// `EXDEV`
+ #[cfg(not(windows))]
+ pub const XDEV: Self = Self(c::EXDEV);
+ /// `EXFULL`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "l4re",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ pub const XFULL: Self = Self(c::EXFULL);
+}
+
+impl Errno {
+ /// Extract an `Errno` value from a `std::io::Error`.
+ ///
+ /// This isn't a `From` conversion because it's expected to be relatively
+ /// uncommon.
+ #[cfg(feature = "std")]
+ #[inline]
+ pub fn from_io_error(io_err: &std::io::Error) -> Option<Self> {
+ io_err
+ .raw_os_error()
+ .and_then(|raw| if raw != 0 { Some(Self(raw)) } else { None })
+ }
+
+ /// Extract the raw OS error number from this error.
+ #[inline]
+ pub const fn raw_os_error(self) -> i32 {
+ self.0
+ }
+
+ /// Construct an `Errno` from a raw OS error number.
+ #[inline]
+ pub const fn from_raw_os_error(raw: i32) -> Self {
+ Self(raw)
+ }
+
+ pub(crate) fn last_os_error() -> Self {
+ Self(errno().0)
+ }
+}
diff --git a/src/backend/libc/io/mod.rs b/src/backend/libc/io/mod.rs
new file mode 100644
index 0000000..4873885
--- /dev/null
+++ b/src/backend/libc/io/mod.rs
@@ -0,0 +1,6 @@
+pub(crate) mod errno;
+#[cfg(not(windows))]
+pub(crate) mod types;
+
+#[cfg_attr(windows, path = "windows_syscalls.rs")]
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/io/syscalls.rs b/src/backend/libc/io/syscalls.rs
new file mode 100644
index 0000000..1a5e1fb
--- /dev/null
+++ b/src/backend/libc/io/syscalls.rs
@@ -0,0 +1,340 @@
+//! libc syscalls supporting `rustix::io`.
+
+use crate::backend::c;
+#[cfg(not(target_os = "wasi"))]
+use crate::backend::conv::ret_discarded_fd;
+use crate::backend::conv::{borrowed_fd, ret, ret_c_int, ret_owned_fd, ret_usize};
+use crate::fd::{AsFd, BorrowedFd, OwnedFd, RawFd};
+#[cfg(not(any(
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::io::DupFlags;
+#[cfg(feature = "linux-raw-sys")]
+use crate::io::ReadWriteFlags;
+use crate::io::{self, FdFlags};
+use crate::ioctl::{IoctlOutput, RawOpcode};
+use core::cmp::min;
+#[cfg(all(feature = "fs", feature = "net"))]
+use errno::errno;
+#[cfg(not(target_os = "espidf"))]
+use {
+ crate::backend::MAX_IOV,
+ crate::io::{IoSlice, IoSliceMut},
+};
+
+pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: *mut u8, len: usize) -> io::Result<usize> {
+ ret_usize(c::read(borrowed_fd(fd), buf.cast(), min(len, READ_LIMIT)))
+}
+
+pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
+ unsafe {
+ ret_usize(c::write(
+ borrowed_fd(fd),
+ buf.as_ptr().cast(),
+ min(buf.len(), READ_LIMIT),
+ ))
+ }
+}
+
+pub(crate) unsafe fn pread(
+ fd: BorrowedFd<'_>,
+ buf: *mut u8,
+ len: usize,
+ offset: u64,
+) -> io::Result<usize> {
+ let len = min(len, READ_LIMIT);
+
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+
+ // ESP-IDF and Vita don't support 64-bit offsets.
+ #[cfg(any(target_os = "espidf", target_os = "vita"))]
+ let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+
+ ret_usize(c::pread(borrowed_fd(fd), buf.cast(), len, offset))
+}
+
+pub(crate) fn pwrite(fd: BorrowedFd<'_>, buf: &[u8], offset: u64) -> io::Result<usize> {
+ let len = min(buf.len(), READ_LIMIT);
+
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+
+ // ESP-IDF and Vita don't support 64-bit offsets.
+ #[cfg(any(target_os = "espidf", target_os = "vita"))]
+ let offset: i32 = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
+
+ unsafe { ret_usize(c::pwrite(borrowed_fd(fd), buf.as_ptr().cast(), len, offset)) }
+}
+
+#[cfg(not(target_os = "espidf"))]
+pub(crate) fn readv(fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+ unsafe {
+ ret_usize(c::readv(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV) as c::c_int,
+ ))
+ }
+}
+
+#[cfg(not(target_os = "espidf"))]
+pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+ unsafe {
+ ret_usize(c::writev(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV) as c::c_int,
+ ))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "solaris",
+ target_os = "vita"
+)))]
+pub(crate) fn preadv(
+ fd: BorrowedFd<'_>,
+ bufs: &mut [IoSliceMut<'_>],
+ offset: u64,
+) -> io::Result<usize> {
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+ unsafe {
+ ret_usize(c::preadv(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV) as c::c_int,
+ offset,
+ ))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "solaris",
+ target_os = "vita"
+)))]
+pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+ unsafe {
+ ret_usize(c::pwritev(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV) as c::c_int,
+ offset,
+ ))
+ }
+}
+
+#[cfg(feature = "linux-raw-sys")]
+pub(crate) fn preadv2(
+ fd: BorrowedFd<'_>,
+ bufs: &mut [IoSliceMut<'_>],
+ offset: u64,
+ flags: ReadWriteFlags,
+) -> io::Result<usize> {
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+ unsafe {
+ ret_usize(c::preadv2(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV) as c::c_int,
+ offset,
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(feature = "linux-raw-sys")]
+pub(crate) fn pwritev2(
+ fd: BorrowedFd<'_>,
+ bufs: &[IoSlice<'_>],
+ offset: u64,
+ flags: ReadWriteFlags,
+) -> io::Result<usize> {
+ // Silently cast; we'll get `EINVAL` if the value is negative.
+ let offset = offset as i64;
+ unsafe {
+ ret_usize(c::pwritev2(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV) as c::c_int,
+ offset,
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+// These functions are derived from Rust's library/std/src/sys/unix/fd.rs at
+// revision 326ef470a8b379a180d6dc4bbef08990698a737a.
+
+// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`, with the
+// manual page quoting that if the count of bytes to read is greater than
+// `SSIZE_MAX` the result is “unspecified”.
+//
+// On macOS, however, apparently the 64-bit libc is either buggy or
+// intentionally showing odd behavior by rejecting any read with a size larger
+// than or equal to `INT_MAX`. To handle both of these the read size is capped
+// on both platforms.
+#[cfg(target_os = "macos")]
+const READ_LIMIT: usize = c::c_int::MAX as usize - 1;
+#[cfg(not(target_os = "macos"))]
+const READ_LIMIT: usize = c::ssize_t::MAX as usize;
+
+pub(crate) unsafe fn close(raw_fd: RawFd) {
+ let _ = c::close(raw_fd as c::c_int);
+}
+
+#[inline]
+pub(crate) unsafe fn ioctl(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> io::Result<IoctlOutput> {
+ ret_c_int(c::ioctl(borrowed_fd(fd), request, arg))
+}
+
+#[inline]
+pub(crate) unsafe fn ioctl_readonly(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> io::Result<IoctlOutput> {
+ ioctl(fd, request, arg)
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[cfg(all(feature = "fs", feature = "net"))]
+pub(crate) fn is_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> {
+ use core::mem::MaybeUninit;
+
+ let (mut read, mut write) = crate::fs::fd::_is_file_read_write(fd)?;
+ let mut not_socket = false;
+ if read {
+ // Do a `recv` with `PEEK` and `DONTWAIT` for 1 byte. A 0 indicates
+ // the read side is shut down; an `EWOULDBLOCK` indicates the read
+ // side is still open.
+ match unsafe {
+ c::recv(
+ borrowed_fd(fd),
+ MaybeUninit::<[u8; 1]>::uninit()
+ .as_mut_ptr()
+ .cast::<c::c_void>(),
+ 1,
+ c::MSG_PEEK | c::MSG_DONTWAIT,
+ )
+ } {
+ 0 => read = false,
+ -1 => {
+ #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK`
+ match errno().0 {
+ c::EAGAIN | c::EWOULDBLOCK => (),
+ c::ENOTSOCK => not_socket = true,
+ err => return Err(io::Errno(err)),
+ }
+ }
+ _ => (),
+ }
+ }
+ if write && !not_socket {
+ // Do a `send` with `DONTWAIT` for 0 bytes. An `EPIPE` indicates
+ // the write side is shut down.
+ if unsafe { c::send(borrowed_fd(fd), [].as_ptr(), 0, c::MSG_DONTWAIT) } == -1 {
+ #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK`
+ match errno().0 {
+ c::EAGAIN | c::EWOULDBLOCK | c::ENOTSOCK => (),
+ c::EPIPE => write = false,
+ err => return Err(io::Errno(err)),
+ }
+ }
+ }
+ Ok((read, write))
+}
+
+#[cfg(target_os = "wasi")]
+#[cfg(all(feature = "fs", feature = "net"))]
+pub(crate) fn is_read_write(_fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> {
+ todo!("Implement is_read_write for WASI in terms of fd_fdstat_get");
+}
+
+pub(crate) fn fcntl_getfd(fd: BorrowedFd<'_>) -> io::Result<FdFlags> {
+ let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFD))? };
+ Ok(FdFlags::from_bits_retain(bitcast!(flags)))
+}
+
+pub(crate) fn fcntl_setfd(fd: BorrowedFd<'_>, flags: FdFlags) -> io::Result<()> {
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFD, flags.bits())) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn fcntl_dupfd_cloexec(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD_CLOEXEC, min)) }
+}
+
+#[cfg(target_os = "espidf")]
+pub(crate) fn fcntl_dupfd(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD, min)) }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::dup(borrowed_fd(fd))) }
+}
+
+#[allow(clippy::needless_pass_by_ref_mut)]
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> {
+ unsafe { ret_discarded_fd(c::dup2(borrowed_fd(fd), borrowed_fd(new.as_fd()))) }
+}
+
+#[allow(clippy::needless_pass_by_ref_mut)]
+#[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
+ unsafe {
+ ret_discarded_fd(c::dup3(
+ borrowed_fd(fd),
+ borrowed_fd(new.as_fd()),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(any(
+ apple,
+ target_os = "android",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "redox",
+))]
+pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, _flags: DupFlags) -> io::Result<()> {
+ // Android 5.0 has `dup3`, but libc doesn't have bindings. Emulate it
+ // using `dup2`. We don't need to worry about the difference between
+ // `dup2` and `dup3` when the file descriptors are equal because we
+ // have an `&mut OwnedFd` which means `fd` doesn't alias it.
+ dup2(fd, new)
+}
diff --git a/src/backend/libc/io/types.rs b/src/backend/libc/io/types.rs
new file mode 100644
index 0000000..a86dc26
--- /dev/null
+++ b/src/backend/libc/io/types.rs
@@ -0,0 +1,65 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `FD_*` constants for use with [`fcntl_getfd`] and [`fcntl_setfd`].
+ ///
+ /// [`fcntl_getfd`]: crate::io::fcntl_getfd
+ /// [`fcntl_setfd`]: crate::io::fcntl_setfd
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FdFlags: u32 {
+ /// `FD_CLOEXEC`
+ const CLOEXEC = bitcast!(c::FD_CLOEXEC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "linux-raw-sys")]
+bitflags! {
+ /// `RWF_*` constants for use with [`preadv2`] and [`pwritev2`].
+ ///
+ /// [`preadv2`]: crate::io::preadv2
+ /// [`pwritev2`]: crate::io::pwritev
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ReadWriteFlags: u32 {
+ /// `RWF_DSYNC` (since Linux 4.7)
+ const DSYNC = linux_raw_sys::general::RWF_DSYNC;
+ /// `RWF_HIPRI` (since Linux 4.6)
+ const HIPRI = linux_raw_sys::general::RWF_HIPRI;
+ /// `RWF_SYNC` (since Linux 4.7)
+ const SYNC = linux_raw_sys::general::RWF_SYNC;
+ /// `RWF_NOWAIT` (since Linux 4.14)
+ const NOWAIT = linux_raw_sys::general::RWF_NOWAIT;
+ /// `RWF_APPEND` (since Linux 4.16)
+ const APPEND = linux_raw_sys::general::RWF_APPEND;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+bitflags! {
+ /// `O_*` constants for use with [`dup2`].
+ ///
+ /// [`dup2`]: crate::io::dup2
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct DupFlags: u32 {
+ /// `O_CLOEXEC`
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "redox",
+ )))] // Android 5.0 has dup3, but libc doesn't have bindings
+ const CLOEXEC = bitcast!(c::O_CLOEXEC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/libc/io/windows_syscalls.rs b/src/backend/libc/io/windows_syscalls.rs
new file mode 100644
index 0000000..049221d
--- /dev/null
+++ b/src/backend/libc/io/windows_syscalls.rs
@@ -0,0 +1,30 @@
+//! Windows system calls in the `io` module.
+
+use crate::backend::c;
+use crate::backend::conv::{borrowed_fd, ret_c_int};
+use crate::backend::fd::LibcFd;
+use crate::fd::{BorrowedFd, RawFd};
+use crate::io;
+use crate::ioctl::{IoctlOutput, RawOpcode};
+
+pub(crate) unsafe fn close(raw_fd: RawFd) {
+ let _ = c::close(raw_fd as LibcFd);
+}
+
+#[inline]
+pub(crate) unsafe fn ioctl(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> io::Result<IoctlOutput> {
+ ret_c_int(c::ioctl(borrowed_fd(fd), request, arg.cast()))
+}
+
+#[inline]
+pub(crate) unsafe fn ioctl_readonly(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> io::Result<IoctlOutput> {
+ ioctl(fd, request, arg)
+}
diff --git a/src/backend/libc/io_uring/mod.rs b/src/backend/libc/io_uring/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/libc/io_uring/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/io_uring/syscalls.rs b/src/backend/libc/io_uring/syscalls.rs
new file mode 100644
index 0000000..8e81824
--- /dev/null
+++ b/src/backend/libc/io_uring/syscalls.rs
@@ -0,0 +1,70 @@
+//! libc syscalls supporting `rustix::io_uring`.
+
+use crate::backend::c;
+use crate::backend::conv::{borrowed_fd, ret_owned_fd, ret_u32};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+use crate::io_uring::{io_uring_params, IoringEnterFlags, IoringRegisterOp};
+
+#[inline]
+pub(crate) fn io_uring_setup(entries: u32, params: &mut io_uring_params) -> io::Result<OwnedFd> {
+ syscall! {
+ fn io_uring_setup(
+ entries: u32,
+ params: *mut io_uring_params
+ ) via SYS_io_uring_setup -> c::c_int
+ }
+ unsafe { ret_owned_fd(io_uring_setup(entries, params)) }
+}
+
+#[inline]
+pub(crate) unsafe fn io_uring_register(
+ fd: BorrowedFd<'_>,
+ opcode: IoringRegisterOp,
+ arg: *const c::c_void,
+ nr_args: u32,
+) -> io::Result<u32> {
+ syscall! {
+ fn io_uring_register(
+ fd: c::c_uint,
+ opcode: c::c_uint,
+ arg: *const c::c_void,
+ nr_args: c::c_uint
+ ) via SYS_io_uring_register -> c::c_int
+ }
+ ret_u32(io_uring_register(
+ borrowed_fd(fd) as _,
+ opcode as u32,
+ arg,
+ nr_args,
+ ))
+}
+
+#[inline]
+pub(crate) unsafe fn io_uring_enter(
+ fd: BorrowedFd<'_>,
+ to_submit: u32,
+ min_complete: u32,
+ flags: IoringEnterFlags,
+ arg: *const c::c_void,
+ size: usize,
+) -> io::Result<u32> {
+ syscall! {
+ fn io_uring_enter2(
+ fd: c::c_uint,
+ to_submit: c::c_uint,
+ min_complete: c::c_uint,
+ flags: c::c_uint,
+ arg: *const c::c_void,
+ size: usize
+ ) via SYS_io_uring_enter -> c::c_int
+ }
+ ret_u32(io_uring_enter2(
+ borrowed_fd(fd) as _,
+ to_submit,
+ min_complete,
+ bitflags_bits!(flags),
+ arg,
+ size,
+ ))
+}
diff --git a/src/backend/libc/mm/mod.rs b/src/backend/libc/mm/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/libc/mm/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/mm/syscalls.rs b/src/backend/libc/mm/syscalls.rs
new file mode 100644
index 0000000..33bc9ca
--- /dev/null
+++ b/src/backend/libc/mm/syscalls.rs
@@ -0,0 +1,244 @@
+//! libc syscalls supporting `rustix::mm`.
+
+#[cfg(not(target_os = "redox"))]
+use super::types::Advice;
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+use super::types::MlockAllFlags;
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+use super::types::MremapFlags;
+use super::types::{MapFlags, MprotectFlags, MsyncFlags, ProtFlags};
+#[cfg(linux_kernel)]
+use super::types::{MlockFlags, UserfaultfdFlags};
+use crate::backend::c;
+#[cfg(linux_kernel)]
+use crate::backend::conv::ret_owned_fd;
+use crate::backend::conv::{borrowed_fd, no_fd, ret};
+use crate::fd::BorrowedFd;
+#[cfg(linux_kernel)]
+use crate::fd::OwnedFd;
+use crate::io;
+
+#[cfg(not(target_os = "redox"))]
+pub(crate) fn madvise(addr: *mut c::c_void, len: usize, advice: Advice) -> io::Result<()> {
+ // On Linux platforms, `MADV_DONTNEED` has the same value as
+ // `POSIX_MADV_DONTNEED` but different behavior. We remap it to a different
+ // value, and check for it here.
+ #[cfg(target_os = "linux")]
+ if let Advice::LinuxDontNeed = advice {
+ return unsafe { ret(c::madvise(addr, len, c::MADV_DONTNEED)) };
+ }
+
+ #[cfg(not(target_os = "android"))]
+ {
+ let err = unsafe { c::posix_madvise(addr, len, advice as c::c_int) };
+
+ // `posix_madvise` returns its error status rather than using `errno`.
+ if err == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno(err))
+ }
+ }
+
+ #[cfg(target_os = "android")]
+ {
+ if let Advice::DontNeed = advice {
+ // Do nothing. Linux's `MADV_DONTNEED` isn't the same as
+ // `POSIX_MADV_DONTNEED`, so just discard `MADV_DONTNEED`.
+ Ok(())
+ } else {
+ unsafe { ret(c::madvise(addr, len, advice as c::c_int)) }
+ }
+ }
+}
+
+pub(crate) unsafe fn msync(addr: *mut c::c_void, len: usize, flags: MsyncFlags) -> io::Result<()> {
+ let err = c::msync(addr, len, bitflags_bits!(flags));
+
+ // `msync` returns its error status rather than using `errno`.
+ if err == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno(err))
+ }
+}
+
+/// # Safety
+///
+/// `mmap` is primarily unsafe due to the `addr` parameter, as anything working
+/// with memory pointed to by raw pointers is unsafe.
+pub(crate) unsafe fn mmap(
+ ptr: *mut c::c_void,
+ len: usize,
+ prot: ProtFlags,
+ flags: MapFlags,
+ fd: BorrowedFd<'_>,
+ offset: u64,
+) -> io::Result<*mut c::c_void> {
+ let res = c::mmap(
+ ptr,
+ len,
+ bitflags_bits!(prot),
+ bitflags_bits!(flags),
+ borrowed_fd(fd),
+ offset as i64,
+ );
+ if res == c::MAP_FAILED {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(res)
+ }
+}
+
+/// # Safety
+///
+/// `mmap` is primarily unsafe due to the `addr` parameter, as anything working
+/// with memory pointed to by raw pointers is unsafe.
+pub(crate) unsafe fn mmap_anonymous(
+ ptr: *mut c::c_void,
+ len: usize,
+ prot: ProtFlags,
+ flags: MapFlags,
+) -> io::Result<*mut c::c_void> {
+ let res = c::mmap(
+ ptr,
+ len,
+ bitflags_bits!(prot),
+ bitflags_bits!(flags | MapFlags::from_bits_retain(bitcast!(c::MAP_ANONYMOUS))),
+ no_fd(),
+ 0,
+ );
+ if res == c::MAP_FAILED {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(res)
+ }
+}
+
+pub(crate) unsafe fn mprotect(
+ ptr: *mut c::c_void,
+ len: usize,
+ flags: MprotectFlags,
+) -> io::Result<()> {
+ ret(c::mprotect(ptr, len, bitflags_bits!(flags)))
+}
+
+pub(crate) unsafe fn munmap(ptr: *mut c::c_void, len: usize) -> io::Result<()> {
+ ret(c::munmap(ptr, len))
+}
+
+/// # Safety
+///
+/// `mremap` is primarily unsafe due to the `old_address` parameter, as
+/// anything working with memory pointed to by raw pointers is unsafe.
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+pub(crate) unsafe fn mremap(
+ old_address: *mut c::c_void,
+ old_size: usize,
+ new_size: usize,
+ flags: MremapFlags,
+) -> io::Result<*mut c::c_void> {
+ let res = c::mremap(old_address, old_size, new_size, bitflags_bits!(flags));
+ if res == c::MAP_FAILED {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(res)
+ }
+}
+
+/// # Safety
+///
+/// `mremap_fixed` is primarily unsafe due to the `old_address` and
+/// `new_address` parameters, as anything working with memory pointed to by raw
+/// pointers is unsafe.
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+pub(crate) unsafe fn mremap_fixed(
+ old_address: *mut c::c_void,
+ old_size: usize,
+ new_size: usize,
+ flags: MremapFlags,
+ new_address: *mut c::c_void,
+) -> io::Result<*mut c::c_void> {
+ let res = c::mremap(
+ old_address,
+ old_size,
+ new_size,
+ bitflags_bits!(flags | MremapFlags::from_bits_retain(bitcast!(c::MAP_FIXED))),
+ new_address,
+ );
+ if res == c::MAP_FAILED {
+ Err(io::Errno::last_os_error())
+ } else {
+ Ok(res)
+ }
+}
+
+/// # Safety
+///
+/// `mlock` operates on raw pointers and may round out to the nearest page
+/// boundaries.
+#[inline]
+pub(crate) unsafe fn mlock(addr: *mut c::c_void, length: usize) -> io::Result<()> {
+ ret(c::mlock(addr, length))
+}
+
+/// # Safety
+///
+/// `mlock_with` operates on raw pointers and may round out to the nearest page
+/// boundaries.
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) unsafe fn mlock_with(
+ addr: *mut c::c_void,
+ length: usize,
+ flags: MlockFlags,
+) -> io::Result<()> {
+ weak_or_syscall! {
+ fn mlock2(
+ addr: *const c::c_void,
+ len: c::size_t,
+ flags: c::c_int
+ ) via SYS_mlock2 -> c::c_int
+ }
+
+ ret(mlock2(addr, length, bitflags_bits!(flags)))
+}
+
+/// # Safety
+///
+/// `munlock` operates on raw pointers and may round out to the nearest page
+/// boundaries.
+#[inline]
+pub(crate) unsafe fn munlock(addr: *mut c::c_void, length: usize) -> io::Result<()> {
+ ret(c::munlock(addr, length))
+}
+
+#[cfg(linux_kernel)]
+pub(crate) unsafe fn userfaultfd(flags: UserfaultfdFlags) -> io::Result<OwnedFd> {
+ syscall! {
+ fn userfaultfd(
+ flags: c::c_int
+ ) via SYS_userfaultfd -> c::c_int
+ }
+ ret_owned_fd(userfaultfd(bitflags_bits!(flags)))
+}
+
+/// Locks all pages mapped into the address space of the calling process.
+///
+/// This includes the pages of the code, data, and stack segment, as well as
+/// shared libraries, user space kernel data, shared memory, and memory-mapped
+/// files. All mapped pages are guaranteed to be resident in RAM when the call
+/// returns successfully; the pages are guaranteed to stay in RAM until later
+/// unlocked.
+#[inline]
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+pub(crate) fn mlockall(flags: MlockAllFlags) -> io::Result<()> {
+ unsafe { ret(c::mlockall(bitflags_bits!(flags))) }
+}
+
+/// Unlocks all pages mapped into the address space of the calling process.
+#[inline]
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+pub(crate) fn munlockall() -> io::Result<()> {
+ unsafe { ret(c::munlockall()) }
+}
diff --git a/src/backend/libc/mm/types.rs b/src/backend/libc/mm/types.rs
new file mode 100644
index 0000000..afd9ec0
--- /dev/null
+++ b/src/backend/libc/mm/types.rs
@@ -0,0 +1,477 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `PROT_*` flags for use with [`mmap`].
+ ///
+ /// For `PROT_NONE`, use `ProtFlags::empty()`.
+ ///
+ /// [`mmap`]: crate::mm::mmap
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ProtFlags: u32 {
+ /// `PROT_READ`
+ const READ = bitcast!(c::PROT_READ);
+ /// `PROT_WRITE`
+ const WRITE = bitcast!(c::PROT_WRITE);
+ /// `PROT_EXEC`
+ const EXEC = bitcast!(c::PROT_EXEC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `PROT_*` flags for use with [`mprotect`].
+ ///
+ /// For `PROT_NONE`, use `MprotectFlags::empty()`.
+ ///
+ /// [`mprotect`]: crate::mm::mprotect
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MprotectFlags: u32 {
+ /// `PROT_READ`
+ const READ = bitcast!(c::PROT_READ);
+ /// `PROT_WRITE`
+ const WRITE = bitcast!(c::PROT_WRITE);
+ /// `PROT_EXEC`
+ const EXEC = bitcast!(c::PROT_EXEC);
+ /// `PROT_GROWSUP`
+ #[cfg(linux_kernel)]
+ const GROWSUP = bitcast!(c::PROT_GROWSUP);
+ /// `PROT_GROWSDOWN`
+ #[cfg(linux_kernel)]
+ const GROWSDOWN = bitcast!(c::PROT_GROWSDOWN);
+ /// `PROT_SEM`
+ #[cfg(feature = "linux-raw-sys")]
+ const SEM = linux_raw_sys::general::PROT_SEM;
+ /// `PROT_BTI`
+ #[cfg(all(feature = "linux-raw-sys", target_arch = "aarch64"))]
+ const BTI = linux_raw_sys::general::PROT_BTI;
+ /// `PROT_MTE`
+ #[cfg(all(feature = "linux-raw-sys", target_arch = "aarch64"))]
+ const MTE = linux_raw_sys::general::PROT_MTE;
+ /// `PROT_SAO`
+ #[cfg(all(feature = "linux-raw-sys", any(target_arch = "powerpc", target_arch = "powerpc64")))]
+ const SAO = linux_raw_sys::general::PROT_SAO;
+ /// `PROT_ADI`
+ #[cfg(all(feature = "linux-raw-sys", any(target_arch = "sparc", target_arch = "sparc64")))]
+ const ADI = linux_raw_sys::general::PROT_ADI;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `MAP_*` flags for use with [`mmap`].
+ ///
+ /// For `MAP_ANONYMOUS` (aka `MAP_ANON`), see [`mmap_anonymous`].
+ ///
+ /// [`mmap`]: crate::mm::mmap
+ /// [`mmap_anonymous`]: crates::mm::mmap_anonymous
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MapFlags: u32 {
+ /// `MAP_SHARED`
+ const SHARED = bitcast!(c::MAP_SHARED);
+ /// `MAP_SHARED_VALIDATE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const SHARED_VALIDATE = bitcast!(c::MAP_SHARED_VALIDATE);
+ /// `MAP_PRIVATE`
+ const PRIVATE = bitcast!(c::MAP_PRIVATE);
+ /// `MAP_DENYWRITE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const DENYWRITE = bitcast!(c::MAP_DENYWRITE);
+ /// `MAP_FIXED`
+ const FIXED = bitcast!(c::MAP_FIXED);
+ /// `MAP_FIXED_NOREPLACE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const FIXED_NOREPLACE = bitcast!(c::MAP_FIXED_NOREPLACE);
+ /// `MAP_GROWSDOWN`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const GROWSDOWN = bitcast!(c::MAP_GROWSDOWN);
+ /// `MAP_HUGETLB`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const HUGETLB = bitcast!(c::MAP_HUGETLB);
+ /// `MAP_HUGE_2MB`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const HUGE_2MB = bitcast!(c::MAP_HUGE_2MB);
+ /// `MAP_HUGE_1GB`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const HUGE_1GB = bitcast!(c::MAP_HUGE_1GB);
+ /// `MAP_LOCKED`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const LOCKED = bitcast!(c::MAP_LOCKED);
+ /// `MAP_NOCORE`
+ #[cfg(freebsdlike)]
+ const NOCORE = bitcast!(c::MAP_NOCORE);
+ /// `MAP_NORESERVE`
+ #[cfg(not(any(
+ freebsdlike,
+ target_os = "aix",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const NORESERVE = bitcast!(c::MAP_NORESERVE);
+ /// `MAP_NOSYNC`
+ #[cfg(freebsdlike)]
+ const NOSYNC = bitcast!(c::MAP_NOSYNC);
+ /// `MAP_POPULATE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const POPULATE = bitcast!(c::MAP_POPULATE);
+ /// `MAP_STACK`
+ #[cfg(not(any(
+ apple,
+ solarish,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "redox",
+ )))]
+ const STACK = bitcast!(c::MAP_STACK);
+ /// `MAP_PREFAULT_READ`
+ #[cfg(target_os = "freebsd")]
+ const PREFAULT_READ = bitcast!(c::MAP_PREFAULT_READ);
+ /// `MAP_SYNC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ all(
+ linux_kernel,
+ any(target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6"),
+ )
+ )))]
+ const SYNC = bitcast!(c::MAP_SYNC);
+ /// `MAP_UNINITIALIZED`
+ #[cfg(any())]
+ const UNINITIALIZED = bitcast!(c::MAP_UNINITIALIZED);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+bitflags! {
+ /// `MREMAP_*` flags for use with [`mremap`].
+ ///
+ /// For `MREMAP_FIXED`, see [`mremap_fixed`].
+ ///
+ /// [`mremap`]: crate::mm::mremap
+ /// [`mremap_fixed`]: crate::mm::mremap_fixed
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MremapFlags: u32 {
+ /// `MREMAP_MAYMOVE`
+ const MAYMOVE = bitcast!(c::MREMAP_MAYMOVE);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `MS_*` flags for use with [`msync`].
+ ///
+ /// [`msync`]: crate::mm::msync
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MsyncFlags: u32 {
+ /// `MS_SYNC`—Requests an update and waits for it to complete.
+ const SYNC = bitcast!(c::MS_SYNC);
+ /// `MS_ASYNC`—Specifies that an update be scheduled, but the call
+ /// returns immediately.
+ const ASYNC = bitcast!(c::MS_ASYNC);
+ /// `MS_INVALIDATE`—Asks to invalidate other mappings of the same
+ /// file (so that they can be updated with the fresh values just
+ /// written).
+ const INVALIDATE = bitcast!(c::MS_INVALIDATE);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `MLOCK_*` flags for use with [`mlock_with`].
+ ///
+ /// [`mlock_with`]: crate::mm::mlock_with
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MlockFlags: u32 {
+ /// `MLOCK_ONFAULT`
+ const ONFAULT = bitcast!(c::MLOCK_ONFAULT);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `POSIX_MADV_*` constants for use with [`madvise`].
+///
+/// [`madvise`]: crate::mm::madvise
+#[cfg(not(target_os = "redox"))]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+#[non_exhaustive]
+pub enum Advice {
+ /// `POSIX_MADV_NORMAL`
+ #[cfg(not(any(target_os = "android", target_os = "haiku")))]
+ Normal = bitcast!(c::POSIX_MADV_NORMAL),
+
+ /// `POSIX_MADV_NORMAL`
+ #[cfg(any(target_os = "android", target_os = "haiku"))]
+ Normal = bitcast!(c::MADV_NORMAL),
+
+ /// `POSIX_MADV_SEQUENTIAL`
+ #[cfg(not(any(target_os = "android", target_os = "haiku")))]
+ Sequential = bitcast!(c::POSIX_MADV_SEQUENTIAL),
+
+ /// `POSIX_MADV_SEQUENTIAL`
+ #[cfg(any(target_os = "android", target_os = "haiku"))]
+ Sequential = bitcast!(c::MADV_SEQUENTIAL),
+
+ /// `POSIX_MADV_RANDOM`
+ #[cfg(not(any(target_os = "android", target_os = "haiku")))]
+ Random = bitcast!(c::POSIX_MADV_RANDOM),
+
+ /// `POSIX_MADV_RANDOM`
+ #[cfg(any(target_os = "android", target_os = "haiku"))]
+ Random = bitcast!(c::MADV_RANDOM),
+
+ /// `POSIX_MADV_WILLNEED`
+ #[cfg(not(any(target_os = "android", target_os = "haiku")))]
+ WillNeed = bitcast!(c::POSIX_MADV_WILLNEED),
+
+ /// `POSIX_MADV_WILLNEED`
+ #[cfg(any(target_os = "android", target_os = "haiku"))]
+ WillNeed = bitcast!(c::MADV_WILLNEED),
+
+ /// `POSIX_MADV_DONTNEED`
+ #[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "haiku")))]
+ DontNeed = bitcast!(c::POSIX_MADV_DONTNEED),
+
+ /// `POSIX_MADV_DONTNEED`
+ #[cfg(any(target_os = "android", target_os = "haiku"))]
+ DontNeed = bitcast!(i32::MAX - 1),
+
+ /// `MADV_DONTNEED`
+ // `MADV_DONTNEED` has the same value as `POSIX_MADV_DONTNEED`. We don't
+ // have a separate `posix_madvise` from `madvise`, so we expose a special
+ // value which we special-case.
+ #[cfg(target_os = "linux")]
+ LinuxDontNeed = bitcast!(i32::MAX),
+
+ /// `MADV_DONTNEED`
+ #[cfg(target_os = "android")]
+ LinuxDontNeed = bitcast!(c::MADV_DONTNEED),
+ /// `MADV_FREE`
+ #[cfg(linux_kernel)]
+ LinuxFree = bitcast!(c::MADV_FREE),
+ /// `MADV_REMOVE`
+ #[cfg(linux_kernel)]
+ LinuxRemove = bitcast!(c::MADV_REMOVE),
+ /// `MADV_DONTFORK`
+ #[cfg(linux_kernel)]
+ LinuxDontFork = bitcast!(c::MADV_DONTFORK),
+ /// `MADV_DOFORK`
+ #[cfg(linux_kernel)]
+ LinuxDoFork = bitcast!(c::MADV_DOFORK),
+ /// `MADV_HWPOISON`
+ #[cfg(linux_kernel)]
+ LinuxHwPoison = bitcast!(c::MADV_HWPOISON),
+ /// `MADV_SOFT_OFFLINE`
+ #[cfg(all(
+ linux_kernel,
+ not(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ ))
+ ))]
+ LinuxSoftOffline = bitcast!(c::MADV_SOFT_OFFLINE),
+ /// `MADV_MERGEABLE`
+ #[cfg(linux_kernel)]
+ LinuxMergeable = bitcast!(c::MADV_MERGEABLE),
+ /// `MADV_UNMERGEABLE`
+ #[cfg(linux_kernel)]
+ LinuxUnmergeable = bitcast!(c::MADV_UNMERGEABLE),
+ /// `MADV_HUGEPAGE`
+ #[cfg(linux_kernel)]
+ LinuxHugepage = bitcast!(c::MADV_HUGEPAGE),
+ /// `MADV_NOHUGEPAGE`
+ #[cfg(linux_kernel)]
+ LinuxNoHugepage = bitcast!(c::MADV_NOHUGEPAGE),
+ /// `MADV_DONTDUMP` (since Linux 3.4)
+ #[cfg(linux_kernel)]
+ LinuxDontDump = bitcast!(c::MADV_DONTDUMP),
+ /// `MADV_DODUMP` (since Linux 3.4)
+ #[cfg(linux_kernel)]
+ LinuxDoDump = bitcast!(c::MADV_DODUMP),
+ /// `MADV_WIPEONFORK` (since Linux 4.14)
+ #[cfg(linux_kernel)]
+ LinuxWipeOnFork = bitcast!(c::MADV_WIPEONFORK),
+ /// `MADV_KEEPONFORK` (since Linux 4.14)
+ #[cfg(linux_kernel)]
+ LinuxKeepOnFork = bitcast!(c::MADV_KEEPONFORK),
+ /// `MADV_COLD` (since Linux 5.4)
+ #[cfg(linux_kernel)]
+ LinuxCold = bitcast!(c::MADV_COLD),
+ /// `MADV_PAGEOUT` (since Linux 5.4)
+ #[cfg(linux_kernel)]
+ LinuxPageOut = bitcast!(c::MADV_PAGEOUT),
+ /// `MADV_POPULATE_READ` (since Linux 5.14)
+ #[cfg(linux_kernel)]
+ LinuxPopulateRead = bitcast!(c::MADV_POPULATE_READ),
+ /// `MADV_POPULATE_WRITE` (since Linux 5.14)
+ #[cfg(linux_kernel)]
+ LinuxPopulateWrite = bitcast!(c::MADV_POPULATE_WRITE),
+ /// `MADV_DONTNEED_LOCKED` (since Linux 5.18)
+ #[cfg(linux_kernel)]
+ LinuxDontneedLocked = bitcast!(c::MADV_DONTNEED_LOCKED),
+}
+
+#[cfg(target_os = "emscripten")]
+#[allow(non_upper_case_globals)]
+impl Advice {
+ /// `POSIX_MADV_DONTNEED`
+ pub const DontNeed: Self = Self::Normal;
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `O_*` flags for use with [`userfaultfd`].
+ ///
+ /// [`userfaultfd`]: crate::mm::userfaultfd
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UserfaultfdFlags: u32 {
+ /// `O_CLOEXEC`
+ const CLOEXEC = bitcast!(c::O_CLOEXEC);
+ /// `O_NONBLOCK`
+ const NONBLOCK = bitcast!(c::O_NONBLOCK);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+bitflags! {
+ /// `MCL_*` flags for use with [`mlockall`].
+ ///
+ /// [`mlockall`]: crate::mm::mlockall
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MlockAllFlags: u32 {
+ /// Used together with `MCL_CURRENT`, `MCL_FUTURE`, or both. Mark all
+ /// current (with `MCL_CURRENT`) or future (with `MCL_FUTURE`) mappings
+ /// to lock pages when they are faulted in. When used with
+ /// `MCL_CURRENT`, all present pages are locked, but `mlockall` will
+ /// not fault in non-present pages. When used with `MCL_FUTURE`, all
+ /// future mappings will be marked to lock pages when they are faulted
+ /// in, but they will not be populated by the lock when the mapping is
+ /// created. `MCL_ONFAULT` must be used with either `MCL_CURRENT` or
+ /// `MCL_FUTURE` or both.
+ #[cfg(linux_kernel)]
+ const ONFAULT = bitcast!(libc::MCL_ONFAULT);
+ /// Lock all pages which will become mapped into the address space of
+ /// the process in the future. These could be, for instance, new pages
+ /// required by a growing heap and stack as well as new memory-mapped
+ /// files or shared memory regions.
+ const FUTURE = bitcast!(libc::MCL_FUTURE);
+ /// Lock all pages which are currently mapped into the address space of
+ /// the process.
+ const CURRENT = bitcast!(libc::MCL_CURRENT);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/libc/mod.rs b/src/backend/libc/mod.rs
new file mode 100644
index 0000000..9ecb09f
--- /dev/null
+++ b/src/backend/libc/mod.rs
@@ -0,0 +1,215 @@
+//! The libc backend.
+//!
+//! On most platforms, this uses the `libc` crate to make system calls. On
+//! Windows, this uses the Winsock API in `windows-sys`, which can be adapted
+//! to have a very `libc`-like interface.
+
+// Every FFI call requires an unsafe block, and there are a lot of FFI
+// calls. For now, set this to allow for the libc backend.
+#![allow(clippy::undocumented_unsafe_blocks)]
+// Lots of libc types vary between platforms, so we often need a `.into()` on
+// one platform where it's redundant on another.
+#![allow(clippy::useless_conversion)]
+
+mod conv;
+
+#[cfg(windows)]
+pub(crate) mod fd {
+ pub use crate::maybe_polyfill::os::windows::io::{
+ AsRawSocket, AsSocket, BorrowedSocket as BorrowedFd, FromRawSocket, IntoRawSocket,
+ OwnedSocket as OwnedFd, RawSocket as RawFd,
+ };
+ pub(crate) use windows_sys::Win32::Networking::WinSock::SOCKET as LibcFd;
+
+ /// A version of [`AsRawFd`] for use with Winsock API.
+ ///
+ /// [`AsRawFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.AsRawFd.html
+ pub trait AsRawFd {
+ /// A version of [`as_raw_fd`] for use with Winsock API.
+ ///
+ /// [`as_raw_fd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.html#tymethod.as_raw_fd
+ fn as_raw_fd(&self) -> RawFd;
+ }
+ impl<T: AsRawSocket> AsRawFd for T {
+ #[inline]
+ fn as_raw_fd(&self) -> RawFd {
+ self.as_raw_socket()
+ }
+ }
+
+ /// A version of [`IntoRawFd`] for use with Winsock API.
+ ///
+ /// [`IntoRawFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.IntoRawFd.html
+ pub trait IntoRawFd {
+ /// A version of [`into_raw_fd`] for use with Winsock API.
+ ///
+ /// [`into_raw_fd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.html#tymethod.into_raw_fd
+ fn into_raw_fd(self) -> RawFd;
+ }
+ impl<T: IntoRawSocket> IntoRawFd for T {
+ #[inline]
+ fn into_raw_fd(self) -> RawFd {
+ self.into_raw_socket()
+ }
+ }
+
+ /// A version of [`FromRawFd`] for use with Winsock API.
+ ///
+ /// [`FromRawFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.html
+ pub trait FromRawFd {
+ /// A version of [`from_raw_fd`] for use with Winsock API.
+ ///
+ /// # Safety
+ ///
+ /// See the [safety requirements] for [`from_raw_fd`].
+ ///
+ /// [`from_raw_fd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.html#tymethod.from_raw_fd
+ /// [safety requirements]: https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.html#safety
+ unsafe fn from_raw_fd(raw_fd: RawFd) -> Self;
+ }
+ impl<T: FromRawSocket> FromRawFd for T {
+ #[inline]
+ unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+ Self::from_raw_socket(raw_fd)
+ }
+ }
+
+ /// A version of [`AsFd`] for use with Winsock API.
+ ///
+ /// [`AsFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.AsFd.html
+ pub trait AsFd {
+ /// An `as_fd` function for Winsock, where a `Fd` is a `Socket`.
+ fn as_fd(&self) -> BorrowedFd;
+ }
+ impl<T: AsSocket> AsFd for T {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd {
+ self.as_socket()
+ }
+ }
+}
+#[cfg(not(windows))]
+pub(crate) mod fd {
+ pub use crate::maybe_polyfill::os::fd::{
+ AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd,
+ };
+ #[allow(unused_imports)]
+ pub(crate) use RawFd as LibcFd;
+}
+
+// On Windows we emulate selected libc-compatible interfaces. On non-Windows,
+// we just use libc here, since this is the libc backend.
+#[cfg_attr(windows, path = "winsock_c.rs")]
+pub(crate) mod c;
+
+#[cfg(feature = "event")]
+pub(crate) mod event;
+#[cfg(not(windows))]
+#[cfg(feature = "fs")]
+pub(crate) mod fs;
+pub(crate) mod io;
+#[cfg(linux_kernel)]
+#[cfg(feature = "io_uring")]
+pub(crate) mod io_uring;
+#[cfg(not(any(windows, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[cfg(feature = "mm")]
+pub(crate) mod mm;
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) mod mount;
+#[cfg(linux_kernel)]
+#[cfg(all(feature = "fs", not(feature = "mount")))]
+pub(crate) mod mount; // for deprecated mount functions in "fs"
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[cfg(feature = "net")]
+pub(crate) mod net;
+#[cfg(not(any(windows, target_os = "espidf")))]
+#[cfg(any(
+ feature = "param",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+))]
+pub(crate) mod param;
+#[cfg(not(windows))]
+#[cfg(feature = "pipe")]
+pub(crate) mod pipe;
+#[cfg(not(windows))]
+#[cfg(feature = "process")]
+pub(crate) mod process;
+#[cfg(not(windows))]
+#[cfg(not(target_os = "wasi"))]
+#[cfg(feature = "pty")]
+pub(crate) mod pty;
+#[cfg(not(windows))]
+#[cfg(feature = "rand")]
+pub(crate) mod rand;
+#[cfg(not(windows))]
+#[cfg(not(target_os = "wasi"))]
+#[cfg(feature = "system")]
+pub(crate) mod system;
+#[cfg(not(any(windows, target_os = "vita")))]
+#[cfg(feature = "termios")]
+pub(crate) mod termios;
+#[cfg(not(windows))]
+#[cfg(feature = "thread")]
+pub(crate) mod thread;
+#[cfg(not(any(windows, target_os = "espidf")))]
+#[cfg(feature = "time")]
+pub(crate) mod time;
+
+/// If the host libc is glibc, return `true` if it is less than version 2.25.
+///
+/// To restate and clarify, this function returning true does not mean the libc
+/// is glibc just that if it is glibc, it is less than version 2.25.
+///
+/// For now, this function is only available on Linux, but if it ends up being
+/// used beyond that, this could be changed to e.g. `#[cfg(unix)]`.
+#[cfg(all(unix, target_env = "gnu"))]
+pub(crate) fn if_glibc_is_less_than_2_25() -> bool {
+ // This is also defined inside `weak_or_syscall!` in
+ // backend/libc/rand/syscalls.rs, but it's not convenient to re-export the
+ // weak symbol from that macro, so we duplicate it at a small cost here.
+ weak! { fn getrandom(*mut c::c_void, c::size_t, c::c_uint) -> c::ssize_t }
+
+ // glibc 2.25 has `getrandom`, which is how we satisfy the API contract of
+ // this function. But, there are likely other libc versions which have it.
+ getrandom.get().is_none()
+}
+
+// Private modules used by multiple public modules.
+#[cfg(any(feature = "procfs", feature = "process", feature = "runtime"))]
+#[cfg(not(any(windows, target_os = "wasi")))]
+pub(crate) mod pid;
+#[cfg(any(feature = "process", feature = "thread"))]
+#[cfg(linux_kernel)]
+pub(crate) mod prctl;
+#[cfg(not(any(
+ windows,
+ target_os = "android",
+ target_os = "espidf",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[cfg(feature = "shm")]
+pub(crate) mod shm;
+#[cfg(any(feature = "fs", feature = "thread", feature = "process"))]
+#[cfg(not(any(windows, target_os = "wasi")))]
+pub(crate) mod ugid;
+
+#[cfg(bsd)]
+const MAX_IOV: usize = c::IOV_MAX as usize;
+
+#[cfg(any(linux_kernel, target_os = "emscripten", target_os = "nto"))]
+const MAX_IOV: usize = c::UIO_MAXIOV as usize;
+
+#[cfg(not(any(
+ bsd,
+ linux_kernel,
+ windows,
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "horizon",
+)))]
+const MAX_IOV: usize = 16; // The minimum value required by POSIX.
diff --git a/src/backend/libc/mount/mod.rs b/src/backend/libc/mount/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/libc/mount/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/mount/syscalls.rs b/src/backend/libc/mount/syscalls.rs
new file mode 100644
index 0000000..7245114
--- /dev/null
+++ b/src/backend/libc/mount/syscalls.rs
@@ -0,0 +1,272 @@
+use crate::backend::c;
+use crate::backend::conv::ret;
+#[cfg(feature = "mount")]
+use crate::backend::conv::{borrowed_fd, c_str, ret_owned_fd};
+#[cfg(feature = "mount")]
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::ffi::CStr;
+use crate::io;
+use core::ptr::null;
+
+#[cfg(linux_kernel)]
+pub(crate) fn mount(
+ source: Option<&CStr>,
+ target: &CStr,
+ file_system_type: Option<&CStr>,
+ flags: super::types::MountFlagsArg,
+ data: Option<&CStr>,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::mount(
+ source.map_or_else(null, CStr::as_ptr),
+ target.as_ptr(),
+ file_system_type.map_or_else(null, CStr::as_ptr),
+ flags.0,
+ data.map_or_else(null, CStr::as_ptr).cast(),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn unmount(target: &CStr, flags: super::types::UnmountFlags) -> io::Result<()> {
+ unsafe { ret(c::umount2(target.as_ptr(), bitflags_bits!(flags))) }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsopen(fs_name: &CStr, flags: super::types::FsOpenFlags) -> io::Result<OwnedFd> {
+ syscall! {
+ fn fsopen(
+ fs_name: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_fsopen -> c::c_int
+ }
+ unsafe { ret_owned_fd(fsopen(c_str(fs_name), flags.bits())) }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsmount(
+ fs_fd: BorrowedFd<'_>,
+ flags: super::types::FsMountFlags,
+ attr_flags: super::types::MountAttrFlags,
+) -> io::Result<OwnedFd> {
+ syscall! {
+ fn fsmount(
+ fs_fd: c::c_int,
+ flags: c::c_uint,
+ attr_flags: c::c_uint
+ ) via SYS_fsmount -> c::c_int
+ }
+ unsafe { ret_owned_fd(fsmount(borrowed_fd(fs_fd), flags.bits(), attr_flags.bits())) }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn move_mount(
+ from_dfd: BorrowedFd<'_>,
+ from_pathname: &CStr,
+ to_dfd: BorrowedFd<'_>,
+ to_pathname: &CStr,
+ flags: super::types::MoveMountFlags,
+) -> io::Result<()> {
+ syscall! {
+ fn move_mount(
+ from_dfd: c::c_int,
+ from_pathname: *const c::c_char,
+ to_dfd: c::c_int,
+ to_pathname: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_move_mount -> c::c_int
+ }
+ unsafe {
+ ret(move_mount(
+ borrowed_fd(from_dfd),
+ c_str(from_pathname),
+ borrowed_fd(to_dfd),
+ c_str(to_pathname),
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn open_tree(
+ dfd: BorrowedFd<'_>,
+ filename: &CStr,
+ flags: super::types::OpenTreeFlags,
+) -> io::Result<OwnedFd> {
+ syscall! {
+ fn open_tree(
+ dfd: c::c_int,
+ filename: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_open_tree -> c::c_int
+ }
+
+ unsafe { ret_owned_fd(open_tree(borrowed_fd(dfd), c_str(filename), flags.bits())) }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fspick(
+ dfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: super::types::FsPickFlags,
+) -> io::Result<OwnedFd> {
+ syscall! {
+ fn fspick(
+ dfd: c::c_int,
+ path: *const c::c_char,
+ flags: c::c_uint
+ ) via SYS_fspick -> c::c_int
+ }
+
+ unsafe { ret_owned_fd(fspick(borrowed_fd(dfd), c_str(path), flags.bits())) }
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+syscall! {
+ fn fsconfig(
+ fs_fd: c::c_int,
+ cmd: c::c_uint,
+ key: *const c::c_char,
+ val: *const c::c_char,
+ aux: c::c_int
+ ) via SYS_fsconfig -> c::c_int
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_set_flag(fs_fd: BorrowedFd<'_>, key: &CStr) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::SetFlag as _,
+ c_str(key),
+ null(),
+ 0,
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_set_string(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ value: &CStr,
+) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::SetString as _,
+ c_str(key),
+ c_str(value),
+ 0,
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_set_binary(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ value: &[u8],
+) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::SetBinary as _,
+ c_str(key),
+ value.as_ptr().cast(),
+ value.len().try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_set_fd(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::SetFd as _,
+ c_str(key),
+ null(),
+ borrowed_fd(fd),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_set_path(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ path: &CStr,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::SetPath as _,
+ c_str(key),
+ c_str(path),
+ borrowed_fd(fd),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_set_path_empty(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::SetPathEmpty as _,
+ c_str(key),
+ c_str(cstr!("")),
+ borrowed_fd(fd),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_create(fs_fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::Create as _,
+ null(),
+ null(),
+ 0,
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+pub(crate) fn fsconfig_reconfigure(fs_fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe {
+ ret(fsconfig(
+ borrowed_fd(fs_fd),
+ super::types::FsConfigCmd::Reconfigure as _,
+ null(),
+ null(),
+ 0,
+ ))
+ }
+}
diff --git a/src/backend/libc/mount/types.rs b/src/backend/libc/mount/types.rs
new file mode 100644
index 0000000..1023686
--- /dev/null
+++ b/src/backend/libc/mount/types.rs
@@ -0,0 +1,340 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `MS_*` constants for use with [`mount`].
+ ///
+ /// [`mount`]: crate::mount::mount
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MountFlags: c::c_ulong {
+ /// `MS_BIND`
+ const BIND = c::MS_BIND;
+
+ /// `MS_DIRSYNC`
+ const DIRSYNC = c::MS_DIRSYNC;
+
+ /// `MS_LAZYTIME`
+ const LAZYTIME = c::MS_LAZYTIME;
+
+ /// `MS_MANDLOCK`
+ #[doc(alias = "MANDLOCK")]
+ const PERMIT_MANDATORY_FILE_LOCKING = c::MS_MANDLOCK;
+
+ /// `MS_NOATIME`
+ const NOATIME = c::MS_NOATIME;
+
+ /// `MS_NODEV`
+ const NODEV = c::MS_NODEV;
+
+ /// `MS_NODIRATIME`
+ const NODIRATIME = c::MS_NODIRATIME;
+
+ /// `MS_NOEXEC`
+ const NOEXEC = c::MS_NOEXEC;
+
+ /// `MS_NOSUID`
+ const NOSUID = c::MS_NOSUID;
+
+ /// `MS_RDONLY`
+ const RDONLY = c::MS_RDONLY;
+
+ /// `MS_REC`
+ const REC = c::MS_REC;
+
+ /// `MS_RELATIME`
+ const RELATIME = c::MS_RELATIME;
+
+ /// `MS_SILENT`
+ const SILENT = c::MS_SILENT;
+
+ /// `MS_STRICTATIME`
+ const STRICTATIME = c::MS_STRICTATIME;
+
+ /// `MS_SYNCHRONOUS`
+ const SYNCHRONOUS = c::MS_SYNCHRONOUS;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `MNT_*` constants for use with [`unmount`].
+ ///
+ /// [`unmount`]: crate::mount::unmount
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UnmountFlags: u32 {
+ /// `MNT_FORCE`
+ const FORCE = bitcast!(c::MNT_FORCE);
+ /// `MNT_DETACH`
+ const DETACH = bitcast!(c::MNT_DETACH);
+ /// `MNT_EXPIRE`
+ const EXPIRE = bitcast!(c::MNT_EXPIRE);
+ /// `UMOUNT_NOFOLLOW`
+ const NOFOLLOW = bitcast!(c::UMOUNT_NOFOLLOW);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `FSOPEN_*` constants for use with [`fsopen`].
+ ///
+ /// [`fsopen`]: crate::mount::fsopen
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FsOpenFlags: c::c_uint {
+ /// `FSOPEN_CLOEXEC`
+ const FSOPEN_CLOEXEC = 0x0000_0001;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `FSMOUNT_*` constants for use with [`fsmount`].
+ ///
+ /// [`fsmount`]: crate::mount::fsmount
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FsMountFlags: c::c_uint {
+ /// `FSMOUNT_CLOEXEC`
+ const FSMOUNT_CLOEXEC = 0x0000_0001;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `FSCONFIG_*` constants for use with the `fsconfig` syscall.
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub(crate) enum FsConfigCmd {
+ /// `FSCONFIG_SET_FLAG`
+ SetFlag = 0,
+
+ /// `FSCONFIG_SET_STRING`
+ SetString = 1,
+
+ /// `FSCONFIG_SET_BINARY`
+ SetBinary = 2,
+
+ /// `FSCONFIG_SET_PATH`
+ SetPath = 3,
+
+ /// `FSCONFIG_SET_PATH_EMPTY`
+ SetPathEmpty = 4,
+
+ /// `FSCONFIG_SET_FD`
+ SetFd = 5,
+
+ /// `FSCONFIG_CMD_CREATE`
+ Create = 6,
+
+ /// `FSCONFIG_CMD_RECONFIGURE`
+ Reconfigure = 7,
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `MOUNT_ATTR_*` constants for use with [`fsmount`].
+ ///
+ /// [`fsmount`]: crate::mount::fsmount
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MountAttrFlags: c::c_uint {
+ /// `MOUNT_ATTR_RDONLY`
+ const MOUNT_ATTR_RDONLY = 0x0000_0001;
+
+ /// `MOUNT_ATTR_NOSUID`
+ const MOUNT_ATTR_NOSUID = 0x0000_0002;
+
+ /// `MOUNT_ATTR_NODEV`
+ const MOUNT_ATTR_NODEV = 0x0000_0004;
+
+ /// `MOUNT_ATTR_NOEXEC`
+ const MOUNT_ATTR_NOEXEC = 0x0000_0008;
+
+ /// `MOUNT_ATTR__ATIME`
+ const MOUNT_ATTR__ATIME = 0x0000_0070;
+
+ /// `MOUNT_ATTR_RELATIME`
+ const MOUNT_ATTR_RELATIME = 0x0000_0000;
+
+ /// `MOUNT_ATTR_NOATIME`
+ const MOUNT_ATTR_NOATIME = 0x0000_0010;
+
+ /// `MOUNT_ATTR_STRICTATIME`
+ const MOUNT_ATTR_STRICTATIME = 0x0000_0020;
+
+ /// `MOUNT_ATTR_NODIRATIME`
+ const MOUNT_ATTR_NODIRATIME = 0x0000_0080;
+
+ /// `MOUNT_ATTR_NOUSER`
+ const MOUNT_ATTR_IDMAP = 0x0010_0000;
+
+ /// `MOUNT_ATTR__ATIME_FLAGS`
+ const MOUNT_ATTR_NOSYMFOLLOW = 0x0020_0000;
+
+ /// `MOUNT_ATTR__ATIME_FLAGS`
+ const MOUNT_ATTR_SIZE_VER0 = 32;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `MOVE_MOUNT_*` constants for use with [`move_mount`].
+ ///
+ /// [`move_mount`]: crate::mount::move_mount
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MoveMountFlags: c::c_uint {
+ /// `MOVE_MOUNT_F_EMPTY_PATH`
+ const MOVE_MOUNT_F_SYMLINKS = 0x0000_0001;
+
+ /// `MOVE_MOUNT_F_AUTOMOUNTS`
+ const MOVE_MOUNT_F_AUTOMOUNTS = 0x0000_0002;
+
+ /// `MOVE_MOUNT_F_EMPTY_PATH`
+ const MOVE_MOUNT_F_EMPTY_PATH = 0x0000_0004;
+
+ /// `MOVE_MOUNT_T_SYMLINKS`
+ const MOVE_MOUNT_T_SYMLINKS = 0x0000_0010;
+
+ /// `MOVE_MOUNT_T_AUTOMOUNTS`
+ const MOVE_MOUNT_T_AUTOMOUNTS = 0x0000_0020;
+
+ /// `MOVE_MOUNT_T_EMPTY_PATH`
+ const MOVE_MOUNT_T_EMPTY_PATH = 0x0000_0040;
+
+ /// `MOVE_MOUNT__MASK`
+ const MOVE_MOUNT_SET_GROUP = 0x0000_0100;
+
+ // TODO: add when Linux 6.5 is released
+ // /// `MOVE_MOUNT_BENEATH`
+ // const MOVE_MOUNT_BENEATH = 0x0000_0200;
+
+ /// `MOVE_MOUNT__MASK`
+ const MOVE_MOUNT__MASK = 0x0000_0377;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `OPENTREE_*` constants for use with [`open_tree`].
+ ///
+ /// [`open_tree`]: crate::mount::open_tree
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct OpenTreeFlags: c::c_uint {
+ /// `OPENTREE_CLONE`
+ const OPEN_TREE_CLONE = 1;
+
+ /// `OPENTREE_CLOEXEC`
+ const OPEN_TREE_CLOEXEC = c::O_CLOEXEC as c::c_uint;
+
+ /// `AT_EMPTY_PATH`
+ const AT_EMPTY_PATH = c::AT_EMPTY_PATH as c::c_uint;
+
+ /// `AT_NO_AUTOMOUNT`
+ const AT_NO_AUTOMOUNT = c::AT_NO_AUTOMOUNT as c::c_uint;
+
+ /// `AT_RECURSIVE`
+ const AT_RECURSIVE = c::AT_RECURSIVE as c::c_uint;
+
+ /// `AT_SYMLINK_NOFOLLOW`
+ const AT_SYMLINK_NOFOLLOW = c::AT_SYMLINK_NOFOLLOW as c::c_uint;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `FSPICK_*` constants for use with [`fspick`].
+ ///
+ /// [`fspick`]: crate::mount::fspick
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FsPickFlags: c::c_uint {
+ /// `FSPICK_CLOEXEC`
+ const FSPICK_CLOEXEC = 0x0000_0001;
+
+ /// `FSPICK_SYMLINK_NOFOLLOW`
+ const FSPICK_SYMLINK_NOFOLLOW = 0x0000_0002;
+
+ /// `FSPICK_NO_AUTOMOUNT`
+ const FSPICK_NO_AUTOMOUNT = 0x0000_0004;
+
+ /// `FSPICK_EMPTY_PATH`
+ const FSPICK_EMPTY_PATH = 0x0000_0008;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `MS_*` constants for use with [`mount_change`].
+ ///
+ /// [`mount_change`]: crate::mount::mount_change
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MountPropagationFlags: c::c_ulong {
+ /// `MS_SILENT`
+ const SILENT = c::MS_SILENT;
+ /// `MS_SHARED`
+ const SHARED = c::MS_SHARED;
+ /// `MS_PRIVATE`
+ const PRIVATE = c::MS_PRIVATE;
+ /// `MS_SLAVE`
+ const SLAVE = c::MS_SLAVE;
+ /// `MS_UNBINDABLE`
+ const UNBINDABLE = c::MS_UNBINDABLE;
+ /// `MS_REC`
+ const REC = c::MS_REC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub(crate) struct InternalMountFlags: c::c_ulong {
+ const REMOUNT = c::MS_REMOUNT;
+ const MOVE = c::MS_MOVE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) struct MountFlagsArg(pub(crate) c::c_ulong);
diff --git a/src/backend/libc/net/addr.rs b/src/backend/libc/net/addr.rs
new file mode 100644
index 0000000..719a549
--- /dev/null
+++ b/src/backend/libc/net/addr.rs
@@ -0,0 +1,245 @@
+//! Socket address utilities.
+
+use crate::backend::c;
+#[cfg(unix)]
+use {
+ crate::ffi::CStr,
+ crate::io,
+ crate::path,
+ core::cmp::Ordering,
+ core::fmt,
+ core::hash::{Hash, Hasher},
+ core::slice,
+};
+
+/// `struct sockaddr_un`
+#[cfg(unix)]
+#[derive(Clone)]
+#[doc(alias = "sockaddr_un")]
+pub struct SocketAddrUnix {
+ pub(crate) unix: c::sockaddr_un,
+ #[cfg(not(any(bsd, target_os = "haiku")))]
+ len: c::socklen_t,
+}
+
+#[cfg(unix)]
+impl SocketAddrUnix {
+ /// Construct a new Unix-domain address from a filesystem path.
+ #[inline]
+ pub fn new<P: path::Arg>(path: P) -> io::Result<Self> {
+ path.into_with_c_str(Self::_new)
+ }
+
+ #[inline]
+ fn _new(path: &CStr) -> io::Result<Self> {
+ let mut unix = Self::init();
+ let bytes = path.to_bytes_with_nul();
+ if bytes.len() > unix.sun_path.len() {
+ return Err(io::Errno::NAMETOOLONG);
+ }
+ for (i, b) in bytes.iter().enumerate() {
+ unix.sun_path[i] = *b as c::c_char;
+ }
+
+ #[cfg(any(bsd, target_os = "haiku"))]
+ {
+ unix.sun_len = (offsetof_sun_path() + bytes.len()).try_into().unwrap();
+ }
+
+ Ok(Self {
+ unix,
+ #[cfg(not(any(bsd, target_os = "haiku")))]
+ len: (offsetof_sun_path() + bytes.len()).try_into().unwrap(),
+ })
+ }
+
+ /// Construct a new abstract Unix-domain address from a byte slice.
+ #[cfg(linux_kernel)]
+ #[inline]
+ pub fn new_abstract_name(name: &[u8]) -> io::Result<Self> {
+ let mut unix = Self::init();
+ if 1 + name.len() > unix.sun_path.len() {
+ return Err(io::Errno::NAMETOOLONG);
+ }
+ unix.sun_path[0] = 0;
+ for (i, b) in name.iter().enumerate() {
+ unix.sun_path[1 + i] = *b as c::c_char;
+ }
+ let len = offsetof_sun_path() + 1 + name.len();
+ let len = len.try_into().unwrap();
+ Ok(Self {
+ unix,
+ #[cfg(not(any(bsd, target_os = "haiku")))]
+ len,
+ })
+ }
+
+ fn init() -> c::sockaddr_un {
+ c::sockaddr_un {
+ #[cfg(any(bsd, target_os = "aix", target_os = "haiku", target_os = "nto"))]
+ sun_len: 0,
+ #[cfg(target_os = "vita")]
+ ss_len: 0,
+ sun_family: c::AF_UNIX as _,
+ #[cfg(any(bsd, target_os = "nto"))]
+ sun_path: [0; 104],
+ #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "nto")))]
+ sun_path: [0; 108],
+ #[cfg(target_os = "haiku")]
+ sun_path: [0; 126],
+ #[cfg(target_os = "aix")]
+ sun_path: [0; 1023],
+ }
+ }
+
+ /// For a filesystem path address, return the path.
+ #[inline]
+ pub fn path(&self) -> Option<&CStr> {
+ let len = self.len();
+ if len != 0 && self.unix.sun_path[0] != 0 {
+ let end = len as usize - offsetof_sun_path();
+ let bytes = &self.unix.sun_path[..end];
+ // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`.
+ // And `from_bytes_with_nul_unchecked` since the string is
+ // NUL-terminated.
+ unsafe {
+ Some(CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
+ bytes.as_ptr().cast(),
+ bytes.len(),
+ )))
+ }
+ } else {
+ None
+ }
+ }
+
+ /// For an abstract address, return the identifier.
+ #[cfg(linux_kernel)]
+ #[inline]
+ pub fn abstract_name(&self) -> Option<&[u8]> {
+ let len = self.len();
+ if len != 0 && self.unix.sun_path[0] == 0 {
+ let end = len as usize - offsetof_sun_path();
+ let bytes = &self.unix.sun_path[1..end];
+ // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`.
+ unsafe { Some(slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len())) }
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub(crate) fn addr_len(&self) -> c::socklen_t {
+ #[cfg(not(any(bsd, target_os = "haiku")))]
+ {
+ self.len
+ }
+ #[cfg(any(bsd, target_os = "haiku"))]
+ {
+ c::socklen_t::from(self.unix.sun_len)
+ }
+ }
+
+ #[inline]
+ pub(crate) fn len(&self) -> usize {
+ self.addr_len() as usize
+ }
+}
+
+#[cfg(unix)]
+impl PartialEq for SocketAddrUnix {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ let self_len = self.len() - offsetof_sun_path();
+ let other_len = other.len() - offsetof_sun_path();
+ self.unix.sun_path[..self_len].eq(&other.unix.sun_path[..other_len])
+ }
+}
+
+#[cfg(unix)]
+impl Eq for SocketAddrUnix {}
+
+#[cfg(unix)]
+impl PartialOrd for SocketAddrUnix {
+ #[inline]
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg(unix)]
+impl Ord for SocketAddrUnix {
+ #[inline]
+ fn cmp(&self, other: &Self) -> Ordering {
+ let self_len = self.len() - offsetof_sun_path();
+ let other_len = other.len() - offsetof_sun_path();
+ self.unix.sun_path[..self_len].cmp(&other.unix.sun_path[..other_len])
+ }
+}
+
+#[cfg(unix)]
+impl Hash for SocketAddrUnix {
+ #[inline]
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ let self_len = self.len() - offsetof_sun_path();
+ self.unix.sun_path[..self_len].hash(state)
+ }
+}
+
+#[cfg(unix)]
+impl fmt::Debug for SocketAddrUnix {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(path) = self.path() {
+ path.fmt(fmt)
+ } else {
+ #[cfg(linux_kernel)]
+ if let Some(name) = self.abstract_name() {
+ return name.fmt(fmt);
+ }
+
+ "(unnamed)".fmt(fmt)
+ }
+ }
+}
+
+/// `struct sockaddr_storage` as a raw struct.
+pub type SocketAddrStorage = c::sockaddr_storage;
+
+/// Return the offset of the `sun_path` field of `sockaddr_un`.
+#[cfg(not(windows))]
+#[inline]
+pub(crate) fn offsetof_sun_path() -> usize {
+ let z = c::sockaddr_un {
+ #[cfg(any(bsd, target_os = "aix", target_os = "haiku", target_os = "nto"))]
+ sun_len: 0_u8,
+ #[cfg(target_os = "vita")]
+ ss_len: 0,
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ sun_family: 0_u8,
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ sun_family: 0_u16,
+ #[cfg(any(bsd, target_os = "nto"))]
+ sun_path: [0; 104],
+ #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "nto")))]
+ sun_path: [0; 108],
+ #[cfg(target_os = "haiku")]
+ sun_path: [0; 126],
+ #[cfg(target_os = "aix")]
+ sun_path: [0; 1023],
+ };
+ (crate::utils::as_ptr(&z.sun_path) as usize) - (crate::utils::as_ptr(&z) as usize)
+}
diff --git a/src/backend/libc/net/ext.rs b/src/backend/libc/net/ext.rs
new file mode 100644
index 0000000..2e11c05
--- /dev/null
+++ b/src/backend/libc/net/ext.rs
@@ -0,0 +1,135 @@
+use crate::backend::c;
+
+/// The windows `sockaddr_in6` type is a union with accessor functions which
+/// are not `const fn`. Define our own layout-compatible version so that we
+/// can transmute in and out of it.
+#[cfg(windows)]
+#[repr(C)]
+struct sockaddr_in6 {
+ sin6_family: u16,
+ sin6_port: u16,
+ sin6_flowinfo: u32,
+ sin6_addr: c::in6_addr,
+ sin6_scope_id: u32,
+}
+
+#[cfg(not(windows))]
+#[inline]
+pub(crate) const fn in_addr_s_addr(addr: c::in_addr) -> u32 {
+ addr.s_addr
+}
+
+#[cfg(windows)]
+#[inline]
+pub(crate) const fn in_addr_s_addr(addr: c::in_addr) -> u32 {
+ // This should be `*addr.S_un.S_addr()`, except that isn't a `const fn`.
+ unsafe { core::mem::transmute(addr) }
+}
+
+#[cfg(not(windows))]
+#[inline]
+pub(crate) const fn in_addr_new(s_addr: u32) -> c::in_addr {
+ c::in_addr { s_addr }
+}
+
+#[cfg(windows)]
+#[inline]
+pub(crate) const fn in_addr_new(s_addr: u32) -> c::in_addr {
+ unsafe { core::mem::transmute(s_addr) }
+}
+
+#[cfg(not(windows))]
+#[inline]
+pub(crate) const fn in6_addr_s6_addr(addr: c::in6_addr) -> [u8; 16] {
+ addr.s6_addr
+}
+
+#[cfg(windows)]
+#[inline]
+pub(crate) const fn in6_addr_s6_addr(addr: c::in6_addr) -> [u8; 16] {
+ unsafe { core::mem::transmute(addr) }
+}
+
+#[cfg(not(windows))]
+#[inline]
+pub(crate) const fn in6_addr_new(s6_addr: [u8; 16]) -> c::in6_addr {
+ c::in6_addr { s6_addr }
+}
+
+#[cfg(windows)]
+#[inline]
+pub(crate) const fn in6_addr_new(s6_addr: [u8; 16]) -> c::in6_addr {
+ unsafe { core::mem::transmute(s6_addr) }
+}
+
+#[cfg(not(windows))]
+#[inline]
+pub(crate) const fn sockaddr_in6_sin6_scope_id(addr: &c::sockaddr_in6) -> u32 {
+ addr.sin6_scope_id
+}
+
+#[cfg(windows)]
+#[inline]
+pub(crate) const fn sockaddr_in6_sin6_scope_id(addr: &c::sockaddr_in6) -> u32 {
+ let addr: &sockaddr_in6 = unsafe { core::mem::transmute(addr) };
+ addr.sin6_scope_id
+}
+
+#[cfg(not(windows))]
+#[inline]
+pub(crate) const fn sockaddr_in6_new(
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ sin6_len: u8,
+ sin6_family: c::sa_family_t,
+ sin6_port: u16,
+ sin6_flowinfo: u32,
+ sin6_addr: c::in6_addr,
+ sin6_scope_id: u32,
+) -> c::sockaddr_in6 {
+ c::sockaddr_in6 {
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ sin6_len,
+ sin6_family,
+ sin6_port,
+ sin6_flowinfo,
+ sin6_addr,
+ sin6_scope_id,
+ #[cfg(solarish)]
+ __sin6_src_id: 0,
+ #[cfg(target_os = "vita")]
+ sin6_vport: 0,
+ }
+}
+
+#[cfg(windows)]
+#[inline]
+pub(crate) const fn sockaddr_in6_new(
+ sin6_family: u16,
+ sin6_port: u16,
+ sin6_flowinfo: u32,
+ sin6_addr: c::in6_addr,
+ sin6_scope_id: u32,
+) -> c::sockaddr_in6 {
+ let addr = sockaddr_in6 {
+ sin6_family,
+ sin6_port,
+ sin6_flowinfo,
+ sin6_addr,
+ sin6_scope_id,
+ };
+ unsafe { core::mem::transmute(addr) }
+}
diff --git a/src/backend/libc/net/mod.rs b/src/backend/libc/net/mod.rs
new file mode 100644
index 0000000..4f8e99a
--- /dev/null
+++ b/src/backend/libc/net/mod.rs
@@ -0,0 +1,18 @@
+pub(crate) mod addr;
+pub(crate) mod ext;
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) mod msghdr;
+#[cfg(linux_kernel)]
+pub(crate) mod netdevice;
+pub(crate) mod read_sockaddr;
+pub(crate) mod send_recv;
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) mod sockopt;
+pub(crate) mod syscalls;
+pub(crate) mod write_sockaddr;
diff --git a/src/backend/libc/net/msghdr.rs b/src/backend/libc/net/msghdr.rs
new file mode 100644
index 0000000..d212c65
--- /dev/null
+++ b/src/backend/libc/net/msghdr.rs
@@ -0,0 +1,160 @@
+//! Utilities for dealing with message headers.
+//!
+//! These take closures rather than returning a `c::msghdr` directly because
+//! the message headers may reference stack-local data.
+
+use crate::backend::c;
+use crate::backend::conv::{msg_control_len, msg_iov_len};
+#[cfg(target_os = "linux")]
+use crate::backend::net::write_sockaddr::encode_sockaddr_xdp;
+use crate::backend::net::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};
+
+use crate::io::{self, IoSlice, IoSliceMut};
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+use crate::net::{RecvAncillaryBuffer, SendAncillaryBuffer, SocketAddrV4, SocketAddrV6};
+use crate::utils::as_ptr;
+
+use core::mem::{size_of, zeroed, MaybeUninit};
+
+/// Create a message header intended to receive a datagram.
+pub(crate) fn with_recv_msghdr<R>(
+ name: &mut MaybeUninit<c::sockaddr_storage>,
+ iov: &mut [IoSliceMut<'_>],
+ control: &mut RecvAncillaryBuffer<'_>,
+ f: impl FnOnce(&mut c::msghdr) -> io::Result<R>,
+) -> io::Result<R> {
+ control.clear();
+
+ let namelen = size_of::<c::sockaddr_storage>() as c::socklen_t;
+ let mut msghdr = {
+ let mut h = zero_msghdr();
+ h.msg_name = name.as_mut_ptr().cast();
+ h.msg_namelen = namelen;
+ h.msg_iov = iov.as_mut_ptr().cast();
+ h.msg_iovlen = msg_iov_len(iov.len());
+ h.msg_control = control.as_control_ptr().cast();
+ h.msg_controllen = msg_control_len(control.control_len());
+ h
+ };
+
+ let res = f(&mut msghdr);
+
+ // Reset the control length.
+ if res.is_ok() {
+ unsafe {
+ control.set_control_len(msghdr.msg_controllen.try_into().unwrap_or(usize::MAX));
+ }
+ }
+
+ res
+}
+
+/// Create a message header intended to send without an address.
+pub(crate) fn with_noaddr_msghdr<R>(
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+ f({
+ let mut h = zero_msghdr();
+ h.msg_iov = iov.as_ptr() as _;
+ h.msg_iovlen = msg_iov_len(iov.len());
+ h.msg_control = control.as_control_ptr().cast();
+ h.msg_controllen = msg_control_len(control.control_len());
+ h
+ })
+}
+
+/// Create a message header intended to send with an IPv4 address.
+pub(crate) fn with_v4_msghdr<R>(
+ addr: &SocketAddrV4,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+ let encoded = encode_sockaddr_v4(addr);
+
+ f({
+ let mut h = zero_msghdr();
+ h.msg_name = as_ptr(&encoded) as _;
+ h.msg_namelen = size_of::<SocketAddrV4>() as _;
+ h.msg_iov = iov.as_ptr() as _;
+ h.msg_iovlen = msg_iov_len(iov.len());
+ h.msg_control = control.as_control_ptr().cast();
+ h.msg_controllen = msg_control_len(control.control_len());
+ h
+ })
+}
+
+/// Create a message header intended to send with an IPv6 address.
+pub(crate) fn with_v6_msghdr<R>(
+ addr: &SocketAddrV6,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+ let encoded = encode_sockaddr_v6(addr);
+
+ f({
+ let mut h = zero_msghdr();
+ h.msg_name = as_ptr(&encoded) as _;
+ h.msg_namelen = size_of::<SocketAddrV6>() as _;
+ h.msg_iov = iov.as_ptr() as _;
+ h.msg_iovlen = msg_iov_len(iov.len());
+ h.msg_control = control.as_control_ptr().cast();
+ h.msg_controllen = msg_control_len(control.control_len());
+ h
+ })
+}
+
+/// Create a message header intended to send with a Unix address.
+#[cfg(all(unix, not(target_os = "redox")))]
+pub(crate) fn with_unix_msghdr<R>(
+ addr: &crate::net::SocketAddrUnix,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+ f({
+ let mut h = zero_msghdr();
+ h.msg_name = as_ptr(&addr.unix) as _;
+ h.msg_namelen = addr.addr_len();
+ h.msg_iov = iov.as_ptr() as _;
+ h.msg_iovlen = msg_iov_len(iov.len());
+ h.msg_control = control.as_control_ptr().cast();
+ h.msg_controllen = msg_control_len(control.control_len());
+ h
+ })
+}
+
+/// Create a message header intended to send with an IPv6 address.
+#[cfg(target_os = "linux")]
+pub(crate) fn with_xdp_msghdr<R>(
+ addr: &SocketAddrXdp,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+ let encoded = encode_sockaddr_xdp(addr);
+
+ f({
+ let mut h = zero_msghdr();
+ h.msg_name = as_ptr(&encoded) as _;
+ h.msg_namelen = size_of::<SocketAddrXdp>() as _;
+ h.msg_iov = iov.as_ptr() as _;
+ h.msg_iovlen = msg_iov_len(iov.len());
+ h.msg_control = control.as_control_ptr().cast();
+ h.msg_controllen = msg_control_len(control.control_len());
+ h
+ })
+}
+
+/// Create a zero-initialized message header struct value.
+#[cfg(all(unix, not(target_os = "redox")))]
+pub(crate) fn zero_msghdr() -> c::msghdr {
+ // SAFETY: We can't initialize all the fields by value because on some
+ // platforms the `msghdr` struct in the libc crate contains private padding
+ // fields. But it is still a C type that's meant to be zero-initializable.
+ unsafe { zeroed() }
+}
diff --git a/src/backend/libc/net/netdevice.rs b/src/backend/libc/net/netdevice.rs
new file mode 100644
index 0000000..2fbb55b
--- /dev/null
+++ b/src/backend/libc/net/netdevice.rs
@@ -0,0 +1,52 @@
+#![allow(unsafe_code)]
+
+#[cfg(feature = "alloc")]
+use crate::alloc::string::String;
+use crate::backend::io::syscalls::ioctl;
+use crate::fd::AsFd;
+use crate::io;
+#[cfg(feature = "alloc")]
+use libc::SIOCGIFNAME;
+use libc::{__c_anonymous_ifr_ifru, c_char, ifreq, IFNAMSIZ, SIOCGIFINDEX};
+
+pub(crate) fn name_to_index(fd: impl AsFd, if_name: &str) -> io::Result<u32> {
+ let if_name_bytes = if_name.as_bytes();
+ if if_name_bytes.len() >= IFNAMSIZ as usize {
+ return Err(io::Errno::NODEV);
+ }
+
+ let mut ifreq = ifreq {
+ ifr_name: [0; 16],
+ ifr_ifru: __c_anonymous_ifr_ifru { ifru_ifindex: 0 },
+ };
+
+ let mut if_name_c_char_iter = if_name_bytes.iter().map(|byte| *byte as c_char);
+ ifreq.ifr_name[..if_name_bytes.len()].fill_with(|| if_name_c_char_iter.next().unwrap());
+
+ unsafe { ioctl(fd.as_fd(), SIOCGIFINDEX as _, &mut ifreq as *mut ifreq as _) }?;
+ let index = unsafe { ifreq.ifr_ifru.ifru_ifindex };
+ Ok(index as u32)
+}
+
+#[cfg(feature = "alloc")]
+pub(crate) fn index_to_name(fd: impl AsFd, index: u32) -> io::Result<String> {
+ let mut ifreq = ifreq {
+ ifr_name: [0; 16],
+ ifr_ifru: __c_anonymous_ifr_ifru {
+ ifru_ifindex: index as _,
+ },
+ };
+
+ unsafe { ioctl(fd.as_fd(), SIOCGIFNAME as _, &mut ifreq as *mut ifreq as _) }?;
+
+ if let Some(nul_byte) = ifreq.ifr_name.iter().position(|char| *char == 0) {
+ let name: String = ifreq.ifr_name[..nul_byte]
+ .iter()
+ .map(|v| *v as u8 as char)
+ .collect();
+
+ Ok(name)
+ } else {
+ Err(io::Errno::INVAL)
+ }
+}
diff --git a/src/backend/libc/net/read_sockaddr.rs b/src/backend/libc/net/read_sockaddr.rs
new file mode 100644
index 0000000..08939d4
--- /dev/null
+++ b/src/backend/libc/net/read_sockaddr.rs
@@ -0,0 +1,332 @@
+//! The BSD sockets API requires us to read the `ss_family` field before we can
+//! interpret the rest of a `sockaddr` produced by the kernel.
+
+#[cfg(unix)]
+use super::addr::SocketAddrUnix;
+use super::ext::{in6_addr_s6_addr, in_addr_s_addr, sockaddr_in6_sin6_scope_id};
+use crate::backend::c;
+#[cfg(not(windows))]
+use crate::ffi::CStr;
+use crate::io;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::{SockaddrXdpFlags, SocketAddrXdp};
+use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddrAny, SocketAddrV4, SocketAddrV6};
+use core::mem::size_of;
+
+// This must match the header of `sockaddr`.
+#[repr(C)]
+struct sockaddr_header {
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ sa_len: u8,
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ ss_family: u8,
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ ss_family: u16,
+}
+
+/// Read the `ss_family` field from a socket address returned from the OS.
+///
+/// # Safety
+///
+/// `storage` must point to a valid socket address returned from the OS.
+#[inline]
+unsafe fn read_ss_family(storage: *const c::sockaddr_storage) -> u16 {
+ // Assert that we know the layout of `sockaddr`.
+ let _ = c::sockaddr {
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ sa_len: 0_u8,
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ sa_family: 0_u8,
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ sa_family: 0_u16,
+ #[cfg(not(target_os = "haiku"))]
+ sa_data: [0; 14],
+ #[cfg(target_os = "haiku")]
+ sa_data: [0; 30],
+ };
+
+ (*storage.cast::<sockaddr_header>()).ss_family.into()
+}
+
+/// Set the `ss_family` field of a socket address to `AF_UNSPEC`, so that we
+/// can test for `AF_UNSPEC` to test whether it was stored to.
+pub(crate) unsafe fn initialize_family_to_unspec(storage: *mut c::sockaddr_storage) {
+ (*storage.cast::<sockaddr_header>()).ss_family = c::AF_UNSPEC as _;
+}
+
+/// Read a socket address encoded in a platform-specific format.
+///
+/// # Safety
+///
+/// `storage` must point to valid socket address storage.
+pub(crate) unsafe fn read_sockaddr(
+ storage: *const c::sockaddr_storage,
+ len: usize,
+) -> io::Result<SocketAddrAny> {
+ #[cfg(unix)]
+ let offsetof_sun_path = super::addr::offsetof_sun_path();
+
+ if len < size_of::<c::sa_family_t>() {
+ return Err(io::Errno::INVAL);
+ }
+ match read_ss_family(storage).into() {
+ c::AF_INET => {
+ if len < size_of::<c::sockaddr_in>() {
+ return Err(io::Errno::INVAL);
+ }
+ let decode = &*storage.cast::<c::sockaddr_in>();
+ Ok(SocketAddrAny::V4(SocketAddrV4::new(
+ Ipv4Addr::from(u32::from_be(in_addr_s_addr(decode.sin_addr))),
+ u16::from_be(decode.sin_port),
+ )))
+ }
+ c::AF_INET6 => {
+ if len < size_of::<c::sockaddr_in6>() {
+ return Err(io::Errno::INVAL);
+ }
+ let decode = &*storage.cast::<c::sockaddr_in6>();
+ #[cfg(not(windows))]
+ let s6_addr = decode.sin6_addr.s6_addr;
+ #[cfg(windows)]
+ let s6_addr = decode.sin6_addr.u.Byte;
+ #[cfg(not(windows))]
+ let sin6_scope_id = decode.sin6_scope_id;
+ #[cfg(windows)]
+ let sin6_scope_id = decode.Anonymous.sin6_scope_id;
+ Ok(SocketAddrAny::V6(SocketAddrV6::new(
+ Ipv6Addr::from(s6_addr),
+ u16::from_be(decode.sin6_port),
+ u32::from_be(decode.sin6_flowinfo),
+ sin6_scope_id,
+ )))
+ }
+ #[cfg(unix)]
+ c::AF_UNIX => {
+ if len < offsetof_sun_path {
+ return Err(io::Errno::INVAL);
+ }
+ if len == offsetof_sun_path {
+ SocketAddrUnix::new(&[][..]).map(SocketAddrAny::Unix)
+ } else {
+ let decode = &*storage.cast::<c::sockaddr_un>();
+
+ // On Linux check for Linux's [abstract namespace].
+ //
+ // [abstract namespace]: https://man7.org/linux/man-pages/man7/unix.7.html
+ #[cfg(linux_kernel)]
+ if decode.sun_path[0] == 0 {
+ return SocketAddrUnix::new_abstract_name(core::mem::transmute::<
+ &[c::c_char],
+ &[u8],
+ >(
+ &decode.sun_path[1..len - offsetof_sun_path],
+ ))
+ .map(SocketAddrAny::Unix);
+ }
+
+ // Otherwise we expect a NUL-terminated filesystem path.
+
+ // Trim off unused bytes from the end of `path_bytes`.
+ let path_bytes = if cfg!(any(solarish, target_os = "freebsd")) {
+ // FreeBSD and illumos sometimes set the length to longer
+ // than the length of the NUL-terminated string. Find the
+ // NUL and truncate the string accordingly.
+ &decode.sun_path[..decode
+ .sun_path
+ .iter()
+ .position(|b| *b == 0)
+ .ok_or(io::Errno::INVAL)?]
+ } else {
+ // Otherwise, use the provided length.
+ let provided_len = len - 1 - offsetof_sun_path;
+ if decode.sun_path[provided_len] != 0 {
+ return Err(io::Errno::INVAL);
+ }
+ debug_assert_eq!(
+ CStr::from_ptr(decode.sun_path.as_ptr()).to_bytes().len(),
+ provided_len
+ );
+ &decode.sun_path[..provided_len]
+ };
+
+ SocketAddrUnix::new(core::mem::transmute::<&[c::c_char], &[u8]>(path_bytes))
+ .map(SocketAddrAny::Unix)
+ }
+ }
+ #[cfg(target_os = "linux")]
+ c::AF_XDP => {
+ if len < size_of::<c::sockaddr_xdp>() {
+ return Err(io::Errno::INVAL);
+ }
+ let decode = &*storage.cast::<c::sockaddr_xdp>();
+ Ok(SocketAddrAny::Xdp(SocketAddrXdp::new(
+ SockaddrXdpFlags::from_bits_retain(decode.sxdp_flags),
+ u32::from_be(decode.sxdp_ifindex),
+ u32::from_be(decode.sxdp_queue_id),
+ u32::from_be(decode.sxdp_shared_umem_fd),
+ )))
+ }
+ _ => Err(io::Errno::INVAL),
+ }
+}
+
+/// Read an optional socket address returned from the OS.
+///
+/// # Safety
+///
+/// `storage` must point to a valid socket address returned from the OS.
+pub(crate) unsafe fn maybe_read_sockaddr_os(
+ storage: *const c::sockaddr_storage,
+ len: usize,
+) -> Option<SocketAddrAny> {
+ if len == 0 {
+ return None;
+ }
+
+ assert!(len >= size_of::<c::sa_family_t>());
+ let family = read_ss_family(storage).into();
+ if family == c::AF_UNSPEC {
+ None
+ } else {
+ Some(inner_read_sockaddr_os(family, storage, len))
+ }
+}
+
+/// Read a socket address returned from the OS.
+///
+/// # Safety
+///
+/// `storage` must point to a valid socket address returned from the OS.
+pub(crate) unsafe fn read_sockaddr_os(
+ storage: *const c::sockaddr_storage,
+ len: usize,
+) -> SocketAddrAny {
+ assert!(len >= size_of::<c::sa_family_t>());
+ let family = read_ss_family(storage).into();
+ inner_read_sockaddr_os(family, storage, len)
+}
+
+unsafe fn inner_read_sockaddr_os(
+ family: c::c_int,
+ storage: *const c::sockaddr_storage,
+ len: usize,
+) -> SocketAddrAny {
+ #[cfg(unix)]
+ let offsetof_sun_path = super::addr::offsetof_sun_path();
+
+ assert!(len >= size_of::<c::sa_family_t>());
+ match family {
+ c::AF_INET => {
+ assert!(len >= size_of::<c::sockaddr_in>());
+ let decode = &*storage.cast::<c::sockaddr_in>();
+ SocketAddrAny::V4(SocketAddrV4::new(
+ Ipv4Addr::from(u32::from_be(in_addr_s_addr(decode.sin_addr))),
+ u16::from_be(decode.sin_port),
+ ))
+ }
+ c::AF_INET6 => {
+ assert!(len >= size_of::<c::sockaddr_in6>());
+ let decode = &*storage.cast::<c::sockaddr_in6>();
+ SocketAddrAny::V6(SocketAddrV6::new(
+ Ipv6Addr::from(in6_addr_s6_addr(decode.sin6_addr)),
+ u16::from_be(decode.sin6_port),
+ u32::from_be(decode.sin6_flowinfo),
+ sockaddr_in6_sin6_scope_id(decode),
+ ))
+ }
+ #[cfg(unix)]
+ c::AF_UNIX => {
+ assert!(len >= offsetof_sun_path);
+ if len == offsetof_sun_path {
+ SocketAddrAny::Unix(SocketAddrUnix::new(&[][..]).unwrap())
+ } else {
+ let decode = &*storage.cast::<c::sockaddr_un>();
+
+ // On Linux check for Linux's [abstract namespace].
+ //
+ // [abstract namespace]: https://man7.org/linux/man-pages/man7/unix.7.html
+ #[cfg(linux_kernel)]
+ if decode.sun_path[0] == 0 {
+ return SocketAddrAny::Unix(
+ SocketAddrUnix::new_abstract_name(core::mem::transmute::<
+ &[c::c_char],
+ &[u8],
+ >(
+ &decode.sun_path[1..len - offsetof_sun_path],
+ ))
+ .unwrap(),
+ );
+ }
+
+ // Otherwise we expect a NUL-terminated filesystem path.
+ assert_eq!(decode.sun_path[len - 1 - offsetof_sun_path], 0);
+ let path_bytes = &decode.sun_path[..len - 1 - offsetof_sun_path];
+
+ // FreeBSD and illumos sometimes set the length to longer than
+ // the length of the NUL-terminated string. Find the NUL and
+ // truncate the string accordingly.
+ #[cfg(any(solarish, target_os = "freebsd"))]
+ let path_bytes = &path_bytes[..path_bytes.iter().position(|b| *b == 0).unwrap()];
+
+ SocketAddrAny::Unix(
+ SocketAddrUnix::new(core::mem::transmute::<&[c::c_char], &[u8]>(path_bytes))
+ .unwrap(),
+ )
+ }
+ }
+ #[cfg(target_os = "linux")]
+ c::AF_XDP => {
+ assert!(len >= size_of::<c::sockaddr_xdp>());
+ let decode = &*storage.cast::<c::sockaddr_xdp>();
+ SocketAddrAny::Xdp(SocketAddrXdp::new(
+ SockaddrXdpFlags::from_bits_retain(decode.sxdp_flags),
+ u32::from_be(decode.sxdp_ifindex),
+ u32::from_be(decode.sxdp_queue_id),
+ u32::from_be(decode.sxdp_shared_umem_fd),
+ ))
+ }
+ other => unimplemented!("{:?}", other),
+ }
+}
diff --git a/src/backend/libc/net/send_recv.rs b/src/backend/libc/net/send_recv.rs
new file mode 100644
index 0000000..5dc60dd
--- /dev/null
+++ b/src/backend/libc/net/send_recv.rs
@@ -0,0 +1,103 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `MSG_*` flags for use with [`send`], [`send_to`], and related
+ /// functions.
+ ///
+ /// [`send`]: crate::net::send
+ /// [`sendto`]: crate::net::sendto
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SendFlags: u32 {
+ /// `MSG_CONFIRM`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "haiku",
+ target_os = "vita",
+ )))]
+ const CONFIRM = bitcast!(c::MSG_CONFIRM);
+ /// `MSG_DONTROUTE`
+ const DONTROUTE = bitcast!(c::MSG_DONTROUTE);
+ /// `MSG_DONTWAIT`
+ #[cfg(not(windows))]
+ const DONTWAIT = bitcast!(c::MSG_DONTWAIT);
+ /// `MSG_EOR`
+ #[cfg(not(windows))]
+ const EOT = bitcast!(c::MSG_EOR);
+ /// `MSG_MORE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ const MORE = bitcast!(c::MSG_MORE);
+ #[cfg(not(any(apple, windows, target_os = "vita")))]
+ /// `MSG_NOSIGNAL`
+ const NOSIGNAL = bitcast!(c::MSG_NOSIGNAL);
+ /// `MSG_OOB`
+ const OOB = bitcast!(c::MSG_OOB);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `MSG_*` flags for use with [`recv`], [`recvfrom`], and related
+ /// functions.
+ ///
+ /// [`recv`]: crate::net::recv
+ /// [`recvfrom`]: crate::net::recvfrom
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct RecvFlags: u32 {
+ #[cfg(not(any(
+ apple,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ /// `MSG_CMSG_CLOEXEC`
+ const CMSG_CLOEXEC = bitcast!(c::MSG_CMSG_CLOEXEC);
+ /// `MSG_DONTWAIT`
+ #[cfg(not(windows))]
+ const DONTWAIT = bitcast!(c::MSG_DONTWAIT);
+ /// `MSG_ERRQUEUE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ const ERRQUEUE = bitcast!(c::MSG_ERRQUEUE);
+ /// `MSG_OOB`
+ const OOB = bitcast!(c::MSG_OOB);
+ /// `MSG_PEEK`
+ const PEEK = bitcast!(c::MSG_PEEK);
+ /// `MSG_TRUNC`
+ const TRUNC = bitcast!(c::MSG_TRUNC);
+ /// `MSG_WAITALL`
+ const WAITALL = bitcast!(c::MSG_WAITALL);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/libc/net/sockopt.rs b/src/backend/libc/net/sockopt.rs
new file mode 100644
index 0000000..42bbdad
--- /dev/null
+++ b/src/backend/libc/net/sockopt.rs
@@ -0,0 +1,1233 @@
+//! libc syscalls supporting `rustix::net::sockopt`.
+
+use super::ext::{in6_addr_new, in_addr_new};
+use crate::backend::c;
+use crate::backend::conv::{borrowed_fd, ret};
+use crate::fd::BorrowedFd;
+#[cfg(feature = "alloc")]
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+use crate::ffi::CStr;
+use crate::io;
+use crate::net::sockopt::Timeout;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, XdpRingOffset, XdpStatistics, XdpUmemReg};
+#[cfg(not(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "vita",
+)))]
+use crate::net::AddressFamily;
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_env = "newlib"
+))]
+use crate::net::Protocol;
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_env = "newlib"
+))]
+use crate::net::RawProtocol;
+use crate::net::{Ipv4Addr, Ipv6Addr, SocketType};
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+use crate::net::{SocketAddrAny, SocketAddrStorage, SocketAddrV4};
+#[cfg(linux_kernel)]
+use crate::net::{SocketAddrV6, UCred};
+use crate::utils::as_mut_ptr;
+#[cfg(feature = "alloc")]
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+use alloc::borrow::ToOwned;
+#[cfg(feature = "alloc")]
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+use alloc::string::String;
+#[cfg(apple)]
+use c::TCP_KEEPALIVE as TCP_KEEPIDLE;
+#[cfg(not(any(apple, target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+use c::TCP_KEEPIDLE;
+use core::mem::{size_of, MaybeUninit};
+use core::time::Duration;
+#[cfg(target_os = "linux")]
+use linux_raw_sys::xdp::{xdp_mmap_offsets, xdp_statistics, xdp_statistics_v1};
+#[cfg(windows)]
+use windows_sys::Win32::Foundation::BOOL;
+
+#[inline]
+fn getsockopt<T: Copy>(fd: BorrowedFd<'_>, level: i32, optname: i32) -> io::Result<T> {
+ let mut optlen = core::mem::size_of::<T>().try_into().unwrap();
+ debug_assert!(
+ optlen as usize >= core::mem::size_of::<c::c_int>(),
+ "Socket APIs don't ever use `bool` directly"
+ );
+
+ let mut value = MaybeUninit::<T>::zeroed();
+ getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
+
+ // On Windows at least, `getsockopt` has been observed writing 1
+ // byte on at least (`IPPROTO_TCP`, `TCP_NODELAY`), even though
+ // Windows' documentation says that should write a 4-byte `BOOL`.
+ // So, we initialize the memory to zeros above, and just assert
+ // that `getsockopt` doesn't write too many bytes here.
+ assert!(
+ optlen as usize <= size_of::<T>(),
+ "unexpected getsockopt size"
+ );
+
+ unsafe { Ok(value.assume_init()) }
+}
+
+#[inline]
+fn getsockopt_raw<T>(
+ fd: BorrowedFd<'_>,
+ level: i32,
+ optname: i32,
+ value: &mut MaybeUninit<T>,
+ optlen: &mut c::socklen_t,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::getsockopt(
+ borrowed_fd(fd),
+ level,
+ optname,
+ as_mut_ptr(value).cast(),
+ optlen,
+ ))
+ }
+}
+
+#[inline]
+fn setsockopt<T: Copy>(fd: BorrowedFd<'_>, level: i32, optname: i32, value: T) -> io::Result<()> {
+ let optlen = core::mem::size_of::<T>().try_into().unwrap();
+ debug_assert!(
+ optlen as usize >= core::mem::size_of::<c::c_int>(),
+ "Socket APIs don't ever use `bool` directly"
+ );
+ setsockopt_raw(fd, level, optname, &value, optlen)
+}
+
+#[inline]
+fn setsockopt_raw<T>(
+ fd: BorrowedFd<'_>,
+ level: i32,
+ optname: i32,
+ ptr: *const T,
+ optlen: c::socklen_t,
+) -> io::Result<()> {
+ unsafe {
+ ret(c::setsockopt(
+ borrowed_fd(fd),
+ level,
+ optname,
+ ptr.cast(),
+ optlen,
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn get_socket_type(fd: BorrowedFd<'_>) -> io::Result<SocketType> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_TYPE)
+}
+
+#[inline]
+pub(crate) fn set_socket_reuseaddr(fd: BorrowedFd<'_>, reuseaddr: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEADDR, from_bool(reuseaddr))
+}
+
+#[inline]
+pub(crate) fn get_socket_reuseaddr(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEADDR).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_socket_broadcast(fd: BorrowedFd<'_>, broadcast: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_BROADCAST, from_bool(broadcast))
+}
+
+#[inline]
+pub(crate) fn get_socket_broadcast(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_BROADCAST).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_socket_linger(fd: BorrowedFd<'_>, linger: Option<Duration>) -> io::Result<()> {
+ // Convert `linger` to seconds, rounding up.
+ let l_linger = if let Some(linger) = linger {
+ duration_to_secs(linger)?
+ } else {
+ 0
+ };
+ let linger = c::linger {
+ l_onoff: linger.is_some().into(),
+ l_linger,
+ };
+ setsockopt(fd, c::SOL_SOCKET, c::SO_LINGER, linger)
+}
+
+#[inline]
+pub(crate) fn get_socket_linger(fd: BorrowedFd<'_>) -> io::Result<Option<Duration>> {
+ let linger: c::linger = getsockopt(fd, c::SOL_SOCKET, c::SO_LINGER)?;
+ Ok((linger.l_onoff != 0).then(|| Duration::from_secs(linger.l_linger as u64)))
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn set_socket_passcred(fd: BorrowedFd<'_>, passcred: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_PASSCRED, from_bool(passcred))
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn get_socket_passcred(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_PASSCRED).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_socket_timeout(
+ fd: BorrowedFd<'_>,
+ id: Timeout,
+ timeout: Option<Duration>,
+) -> io::Result<()> {
+ let optname = match id {
+ Timeout::Recv => c::SO_RCVTIMEO,
+ Timeout::Send => c::SO_SNDTIMEO,
+ };
+
+ #[cfg(not(windows))]
+ let timeout = match timeout {
+ Some(timeout) => {
+ if timeout == Duration::ZERO {
+ return Err(io::Errno::INVAL);
+ }
+
+ // Rust's musl libc bindings deprecated `time_t` while they
+ // transition to 64-bit `time_t`. What we want here is just
+ // “whatever type `timeval`'s `tv_sec` is”, so we're ok using
+ // the deprecated type.
+ #[allow(deprecated)]
+ let tv_sec = timeout.as_secs().try_into().unwrap_or(c::time_t::MAX);
+
+ // `subsec_micros` rounds down, so we use `subsec_nanos` and
+ // manually round up.
+ let mut timeout = c::timeval {
+ tv_sec,
+ tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
+ };
+ if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
+ timeout.tv_usec = 1;
+ }
+ timeout
+ }
+ None => c::timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ },
+ };
+
+ #[cfg(windows)]
+ let timeout: u32 = match timeout {
+ Some(timeout) => {
+ if timeout == Duration::ZERO {
+ return Err(io::Errno::INVAL);
+ }
+
+ // `as_millis` rounds down, so we use `as_nanos` and
+ // manually round up.
+ let mut timeout: u32 = ((timeout.as_nanos() + 999_999) / 1_000_000)
+ .try_into()
+ .map_err(|_convert_err| io::Errno::INVAL)?;
+ if timeout == 0 {
+ timeout = 1;
+ }
+ timeout
+ }
+ None => 0,
+ };
+
+ setsockopt(fd, c::SOL_SOCKET, optname, timeout)
+}
+
+#[inline]
+pub(crate) fn get_socket_timeout(fd: BorrowedFd<'_>, id: Timeout) -> io::Result<Option<Duration>> {
+ let optname = match id {
+ Timeout::Recv => c::SO_RCVTIMEO,
+ Timeout::Send => c::SO_SNDTIMEO,
+ };
+
+ #[cfg(not(windows))]
+ {
+ let timeout: c::timeval = getsockopt(fd, c::SOL_SOCKET, optname)?;
+ if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
+ Ok(None)
+ } else {
+ Ok(Some(
+ Duration::from_secs(timeout.tv_sec as u64)
+ + Duration::from_micros(timeout.tv_usec as u64),
+ ))
+ }
+ }
+
+ #[cfg(windows)]
+ {
+ let timeout: u32 = getsockopt(fd, c::SOL_SOCKET, optname)?;
+ if timeout == 0 {
+ Ok(None)
+ } else {
+ Ok(Some(Duration::from_millis(timeout as u64)))
+ }
+ }
+}
+
+#[cfg(any(apple, freebsdlike, target_os = "netbsd"))]
+#[inline]
+pub(crate) fn get_socket_nosigpipe(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_NOSIGPIPE).map(to_bool)
+}
+
+#[cfg(any(apple, freebsdlike, target_os = "netbsd"))]
+#[inline]
+pub(crate) fn set_socket_nosigpipe(fd: BorrowedFd<'_>, val: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_NOSIGPIPE, from_bool(val))
+}
+
+#[inline]
+pub(crate) fn get_socket_error(fd: BorrowedFd<'_>) -> io::Result<Result<(), io::Errno>> {
+ let err: c::c_int = getsockopt(fd, c::SOL_SOCKET, c::SO_ERROR)?;
+ Ok(if err == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno::from_raw_os_error(err))
+ })
+}
+
+#[inline]
+pub(crate) fn set_socket_keepalive(fd: BorrowedFd<'_>, keepalive: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_KEEPALIVE, from_bool(keepalive))
+}
+
+#[inline]
+pub(crate) fn get_socket_keepalive(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_KEEPALIVE).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_socket_recv_buffer_size(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
+ let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?;
+ setsockopt(fd, c::SOL_SOCKET, c::SO_RCVBUF, size)
+}
+
+#[inline]
+pub(crate) fn get_socket_recv_buffer_size(fd: BorrowedFd<'_>) -> io::Result<usize> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_RCVBUF).map(|size: u32| size as usize)
+}
+
+#[inline]
+pub(crate) fn set_socket_send_buffer_size(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
+ let size: c::c_int = size.try_into().map_err(|_| io::Errno::INVAL)?;
+ setsockopt(fd, c::SOL_SOCKET, c::SO_SNDBUF, size)
+}
+
+#[inline]
+pub(crate) fn get_socket_send_buffer_size(fd: BorrowedFd<'_>) -> io::Result<usize> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_SNDBUF).map(|size: u32| size as usize)
+}
+
+#[inline]
+#[cfg(not(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "vita",
+)))]
+pub(crate) fn get_socket_domain(fd: BorrowedFd<'_>) -> io::Result<AddressFamily> {
+ let domain: c::c_int = getsockopt(fd, c::SOL_SOCKET, c::SO_DOMAIN)?;
+ Ok(AddressFamily(
+ domain.try_into().map_err(|_| io::Errno::OPNOTSUPP)?,
+ ))
+}
+
+#[inline]
+#[cfg(not(apple))] // Apple platforms declare the constant, but do not actually implement it.
+pub(crate) fn get_socket_acceptconn(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_ACCEPTCONN).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_socket_oobinline(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_socket_oobinline(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_OOBINLINE).map(to_bool)
+}
+
+#[cfg(not(any(solarish, windows)))]
+#[inline]
+pub(crate) fn set_socket_reuseport(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT, from_bool(value))
+}
+
+#[cfg(not(any(solarish, windows)))]
+#[inline]
+pub(crate) fn get_socket_reuseport(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT).map(to_bool)
+}
+
+#[cfg(target_os = "freebsd")]
+#[inline]
+pub(crate) fn set_socket_reuseport_lb(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT_LB, from_bool(value))
+}
+
+#[cfg(target_os = "freebsd")]
+#[inline]
+pub(crate) fn get_socket_reuseport_lb(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT_LB).map(to_bool)
+}
+
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_env = "newlib"
+))]
+#[inline]
+pub(crate) fn get_socket_protocol(fd: BorrowedFd<'_>) -> io::Result<Option<Protocol>> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_PROTOCOL)
+ .map(|raw| RawProtocol::new(raw).map(Protocol::from_raw))
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn get_socket_cookie(fd: BorrowedFd<'_>) -> io::Result<u64> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_COOKIE)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn get_socket_incoming_cpu(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn set_socket_incoming_cpu(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU, value)
+}
+
+#[inline]
+pub(crate) fn set_ip_ttl(fd: BorrowedFd<'_>, ttl: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_TTL, ttl)
+}
+
+#[inline]
+pub(crate) fn get_ip_ttl(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_TTL)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_v6only(fd: BorrowedFd<'_>, only_v6: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_V6ONLY, from_bool(only_v6))
+}
+
+#[inline]
+pub(crate) fn get_ipv6_v6only(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_V6ONLY).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_ip_multicast_loop(fd: BorrowedFd<'_>, multicast_loop: bool) -> io::Result<()> {
+ setsockopt(
+ fd,
+ c::IPPROTO_IP,
+ c::IP_MULTICAST_LOOP,
+ from_bool(multicast_loop),
+ )
+}
+
+#[inline]
+pub(crate) fn get_ip_multicast_loop(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_MULTICAST_LOOP).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_ip_multicast_ttl(fd: BorrowedFd<'_>, multicast_ttl: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_MULTICAST_TTL, multicast_ttl)
+}
+
+#[inline]
+pub(crate) fn get_ip_multicast_ttl(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_MULTICAST_TTL)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_multicast_loop(fd: BorrowedFd<'_>, multicast_loop: bool) -> io::Result<()> {
+ setsockopt(
+ fd,
+ c::IPPROTO_IPV6,
+ c::IPV6_MULTICAST_LOOP,
+ from_bool(multicast_loop),
+ )
+}
+
+#[inline]
+pub(crate) fn get_ipv6_multicast_loop(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_MULTICAST_LOOP).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_multicast_hops(fd: BorrowedFd<'_>, multicast_hops: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS, multicast_hops)
+}
+
+#[inline]
+pub(crate) fn get_ipv6_multicast_hops(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_IP, c::IPV6_MULTICAST_HOPS)
+}
+
+#[inline]
+pub(crate) fn set_ip_add_membership(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+) -> io::Result<()> {
+ let mreq = to_ip_mreq(multiaddr, interface);
+ setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_MEMBERSHIP, mreq)
+}
+
+#[cfg(any(
+ apple,
+ freebsdlike,
+ linux_like,
+ target_os = "fuchsia",
+ target_os = "openbsd"
+))]
+#[inline]
+pub(crate) fn set_ip_add_membership_with_ifindex(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv4Addr,
+ address: &Ipv4Addr,
+ ifindex: i32,
+) -> io::Result<()> {
+ let mreqn = to_ip_mreqn(multiaddr, address, ifindex);
+ setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_MEMBERSHIP, mreqn)
+}
+
+#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
+#[inline]
+pub(crate) fn set_ip_add_source_membership(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+ sourceaddr: &Ipv4Addr,
+) -> io::Result<()> {
+ let mreq_source = to_imr_source(multiaddr, interface, sourceaddr);
+ setsockopt(fd, c::IPPROTO_IP, c::IP_ADD_SOURCE_MEMBERSHIP, mreq_source)
+}
+
+#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
+#[inline]
+pub(crate) fn set_ip_drop_source_membership(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+ sourceaddr: &Ipv4Addr,
+) -> io::Result<()> {
+ let mreq_source = to_imr_source(multiaddr, interface, sourceaddr);
+ setsockopt(fd, c::IPPROTO_IP, c::IP_DROP_SOURCE_MEMBERSHIP, mreq_source)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_add_membership(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv6Addr,
+ interface: u32,
+) -> io::Result<()> {
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "nto"
+ )))]
+ use c::IPV6_ADD_MEMBERSHIP;
+ #[cfg(any(
+ bsd,
+ solarish,
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "nto"
+ ))]
+ use c::IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP;
+
+ let mreq = to_ipv6mr(multiaddr, interface);
+ setsockopt(fd, c::IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq)
+}
+
+#[inline]
+pub(crate) fn set_ip_drop_membership(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+) -> io::Result<()> {
+ let mreq = to_ip_mreq(multiaddr, interface);
+ setsockopt(fd, c::IPPROTO_IP, c::IP_DROP_MEMBERSHIP, mreq)
+}
+
+#[cfg(any(
+ apple,
+ freebsdlike,
+ linux_like,
+ target_os = "fuchsia",
+ target_os = "openbsd"
+))]
+#[inline]
+pub(crate) fn set_ip_drop_membership_with_ifindex(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv4Addr,
+ address: &Ipv4Addr,
+ ifindex: i32,
+) -> io::Result<()> {
+ let mreqn = to_ip_mreqn(multiaddr, address, ifindex);
+ setsockopt(fd, c::IPPROTO_IP, c::IP_DROP_MEMBERSHIP, mreqn)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_drop_membership(
+ fd: BorrowedFd<'_>,
+ multiaddr: &Ipv6Addr,
+ interface: u32,
+) -> io::Result<()> {
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "nto"
+ )))]
+ use c::IPV6_DROP_MEMBERSHIP;
+ #[cfg(any(
+ bsd,
+ solarish,
+ target_os = "haiku",
+ target_os = "l4re",
+ target_os = "nto"
+ ))]
+ use c::IPV6_LEAVE_GROUP as IPV6_DROP_MEMBERSHIP;
+
+ let mreq = to_ipv6mr(multiaddr, interface);
+ setsockopt(fd, c::IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, mreq)
+}
+
+#[inline]
+pub(crate) fn get_ipv6_unicast_hops(fd: BorrowedFd<'_>) -> io::Result<u8> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_UNICAST_HOPS).map(|hops: c::c_int| hops as u8)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_unicast_hops(fd: BorrowedFd<'_>, hops: Option<u8>) -> io::Result<()> {
+ let hops = match hops {
+ Some(hops) => hops as c::c_int,
+ None => -1,
+ };
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_UNICAST_HOPS, hops)
+}
+
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_env = "newlib"
+))]
+#[inline]
+pub(crate) fn set_ip_tos(fd: BorrowedFd<'_>, value: u8) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_TOS, i32::from(value))
+}
+
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_env = "newlib"
+))]
+#[inline]
+pub(crate) fn get_ip_tos(fd: BorrowedFd<'_>) -> io::Result<u8> {
+ let value: i32 = getsockopt(fd, c::IPPROTO_IP, c::IP_TOS)?;
+ Ok(value as u8)
+}
+
+#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn set_ip_recvtos(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_RECVTOS, from_bool(value))
+}
+
+#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn get_ip_recvtos(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_RECVTOS).map(to_bool)
+}
+
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "nto"
+))]
+#[inline]
+pub(crate) fn set_ipv6_recvtclass(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS, from_bool(value))
+}
+
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "nto"
+))]
+#[inline]
+pub(crate) fn get_ipv6_recvtclass(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS).map(to_bool)
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn set_ip_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND, from_bool(value))
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn get_ip_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND).map(to_bool)
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn set_ipv6_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND, from_bool(value))
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn get_ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool)
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn get_ip_original_dst(fd: BorrowedFd<'_>) -> io::Result<SocketAddrV4> {
+ let level = c::IPPROTO_IP;
+ let optname = c::SO_ORIGINAL_DST;
+ let mut value = MaybeUninit::<SocketAddrStorage>::uninit();
+ let mut optlen = core::mem::size_of_val(&value).try_into().unwrap();
+
+ getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
+
+ let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? };
+ match any {
+ SocketAddrAny::V4(v4) => Ok(v4),
+ _ => unreachable!(),
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn get_ipv6_original_dst(fd: BorrowedFd<'_>) -> io::Result<SocketAddrV6> {
+ let level = c::IPPROTO_IPV6;
+ let optname = c::IP6T_SO_ORIGINAL_DST;
+ let mut value = MaybeUninit::<SocketAddrStorage>::uninit();
+ let mut optlen = core::mem::size_of_val(&value).try_into().unwrap();
+
+ getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
+
+ let any = unsafe { SocketAddrAny::read(value.as_ptr(), optlen as usize)? };
+ match any {
+ SocketAddrAny::V6(v6) => Ok(v6),
+ _ => unreachable!(),
+ }
+}
+
+#[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+)))]
+#[inline]
+pub(crate) fn set_ipv6_tclass(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS, value)
+}
+
+#[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+)))]
+#[inline]
+pub(crate) fn get_ipv6_tclass(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS)
+}
+
+#[inline]
+pub(crate) fn set_tcp_nodelay(fd: BorrowedFd<'_>, nodelay: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY, from_bool(nodelay))
+}
+
+#[inline]
+pub(crate) fn get_tcp_nodelay(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_NODELAY).map(to_bool)
+}
+
+#[inline]
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+pub(crate) fn set_tcp_keepcnt(fd: BorrowedFd<'_>, count: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPCNT, count)
+}
+
+#[inline]
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+pub(crate) fn get_tcp_keepcnt(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPCNT)
+}
+
+#[inline]
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+pub(crate) fn set_tcp_keepidle(fd: BorrowedFd<'_>, duration: Duration) -> io::Result<()> {
+ let secs: c::c_uint = duration_to_secs(duration)?;
+ setsockopt(fd, c::IPPROTO_TCP, TCP_KEEPIDLE, secs)
+}
+
+#[inline]
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+pub(crate) fn get_tcp_keepidle(fd: BorrowedFd<'_>) -> io::Result<Duration> {
+ let secs: c::c_uint = getsockopt(fd, c::IPPROTO_TCP, TCP_KEEPIDLE)?;
+ Ok(Duration::from_secs(secs as u64))
+}
+
+#[inline]
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+pub(crate) fn set_tcp_keepintvl(fd: BorrowedFd<'_>, duration: Duration) -> io::Result<()> {
+ let secs: c::c_uint = duration_to_secs(duration)?;
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPINTVL, secs)
+}
+
+#[inline]
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+pub(crate) fn get_tcp_keepintvl(fd: BorrowedFd<'_>) -> io::Result<Duration> {
+ let secs: c::c_uint = getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPINTVL)?;
+ Ok(Duration::from_secs(secs as u64))
+}
+
+#[inline]
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+pub(crate) fn set_tcp_user_timeout(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT, value)
+}
+
+#[inline]
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+pub(crate) fn get_tcp_user_timeout(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT)
+}
+
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn set_tcp_quickack(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK, from_bool(value))
+}
+
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn get_tcp_quickack(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK).map(to_bool)
+}
+
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+#[inline]
+pub(crate) fn set_tcp_congestion(fd: BorrowedFd<'_>, value: &str) -> io::Result<()> {
+ let level = c::IPPROTO_TCP;
+ let optname = c::TCP_CONGESTION;
+ let optlen = value.len().try_into().unwrap();
+ setsockopt_raw(fd, level, optname, value.as_ptr(), optlen)
+}
+
+#[cfg(feature = "alloc")]
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+#[inline]
+pub(crate) fn get_tcp_congestion(fd: BorrowedFd<'_>) -> io::Result<String> {
+ let level = c::IPPROTO_TCP;
+ let optname = c::TCP_CONGESTION;
+ const OPTLEN: c::socklen_t = 16;
+ let mut value = MaybeUninit::<[MaybeUninit<u8>; OPTLEN as usize]>::uninit();
+ let mut optlen = OPTLEN;
+ getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
+ unsafe {
+ let value = value.assume_init();
+ let slice: &[u8] = core::mem::transmute(&value[..optlen as usize]);
+ assert!(slice.iter().any(|b| *b == b'\0'));
+ Ok(
+ core::str::from_utf8(CStr::from_ptr(slice.as_ptr().cast()).to_bytes())
+ .unwrap()
+ .to_owned(),
+ )
+ }
+}
+
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn set_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(
+ fd,
+ c::IPPROTO_TCP,
+ c::TCP_THIN_LINEAR_TIMEOUTS,
+ from_bool(value),
+ )
+}
+
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn get_tcp_thin_linear_timeouts(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_THIN_LINEAR_TIMEOUTS).map(to_bool)
+}
+
+#[cfg(any(linux_like, solarish, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn set_tcp_cork(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK, from_bool(value))
+}
+
+#[cfg(any(linux_like, solarish, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn get_tcp_cork(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK).map(to_bool)
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn get_socket_peercred(fd: BorrowedFd<'_>) -> io::Result<UCred> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_PEERCRED)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn set_xdp_umem_reg(fd: BorrowedFd<'_>, value: XdpUmemReg) -> io::Result<()> {
+ setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_REG, value)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn set_xdp_umem_fill_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_FILL_RING, value)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn set_xdp_umem_completion_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::SOL_XDP, c::XDP_UMEM_COMPLETION_RING, value)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn set_xdp_tx_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::SOL_XDP, c::XDP_TX_RING, value)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn set_xdp_rx_ring_size(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::SOL_XDP, c::XDP_RX_RING, value)
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn get_xdp_mmap_offsets(fd: BorrowedFd<'_>) -> io::Result<XdpMmapOffsets> {
+ // The kernel will write `xdp_mmap_offsets` or `xdp_mmap_offsets_v1` to the supplied pointer,
+ // depending on the kernel version. Both structs only contain u64 values.
+ // By using the larger of both as the parameter, we can shuffle the values to the non-v1 version
+ // returned by `get_xdp_mmap_offsets` while keeping the return type unaffected by the kernel
+ // version. This works because C will layout all struct members one after the other.
+
+ let mut optlen = core::mem::size_of::<xdp_mmap_offsets>().try_into().unwrap();
+ debug_assert!(
+ optlen as usize >= core::mem::size_of::<c::c_int>(),
+ "Socket APIs don't ever use `bool` directly"
+ );
+ let mut value = MaybeUninit::<xdp_mmap_offsets>::zeroed();
+ getsockopt_raw(fd, c::SOL_XDP, c::XDP_MMAP_OFFSETS, &mut value, &mut optlen)?;
+
+ if optlen as usize == core::mem::size_of::<c::xdp_mmap_offsets_v1>() {
+ // Safety: All members of xdp_mmap_offsets are u64 and thus are correctly initialized
+ // by `MaybeUninit::<xdp_statistics>::zeroed()`
+ let xpd_mmap_offsets = unsafe { value.assume_init() };
+ Ok(XdpMmapOffsets {
+ rx: XdpRingOffset {
+ producer: xpd_mmap_offsets.rx.producer,
+ consumer: xpd_mmap_offsets.rx.consumer,
+ desc: xpd_mmap_offsets.rx.desc,
+ flags: None,
+ },
+ tx: XdpRingOffset {
+ producer: xpd_mmap_offsets.rx.flags,
+ consumer: xpd_mmap_offsets.tx.producer,
+ desc: xpd_mmap_offsets.tx.consumer,
+ flags: None,
+ },
+ fr: XdpRingOffset {
+ producer: xpd_mmap_offsets.tx.desc,
+ consumer: xpd_mmap_offsets.tx.flags,
+ desc: xpd_mmap_offsets.fr.producer,
+ flags: None,
+ },
+ cr: XdpRingOffset {
+ producer: xpd_mmap_offsets.fr.consumer,
+ consumer: xpd_mmap_offsets.fr.desc,
+ desc: xpd_mmap_offsets.fr.flags,
+ flags: None,
+ },
+ })
+ } else {
+ assert_eq!(
+ optlen as usize,
+ core::mem::size_of::<xdp_mmap_offsets>(),
+ "unexpected getsockopt size"
+ );
+ // Safety: All members of xdp_mmap_offsets are u64 and thus are correctly initialized
+ // by `MaybeUninit::<xdp_statistics>::zeroed()`
+ let xpd_mmap_offsets = unsafe { value.assume_init() };
+ Ok(XdpMmapOffsets {
+ rx: XdpRingOffset {
+ producer: xpd_mmap_offsets.rx.producer,
+ consumer: xpd_mmap_offsets.rx.consumer,
+ desc: xpd_mmap_offsets.rx.desc,
+ flags: Some(xpd_mmap_offsets.rx.flags),
+ },
+ tx: XdpRingOffset {
+ producer: xpd_mmap_offsets.tx.producer,
+ consumer: xpd_mmap_offsets.tx.consumer,
+ desc: xpd_mmap_offsets.tx.desc,
+ flags: Some(xpd_mmap_offsets.tx.flags),
+ },
+ fr: XdpRingOffset {
+ producer: xpd_mmap_offsets.fr.producer,
+ consumer: xpd_mmap_offsets.fr.consumer,
+ desc: xpd_mmap_offsets.fr.desc,
+ flags: Some(xpd_mmap_offsets.fr.flags),
+ },
+ cr: XdpRingOffset {
+ producer: xpd_mmap_offsets.cr.producer,
+ consumer: xpd_mmap_offsets.cr.consumer,
+ desc: xpd_mmap_offsets.cr.desc,
+ flags: Some(xpd_mmap_offsets.cr.flags),
+ },
+ })
+ }
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn get_xdp_statistics(fd: BorrowedFd<'_>) -> io::Result<XdpStatistics> {
+ let mut optlen = core::mem::size_of::<xdp_statistics>().try_into().unwrap();
+ debug_assert!(
+ optlen as usize >= core::mem::size_of::<c::c_int>(),
+ "Socket APIs don't ever use `bool` directly"
+ );
+ let mut value = MaybeUninit::<xdp_statistics>::zeroed();
+ getsockopt_raw(fd, c::SOL_XDP, c::XDP_STATISTICS, &mut value, &mut optlen)?;
+
+ if optlen as usize == core::mem::size_of::<xdp_statistics_v1>() {
+ // Safety: All members of xdp_statistics are u64 and thus are correctly initialized
+ // by `MaybeUninit::<xdp_statistics>::zeroed()`
+ let xdp_statistics = unsafe { value.assume_init() };
+ Ok(XdpStatistics {
+ rx_dropped: xdp_statistics.rx_dropped,
+ rx_invalid_descs: xdp_statistics.rx_dropped,
+ tx_invalid_descs: xdp_statistics.rx_dropped,
+ rx_ring_full: None,
+ rx_fill_ring_empty_descs: None,
+ tx_ring_empty_descs: None,
+ })
+ } else {
+ assert_eq!(
+ optlen as usize,
+ core::mem::size_of::<xdp_statistics>(),
+ "unexpected getsockopt size"
+ );
+ // Safety: All members of xdp_statistics are u64 and thus are correctly initialized
+ // by `MaybeUninit::<xdp_statistics>::zeroed()`
+ let xdp_statistics = unsafe { value.assume_init() };
+ Ok(XdpStatistics {
+ rx_dropped: xdp_statistics.rx_dropped,
+ rx_invalid_descs: xdp_statistics.rx_invalid_descs,
+ tx_invalid_descs: xdp_statistics.tx_invalid_descs,
+ rx_ring_full: Some(xdp_statistics.rx_ring_full),
+ rx_fill_ring_empty_descs: Some(xdp_statistics.rx_fill_ring_empty_descs),
+ tx_ring_empty_descs: Some(xdp_statistics.tx_ring_empty_descs),
+ })
+ }
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn get_xdp_options(fd: BorrowedFd<'_>) -> io::Result<XdpOptionsFlags> {
+ getsockopt(fd, c::SOL_XDP, c::XDP_OPTIONS)
+}
+
+#[inline]
+fn to_ip_mreq(multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> c::ip_mreq {
+ c::ip_mreq {
+ imr_multiaddr: to_imr_addr(multiaddr),
+ imr_interface: to_imr_addr(interface),
+ }
+}
+
+#[cfg(any(
+ apple,
+ freebsdlike,
+ linux_like,
+ target_os = "fuchsia",
+ target_os = "openbsd"
+))]
+#[inline]
+fn to_ip_mreqn(multiaddr: &Ipv4Addr, address: &Ipv4Addr, ifindex: i32) -> c::ip_mreqn {
+ c::ip_mreqn {
+ imr_multiaddr: to_imr_addr(multiaddr),
+ imr_address: to_imr_addr(address),
+ imr_ifindex: ifindex,
+ }
+}
+
+#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
+#[inline]
+fn to_imr_source(
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+ sourceaddr: &Ipv4Addr,
+) -> c::ip_mreq_source {
+ c::ip_mreq_source {
+ imr_multiaddr: to_imr_addr(multiaddr),
+ imr_interface: to_imr_addr(interface),
+ imr_sourceaddr: to_imr_addr(sourceaddr),
+ }
+}
+
+#[inline]
+fn to_imr_addr(addr: &Ipv4Addr) -> c::in_addr {
+ in_addr_new(u32::from_ne_bytes(addr.octets()))
+}
+
+#[inline]
+fn to_ipv6mr(multiaddr: &Ipv6Addr, interface: u32) -> c::ipv6_mreq {
+ c::ipv6_mreq {
+ ipv6mr_multiaddr: to_ipv6mr_multiaddr(multiaddr),
+ ipv6mr_interface: to_ipv6mr_interface(interface),
+ }
+}
+
+#[inline]
+fn to_ipv6mr_multiaddr(multiaddr: &Ipv6Addr) -> c::in6_addr {
+ in6_addr_new(multiaddr.octets())
+}
+
+#[cfg(target_os = "android")]
+#[inline]
+fn to_ipv6mr_interface(interface: u32) -> c::c_int {
+ interface as c::c_int
+}
+
+#[cfg(not(target_os = "android"))]
+#[inline]
+fn to_ipv6mr_interface(interface: u32) -> c::c_uint {
+ interface as c::c_uint
+}
+
+// `getsockopt` and `setsockopt` represent boolean values as integers.
+#[cfg(not(windows))]
+type RawSocketBool = c::c_int;
+#[cfg(windows)]
+type RawSocketBool = BOOL;
+
+// Wrap `RawSocketBool` in a newtype to discourage misuse.
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+struct SocketBool(RawSocketBool);
+
+// Convert from a `bool` to a `SocketBool`.
+#[inline]
+fn from_bool(value: bool) -> SocketBool {
+ SocketBool(value.into())
+}
+
+// Convert from a `SocketBool` to a `bool`.
+#[inline]
+fn to_bool(value: SocketBool) -> bool {
+ value.0 != 0
+}
+
+/// Convert to seconds, rounding up if necessary.
+#[inline]
+fn duration_to_secs<T: TryFrom<u64>>(duration: Duration) -> io::Result<T> {
+ let mut secs = duration.as_secs();
+ if duration.subsec_nanos() != 0 {
+ secs = secs.checked_add(1).ok_or(io::Errno::INVAL)?;
+ }
+ T::try_from(secs).map_err(|_e| io::Errno::INVAL)
+}
diff --git a/src/backend/libc/net/syscalls.rs b/src/backend/libc/net/syscalls.rs
new file mode 100644
index 0000000..3fdb776
--- /dev/null
+++ b/src/backend/libc/net/syscalls.rs
@@ -0,0 +1,621 @@
+//! libc syscalls supporting `rustix::net`.
+
+#[cfg(unix)]
+use super::addr::SocketAddrUnix;
+#[cfg(target_os = "linux")]
+use super::msghdr::with_xdp_msghdr;
+#[cfg(target_os = "linux")]
+use super::write_sockaddr::encode_sockaddr_xdp;
+use crate::backend::c;
+use crate::backend::conv::{borrowed_fd, ret, ret_owned_fd, ret_send_recv, send_recv_len};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+use crate::net::{SocketAddrAny, SocketAddrV4, SocketAddrV6};
+use crate::utils::as_ptr;
+use core::mem::{size_of, MaybeUninit};
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use {
+ super::msghdr::{with_noaddr_msghdr, with_recv_msghdr, with_v4_msghdr, with_v6_msghdr},
+ crate::io::{IoSlice, IoSliceMut},
+ crate::net::{RecvAncillaryBuffer, RecvMsgReturn, SendAncillaryBuffer},
+};
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+use {
+ super::read_sockaddr::{initialize_family_to_unspec, maybe_read_sockaddr_os, read_sockaddr_os},
+ super::send_recv::{RecvFlags, SendFlags},
+ super::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6},
+ crate::net::{AddressFamily, Protocol, Shutdown, SocketFlags, SocketType},
+ core::ptr::null_mut,
+};
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) unsafe fn recv(
+ fd: BorrowedFd<'_>,
+ buf: *mut u8,
+ len: usize,
+ flags: RecvFlags,
+) -> io::Result<usize> {
+ ret_send_recv(c::recv(
+ borrowed_fd(fd),
+ buf.cast(),
+ send_recv_len(len),
+ bitflags_bits!(flags),
+ ))
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn send(fd: BorrowedFd<'_>, buf: &[u8], flags: SendFlags) -> io::Result<usize> {
+ unsafe {
+ ret_send_recv(c::send(
+ borrowed_fd(fd),
+ buf.as_ptr().cast(),
+ send_recv_len(buf.len()),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) unsafe fn recvfrom(
+ fd: BorrowedFd<'_>,
+ buf: *mut u8,
+ buf_len: usize,
+ flags: RecvFlags,
+) -> io::Result<(usize, Option<SocketAddrAny>)> {
+ let mut storage = MaybeUninit::<c::sockaddr_storage>::uninit();
+ let mut len = size_of::<c::sockaddr_storage>() as c::socklen_t;
+
+ // `recvfrom` does not write to the storage if the socket is
+ // connection-oriented sockets, so we initialize the family field to
+ // `AF_UNSPEC` so that we can detect this case.
+ initialize_family_to_unspec(storage.as_mut_ptr());
+
+ ret_send_recv(c::recvfrom(
+ borrowed_fd(fd),
+ buf.cast(),
+ send_recv_len(buf_len),
+ bitflags_bits!(flags),
+ storage.as_mut_ptr().cast(),
+ &mut len,
+ ))
+ .map(|nread| {
+ (
+ nread,
+ maybe_read_sockaddr_os(storage.as_ptr(), len.try_into().unwrap()),
+ )
+ })
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn sendto_v4(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrV4,
+) -> io::Result<usize> {
+ unsafe {
+ ret_send_recv(c::sendto(
+ borrowed_fd(fd),
+ buf.as_ptr().cast(),
+ send_recv_len(buf.len()),
+ bitflags_bits!(flags),
+ as_ptr(&encode_sockaddr_v4(addr)).cast::<c::sockaddr>(),
+ size_of::<c::sockaddr_in>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn sendto_v6(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrV6,
+) -> io::Result<usize> {
+ unsafe {
+ ret_send_recv(c::sendto(
+ borrowed_fd(fd),
+ buf.as_ptr().cast(),
+ send_recv_len(buf.len()),
+ bitflags_bits!(flags),
+ as_ptr(&encode_sockaddr_v6(addr)).cast::<c::sockaddr>(),
+ size_of::<c::sockaddr_in6>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+pub(crate) fn sendto_unix(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrUnix,
+) -> io::Result<usize> {
+ unsafe {
+ ret_send_recv(c::sendto(
+ borrowed_fd(fd),
+ buf.as_ptr().cast(),
+ send_recv_len(buf.len()),
+ bitflags_bits!(flags),
+ as_ptr(&addr.unix).cast(),
+ addr.addr_len(),
+ ))
+ }
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn sendto_xdp(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrXdp,
+) -> io::Result<usize> {
+ unsafe {
+ ret_send_recv(c::sendto(
+ borrowed_fd(fd),
+ buf.as_ptr().cast(),
+ send_recv_len(buf.len()),
+ bitflags_bits!(flags),
+ as_ptr(&encode_sockaddr_xdp(addr)).cast::<c::sockaddr>(),
+ size_of::<c::sockaddr_xdp>() as _,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn socket(
+ domain: AddressFamily,
+ type_: SocketType,
+ protocol: Option<Protocol>,
+) -> io::Result<OwnedFd> {
+ let raw_protocol = match protocol {
+ Some(p) => p.0.get(),
+ None => 0,
+ };
+ unsafe {
+ ret_owned_fd(c::socket(
+ domain.0 as c::c_int,
+ type_.0 as c::c_int,
+ raw_protocol as c::c_int,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn socket_with(
+ domain: AddressFamily,
+ type_: SocketType,
+ flags: SocketFlags,
+ protocol: Option<Protocol>,
+) -> io::Result<OwnedFd> {
+ let raw_protocol = match protocol {
+ Some(p) => p.0.get(),
+ None => 0,
+ };
+ unsafe {
+ ret_owned_fd(c::socket(
+ domain.0 as c::c_int,
+ (type_.0 | flags.bits()) as c::c_int,
+ raw_protocol as c::c_int,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn bind_v4(sockfd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> {
+ unsafe {
+ ret(c::bind(
+ borrowed_fd(sockfd),
+ as_ptr(&encode_sockaddr_v4(addr)).cast(),
+ size_of::<c::sockaddr_in>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn bind_v6(sockfd: BorrowedFd<'_>, addr: &SocketAddrV6) -> io::Result<()> {
+ unsafe {
+ ret(c::bind(
+ borrowed_fd(sockfd),
+ as_ptr(&encode_sockaddr_v6(addr)).cast(),
+ size_of::<c::sockaddr_in6>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+pub(crate) fn bind_unix(sockfd: BorrowedFd<'_>, addr: &SocketAddrUnix) -> io::Result<()> {
+ unsafe {
+ ret(c::bind(
+ borrowed_fd(sockfd),
+ as_ptr(&addr.unix).cast(),
+ addr.addr_len(),
+ ))
+ }
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn bind_xdp(sockfd: BorrowedFd<'_>, addr: &SocketAddrXdp) -> io::Result<()> {
+ unsafe {
+ ret(c::bind(
+ borrowed_fd(sockfd),
+ as_ptr(&encode_sockaddr_xdp(addr)).cast(),
+ size_of::<c::sockaddr_xdp>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn connect_v4(sockfd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> {
+ unsafe {
+ ret(c::connect(
+ borrowed_fd(sockfd),
+ as_ptr(&encode_sockaddr_v4(addr)).cast(),
+ size_of::<c::sockaddr_in>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn connect_v6(sockfd: BorrowedFd<'_>, addr: &SocketAddrV6) -> io::Result<()> {
+ unsafe {
+ ret(c::connect(
+ borrowed_fd(sockfd),
+ as_ptr(&encode_sockaddr_v6(addr)).cast(),
+ size_of::<c::sockaddr_in6>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+pub(crate) fn connect_unix(sockfd: BorrowedFd<'_>, addr: &SocketAddrUnix) -> io::Result<()> {
+ unsafe {
+ ret(c::connect(
+ borrowed_fd(sockfd),
+ as_ptr(&addr.unix).cast(),
+ addr.addr_len(),
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn connect_unspec(sockfd: BorrowedFd<'_>) -> io::Result<()> {
+ debug_assert_eq!(c::AF_UNSPEC, 0);
+ let addr = MaybeUninit::<c::sockaddr_storage>::zeroed();
+ unsafe {
+ ret(c::connect(
+ borrowed_fd(sockfd),
+ as_ptr(&addr).cast(),
+ size_of::<c::sockaddr_storage>() as c::socklen_t,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn listen(sockfd: BorrowedFd<'_>, backlog: c::c_int) -> io::Result<()> {
+ unsafe { ret(c::listen(borrowed_fd(sockfd), backlog)) }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn accept(sockfd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
+ unsafe {
+ let owned_fd = ret_owned_fd(c::accept(borrowed_fd(sockfd), null_mut(), null_mut()))?;
+ Ok(owned_fd)
+ }
+}
+
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn recvmsg(
+ sockfd: BorrowedFd<'_>,
+ iov: &mut [IoSliceMut<'_>],
+ control: &mut RecvAncillaryBuffer<'_>,
+ msg_flags: RecvFlags,
+) -> io::Result<RecvMsgReturn> {
+ let mut storage = MaybeUninit::<c::sockaddr_storage>::uninit();
+
+ with_recv_msghdr(&mut storage, iov, control, |msghdr| {
+ let result = unsafe {
+ ret_send_recv(c::recvmsg(
+ borrowed_fd(sockfd),
+ msghdr,
+ bitflags_bits!(msg_flags),
+ ))
+ };
+
+ result.map(|bytes| {
+ // Get the address of the sender, if any.
+ let addr =
+ unsafe { maybe_read_sockaddr_os(msghdr.msg_name as _, msghdr.msg_namelen as _) };
+
+ RecvMsgReturn {
+ bytes,
+ address: addr,
+ flags: RecvFlags::from_bits_retain(bitcast!(msghdr.msg_flags)),
+ }
+ })
+ })
+}
+
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn sendmsg(
+ sockfd: BorrowedFd<'_>,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ with_noaddr_msghdr(iov, control, |msghdr| unsafe {
+ ret_send_recv(c::sendmsg(
+ borrowed_fd(sockfd),
+ &msghdr,
+ bitflags_bits!(msg_flags),
+ ))
+ })
+}
+
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn sendmsg_v4(
+ sockfd: BorrowedFd<'_>,
+ addr: &SocketAddrV4,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ with_v4_msghdr(addr, iov, control, |msghdr| unsafe {
+ ret_send_recv(c::sendmsg(
+ borrowed_fd(sockfd),
+ &msghdr,
+ bitflags_bits!(msg_flags),
+ ))
+ })
+}
+
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn sendmsg_v6(
+ sockfd: BorrowedFd<'_>,
+ addr: &SocketAddrV6,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ with_v6_msghdr(addr, iov, control, |msghdr| unsafe {
+ ret_send_recv(c::sendmsg(
+ borrowed_fd(sockfd),
+ &msghdr,
+ bitflags_bits!(msg_flags),
+ ))
+ })
+}
+
+#[cfg(all(
+ unix,
+ not(any(target_os = "espidf", target_os = "redox", target_os = "vita"))
+))]
+pub(crate) fn sendmsg_unix(
+ sockfd: BorrowedFd<'_>,
+ addr: &SocketAddrUnix,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ super::msghdr::with_unix_msghdr(addr, iov, control, |msghdr| unsafe {
+ ret_send_recv(c::sendmsg(
+ borrowed_fd(sockfd),
+ &msghdr,
+ bitflags_bits!(msg_flags),
+ ))
+ })
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn sendmsg_xdp(
+ sockfd: BorrowedFd<'_>,
+ addr: &SocketAddrXdp,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ with_xdp_msghdr(addr, iov, control, |msghdr| unsafe {
+ ret_send_recv(c::sendmsg(
+ borrowed_fd(sockfd),
+ &msghdr,
+ bitflags_bits!(msg_flags),
+ ))
+ })
+}
+
+#[cfg(not(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+pub(crate) fn accept_with(sockfd: BorrowedFd<'_>, flags: SocketFlags) -> io::Result<OwnedFd> {
+ unsafe {
+ let owned_fd = ret_owned_fd(c::accept4(
+ borrowed_fd(sockfd),
+ null_mut(),
+ null_mut(),
+ flags.bits() as c::c_int,
+ ))?;
+ Ok(owned_fd)
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn acceptfrom(sockfd: BorrowedFd<'_>) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ unsafe {
+ let mut storage = MaybeUninit::<c::sockaddr_storage>::uninit();
+ let mut len = size_of::<c::sockaddr_storage>() as c::socklen_t;
+ let owned_fd = ret_owned_fd(c::accept(
+ borrowed_fd(sockfd),
+ storage.as_mut_ptr().cast(),
+ &mut len,
+ ))?;
+ Ok((
+ owned_fd,
+ maybe_read_sockaddr_os(storage.as_ptr(), len.try_into().unwrap()),
+ ))
+ }
+}
+
+#[cfg(not(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+pub(crate) fn acceptfrom_with(
+ sockfd: BorrowedFd<'_>,
+ flags: SocketFlags,
+) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ unsafe {
+ let mut storage = MaybeUninit::<c::sockaddr_storage>::uninit();
+ let mut len = size_of::<c::sockaddr_storage>() as c::socklen_t;
+ let owned_fd = ret_owned_fd(c::accept4(
+ borrowed_fd(sockfd),
+ storage.as_mut_ptr().cast(),
+ &mut len,
+ flags.bits() as c::c_int,
+ ))?;
+ Ok((
+ owned_fd,
+ maybe_read_sockaddr_os(storage.as_ptr(), len.try_into().unwrap()),
+ ))
+ }
+}
+
+/// Darwin lacks `accept4`, but does have `accept`. We define
+/// `SocketFlags` to have no flags, so we can discard it here.
+#[cfg(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+))]
+pub(crate) fn accept_with(sockfd: BorrowedFd<'_>, _flags: SocketFlags) -> io::Result<OwnedFd> {
+ accept(sockfd)
+}
+
+/// Darwin lacks `accept4`, but does have `accept`. We define
+/// `SocketFlags` to have no flags, so we can discard it here.
+#[cfg(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+))]
+pub(crate) fn acceptfrom_with(
+ sockfd: BorrowedFd<'_>,
+ _flags: SocketFlags,
+) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ acceptfrom(sockfd)
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn shutdown(sockfd: BorrowedFd<'_>, how: Shutdown) -> io::Result<()> {
+ unsafe { ret(c::shutdown(borrowed_fd(sockfd), how as c::c_int)) }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn getsockname(sockfd: BorrowedFd<'_>) -> io::Result<SocketAddrAny> {
+ unsafe {
+ let mut storage = MaybeUninit::<c::sockaddr_storage>::uninit();
+ let mut len = size_of::<c::sockaddr_storage>() as c::socklen_t;
+ ret(c::getsockname(
+ borrowed_fd(sockfd),
+ storage.as_mut_ptr().cast(),
+ &mut len,
+ ))?;
+ Ok(read_sockaddr_os(storage.as_ptr(), len.try_into().unwrap()))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+pub(crate) fn getpeername(sockfd: BorrowedFd<'_>) -> io::Result<Option<SocketAddrAny>> {
+ unsafe {
+ let mut storage = MaybeUninit::<c::sockaddr_storage>::uninit();
+ let mut len = size_of::<c::sockaddr_storage>() as c::socklen_t;
+ ret(c::getpeername(
+ borrowed_fd(sockfd),
+ storage.as_mut_ptr().cast(),
+ &mut len,
+ ))?;
+ Ok(maybe_read_sockaddr_os(
+ storage.as_ptr(),
+ len.try_into().unwrap(),
+ ))
+ }
+}
+
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+pub(crate) fn socketpair(
+ domain: AddressFamily,
+ type_: SocketType,
+ flags: SocketFlags,
+ protocol: Option<Protocol>,
+) -> io::Result<(OwnedFd, OwnedFd)> {
+ let raw_protocol = match protocol {
+ Some(p) => p.0.get(),
+ None => 0,
+ };
+ unsafe {
+ let mut fds = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(c::socketpair(
+ c::c_int::from(domain.0),
+ (type_.0 | flags.bits()) as c::c_int,
+ raw_protocol as c::c_int,
+ fds.as_mut_ptr().cast::<c::c_int>(),
+ ))?;
+
+ let [fd0, fd1] = fds.assume_init();
+ Ok((fd0, fd1))
+ }
+}
diff --git a/src/backend/libc/net/write_sockaddr.rs b/src/backend/libc/net/write_sockaddr.rs
new file mode 100644
index 0000000..fdc5dbb
--- /dev/null
+++ b/src/backend/libc/net/write_sockaddr.rs
@@ -0,0 +1,125 @@
+//! The BSD sockets API requires us to read the `ss_family` field before we can
+//! interpret the rest of a `sockaddr` produced by the kernel.
+
+use super::addr::SocketAddrStorage;
+#[cfg(unix)]
+use super::addr::SocketAddrUnix;
+use super::ext::{in6_addr_new, in_addr_new, sockaddr_in6_new};
+use crate::backend::c;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+use crate::net::{SocketAddrAny, SocketAddrV4, SocketAddrV6};
+use core::mem::size_of;
+
+pub(crate) unsafe fn write_sockaddr(
+ addr: &SocketAddrAny,
+ storage: *mut SocketAddrStorage,
+) -> usize {
+ match addr {
+ SocketAddrAny::V4(v4) => write_sockaddr_v4(v4, storage),
+ SocketAddrAny::V6(v6) => write_sockaddr_v6(v6, storage),
+ #[cfg(unix)]
+ SocketAddrAny::Unix(unix) => write_sockaddr_unix(unix, storage),
+ #[cfg(target_os = "linux")]
+ SocketAddrAny::Xdp(xdp) => write_sockaddr_xdp(xdp, storage),
+ }
+}
+
+pub(crate) fn encode_sockaddr_v4(v4: &SocketAddrV4) -> c::sockaddr_in {
+ c::sockaddr_in {
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ ))]
+ sin_len: size_of::<c::sockaddr_in>() as _,
+ sin_family: c::AF_INET as _,
+ sin_port: u16::to_be(v4.port()),
+ sin_addr: in_addr_new(u32::from_ne_bytes(v4.ip().octets())),
+ #[cfg(not(any(target_os = "haiku", target_os = "vita")))]
+ sin_zero: [0; 8_usize],
+ #[cfg(target_os = "haiku")]
+ sin_zero: [0; 24_usize],
+ #[cfg(target_os = "vita")]
+ sin_zero: [0; 6_usize],
+ #[cfg(target_os = "vita")]
+ sin_vport: 0,
+ }
+}
+
+unsafe fn write_sockaddr_v4(v4: &SocketAddrV4, storage: *mut SocketAddrStorage) -> usize {
+ let encoded = encode_sockaddr_v4(v4);
+ core::ptr::write(storage.cast(), encoded);
+ size_of::<c::sockaddr_in>()
+}
+
+pub(crate) fn encode_sockaddr_v6(v6: &SocketAddrV6) -> c::sockaddr_in6 {
+ #[cfg(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ ))]
+ {
+ sockaddr_in6_new(
+ size_of::<c::sockaddr_in6>() as _,
+ c::AF_INET6 as _,
+ u16::to_be(v6.port()),
+ u32::to_be(v6.flowinfo()),
+ in6_addr_new(v6.ip().octets()),
+ v6.scope_id(),
+ )
+ }
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ {
+ sockaddr_in6_new(
+ c::AF_INET6 as _,
+ u16::to_be(v6.port()),
+ u32::to_be(v6.flowinfo()),
+ in6_addr_new(v6.ip().octets()),
+ v6.scope_id(),
+ )
+ }
+}
+
+unsafe fn write_sockaddr_v6(v6: &SocketAddrV6, storage: *mut SocketAddrStorage) -> usize {
+ let encoded = encode_sockaddr_v6(v6);
+ core::ptr::write(storage.cast(), encoded);
+ size_of::<c::sockaddr_in6>()
+}
+
+#[cfg(unix)]
+unsafe fn write_sockaddr_unix(unix: &SocketAddrUnix, storage: *mut SocketAddrStorage) -> usize {
+ core::ptr::write(storage.cast(), unix.unix);
+ unix.len()
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn encode_sockaddr_xdp(xdp: &SocketAddrXdp) -> c::sockaddr_xdp {
+ c::sockaddr_xdp {
+ sxdp_family: c::AF_XDP as _,
+ sxdp_flags: xdp.flags().bits(),
+ sxdp_ifindex: xdp.interface_index(),
+ sxdp_queue_id: xdp.queue_id(),
+ sxdp_shared_umem_fd: xdp.shared_umem_fd(),
+ }
+}
+
+#[cfg(target_os = "linux")]
+unsafe fn write_sockaddr_xdp(xdp: &SocketAddrXdp, storage: *mut SocketAddrStorage) -> usize {
+ let encoded = encode_sockaddr_xdp(xdp);
+ core::ptr::write(storage.cast(), encoded);
+ size_of::<c::sockaddr_xdp>()
+}
diff --git a/src/backend/libc/param/auxv.rs b/src/backend/libc/param/auxv.rs
new file mode 100644
index 0000000..880a1d4
--- /dev/null
+++ b/src/backend/libc/param/auxv.rs
@@ -0,0 +1,54 @@
+use crate::backend::c;
+#[cfg(any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+))]
+use crate::ffi::CStr;
+
+// `getauxval` wasn't supported in glibc until 2.16.
+#[cfg(any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+))]
+weak!(fn getauxval(c::c_ulong) -> *mut c::c_void);
+
+#[inline]
+pub(crate) fn page_size() -> usize {
+ unsafe { c::sysconf(c::_SC_PAGESIZE) as usize }
+}
+
+#[cfg(not(any(target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(crate) fn clock_ticks_per_second() -> u64 {
+ unsafe { c::sysconf(c::_SC_CLK_TCK) as u64 }
+}
+
+#[cfg(any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+))]
+#[inline]
+pub(crate) fn linux_hwcap() -> (usize, usize) {
+ if let Some(libc_getauxval) = getauxval.get() {
+ unsafe {
+ let hwcap = libc_getauxval(c::AT_HWCAP) as usize;
+ let hwcap2 = libc_getauxval(c::AT_HWCAP2) as usize;
+ (hwcap, hwcap2)
+ }
+ } else {
+ (0, 0)
+ }
+}
+
+#[cfg(any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+))]
+#[inline]
+pub(crate) fn linux_execfn() -> &'static CStr {
+ if let Some(libc_getauxval) = getauxval.get() {
+ unsafe { CStr::from_ptr(libc_getauxval(c::AT_EXECFN).cast()) }
+ } else {
+ cstr!("")
+ }
+}
diff --git a/src/backend/libc/param/mod.rs b/src/backend/libc/param/mod.rs
new file mode 100644
index 0000000..2cb2fe7
--- /dev/null
+++ b/src/backend/libc/param/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod auxv;
diff --git a/src/backend/libc/pid/mod.rs b/src/backend/libc/pid/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/libc/pid/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/pid/syscalls.rs b/src/backend/libc/pid/syscalls.rs
new file mode 100644
index 0000000..d0ed4bc
--- /dev/null
+++ b/src/backend/libc/pid/syscalls.rs
@@ -0,0 +1,14 @@
+//! libc syscalls for PIDs
+
+use crate::backend::c;
+use crate::pid::Pid;
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn getpid() -> Pid {
+ unsafe {
+ let pid = c::getpid();
+ Pid::from_raw_unchecked(pid)
+ }
+}
diff --git a/src/backend/libc/pipe/mod.rs b/src/backend/libc/pipe/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/libc/pipe/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/pipe/syscalls.rs b/src/backend/libc/pipe/syscalls.rs
new file mode 100644
index 0000000..cff932d
--- /dev/null
+++ b/src/backend/libc/pipe/syscalls.rs
@@ -0,0 +1,125 @@
+use crate::backend::c;
+use crate::backend::conv::ret;
+use crate::fd::OwnedFd;
+use crate::io;
+#[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "wasi"
+)))]
+use crate::pipe::PipeFlags;
+use core::mem::MaybeUninit;
+#[cfg(linux_kernel)]
+use {
+ crate::backend::conv::{borrowed_fd, ret_c_int, ret_usize},
+ crate::backend::MAX_IOV,
+ crate::fd::BorrowedFd,
+ crate::pipe::{IoSliceRaw, SpliceFlags},
+ crate::utils::option_as_mut_ptr,
+ core::cmp::min,
+};
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> {
+ unsafe {
+ let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(c::pipe(result.as_mut_ptr().cast::<i32>()))?;
+ let [p0, p1] = result.assume_init();
+ Ok((p0, p1))
+ }
+}
+
+#[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "wasi"
+)))]
+pub(crate) fn pipe_with(flags: PipeFlags) -> io::Result<(OwnedFd, OwnedFd)> {
+ unsafe {
+ let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(c::pipe2(
+ result.as_mut_ptr().cast::<i32>(),
+ bitflags_bits!(flags),
+ ))?;
+ let [p0, p1] = result.assume_init();
+ Ok((p0, p1))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub fn splice(
+ fd_in: BorrowedFd<'_>,
+ off_in: Option<&mut u64>,
+ fd_out: BorrowedFd<'_>,
+ off_out: Option<&mut u64>,
+ len: usize,
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ let off_in = option_as_mut_ptr(off_in).cast();
+ let off_out = option_as_mut_ptr(off_out).cast();
+
+ unsafe {
+ ret_usize(c::splice(
+ borrowed_fd(fd_in),
+ off_in,
+ borrowed_fd(fd_out),
+ off_out,
+ len,
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub unsafe fn vmsplice(
+ fd: BorrowedFd<'_>,
+ bufs: &[IoSliceRaw<'_>],
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ ret_usize(c::vmsplice(
+ borrowed_fd(fd),
+ bufs.as_ptr().cast::<c::iovec>(),
+ min(bufs.len(), MAX_IOV),
+ flags.bits(),
+ ))
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub fn tee(
+ fd_in: BorrowedFd<'_>,
+ fd_out: BorrowedFd<'_>,
+ len: usize,
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ unsafe {
+ ret_usize(c::tee(
+ borrowed_fd(fd_in),
+ borrowed_fd(fd_out),
+ len,
+ flags.bits(),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn fcntl_getpipe_sz(fd: BorrowedFd<'_>) -> io::Result<usize> {
+ unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETPIPE_SZ)).map(|size| size as usize) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn fcntl_setpipe_sz(fd: BorrowedFd<'_>, size: usize) -> io::Result<()> {
+ let size: c::c_int = size.try_into().map_err(|_| io::Errno::PERM)?;
+
+ unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETPIPE_SZ, size)) }
+}
diff --git a/src/backend/libc/pipe/types.rs b/src/backend/libc/pipe/types.rs
new file mode 100644
index 0000000..78fc2fc
--- /dev/null
+++ b/src/backend/libc/pipe/types.rs
@@ -0,0 +1,103 @@
+#[cfg(linux_kernel)]
+use core::marker::PhantomData;
+#[cfg(not(any(apple, target_os = "wasi")))]
+use {crate::backend::c, bitflags::bitflags};
+
+#[cfg(not(any(apple, target_os = "wasi")))]
+bitflags! {
+ /// `O_*` constants for use with [`pipe_with`].
+ ///
+ /// [`pipe_with`]: crate::pipe::pipe_with
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PipeFlags: u32 {
+ /// `O_CLOEXEC`
+ const CLOEXEC = bitcast!(c::O_CLOEXEC);
+ /// `O_DIRECT`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "vita",
+ )))]
+ const DIRECT = bitcast!(c::O_DIRECT);
+ /// `O_NONBLOCK`
+ const NONBLOCK = bitcast!(c::O_NONBLOCK);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `SPLICE_F_*` constants for use with [`splice`], [`vmsplice`], and
+ /// [`tee`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SpliceFlags: c::c_uint {
+ /// `SPLICE_F_MOVE`
+ const MOVE = c::SPLICE_F_MOVE;
+ /// `SPLICE_F_NONBLOCK`
+ const NONBLOCK = c::SPLICE_F_NONBLOCK;
+ /// `SPLICE_F_MORE`
+ const MORE = c::SPLICE_F_MORE;
+ /// `SPLICE_F_GIFT`
+ const GIFT = c::SPLICE_F_GIFT;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// A buffer type for use with [`vmsplice`].
+///
+/// It is guaranteed to be ABI compatible with the iovec type on Unix platforms
+/// and `WSABUF` on Windows. Unlike `IoSlice` and `IoSliceMut` it is
+/// semantically like a raw pointer, and therefore can be shared or mutated as
+/// needed.
+///
+/// [`vmsplice`]: crate::pipe::vmsplice
+#[cfg(linux_kernel)]
+#[repr(transparent)]
+pub struct IoSliceRaw<'a> {
+ _buf: c::iovec,
+ _lifetime: PhantomData<&'a ()>,
+}
+
+#[cfg(linux_kernel)]
+impl<'a> IoSliceRaw<'a> {
+ /// Creates a new `IoSlice` wrapping a byte slice.
+ pub fn from_slice(buf: &'a [u8]) -> Self {
+ IoSliceRaw {
+ _buf: c::iovec {
+ iov_base: buf.as_ptr() as *mut u8 as *mut c::c_void,
+ iov_len: buf.len() as _,
+ },
+ _lifetime: PhantomData,
+ }
+ }
+
+ /// Creates a new `IoSlice` wrapping a mutable byte slice.
+ pub fn from_slice_mut(buf: &'a mut [u8]) -> Self {
+ IoSliceRaw {
+ _buf: c::iovec {
+ iov_base: buf.as_mut_ptr() as *mut c::c_void,
+ iov_len: buf.len() as _,
+ },
+ _lifetime: PhantomData,
+ }
+ }
+}
+
+#[cfg(not(any(apple, target_os = "wasi")))]
+#[test]
+fn test_types() {
+ assert_eq_size!(PipeFlags, c::c_int);
+
+ #[cfg(linux_kernel)]
+ assert_eq_size!(SpliceFlags, c::c_int);
+}
diff --git a/src/backend/libc/prctl/mod.rs b/src/backend/libc/prctl/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/libc/prctl/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/prctl/syscalls.rs b/src/backend/libc/prctl/syscalls.rs
new file mode 100644
index 0000000..451cecc
--- /dev/null
+++ b/src/backend/libc/prctl/syscalls.rs
@@ -0,0 +1,14 @@
+use crate::backend::c;
+use crate::backend::conv::ret_c_int;
+use crate::io;
+
+#[inline]
+pub(crate) unsafe fn prctl(
+ option: c::c_int,
+ arg2: *mut c::c_void,
+ arg3: *mut c::c_void,
+ arg4: *mut c::c_void,
+ arg5: *mut c::c_void,
+) -> io::Result<c::c_int> {
+ ret_c_int(c::prctl(option, arg2, arg3, arg4, arg5))
+}
diff --git a/src/backend/libc/process/cpu_set.rs b/src/backend/libc/process/cpu_set.rs
new file mode 100644
index 0000000..4cf06b9
--- /dev/null
+++ b/src/backend/libc/process/cpu_set.rs
@@ -0,0 +1,50 @@
+//! Rust implementation of the `CPU_*` macro API.
+
+#![allow(non_snake_case)]
+
+use super::types::{RawCpuSet, CPU_SETSIZE};
+use crate::backend::c;
+
+#[inline]
+pub(crate) fn CPU_SET(cpu: usize, cpuset: &mut RawCpuSet) {
+ assert!(
+ cpu < CPU_SETSIZE,
+ "cpu out of bounds: the cpu max is {} but the cpu is {}",
+ CPU_SETSIZE,
+ cpu
+ );
+ unsafe { c::CPU_SET(cpu, cpuset) }
+}
+
+#[inline]
+pub(crate) fn CPU_ZERO(cpuset: &mut RawCpuSet) {
+ unsafe { c::CPU_ZERO(cpuset) }
+}
+
+#[inline]
+pub(crate) fn CPU_CLR(cpu: usize, cpuset: &mut RawCpuSet) {
+ assert!(
+ cpu < CPU_SETSIZE,
+ "cpu out of bounds: the cpu max is {} but the cpu is {}",
+ CPU_SETSIZE,
+ cpu
+ );
+ unsafe { c::CPU_CLR(cpu, cpuset) }
+}
+
+#[inline]
+pub(crate) fn CPU_ISSET(cpu: usize, cpuset: &RawCpuSet) -> bool {
+ assert!(
+ cpu < CPU_SETSIZE,
+ "cpu out of bounds: the cpu max is {} but the cpu is {}",
+ CPU_SETSIZE,
+ cpu
+ );
+ unsafe { c::CPU_ISSET(cpu, cpuset) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn CPU_COUNT(cpuset: &RawCpuSet) -> u32 {
+ unsafe { c::CPU_COUNT(cpuset).try_into().unwrap() }
+}
diff --git a/src/backend/libc/process/mod.rs b/src/backend/libc/process/mod.rs
new file mode 100644
index 0000000..39803b5
--- /dev/null
+++ b/src/backend/libc/process/mod.rs
@@ -0,0 +1,7 @@
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+pub(crate) mod cpu_set;
+#[cfg(not(windows))]
+pub(crate) mod syscalls;
+pub(crate) mod types;
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+pub(crate) mod wait;
diff --git a/src/backend/libc/process/syscalls.rs b/src/backend/libc/process/syscalls.rs
new file mode 100644
index 0000000..8d931b9
--- /dev/null
+++ b/src/backend/libc/process/syscalls.rs
@@ -0,0 +1,715 @@
+//! libc syscalls supporting `rustix::process`.
+
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+use super::types::RawCpuSet;
+use crate::backend::c;
+#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
+use crate::backend::conv::borrowed_fd;
+#[cfg(feature = "fs")]
+use crate::backend::conv::c_str;
+#[cfg(all(feature = "alloc", feature = "fs", not(target_os = "wasi")))]
+use crate::backend::conv::ret_discarded_char_ptr;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::backend::conv::ret_infallible;
+#[cfg(not(target_os = "wasi"))]
+use crate::backend::conv::ret_pid_t;
+#[cfg(linux_kernel)]
+use crate::backend::conv::ret_u32;
+#[cfg(all(feature = "alloc", not(target_os = "wasi")))]
+use crate::backend::conv::ret_usize;
+use crate::backend::conv::{ret, ret_c_int};
+#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
+use crate::fd::BorrowedFd;
+#[cfg(target_os = "linux")]
+use crate::fd::{AsRawFd, OwnedFd, RawFd};
+#[cfg(feature = "fs")]
+use crate::ffi::CStr;
+#[cfg(feature = "fs")]
+use crate::fs::Mode;
+use crate::io;
+#[cfg(all(feature = "alloc", not(target_os = "wasi")))]
+use crate::process::Gid;
+#[cfg(not(target_os = "wasi"))]
+use crate::process::Pid;
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+use crate::process::Signal;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::process::Uid;
+#[cfg(linux_kernel)]
+use crate::process::{Cpuid, MembarrierCommand, MembarrierQuery};
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+use crate::process::{RawPid, WaitOptions, WaitStatus};
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::process::{Resource, Rlimit};
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "openbsd",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::process::{WaitId, WaitidOptions, WaitidStatus};
+use core::mem::MaybeUninit;
+#[cfg(target_os = "linux")]
+use {
+ super::super::conv::ret_owned_fd, crate::process::PidfdFlags, crate::process::PidfdGetfdFlags,
+};
+
+use errno as libc_errno;
+
+#[cfg(any(linux_kernel, target_os = "dragonfly"))]
+#[inline]
+pub(crate) fn sched_getcpu() -> usize {
+ let r = unsafe { libc::sched_getcpu() };
+ debug_assert!(r >= 0);
+ r as usize
+}
+
+#[cfg(feature = "fs")]
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn chdir(path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::chdir(c_str(path))) }
+}
+
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+pub(crate) fn fchdir(dirfd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::fchdir(borrowed_fd(dirfd))) }
+}
+
+#[cfg(feature = "fs")]
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+pub(crate) fn chroot(path: &CStr) -> io::Result<()> {
+ unsafe { ret(c::chroot(c_str(path))) }
+}
+
+#[cfg(all(feature = "alloc", feature = "fs"))]
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn getcwd(buf: &mut [MaybeUninit<u8>]) -> io::Result<()> {
+ unsafe { ret_discarded_char_ptr(c::getcwd(buf.as_mut_ptr().cast(), buf.len())) }
+}
+
+// The `membarrier` syscall has a third argument, but it's only used when
+// the `flags` argument is `MEMBARRIER_CMD_FLAG_CPU`.
+#[cfg(linux_kernel)]
+syscall! {
+ fn membarrier_all(
+ cmd: c::c_int,
+ flags: c::c_uint
+ ) via SYS_membarrier -> c::c_int
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn membarrier_query() -> MembarrierQuery {
+ // glibc does not have a wrapper for `membarrier`; [the documentation]
+ // says to use `syscall`.
+ //
+ // [the documentation]: https://man7.org/linux/man-pages/man2/membarrier.2.html#NOTES
+ const MEMBARRIER_CMD_QUERY: u32 = 0;
+ unsafe {
+ match ret_u32(membarrier_all(MEMBARRIER_CMD_QUERY as i32, 0)) {
+ Ok(query) => MembarrierQuery::from_bits_retain(query),
+ Err(_) => MembarrierQuery::empty(),
+ }
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn membarrier(cmd: MembarrierCommand) -> io::Result<()> {
+ unsafe { ret(membarrier_all(cmd as i32, 0)) }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn membarrier_cpu(cmd: MembarrierCommand, cpu: Cpuid) -> io::Result<()> {
+ const MEMBARRIER_CMD_FLAG_CPU: u32 = 1;
+
+ syscall! {
+ fn membarrier_cpu(
+ cmd: c::c_int,
+ flags: c::c_uint,
+ cpu_id: c::c_int
+ ) via SYS_membarrier -> c::c_int
+ }
+
+ unsafe {
+ ret(membarrier_cpu(
+ cmd as i32,
+ MEMBARRIER_CMD_FLAG_CPU,
+ bitcast!(cpu.as_raw()),
+ ))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn getppid() -> Option<Pid> {
+ unsafe {
+ let pid: i32 = c::getppid();
+ Pid::from_raw(pid)
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub(crate) fn getpgid(pid: Option<Pid>) -> io::Result<Pid> {
+ unsafe {
+ let pgid = ret_pid_t(c::getpgid(Pid::as_raw(pid) as _))?;
+ Ok(Pid::from_raw_unchecked(pgid))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub(crate) fn setpgid(pid: Option<Pid>, pgid: Option<Pid>) -> io::Result<()> {
+ unsafe { ret(c::setpgid(Pid::as_raw(pid) as _, Pid::as_raw(pgid) as _)) }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn getpgrp() -> Pid {
+ unsafe {
+ let pgid = c::getpgrp();
+ Pid::from_raw_unchecked(pgid)
+ }
+}
+
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn sched_getaffinity(pid: Option<Pid>, cpuset: &mut RawCpuSet) -> io::Result<()> {
+ unsafe {
+ ret(c::sched_getaffinity(
+ Pid::as_raw(pid) as _,
+ core::mem::size_of::<RawCpuSet>(),
+ cpuset,
+ ))
+ }
+}
+
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn sched_setaffinity(pid: Option<Pid>, cpuset: &RawCpuSet) -> io::Result<()> {
+ unsafe {
+ ret(c::sched_setaffinity(
+ Pid::as_raw(pid) as _,
+ core::mem::size_of::<RawCpuSet>(),
+ cpuset,
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn sched_yield() {
+ unsafe {
+ let _ = c::sched_yield();
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[cfg(feature = "fs")]
+#[inline]
+pub(crate) fn umask(mask: Mode) -> Mode {
+ unsafe { Mode::from_bits_retain(c::umask(mask.bits() as c::mode_t).into()) }
+}
+
+#[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(crate) fn nice(inc: i32) -> io::Result<i32> {
+ libc_errno::set_errno(libc_errno::Errno(0));
+ let r = unsafe { c::nice(inc) };
+ if libc_errno::errno().0 != 0 {
+ ret_c_int(r)
+ } else {
+ Ok(r)
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn getpriority_user(uid: Uid) -> io::Result<i32> {
+ libc_errno::set_errno(libc_errno::Errno(0));
+ let r = unsafe { c::getpriority(c::PRIO_USER, uid.as_raw() as _) };
+ if libc_errno::errno().0 != 0 {
+ ret_c_int(r)
+ } else {
+ Ok(r)
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn getpriority_pgrp(pgid: Option<Pid>) -> io::Result<i32> {
+ libc_errno::set_errno(libc_errno::Errno(0));
+ let r = unsafe { c::getpriority(c::PRIO_PGRP, Pid::as_raw(pgid) as _) };
+ if libc_errno::errno().0 != 0 {
+ ret_c_int(r)
+ } else {
+ Ok(r)
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn getpriority_process(pid: Option<Pid>) -> io::Result<i32> {
+ libc_errno::set_errno(libc_errno::Errno(0));
+ let r = unsafe { c::getpriority(c::PRIO_PROCESS, Pid::as_raw(pid) as _) };
+ if libc_errno::errno().0 != 0 {
+ ret_c_int(r)
+ } else {
+ Ok(r)
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn setpriority_user(uid: Uid, priority: i32) -> io::Result<()> {
+ unsafe { ret(c::setpriority(c::PRIO_USER, uid.as_raw() as _, priority)) }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn setpriority_pgrp(pgid: Option<Pid>, priority: i32) -> io::Result<()> {
+ unsafe {
+ ret(c::setpriority(
+ c::PRIO_PGRP,
+ Pid::as_raw(pgid) as _,
+ priority,
+ ))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn setpriority_process(pid: Option<Pid>, priority: i32) -> io::Result<()> {
+ unsafe {
+ ret(c::setpriority(
+ c::PRIO_PROCESS,
+ Pid::as_raw(pid) as _,
+ priority,
+ ))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn getrlimit(limit: Resource) -> Rlimit {
+ let mut result = MaybeUninit::<c::rlimit>::uninit();
+ unsafe {
+ ret_infallible(c::getrlimit(limit as _, result.as_mut_ptr()));
+ rlimit_from_libc(result.assume_init())
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn setrlimit(limit: Resource, new: Rlimit) -> io::Result<()> {
+ let lim = rlimit_to_libc(new)?;
+ unsafe { ret(c::setrlimit(limit as _, &lim)) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn prlimit(pid: Option<Pid>, limit: Resource, new: Rlimit) -> io::Result<Rlimit> {
+ let lim = rlimit_to_libc(new)?;
+ let mut result = MaybeUninit::<c::rlimit>::uninit();
+ unsafe {
+ ret(c::prlimit(
+ Pid::as_raw(pid),
+ limit as _,
+ &lim,
+ result.as_mut_ptr(),
+ ))?;
+ Ok(rlimit_from_libc(result.assume_init()))
+ }
+}
+
+/// Convert a Rust [`Rlimit`] to a C `c::rlimit`.
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+fn rlimit_from_libc(lim: c::rlimit) -> Rlimit {
+ let current = if lim.rlim_cur == c::RLIM_INFINITY {
+ None
+ } else {
+ Some(lim.rlim_cur.try_into().unwrap())
+ };
+ let maximum = if lim.rlim_max == c::RLIM_INFINITY {
+ None
+ } else {
+ Some(lim.rlim_max.try_into().unwrap())
+ };
+ Rlimit { current, maximum }
+}
+
+/// Convert a C `c::rlimit` to a Rust `Rlimit`.
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+fn rlimit_to_libc(lim: Rlimit) -> io::Result<c::rlimit> {
+ let Rlimit { current, maximum } = lim;
+ let rlim_cur = match current {
+ Some(r) => r.try_into().map_err(|_e| io::Errno::INVAL)?,
+ None => c::RLIM_INFINITY as _,
+ };
+ let rlim_max = match maximum {
+ Some(r) => r.try_into().map_err(|_e| io::Errno::INVAL)?,
+ None => c::RLIM_INFINITY as _,
+ };
+ Ok(c::rlimit { rlim_cur, rlim_max })
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(crate) fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
+ _waitpid(!0, waitopts)
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(crate) fn waitpid(
+ pid: Option<Pid>,
+ waitopts: WaitOptions,
+) -> io::Result<Option<(Pid, WaitStatus)>> {
+ _waitpid(Pid::as_raw(pid), waitopts)
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(crate) fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
+ _waitpid(-pgid.as_raw_nonzero().get(), waitopts)
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub(crate) fn _waitpid(
+ pid: RawPid,
+ waitopts: WaitOptions,
+) -> io::Result<Option<(Pid, WaitStatus)>> {
+ unsafe {
+ let mut status: c::c_int = 0;
+ let pid = ret_c_int(c::waitpid(pid as _, &mut status, waitopts.bits() as _))?;
+ Ok(Pid::from_raw(pid).map(|pid| (pid, WaitStatus::new(status as _))))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "openbsd",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub(crate) fn waitid(id: WaitId<'_>, options: WaitidOptions) -> io::Result<Option<WaitidStatus>> {
+ // Get the id to wait on.
+ match id {
+ WaitId::All => _waitid_all(options),
+ WaitId::Pid(pid) => _waitid_pid(pid, options),
+ WaitId::Pgid(pgid) => _waitid_pgid(pgid, options),
+ #[cfg(target_os = "linux")]
+ WaitId::PidFd(fd) => _waitid_pidfd(fd, options),
+ #[cfg(not(target_os = "linux"))]
+ WaitId::__EatLifetime(_) => unreachable!(),
+ }
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "openbsd",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+fn _waitid_all(options: WaitidOptions) -> io::Result<Option<WaitidStatus>> {
+ // `waitid` can return successfully without initializing the struct (no
+ // children found when using `WNOHANG`)
+ let mut status = MaybeUninit::<c::siginfo_t>::zeroed();
+ unsafe {
+ ret(c::waitid(
+ c::P_ALL,
+ 0,
+ status.as_mut_ptr(),
+ options.bits() as _,
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "openbsd",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+fn _waitid_pid(pid: Pid, options: WaitidOptions) -> io::Result<Option<WaitidStatus>> {
+ // `waitid` can return successfully without initializing the struct (no
+ // children found when using `WNOHANG`)
+ let mut status = MaybeUninit::<c::siginfo_t>::zeroed();
+ unsafe {
+ ret(c::waitid(
+ c::P_PID,
+ Pid::as_raw(Some(pid)) as _,
+ status.as_mut_ptr(),
+ options.bits() as _,
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "openbsd",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+fn _waitid_pgid(pgid: Option<Pid>, options: WaitidOptions) -> io::Result<Option<WaitidStatus>> {
+ // `waitid` can return successfully without initializing the struct (no
+ // children found when using `WNOHANG`)
+ let mut status = MaybeUninit::<c::siginfo_t>::zeroed();
+ unsafe {
+ ret(c::waitid(
+ c::P_PGID,
+ Pid::as_raw(pgid) as _,
+ status.as_mut_ptr(),
+ options.bits() as _,
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+fn _waitid_pidfd(fd: BorrowedFd<'_>, options: WaitidOptions) -> io::Result<Option<WaitidStatus>> {
+ // `waitid` can return successfully without initializing the struct (no
+ // children found when using `WNOHANG`)
+ let mut status = MaybeUninit::<c::siginfo_t>::zeroed();
+ unsafe {
+ ret(c::waitid(
+ c::P_PIDFD,
+ fd.as_raw_fd() as _,
+ status.as_mut_ptr(),
+ options.bits() as _,
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+/// Convert a `siginfo_t` to a `WaitidStatus`.
+///
+/// # Safety
+///
+/// The caller must ensure that `status` is initialized and that `waitid`
+/// returned successfully.
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+unsafe fn cvt_waitid_status(status: MaybeUninit<c::siginfo_t>) -> Option<WaitidStatus> {
+ let status = status.assume_init();
+ // `si_pid` is supposedly the better way to check that the struct has been
+ // filled, e.g. the Linux manpage says about the `WNOHANG` case “zero out
+ // the si_pid field before the call and check for a nonzero value”.
+ // But e.g. NetBSD/OpenBSD don't have it exposed in the libc crate for now,
+ // and some platforms don't have it at all. For simplicity, always check
+ // `si_signo`. We have zero-initialized the whole struct, and all kernels
+ // should set `SIGCHLD` here.
+ if status.si_signo == 0 {
+ None
+ } else {
+ Some(WaitidStatus(status))
+ }
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub(crate) fn getsid(pid: Option<Pid>) -> io::Result<Pid> {
+ unsafe {
+ let pid = ret_pid_t(c::getsid(Pid::as_raw(pid) as _))?;
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub(crate) fn setsid() -> io::Result<Pid> {
+ unsafe {
+ let pid = ret_c_int(c::setsid())?;
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn kill_process(pid: Pid, sig: Signal) -> io::Result<()> {
+ unsafe { ret(c::kill(pid.as_raw_nonzero().get(), sig as i32)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> {
+ unsafe {
+ ret(c::kill(
+ pid.as_raw_nonzero().get().wrapping_neg(),
+ sig as i32,
+ ))
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn kill_current_process_group(sig: Signal) -> io::Result<()> {
+ unsafe { ret(c::kill(0, sig as i32)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn test_kill_process(pid: Pid) -> io::Result<()> {
+ unsafe { ret(c::kill(pid.as_raw_nonzero().get(), 0)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn test_kill_process_group(pid: Pid) -> io::Result<()> {
+ unsafe { ret(c::kill(pid.as_raw_nonzero().get().wrapping_neg(), 0)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn test_kill_current_process_group() -> io::Result<()> {
+ unsafe { ret(c::kill(0, 0)) }
+}
+
+#[cfg(freebsdlike)]
+#[inline]
+pub(crate) unsafe fn procctl(
+ idtype: c::idtype_t,
+ id: c::id_t,
+ option: c::c_int,
+ data: *mut c::c_void,
+) -> io::Result<()> {
+ ret(c::procctl(idtype, id, option, data))
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn pidfd_open(pid: Pid, flags: PidfdFlags) -> io::Result<OwnedFd> {
+ syscall! {
+ fn pidfd_open(
+ pid: c::pid_t,
+ flags: c::c_uint
+ ) via SYS_pidfd_open -> c::c_int
+ }
+ unsafe {
+ ret_owned_fd(pidfd_open(
+ pid.as_raw_nonzero().get(),
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn pidfd_getfd(
+ pidfd: BorrowedFd<'_>,
+ targetfd: RawFd,
+ flags: PidfdGetfdFlags,
+) -> io::Result<OwnedFd> {
+ syscall! {
+ fn pidfd_getfd(
+ pidfd: c::c_int,
+ targetfd: c::c_int,
+ flags: c::c_uint
+ ) via SYS_pidfd_getfd -> c::c_int
+ }
+ unsafe {
+ ret_owned_fd(pidfd_getfd(
+ borrowed_fd(pidfd),
+ targetfd,
+ bitflags_bits!(flags),
+ ))
+ }
+}
+
+#[cfg(all(feature = "alloc", not(target_os = "wasi")))]
+pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result<usize> {
+ let len = buf.len().try_into().map_err(|_| io::Errno::NOMEM)?;
+
+ unsafe { ret_usize(c::getgroups(len, buf.as_mut_ptr().cast()) as isize) }
+}
diff --git a/src/backend/libc/process/types.rs b/src/backend/libc/process/types.rs
new file mode 100644
index 0000000..f914128
--- /dev/null
+++ b/src/backend/libc/process/types.rs
@@ -0,0 +1,172 @@
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+use crate::backend::c;
+
+/// A command for use with [`membarrier`] and [`membarrier_cpu`].
+///
+/// For `MEMBARRIER_CMD_QUERY`, see [`membarrier_query`].
+///
+/// [`membarrier`]: crate::process::membarrier
+/// [`membarrier_cpu`]: crate::process::membarrier_cpu
+/// [`membarrier_query`]: crate::process::membarrier_query
+#[cfg(linux_kernel)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[repr(u32)]
+pub enum MembarrierCommand {
+ /// `MEMBARRIER_CMD_GLOBAL`
+ #[doc(alias = "Shared")]
+ #[doc(alias = "MEMBARRIER_CMD_SHARED")]
+ Global = c::MEMBARRIER_CMD_GLOBAL as u32,
+ /// `MEMBARRIER_CMD_GLOBAL_EXPEDITED`
+ GlobalExpedited = c::MEMBARRIER_CMD_GLOBAL_EXPEDITED as u32,
+ /// `MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED`
+ RegisterGlobalExpedited = c::MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED as u32,
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED`
+ PrivateExpedited = c::MEMBARRIER_CMD_PRIVATE_EXPEDITED as u32,
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED`
+ RegisterPrivateExpedited = c::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED as u32,
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE`
+ PrivateExpeditedSyncCore = c::MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE as u32,
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE`
+ RegisterPrivateExpeditedSyncCore =
+ c::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE as u32,
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ` (since Linux 5.10)
+ PrivateExpeditedRseq = c::MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ as u32,
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ` (since Linux 5.10)
+ RegisterPrivateExpeditedRseq = c::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ as u32,
+}
+
+/// A resource value for use with [`getrlimit`], [`setrlimit`], and
+/// [`prlimit`].
+///
+/// [`getrlimit`]: crate::process::getrlimit
+/// [`setrlimit`]: crate::process::setrlimit
+/// [`prlimit`]: crate::process::prlimit
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(not(target_os = "l4re"), repr(u32))]
+#[cfg_attr(target_os = "l4re", repr(u64))]
+pub enum Resource {
+ /// `RLIMIT_CPU`
+ Cpu = bitcast!(c::RLIMIT_CPU),
+ /// `RLIMIT_FSIZE`
+ Fsize = bitcast!(c::RLIMIT_FSIZE),
+ /// `RLIMIT_DATA`
+ Data = bitcast!(c::RLIMIT_DATA),
+ /// `RLIMIT_STACK`
+ Stack = bitcast!(c::RLIMIT_STACK),
+ /// `RLIMIT_CORE`
+ #[cfg(not(target_os = "haiku"))]
+ Core = bitcast!(c::RLIMIT_CORE),
+ /// `RLIMIT_RSS`
+ // "nto" has `RLIMIT_RSS`, but it has the same value as `RLIMIT_AS`.
+ #[cfg(not(any(apple, solarish, target_os = "nto", target_os = "haiku")))]
+ Rss = bitcast!(c::RLIMIT_RSS),
+ /// `RLIMIT_NPROC`
+ #[cfg(not(any(solarish, target_os = "haiku")))]
+ Nproc = bitcast!(c::RLIMIT_NPROC),
+ /// `RLIMIT_NOFILE`
+ Nofile = bitcast!(c::RLIMIT_NOFILE),
+ /// `RLIMIT_MEMLOCK`
+ #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))]
+ Memlock = bitcast!(c::RLIMIT_MEMLOCK),
+ /// `RLIMIT_AS`
+ #[cfg(not(target_os = "openbsd"))]
+ As = bitcast!(c::RLIMIT_AS),
+ /// `RLIMIT_LOCKS`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto"
+ )))]
+ Locks = bitcast!(c::RLIMIT_LOCKS),
+ /// `RLIMIT_SIGPENDING`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto"
+ )))]
+ Sigpending = bitcast!(c::RLIMIT_SIGPENDING),
+ /// `RLIMIT_MSGQUEUE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto"
+ )))]
+ Msgqueue = bitcast!(c::RLIMIT_MSGQUEUE),
+ /// `RLIMIT_NICE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto"
+ )))]
+ Nice = bitcast!(c::RLIMIT_NICE),
+ /// `RLIMIT_RTPRIO`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto"
+ )))]
+ Rtprio = bitcast!(c::RLIMIT_RTPRIO),
+ /// `RLIMIT_RTTIME`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "android",
+ target_os = "emscripten",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto",
+ )))]
+ Rttime = bitcast!(c::RLIMIT_RTTIME),
+}
+
+#[cfg(apple)]
+#[allow(non_upper_case_globals)]
+impl Resource {
+ /// `RLIMIT_RSS`
+ pub const Rss: Self = Self::As;
+}
+
+/// A CPU identifier as a raw integer.
+#[cfg(linux_kernel)]
+pub type RawCpuid = u32;
+#[cfg(freebsdlike)]
+pub type RawId = c::id_t;
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+pub(crate) type RawCpuSet = c::cpu_set_t;
+#[cfg(freebsdlike)]
+pub(crate) type RawCpuSet = c::cpuset_t;
+
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+#[inline]
+pub(crate) fn raw_cpu_set_new() -> RawCpuSet {
+ let mut set = unsafe { core::mem::zeroed() };
+ super::cpu_set::CPU_ZERO(&mut set);
+ set
+}
+
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+pub(crate) const CPU_SETSIZE: usize = c::CPU_SETSIZE as usize;
diff --git a/src/backend/libc/process/wait.rs b/src/backend/libc/process/wait.rs
new file mode 100644
index 0000000..9a932cf
--- /dev/null
+++ b/src/backend/libc/process/wait.rs
@@ -0,0 +1,9 @@
+use crate::backend::c;
+
+pub(crate) use c::{
+ WCONTINUED, WEXITSTATUS, WIFCONTINUED, WIFEXITED, WIFSIGNALED, WIFSTOPPED, WNOHANG, WSTOPSIG,
+ WTERMSIG, WUNTRACED,
+};
+
+#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
+pub(crate) use c::{WEXITED, WNOWAIT, WSTOPPED};
diff --git a/src/backend/libc/pty/mod.rs b/src/backend/libc/pty/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/libc/pty/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/pty/syscalls.rs b/src/backend/libc/pty/syscalls.rs
new file mode 100644
index 0000000..85d2255
--- /dev/null
+++ b/src/backend/libc/pty/syscalls.rs
@@ -0,0 +1,108 @@
+//! libc syscalls supporting `rustix::pty`.
+
+use crate::backend::c;
+use crate::backend::conv::{borrowed_fd, ret};
+use crate::fd::BorrowedFd;
+use crate::io;
+#[cfg(all(
+ feature = "alloc",
+ any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia")
+))]
+use {
+ crate::ffi::{CStr, CString},
+ crate::path::SMALL_PATH_BUFFER_SIZE,
+ alloc::borrow::ToOwned,
+ alloc::vec::Vec,
+};
+
+#[cfg(not(linux_kernel))]
+use crate::{backend::conv::ret_owned_fd, fd::OwnedFd, pty::OpenptFlags};
+
+use errno as libc_errno;
+
+#[cfg(not(linux_kernel))]
+#[inline]
+pub(crate) fn openpt(flags: OpenptFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::posix_openpt(flags.bits() as _)) }
+}
+
+#[cfg(all(
+ feature = "alloc",
+ any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia")
+))]
+#[inline]
+pub(crate) fn ptsname(fd: BorrowedFd<'_>, mut buffer: Vec<u8>) -> io::Result<CString> {
+ // This code would benefit from having a better way to read into
+ // uninitialized memory, but that requires `unsafe`.
+ buffer.clear();
+ buffer.reserve(SMALL_PATH_BUFFER_SIZE);
+ buffer.resize(buffer.capacity(), 0_u8);
+
+ loop {
+ // On platforms with `ptsname_r`, use it.
+ #[cfg(any(linux_like, target_os = "fuchsia"))]
+ let r = unsafe { c::ptsname_r(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len()) };
+
+ // FreeBSD 12 doesn't have `ptsname_r`.
+ #[cfg(target_os = "freebsd")]
+ let r = unsafe {
+ weak! {
+ fn ptsname_r(
+ c::c_int,
+ *mut c::c_char,
+ c::size_t
+ ) -> c::c_int
+ }
+ if let Some(func) = ptsname_r.get() {
+ func(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len())
+ } else {
+ libc::ENOSYS
+ }
+ };
+
+ // macOS 10.13.4 has `ptsname_r`; use it if we have it, otherwise fall
+ // back to calling the underlying ioctl directly.
+ #[cfg(apple)]
+ let r = unsafe {
+ weak! { fn ptsname_r(c::c_int, *mut c::c_char, c::size_t) -> c::c_int }
+
+ if let Some(libc_ptsname_r) = ptsname_r.get() {
+ libc_ptsname_r(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len())
+ } else {
+ // The size declared in the `TIOCPTYGNAME` macro in
+ // sys/ttycom.h is 128.
+ let mut name: [u8; 128] = [0_u8; 128];
+ match c::ioctl(borrowed_fd(fd), c::TIOCPTYGNAME as _, &mut name) {
+ 0 => {
+ let len = CStr::from_ptr(name.as_ptr().cast()).to_bytes().len();
+ std::ptr::copy_nonoverlapping(name.as_ptr(), buffer.as_mut_ptr(), len + 1);
+ 0
+ }
+ _ => libc_errno::errno().0,
+ }
+ }
+ };
+
+ if r == 0 {
+ return Ok(unsafe { CStr::from_ptr(buffer.as_ptr().cast()).to_owned() });
+ }
+ if r != c::ERANGE {
+ return Err(io::Errno::from_raw_os_error(r));
+ }
+
+ // Use `Vec` reallocation strategy to grow capacity exponentially.
+ buffer.reserve(1);
+ buffer.resize(buffer.capacity(), 0_u8);
+ }
+}
+
+#[inline]
+pub(crate) fn unlockpt(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::unlockpt(borrowed_fd(fd))) }
+}
+
+#[cfg(not(linux_kernel))]
+#[inline]
+pub(crate) fn grantpt(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::grantpt(borrowed_fd(fd))) }
+}
diff --git a/src/backend/libc/rand/mod.rs b/src/backend/libc/rand/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/libc/rand/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/rand/syscalls.rs b/src/backend/libc/rand/syscalls.rs
new file mode 100644
index 0000000..3a3929e
--- /dev/null
+++ b/src/backend/libc/rand/syscalls.rs
@@ -0,0 +1,18 @@
+//! libc syscalls supporting `rustix::rand`.
+
+#[cfg(linux_kernel)]
+use {crate::backend::c, crate::backend::conv::ret_usize, crate::io, crate::rand::GetRandomFlags};
+
+#[cfg(linux_kernel)]
+pub(crate) unsafe fn getrandom(
+ buf: *mut u8,
+ cap: usize,
+ flags: GetRandomFlags,
+) -> io::Result<usize> {
+ // `getrandom` wasn't supported in glibc until 2.25.
+ weak_or_syscall! {
+ fn getrandom(buf: *mut c::c_void, buflen: c::size_t, flags: c::c_uint) via SYS_getrandom -> c::ssize_t
+ }
+
+ ret_usize(getrandom(buf.cast(), cap, flags.bits()))
+}
diff --git a/src/backend/libc/rand/types.rs b/src/backend/libc/rand/types.rs
new file mode 100644
index 0000000..46690b5
--- /dev/null
+++ b/src/backend/libc/rand/types.rs
@@ -0,0 +1,24 @@
+#[cfg(linux_kernel)]
+use crate::backend::c;
+#[cfg(linux_kernel)]
+use bitflags::bitflags;
+
+#[cfg(linux_kernel)]
+bitflags! {
+ /// `GRND_*` flags for use with [`getrandom`].
+ ///
+ /// [`getrandom`]: crate::rand::getrandom
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct GetRandomFlags: u32 {
+ /// `GRND_RANDOM`
+ const RANDOM = c::GRND_RANDOM;
+ /// `GRND_NONBLOCK`
+ const NONBLOCK = c::GRND_NONBLOCK;
+ /// `GRND_INSECURE`
+ const INSECURE = c::GRND_INSECURE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/libc/shm/mod.rs b/src/backend/libc/shm/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/libc/shm/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/shm/syscalls.rs b/src/backend/libc/shm/syscalls.rs
new file mode 100644
index 0000000..b0d907f
--- /dev/null
+++ b/src/backend/libc/shm/syscalls.rs
@@ -0,0 +1,25 @@
+use crate::ffi::CStr;
+
+use crate::backend::c;
+use crate::backend::conv::{c_str, ret, ret_owned_fd};
+use crate::fd::OwnedFd;
+use crate::fs::Mode;
+use crate::io;
+use crate::shm::ShmOFlags;
+
+pub(crate) fn shm_open(name: &CStr, oflags: ShmOFlags, mode: Mode) -> io::Result<OwnedFd> {
+ // On this platforms, `mode_t` is `u16` and can't be passed directly to a
+ // variadic function.
+ #[cfg(apple)]
+ let mode: c::c_uint = mode.bits().into();
+
+ // Otherwise, cast to `mode_t` as that's what `open` is documented to take.
+ #[cfg(not(apple))]
+ let mode: c::mode_t = mode.bits() as _;
+
+ unsafe { ret_owned_fd(c::shm_open(c_str(name), bitflags_bits!(oflags), mode)) }
+}
+
+pub(crate) fn shm_unlink(name: &CStr) -> io::Result<()> {
+ unsafe { ret(c::shm_unlink(c_str(name))) }
+}
diff --git a/src/backend/libc/shm/types.rs b/src/backend/libc/shm/types.rs
new file mode 100644
index 0000000..6575ef5
--- /dev/null
+++ b/src/backend/libc/shm/types.rs
@@ -0,0 +1,30 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `O_*` constants for use with [`shm_open`].
+ ///
+ /// [`shm_open`]: crate:shm::shm_open
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ShmOFlags: u32 {
+ /// `O_CREAT`
+ #[doc(alias = "CREAT")]
+ const CREATE = bitcast!(c::O_CREAT);
+
+ /// `O_EXCL`
+ const EXCL = bitcast!(c::O_EXCL);
+
+ /// `O_RDONLY`
+ const RDONLY = bitcast!(c::O_RDONLY);
+
+ /// `O_RDWR`
+ const RDWR = bitcast!(c::O_RDWR);
+
+ /// `O_TRUNC`
+ const TRUNC = bitcast!(c::O_TRUNC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/libc/system/mod.rs b/src/backend/libc/system/mod.rs
new file mode 100644
index 0000000..bff7fd5
--- /dev/null
+++ b/src/backend/libc/system/mod.rs
@@ -0,0 +1,3 @@
+#[cfg(not(windows))]
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/system/syscalls.rs b/src/backend/libc/system/syscalls.rs
new file mode 100644
index 0000000..05d674b
--- /dev/null
+++ b/src/backend/libc/system/syscalls.rs
@@ -0,0 +1,67 @@
+//! libc syscalls supporting `rustix::process`.
+
+use super::types::RawUname;
+use crate::backend::c;
+#[cfg(not(target_os = "wasi"))]
+use crate::backend::conv::ret_infallible;
+#[cfg(target_os = "linux")]
+use crate::system::RebootCommand;
+#[cfg(linux_kernel)]
+use crate::system::Sysinfo;
+use core::mem::MaybeUninit;
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use {crate::backend::conv::ret, crate::io};
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub(crate) fn uname() -> RawUname {
+ let mut uname = MaybeUninit::<RawUname>::uninit();
+ unsafe {
+ let r = c::uname(uname.as_mut_ptr());
+
+ // On POSIX, `uname` is documented to return non-negative on success
+ // instead of the usual 0, though some specific systems do document
+ // that they always use zero allowing us to skip this check.
+ #[cfg(not(any(apple, freebsdlike, linux_like, target_os = "netbsd")))]
+ let r = core::cmp::min(r, 0);
+
+ ret_infallible(r);
+ uname.assume_init()
+ }
+}
+
+#[cfg(linux_kernel)]
+pub(crate) fn sysinfo() -> Sysinfo {
+ let mut info = MaybeUninit::<Sysinfo>::uninit();
+ unsafe {
+ ret_infallible(c::sysinfo(info.as_mut_ptr()));
+ info.assume_init()
+ }
+}
+
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> {
+ unsafe {
+ ret(c::sethostname(
+ name.as_ptr().cast(),
+ name.len().try_into().map_err(|_| io::Errno::INVAL)?,
+ ))
+ }
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn reboot(cmd: RebootCommand) -> io::Result<()> {
+ unsafe { ret(c::reboot(cmd as i32)) }
+}
diff --git a/src/backend/libc/system/types.rs b/src/backend/libc/system/types.rs
new file mode 100644
index 0000000..731e89b
--- /dev/null
+++ b/src/backend/libc/system/types.rs
@@ -0,0 +1,8 @@
+use crate::backend::c;
+
+/// `sysinfo`
+#[cfg(linux_kernel)]
+pub type Sysinfo = c::sysinfo;
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) type RawUname = c::utsname;
diff --git a/src/backend/libc/termios/mod.rs b/src/backend/libc/termios/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/libc/termios/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/termios/syscalls.rs b/src/backend/libc/termios/syscalls.rs
new file mode 100644
index 0000000..a833aea
--- /dev/null
+++ b/src/backend/libc/termios/syscalls.rs
@@ -0,0 +1,403 @@
+//! libc syscalls supporting `rustix::termios`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend::syscalls` module documentation for details.
+
+use crate::backend::c;
+#[cfg(not(target_os = "wasi"))]
+use crate::backend::conv::ret_pid_t;
+use crate::backend::conv::{borrowed_fd, ret};
+use crate::fd::BorrowedFd;
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+use crate::ffi::CStr;
+#[cfg(any(
+ not(target_os = "espidf"),
+ all(
+ feature = "procfs",
+ not(any(target_os = "fuchsia", target_os = "wasi"))
+ )
+))]
+use core::mem::MaybeUninit;
+#[cfg(not(target_os = "wasi"))]
+use {crate::io, crate::pid::Pid};
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+use {
+ crate::termios::{Action, OptionalActions, QueueSelector, Termios, Winsize},
+ crate::utils::as_mut_ptr,
+};
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
+ // If we have `TCGETS2`, use it, so that we fill in the `c_ispeed` and
+ // `c_ospeed` fields.
+ #[cfg(linux_kernel)]
+ {
+ use crate::termios::{ControlModes, InputModes, LocalModes, OutputModes, SpecialCodes};
+ use crate::utils::default_array;
+
+ let termios2 = unsafe {
+ let mut termios2 = MaybeUninit::<c::termios2>::uninit();
+
+ // QEMU's `TCGETS2` doesn't currently set `input_speed` or
+ // `output_speed` on PowerPC, so zero out the fields ourselves.
+ #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
+ {
+ termios2.write(core::mem::zeroed());
+ }
+
+ ret(c::ioctl(
+ borrowed_fd(fd),
+ c::TCGETS2 as _,
+ termios2.as_mut_ptr(),
+ ))?;
+
+ termios2.assume_init()
+ };
+
+ // Convert from the Linux `termios2` to our `Termios`.
+ let mut result = Termios {
+ input_modes: InputModes::from_bits_retain(termios2.c_iflag),
+ output_modes: OutputModes::from_bits_retain(termios2.c_oflag),
+ control_modes: ControlModes::from_bits_retain(termios2.c_cflag),
+ local_modes: LocalModes::from_bits_retain(termios2.c_lflag),
+ line_discipline: termios2.c_line,
+ special_codes: SpecialCodes(default_array()),
+ input_speed: termios2.c_ispeed,
+ output_speed: termios2.c_ospeed,
+ };
+
+ // QEMU's `TCGETS2` doesn't currently set `input_speed` or
+ // `output_speed` on PowerPC, so set them manually if we can.
+ #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
+ {
+ use crate::termios::speed;
+
+ if result.output_speed == 0 && (termios2.c_cflag & c::CBAUD) != c::BOTHER {
+ if let Some(output_speed) = speed::decode(termios2.c_cflag & c::CBAUD) {
+ result.output_speed = output_speed;
+ }
+ }
+ if result.input_speed == 0
+ && ((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) != c::BOTHER
+ {
+ // For input speeds, `B0` is special-cased to mean the input
+ // speed is the same as the output speed.
+ if ((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT) == c::B0 {
+ result.input_speed = result.output_speed;
+ } else if let Some(input_speed) =
+ speed::decode((termios2.c_cflag & c::CIBAUD) >> c::IBSHIFT)
+ {
+ result.input_speed = input_speed;
+ }
+ }
+ }
+
+ result.special_codes.0[..termios2.c_cc.len()].copy_from_slice(&termios2.c_cc);
+
+ Ok(result)
+ }
+
+ #[cfg(not(linux_kernel))]
+ unsafe {
+ let mut result = MaybeUninit::<Termios>::uninit();
+
+ // `result` is a `Termios` which starts with the same layout as
+ // `libc::termios`, so we can cast the pointer.
+ ret(c::tcgetattr(borrowed_fd(fd), result.as_mut_ptr().cast()))?;
+
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
+ unsafe {
+ let pid = ret_pid_t(c::tcgetpgrp(borrowed_fd(fd)))?;
+
+ // This doesn't appear to be documented, but on Linux, it appears
+ // `tcsetpgrp` can succceed and set the pid to 0 if we pass it a
+ // pseudo-terminal device fd. For now, translate it into `OPNOTSUPP`.
+ #[cfg(linux_kernel)]
+ if pid == 0 {
+ return Err(io::Errno::OPNOTSUPP);
+ }
+
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
+ unsafe { ret(c::tcsetpgrp(borrowed_fd(fd), pid.as_raw_nonzero().get())) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcsetattr(
+ fd: BorrowedFd<'_>,
+ optional_actions: OptionalActions,
+ termios: &Termios,
+) -> io::Result<()> {
+ // If we have `TCSETS2`, use it, so that we use the `c_ispeed` and
+ // `c_ospeed` fields.
+ #[cfg(linux_kernel)]
+ {
+ use crate::termios::speed;
+ use crate::utils::default_array;
+ use linux_raw_sys::general::{termios2, BOTHER, CBAUD, IBSHIFT};
+
+ #[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
+ use linux_raw_sys::ioctl::{TCSETS, TCSETS2};
+
+ // linux-raw-sys' ioctl-generation script for sparc isn't working yet,
+ // so as a temporary workaround, declare these manually.
+ #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
+ const TCSETS: u32 = 0x8024_5409;
+ #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
+ const TCSETS2: u32 = 0x802c_540d;
+
+ // Translate from `optional_actions` into an ioctl request code. On
+ // MIPS, `optional_actions` already has `TCGETS` added to it.
+ let request = TCSETS2
+ + if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )) {
+ optional_actions as u32 - TCSETS
+ } else {
+ optional_actions as u32
+ };
+
+ let input_speed = termios.input_speed();
+ let output_speed = termios.output_speed();
+ let mut termios2 = termios2 {
+ c_iflag: termios.input_modes.bits(),
+ c_oflag: termios.output_modes.bits(),
+ c_cflag: termios.control_modes.bits(),
+ c_lflag: termios.local_modes.bits(),
+ c_line: termios.line_discipline,
+ c_cc: default_array(),
+ c_ispeed: input_speed,
+ c_ospeed: output_speed,
+ };
+ // Ensure that our input and output speeds are set, as `libc`
+ // routines don't always support setting these separately.
+ termios2.c_cflag &= !CBAUD;
+ termios2.c_cflag |= speed::encode(output_speed).unwrap_or(BOTHER);
+ termios2.c_cflag &= !(CBAUD << IBSHIFT);
+ termios2.c_cflag |= speed::encode(input_speed).unwrap_or(BOTHER) << IBSHIFT;
+ let nccs = termios2.c_cc.len();
+ termios2
+ .c_cc
+ .copy_from_slice(&termios.special_codes.0[..nccs]);
+
+ unsafe { ret(c::ioctl(borrowed_fd(fd), request as _, &termios2)) }
+ }
+
+ #[cfg(not(linux_kernel))]
+ unsafe {
+ ret(c::tcsetattr(
+ borrowed_fd(fd),
+ optional_actions as _,
+ crate::utils::as_ptr(termios).cast(),
+ ))
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::tcsendbreak(borrowed_fd(fd), 0)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(c::tcdrain(borrowed_fd(fd))) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
+ unsafe { ret(c::tcflush(borrowed_fd(fd), queue_selector as _)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
+ unsafe { ret(c::tcflow(borrowed_fd(fd), action as _)) }
+}
+
+#[cfg(not(target_os = "wasi"))]
+pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
+ unsafe {
+ let pid = ret_pid_t(c::tcgetsid(borrowed_fd(fd)))?;
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
+ unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCSWINSZ, &winsize)) }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
+ unsafe {
+ let mut buf = MaybeUninit::<Winsize>::uninit();
+ ret(c::ioctl(
+ borrowed_fd(fd),
+ c::TIOCGWINSZ.into(),
+ buf.as_mut_ptr(),
+ ))?;
+ Ok(buf.assume_init())
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "nto", target_os = "wasi")))]
+#[inline]
+pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
+ #[cfg(bsd)]
+ let encoded_speed = arbitrary_speed;
+
+ #[cfg(not(bsd))]
+ let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
+ Some(encoded_speed) => encoded_speed,
+ #[cfg(linux_kernel)]
+ None => c::BOTHER,
+ #[cfg(not(linux_kernel))]
+ None => return Err(io::Errno::INVAL),
+ };
+
+ #[cfg(not(linux_kernel))]
+ unsafe {
+ ret(c::cfsetspeed(
+ as_mut_ptr(termios).cast(),
+ encoded_speed.into(),
+ ))
+ }
+
+ // Linux libc implementations don't support arbitrary speeds, so we encode
+ // the speed manually.
+ #[cfg(linux_kernel)]
+ {
+ use crate::termios::ControlModes;
+
+ debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
+
+ termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD | c::CIBAUD);
+ termios.control_modes |=
+ ControlModes::from_bits_retain(encoded_speed | (encoded_speed << c::IBSHIFT));
+
+ termios.input_speed = arbitrary_speed;
+ termios.output_speed = arbitrary_speed;
+
+ Ok(())
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
+ #[cfg(bsd)]
+ let encoded_speed = arbitrary_speed;
+
+ #[cfg(not(bsd))]
+ let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
+ Some(encoded_speed) => encoded_speed,
+ #[cfg(linux_kernel)]
+ None => c::BOTHER,
+ #[cfg(not(linux_kernel))]
+ None => return Err(io::Errno::INVAL),
+ };
+
+ #[cfg(not(linux_kernel))]
+ unsafe {
+ ret(c::cfsetospeed(
+ as_mut_ptr(termios).cast(),
+ encoded_speed.into(),
+ ))
+ }
+
+ // Linux libc implementations don't support arbitrary speeds or setting the
+ // input and output speeds separately, so we encode the speed manually.
+ #[cfg(linux_kernel)]
+ {
+ use crate::termios::ControlModes;
+
+ debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
+
+ termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD);
+ termios.control_modes |= ControlModes::from_bits_retain(encoded_speed);
+
+ termios.output_speed = arbitrary_speed;
+
+ Ok(())
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
+ #[cfg(bsd)]
+ let encoded_speed = arbitrary_speed;
+
+ #[cfg(not(bsd))]
+ let encoded_speed = match crate::termios::speed::encode(arbitrary_speed) {
+ Some(encoded_speed) => encoded_speed,
+ #[cfg(linux_kernel)]
+ None => c::BOTHER,
+ #[cfg(not(linux_kernel))]
+ None => return Err(io::Errno::INVAL),
+ };
+
+ #[cfg(not(linux_kernel))]
+ unsafe {
+ ret(c::cfsetispeed(
+ as_mut_ptr(termios).cast(),
+ encoded_speed.into(),
+ ))
+ }
+
+ // Linux libc implementations don't support arbitrary speeds or setting the
+ // input and output speeds separately, so we encode the speed manually.
+ #[cfg(linux_kernel)]
+ {
+ use crate::termios::ControlModes;
+
+ debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
+
+ termios.control_modes -= ControlModes::from_bits_retain(c::CIBAUD);
+ termios.control_modes |= ControlModes::from_bits_retain(encoded_speed << c::IBSHIFT);
+
+ termios.input_speed = arbitrary_speed;
+
+ Ok(())
+ }
+}
+
+#[cfg(not(any(target_os = "espidf", target_os = "nto", target_os = "wasi")))]
+#[inline]
+pub(crate) fn cfmakeraw(termios: &mut Termios) {
+ unsafe { c::cfmakeraw(as_mut_ptr(termios).cast()) }
+}
+
+pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
+ // Use the return value of `isatty` alone. We don't check `errno` because
+ // we return `bool` rather than `io::Result<bool>`, because we assume
+ // `BorrowedFd` protects us from `EBADF`, and any other reasonably
+ // anticipated `errno` value would end up interpreted as “assume it's not a
+ // terminal” anyway.
+ unsafe { c::isatty(borrowed_fd(fd)) != 0 }
+}
+
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+pub(crate) fn ttyname(dirfd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
+ unsafe {
+ // `ttyname_r` returns its error status rather than using `errno`.
+ match c::ttyname_r(borrowed_fd(dirfd), buf.as_mut_ptr().cast(), buf.len()) {
+ 0 => Ok(CStr::from_ptr(buf.as_ptr().cast()).to_bytes().len()),
+ err => Err(io::Errno::from_raw_os_error(err)),
+ }
+ }
+}
diff --git a/src/backend/libc/thread/futex.rs b/src/backend/libc/thread/futex.rs
new file mode 100644
index 0000000..44d96f0
--- /dev/null
+++ b/src/backend/libc/thread/futex.rs
@@ -0,0 +1,43 @@
+use crate::backend::c;
+
+bitflags::bitflags! {
+ /// `FUTEX_*` flags for use with [`futex`].
+ ///
+ /// [`futex`]: crate::thread::futex
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FutexFlags: u32 {
+ /// `FUTEX_PRIVATE_FLAG`
+ const PRIVATE = bitcast!(c::FUTEX_PRIVATE_FLAG);
+ /// `FUTEX_CLOCK_REALTIME`
+ const CLOCK_REALTIME = bitcast!(c::FUTEX_CLOCK_REALTIME);
+ }
+}
+
+/// `FUTEX_*` operations for use with [`futex`].
+///
+/// [`futex`]: crate::thread::futex
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub enum FutexOperation {
+ /// `FUTEX_WAIT`
+ Wait = bitcast!(c::FUTEX_WAIT),
+ /// `FUTEX_WAKE`
+ Wake = bitcast!(c::FUTEX_WAKE),
+ /// `FUTEX_FD`
+ Fd = bitcast!(c::FUTEX_FD),
+ /// `FUTEX_REQUEUE`
+ Requeue = bitcast!(c::FUTEX_REQUEUE),
+ /// `FUTEX_CMP_REQUEUE`
+ CmpRequeue = bitcast!(c::FUTEX_CMP_REQUEUE),
+ /// `FUTEX_WAKE_OP`
+ WakeOp = bitcast!(c::FUTEX_WAKE_OP),
+ /// `FUTEX_LOCK_PI`
+ LockPi = bitcast!(c::FUTEX_LOCK_PI),
+ /// `FUTEX_UNLOCK_PI`
+ UnlockPi = bitcast!(c::FUTEX_UNLOCK_PI),
+ /// `FUTEX_TRYLOCK_PI`
+ TrylockPi = bitcast!(c::FUTEX_TRYLOCK_PI),
+ /// `FUTEX_WAIT_BITSET`
+ WaitBitset = bitcast!(c::FUTEX_WAIT_BITSET),
+}
diff --git a/src/backend/libc/thread/mod.rs b/src/backend/libc/thread/mod.rs
new file mode 100644
index 0000000..4f8c87c
--- /dev/null
+++ b/src/backend/libc/thread/mod.rs
@@ -0,0 +1,4 @@
+#[cfg(linux_kernel)]
+pub(crate) mod futex;
+#[cfg(not(windows))]
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/thread/syscalls.rs b/src/backend/libc/thread/syscalls.rs
new file mode 100644
index 0000000..33750f4
--- /dev/null
+++ b/src/backend/libc/thread/syscalls.rs
@@ -0,0 +1,523 @@
+//! libc syscalls supporting `rustix::thread`.
+
+use crate::backend::c;
+use crate::backend::conv::ret;
+use crate::io;
+#[cfg(not(target_os = "redox"))]
+use crate::thread::{NanosleepRelativeResult, Timespec};
+#[cfg(all(target_env = "gnu", fix_y2038))]
+use crate::timespec::LibcTimespec;
+use core::mem::MaybeUninit;
+#[cfg(linux_kernel)]
+use {
+ crate::backend::conv::{borrowed_fd, ret_c_int, ret_usize},
+ crate::fd::BorrowedFd,
+ crate::pid::Pid,
+ crate::thread::{FutexFlags, FutexOperation},
+ crate::utils::as_mut_ptr,
+};
+#[cfg(not(any(
+ apple,
+ freebsdlike,
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+use {crate::thread::ClockId, core::ptr::null_mut};
+
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __clock_nanosleep_time64(c::clockid_t, c::c_int, *const LibcTimespec, *mut LibcTimespec) -> c::c_int);
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __nanosleep64(*const LibcTimespec, *mut LibcTimespec) -> c::c_int);
+
+#[cfg(not(any(
+ apple,
+ target_os = "dragonfly",
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "freebsd", // FreeBSD 12 has clock_nanosleep, but libc targets FreeBSD 11.
+ target_os = "haiku",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+#[inline]
+pub(crate) fn clock_nanosleep_relative(id: ClockId, request: &Timespec) -> NanosleepRelativeResult {
+ // Old 32-bit version: libc has `clock_nanosleep` but it is not y2038 safe
+ // by default. But there may be a `__clock_nanosleep_time64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_clock_nanosleep) = __clock_nanosleep_time64.get() {
+ let flags = 0;
+ let mut remain = MaybeUninit::<LibcTimespec>::uninit();
+
+ unsafe {
+ return match libc_clock_nanosleep(
+ id as c::clockid_t,
+ flags,
+ &request.clone().into(),
+ remain.as_mut_ptr(),
+ ) {
+ 0 => NanosleepRelativeResult::Ok,
+ err if err == io::Errno::INTR.0 => {
+ NanosleepRelativeResult::Interrupted(remain.assume_init().into())
+ }
+ err => NanosleepRelativeResult::Err(io::Errno(err)),
+ };
+ }
+ }
+
+ clock_nanosleep_relative_old(id, request)
+ }
+
+ // Main version: libc is y2038 safe and has `clock_nanosleep`.
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let flags = 0;
+ let mut remain = MaybeUninit::<Timespec>::uninit();
+
+ match c::clock_nanosleep(id as c::clockid_t, flags, request, remain.as_mut_ptr()) {
+ 0 => NanosleepRelativeResult::Ok,
+ err if err == io::Errno::INTR.0 => {
+ NanosleepRelativeResult::Interrupted(remain.assume_init())
+ }
+ err => NanosleepRelativeResult::Err(io::Errno(err)),
+ }
+ }
+}
+
+#[cfg(all(
+ fix_y2038,
+ not(any(
+ apple,
+ target_os = "emscripten",
+ target_os = "haiku",
+ target_os = "vita"
+ ))
+))]
+fn clock_nanosleep_relative_old(id: ClockId, request: &Timespec) -> NanosleepRelativeResult {
+ let tv_sec = match request.tv_sec.try_into() {
+ Ok(tv_sec) => tv_sec,
+ Err(_) => return NanosleepRelativeResult::Err(io::Errno::OVERFLOW),
+ };
+ let tv_nsec = match request.tv_nsec.try_into() {
+ Ok(tv_nsec) => tv_nsec,
+ Err(_) => return NanosleepRelativeResult::Err(io::Errno::INVAL),
+ };
+ let old_request = c::timespec { tv_sec, tv_nsec };
+ let mut old_remain = MaybeUninit::<c::timespec>::uninit();
+ let flags = 0;
+
+ unsafe {
+ match c::clock_nanosleep(
+ id as c::clockid_t,
+ flags,
+ &old_request,
+ old_remain.as_mut_ptr(),
+ ) {
+ 0 => NanosleepRelativeResult::Ok,
+ err if err == io::Errno::INTR.0 => {
+ let old_remain = old_remain.assume_init();
+ let remain = Timespec {
+ tv_sec: old_remain.tv_sec.into(),
+ tv_nsec: old_remain.tv_nsec.into(),
+ };
+ NanosleepRelativeResult::Interrupted(remain)
+ }
+ err => NanosleepRelativeResult::Err(io::Errno(err)),
+ }
+ }
+}
+
+#[cfg(not(any(
+ apple,
+ target_os = "dragonfly",
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "freebsd", // FreeBSD 12 has clock_nanosleep, but libc targets FreeBSD 11.
+ target_os = "haiku",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+#[inline]
+pub(crate) fn clock_nanosleep_absolute(id: ClockId, request: &Timespec) -> io::Result<()> {
+ // Old 32-bit version: libc has `clock_nanosleep` but it is not y2038 safe
+ // by default. But there may be a `__clock_nanosleep_time64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_clock_nanosleep) = __clock_nanosleep_time64.get() {
+ let flags = c::TIMER_ABSTIME;
+ unsafe {
+ return match {
+ libc_clock_nanosleep(
+ id as c::clockid_t,
+ flags,
+ &request.clone().into(),
+ null_mut(),
+ )
+ } {
+ 0 => Ok(()),
+ err => Err(io::Errno(err)),
+ };
+ }
+ }
+
+ clock_nanosleep_absolute_old(id, request)
+ }
+
+ // Main version: libc is y2038 safe and has `clock_nanosleep`.
+ #[cfg(not(fix_y2038))]
+ {
+ let flags = c::TIMER_ABSTIME;
+
+ match unsafe { c::clock_nanosleep(id as c::clockid_t, flags as _, request, null_mut()) } {
+ 0 => Ok(()),
+ err => Err(io::Errno(err)),
+ }
+ }
+}
+
+#[cfg(all(
+ fix_y2038,
+ not(any(
+ apple,
+ target_os = "emscripten",
+ target_os = "haiku",
+ target_os = "vita"
+ ))
+))]
+fn clock_nanosleep_absolute_old(id: ClockId, request: &Timespec) -> io::Result<()> {
+ let flags = c::TIMER_ABSTIME;
+
+ let old_request = c::timespec {
+ tv_sec: request.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: request.tv_nsec.try_into().map_err(|_| io::Errno::INVAL)?,
+ };
+ match unsafe { c::clock_nanosleep(id as c::clockid_t, flags, &old_request, null_mut()) } {
+ 0 => Ok(()),
+ err => Err(io::Errno(err)),
+ }
+}
+
+#[cfg(not(target_os = "redox"))]
+#[inline]
+pub(crate) fn nanosleep(request: &Timespec) -> NanosleepRelativeResult {
+ // Old 32-bit version: libc has `nanosleep` but it is not y2038 safe by
+ // default. But there may be a `__nanosleep64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_nanosleep) = __nanosleep64.get() {
+ let mut remain = MaybeUninit::<LibcTimespec>::uninit();
+ unsafe {
+ return match ret(libc_nanosleep(&request.clone().into(), remain.as_mut_ptr())) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => {
+ NanosleepRelativeResult::Interrupted(remain.assume_init().into())
+ }
+ Err(err) => NanosleepRelativeResult::Err(err),
+ };
+ }
+ }
+
+ nanosleep_old(request)
+ }
+
+ // Main version: libc is y2038 safe and has `nanosleep`.
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let mut remain = MaybeUninit::<Timespec>::uninit();
+
+ match ret(c::nanosleep(request, remain.as_mut_ptr())) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => NanosleepRelativeResult::Interrupted(remain.assume_init()),
+ Err(err) => NanosleepRelativeResult::Err(err),
+ }
+ }
+}
+
+#[cfg(fix_y2038)]
+fn nanosleep_old(request: &Timespec) -> NanosleepRelativeResult {
+ let tv_sec = match request.tv_sec.try_into() {
+ Ok(tv_sec) => tv_sec,
+ Err(_) => return NanosleepRelativeResult::Err(io::Errno::OVERFLOW),
+ };
+ let tv_nsec = match request.tv_nsec.try_into() {
+ Ok(tv_nsec) => tv_nsec,
+ Err(_) => return NanosleepRelativeResult::Err(io::Errno::INVAL),
+ };
+ let old_request = c::timespec { tv_sec, tv_nsec };
+ let mut old_remain = MaybeUninit::<c::timespec>::uninit();
+
+ unsafe {
+ match ret(c::nanosleep(&old_request, old_remain.as_mut_ptr())) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => {
+ let old_remain = old_remain.assume_init();
+ let remain = Timespec {
+ tv_sec: old_remain.tv_sec.into(),
+ tv_nsec: old_remain.tv_nsec.into(),
+ };
+ NanosleepRelativeResult::Interrupted(remain)
+ }
+ Err(err) => NanosleepRelativeResult::Err(err),
+ }
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+#[must_use]
+pub(crate) fn gettid() -> Pid {
+ // `gettid` wasn't supported in glibc until 2.30, and musl until 1.2.2,
+ // so use `syscall`.
+ // <https://sourceware.org/bugzilla/show_bug.cgi?id=6399#c62>
+ weak_or_syscall! {
+ fn gettid() via SYS_gettid -> c::pid_t
+ }
+
+ unsafe {
+ let tid = gettid();
+ Pid::from_raw_unchecked(tid)
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn setns(fd: BorrowedFd<'_>, nstype: c::c_int) -> io::Result<c::c_int> {
+ // `setns` wasn't supported in glibc until 2.14, and musl until 0.9.5,
+ // so use `syscall`.
+ weak_or_syscall! {
+ fn setns(fd: c::c_int, nstype: c::c_int) via SYS_setns -> c::c_int
+ }
+
+ unsafe { ret_c_int(setns(borrowed_fd(fd), nstype)) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn unshare(flags: crate::thread::UnshareFlags) -> io::Result<()> {
+ unsafe { ret(c::unshare(flags.bits() as i32)) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn capget(
+ header: &mut linux_raw_sys::general::__user_cap_header_struct,
+ data: &mut [MaybeUninit<linux_raw_sys::general::__user_cap_data_struct>],
+) -> io::Result<()> {
+ syscall! {
+ fn capget(
+ hdrp: *mut linux_raw_sys::general::__user_cap_header_struct,
+ data: *mut linux_raw_sys::general::__user_cap_data_struct
+ ) via SYS_capget -> c::c_int
+ }
+
+ unsafe {
+ ret(capget(
+ as_mut_ptr(header),
+ data.as_mut_ptr()
+ .cast::<linux_raw_sys::general::__user_cap_data_struct>(),
+ ))
+ }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn capset(
+ header: &mut linux_raw_sys::general::__user_cap_header_struct,
+ data: &[linux_raw_sys::general::__user_cap_data_struct],
+) -> io::Result<()> {
+ syscall! {
+ fn capset(
+ hdrp: *mut linux_raw_sys::general::__user_cap_header_struct,
+ data: *const linux_raw_sys::general::__user_cap_data_struct
+ ) via SYS_capset -> c::c_int
+ }
+
+ unsafe { ret(capset(as_mut_ptr(header), data.as_ptr())) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn setuid_thread(uid: crate::ugid::Uid) -> io::Result<()> {
+ syscall! {
+ fn setuid(uid: c::uid_t) via SYS_setuid -> c::c_int
+ }
+
+ unsafe { ret(setuid(uid.as_raw())) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn setresuid_thread(
+ ruid: crate::ugid::Uid,
+ euid: crate::ugid::Uid,
+ suid: crate::ugid::Uid,
+) -> io::Result<()> {
+ #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))]
+ const SYS: c::c_long = c::SYS_setresuid32 as c::c_long;
+ #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))]
+ const SYS: c::c_long = c::SYS_setresuid as c::c_long;
+
+ syscall! {
+ fn setresuid(ruid: c::uid_t, euid: c::uid_t, suid: c::uid_t) via SYS -> c::c_int
+ }
+
+ unsafe { ret(setresuid(ruid.as_raw(), euid.as_raw(), suid.as_raw())) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn setgid_thread(gid: crate::ugid::Gid) -> io::Result<()> {
+ syscall! {
+ fn setgid(gid: c::gid_t) via SYS_setgid -> c::c_int
+ }
+
+ unsafe { ret(setgid(gid.as_raw())) }
+}
+
+#[cfg(linux_kernel)]
+#[inline]
+pub(crate) fn setresgid_thread(
+ rgid: crate::ugid::Gid,
+ egid: crate::ugid::Gid,
+ sgid: crate::ugid::Gid,
+) -> io::Result<()> {
+ #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))]
+ const SYS: c::c_long = c::SYS_setresgid32 as c::c_long;
+ #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))]
+ const SYS: c::c_long = c::SYS_setresgid as c::c_long;
+
+ syscall! {
+ fn setresgid(rgid: c::gid_t, egid: c::gid_t, sgid: c::gid_t) via SYS -> c::c_int
+ }
+
+ unsafe { ret(setresgid(rgid.as_raw(), egid.as_raw(), sgid.as_raw())) }
+}
+
+// TODO: This could be de-multiplexed.
+#[cfg(linux_kernel)]
+pub(crate) unsafe fn futex(
+ uaddr: *mut u32,
+ op: FutexOperation,
+ flags: FutexFlags,
+ val: u32,
+ utime: *const Timespec,
+ uaddr2: *mut u32,
+ val3: u32,
+) -> io::Result<usize> {
+ #[cfg(all(
+ target_pointer_width = "32",
+ not(any(target_arch = "aarch64", target_arch = "x86_64"))
+ ))]
+ {
+ // TODO: Upstream this to the libc crate.
+ #[allow(non_upper_case_globals)]
+ const SYS_futex_time64: i32 = linux_raw_sys::general::__NR_futex_time64 as i32;
+
+ syscall! {
+ fn futex_time64(
+ uaddr: *mut u32,
+ futex_op: c::c_int,
+ val: u32,
+ timeout: *const Timespec,
+ uaddr2: *mut u32,
+ val3: u32
+ ) via SYS_futex_time64 -> c::ssize_t
+ }
+
+ ret_usize(futex_time64(
+ uaddr,
+ op as i32 | flags.bits() as i32,
+ val,
+ utime,
+ uaddr2,
+ val3,
+ ))
+ .or_else(|err| {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ if err == io::Errno::NOSYS {
+ futex_old(uaddr, op, flags, val, utime, uaddr2, val3)
+ } else {
+ Err(err)
+ }
+ })
+ }
+
+ #[cfg(any(
+ target_pointer_width = "64",
+ target_arch = "aarch64",
+ target_arch = "x86_64"
+ ))]
+ {
+ syscall! {
+ fn futex(
+ uaddr: *mut u32,
+ futex_op: c::c_int,
+ val: u32,
+ timeout: *const linux_raw_sys::general::__kernel_timespec,
+ uaddr2: *mut u32,
+ val3: u32
+ ) via SYS_futex -> c::c_long
+ }
+
+ ret_usize(futex(
+ uaddr,
+ op as i32 | flags.bits() as i32,
+ val,
+ utime.cast(),
+ uaddr2,
+ val3,
+ ) as isize)
+ }
+}
+
+#[cfg(linux_kernel)]
+#[cfg(all(
+ target_pointer_width = "32",
+ not(any(target_arch = "aarch64", target_arch = "x86_64"))
+))]
+unsafe fn futex_old(
+ uaddr: *mut u32,
+ op: FutexOperation,
+ flags: FutexFlags,
+ val: u32,
+ utime: *const Timespec,
+ uaddr2: *mut u32,
+ val3: u32,
+) -> io::Result<usize> {
+ syscall! {
+ fn futex(
+ uaddr: *mut u32,
+ futex_op: c::c_int,
+ val: u32,
+ timeout: *const linux_raw_sys::general::__kernel_old_timespec,
+ uaddr2: *mut u32,
+ val3: u32
+ ) via SYS_futex -> c::c_long
+ }
+
+ let old_utime = linux_raw_sys::general::__kernel_old_timespec {
+ tv_sec: (*utime).tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
+ tv_nsec: (*utime).tv_nsec.try_into().map_err(|_| io::Errno::INVAL)?,
+ };
+ ret_usize(futex(
+ uaddr,
+ op as i32 | flags.bits() as i32,
+ val,
+ &old_utime,
+ uaddr2,
+ val3,
+ ) as isize)
+}
diff --git a/src/backend/libc/time/mod.rs b/src/backend/libc/time/mod.rs
new file mode 100644
index 0000000..bff7fd5
--- /dev/null
+++ b/src/backend/libc/time/mod.rs
@@ -0,0 +1,3 @@
+#[cfg(not(windows))]
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/libc/time/syscalls.rs b/src/backend/libc/time/syscalls.rs
new file mode 100644
index 0000000..97eae5f
--- /dev/null
+++ b/src/backend/libc/time/syscalls.rs
@@ -0,0 +1,452 @@
+//! libc syscalls supporting `rustix::time`.
+
+use crate::backend::c;
+use crate::backend::conv::ret;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(feature = "time")]
+#[cfg(any(all(target_env = "gnu", fix_y2038), not(fix_y2038)))]
+use crate::backend::time::types::LibcItimerspec;
+#[cfg(not(target_os = "wasi"))]
+use crate::clockid::{ClockId, DynamicClockId};
+use crate::io;
+#[cfg(all(target_env = "gnu", fix_y2038))]
+use crate::timespec::LibcTimespec;
+use crate::timespec::Timespec;
+use core::mem::MaybeUninit;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(feature = "time")]
+use {
+ crate::backend::conv::{borrowed_fd, ret_owned_fd},
+ crate::fd::{BorrowedFd, OwnedFd},
+ crate::time::{Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags},
+};
+
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __clock_gettime64(c::clockid_t, *mut LibcTimespec) -> c::c_int);
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __clock_settime64(c::clockid_t, *const LibcTimespec) -> c::c_int);
+#[cfg(all(target_env = "gnu", fix_y2038))]
+weak!(fn __clock_getres64(c::clockid_t, *mut LibcTimespec) -> c::c_int);
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(all(target_env = "gnu", fix_y2038))]
+#[cfg(feature = "time")]
+weak!(fn __timerfd_gettime64(c::c_int, *mut LibcItimerspec) -> c::c_int);
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(all(target_env = "gnu", fix_y2038))]
+#[cfg(feature = "time")]
+weak!(fn __timerfd_settime64(c::c_int, c::c_int, *const LibcItimerspec, *mut LibcItimerspec) -> c::c_int);
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[inline]
+#[must_use]
+pub(crate) fn clock_getres(id: ClockId) -> Timespec {
+ // Old 32-bit version: libc has `clock_getres` but it is not y2038 safe by
+ // default. But there may be a `__clock_getres64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_clock_getres) = __clock_getres64.get() {
+ let mut timespec = MaybeUninit::<LibcTimespec>::uninit();
+ unsafe {
+ ret(libc_clock_getres(id as c::clockid_t, timespec.as_mut_ptr())).unwrap();
+ return timespec.assume_init().into();
+ }
+ }
+
+ clock_getres_old(id)
+ }
+
+ // Main version: libc is y2038 safe and has `clock_getres`.
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let mut timespec = MaybeUninit::<Timespec>::uninit();
+ let _ = c::clock_getres(id as c::clockid_t, timespec.as_mut_ptr());
+ timespec.assume_init()
+ }
+}
+
+#[cfg(fix_y2038)]
+#[must_use]
+fn clock_getres_old(id: ClockId) -> Timespec {
+ let mut old_timespec = MaybeUninit::<c::timespec>::uninit();
+
+ let old_timespec = unsafe {
+ ret(c::clock_getres(
+ id as c::clockid_t,
+ old_timespec.as_mut_ptr(),
+ ))
+ .unwrap();
+ old_timespec.assume_init()
+ };
+
+ Timespec {
+ tv_sec: old_timespec.tv_sec.into(),
+ tv_nsec: old_timespec.tv_nsec.into(),
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn clock_gettime(id: ClockId) -> Timespec {
+ // Old 32-bit version: libc has `clock_gettime` but it is not y2038 safe by
+ // default. But there may be a `__clock_gettime64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_clock_gettime) = __clock_gettime64.get() {
+ let mut timespec = MaybeUninit::<LibcTimespec>::uninit();
+ unsafe {
+ ret(libc_clock_gettime(
+ id as c::clockid_t,
+ timespec.as_mut_ptr(),
+ ))
+ .unwrap();
+ return timespec.assume_init().into();
+ }
+ }
+
+ clock_gettime_old(id)
+ }
+
+ // Use `.unwrap()` here because `clock_getres` can fail if the clock itself
+ // overflows a number of seconds, but if that happens, the monotonic clocks
+ // can't maintain their invariants, or the realtime clocks aren't properly
+ // configured.
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let mut timespec = MaybeUninit::<Timespec>::uninit();
+ ret(c::clock_gettime(id as c::clockid_t, timespec.as_mut_ptr())).unwrap();
+ timespec.assume_init()
+ }
+}
+
+#[cfg(fix_y2038)]
+#[must_use]
+fn clock_gettime_old(id: ClockId) -> Timespec {
+ let mut old_timespec = MaybeUninit::<c::timespec>::uninit();
+
+ let old_timespec = unsafe {
+ ret(c::clock_gettime(
+ id as c::clockid_t,
+ old_timespec.as_mut_ptr(),
+ ))
+ .unwrap();
+ old_timespec.assume_init()
+ };
+
+ Timespec {
+ tv_sec: old_timespec.tv_sec.into(),
+ tv_nsec: old_timespec.tv_nsec.into(),
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub(crate) fn clock_gettime_dynamic(id: DynamicClockId<'_>) -> io::Result<Timespec> {
+ let id: c::clockid_t = match id {
+ DynamicClockId::Known(id) => id as c::clockid_t,
+
+ #[cfg(linux_kernel)]
+ DynamicClockId::Dynamic(fd) => {
+ use crate::fd::AsRawFd;
+ const CLOCKFD: i32 = 3;
+ (!fd.as_raw_fd() << 3) | CLOCKFD
+ }
+
+ #[cfg(not(linux_kernel))]
+ DynamicClockId::Dynamic(_fd) => {
+ // Dynamic clocks are not supported on this platform.
+ return Err(io::Errno::INVAL);
+ }
+
+ #[cfg(linux_kernel)]
+ DynamicClockId::RealtimeAlarm => c::CLOCK_REALTIME_ALARM,
+
+ #[cfg(linux_kernel)]
+ DynamicClockId::Tai => c::CLOCK_TAI,
+
+ #[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "openbsd"
+ ))]
+ DynamicClockId::Boottime => c::CLOCK_BOOTTIME,
+
+ #[cfg(any(linux_kernel, target_os = "fuchsia"))]
+ DynamicClockId::BoottimeAlarm => c::CLOCK_BOOTTIME_ALARM,
+ };
+
+ // Old 32-bit version: libc has `clock_gettime` but it is not y2038
+ // safe by default. But there may be a `__clock_gettime64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_clock_gettime) = __clock_gettime64.get() {
+ let mut timespec = MaybeUninit::<LibcTimespec>::uninit();
+ unsafe {
+ ret(libc_clock_gettime(
+ id as c::clockid_t,
+ timespec.as_mut_ptr(),
+ ))?;
+
+ return Ok(timespec.assume_init().into());
+ }
+ }
+
+ clock_gettime_dynamic_old(id)
+ }
+
+ // Main version: libc is y2038 safe and has `clock_gettime`.
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let mut timespec = MaybeUninit::<Timespec>::uninit();
+
+ ret(c::clock_gettime(id as c::clockid_t, timespec.as_mut_ptr()))?;
+
+ Ok(timespec.assume_init())
+ }
+}
+
+#[cfg(fix_y2038)]
+#[inline]
+fn clock_gettime_dynamic_old(id: c::clockid_t) -> io::Result<Timespec> {
+ let mut old_timespec = MaybeUninit::<c::timespec>::uninit();
+
+ let old_timespec = unsafe {
+ ret(c::clock_gettime(
+ id as c::clockid_t,
+ old_timespec.as_mut_ptr(),
+ ))?;
+
+ old_timespec.assume_init()
+ };
+
+ Ok(Timespec {
+ tv_sec: old_timespec.tv_sec.into(),
+ tv_nsec: old_timespec.tv_nsec.into(),
+ })
+}
+
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "wasi",
+ all(apple, not(target_os = "macos"))
+)))]
+#[inline]
+pub(crate) fn clock_settime(id: ClockId, timespec: Timespec) -> io::Result<()> {
+ // Old 32-bit version: libc has `clock_gettime` but it is not y2038 safe by
+ // default. But there may be a `__clock_settime64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_clock_settime) = __clock_settime64.get() {
+ unsafe {
+ let mut new_timespec = core::mem::zeroed::<LibcTimespec>();
+ new_timespec.tv_sec = timespec.tv_sec;
+ new_timespec.tv_nsec = timespec.tv_nsec as _;
+ return ret(libc_clock_settime(id as c::clockid_t, &new_timespec));
+ }
+ }
+
+ clock_settime_old(id, timespec)
+ }
+
+ // Main version: libc is y2038 safe and has `clock_settime`.
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ ret(c::clock_settime(id as c::clockid_t, &timespec))
+ }
+}
+
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "wasi",
+ all(apple, not(target_os = "macos"))
+)))]
+#[cfg(fix_y2038)]
+fn clock_settime_old(id: ClockId, timespec: Timespec) -> io::Result<()> {
+ let old_timespec = c::timespec {
+ tv_sec: timespec
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: timespec.tv_nsec as _,
+ };
+
+ unsafe { ret(c::clock_settime(id as c::clockid_t, &old_timespec)) }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(feature = "time")]
+pub(crate) fn timerfd_create(id: TimerfdClockId, flags: TimerfdFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(c::timerfd_create(id as c::clockid_t, bitflags_bits!(flags))) }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(feature = "time")]
+pub(crate) fn timerfd_settime(
+ fd: BorrowedFd<'_>,
+ flags: TimerfdTimerFlags,
+ new_value: &Itimerspec,
+) -> io::Result<Itimerspec> {
+ // Old 32-bit version: libc has `timerfd_settime` but it is not y2038 safe
+ // by default. But there may be a `__timerfd_settime64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_timerfd_settime) = __timerfd_settime64.get() {
+ let mut result = MaybeUninit::<LibcItimerspec>::uninit();
+ unsafe {
+ ret(libc_timerfd_settime(
+ borrowed_fd(fd),
+ bitflags_bits!(flags),
+ &new_value.clone().into(),
+ result.as_mut_ptr(),
+ ))?;
+ return Ok(result.assume_init().into());
+ }
+ }
+
+ timerfd_settime_old(fd, flags, new_value)
+ }
+
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let mut result = MaybeUninit::<LibcItimerspec>::uninit();
+ ret(c::timerfd_settime(
+ borrowed_fd(fd),
+ bitflags_bits!(flags),
+ new_value,
+ result.as_mut_ptr(),
+ ))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+#[cfg(feature = "time")]
+fn timerfd_settime_old(
+ fd: BorrowedFd<'_>,
+ flags: TimerfdTimerFlags,
+ new_value: &Itimerspec,
+) -> io::Result<Itimerspec> {
+ let mut old_result = MaybeUninit::<c::itimerspec>::uninit();
+
+ // Convert `new_value` to the old `itimerspec` format.
+ let old_new_value = c::itimerspec {
+ it_interval: c::timespec {
+ tv_sec: new_value
+ .it_interval
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: new_value
+ .it_interval
+ .tv_nsec
+ .try_into()
+ .map_err(|_| io::Errno::INVAL)?,
+ },
+ it_value: c::timespec {
+ tv_sec: new_value
+ .it_value
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: new_value
+ .it_value
+ .tv_nsec
+ .try_into()
+ .map_err(|_| io::Errno::INVAL)?,
+ },
+ };
+
+ let old_result = unsafe {
+ ret(c::timerfd_settime(
+ borrowed_fd(fd),
+ bitflags_bits!(flags),
+ &old_new_value,
+ old_result.as_mut_ptr(),
+ ))?;
+ old_result.assume_init()
+ };
+
+ Ok(Itimerspec {
+ it_interval: Timespec {
+ tv_sec: old_result
+ .it_interval
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: old_result.it_interval.tv_nsec as _,
+ },
+ it_value: Timespec {
+ tv_sec: old_result
+ .it_interval
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: old_result.it_interval.tv_nsec as _,
+ },
+ })
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(feature = "time")]
+pub(crate) fn timerfd_gettime(fd: BorrowedFd<'_>) -> io::Result<Itimerspec> {
+ // Old 32-bit version: libc has `timerfd_gettime` but it is not y2038 safe
+ // by default. But there may be a `__timerfd_gettime64` we can use.
+ #[cfg(fix_y2038)]
+ {
+ #[cfg(target_env = "gnu")]
+ if let Some(libc_timerfd_gettime) = __timerfd_gettime64.get() {
+ let mut result = MaybeUninit::<LibcItimerspec>::uninit();
+ unsafe {
+ ret(libc_timerfd_gettime(borrowed_fd(fd), result.as_mut_ptr()))?;
+ return Ok(result.assume_init().into());
+ }
+ }
+
+ timerfd_gettime_old(fd)
+ }
+
+ #[cfg(not(fix_y2038))]
+ unsafe {
+ let mut result = MaybeUninit::<LibcItimerspec>::uninit();
+ ret(c::timerfd_gettime(borrowed_fd(fd), result.as_mut_ptr()))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+#[cfg(feature = "time")]
+fn timerfd_gettime_old(fd: BorrowedFd<'_>) -> io::Result<Itimerspec> {
+ let mut old_result = MaybeUninit::<c::itimerspec>::uninit();
+
+ let old_result = unsafe {
+ ret(c::timerfd_gettime(borrowed_fd(fd), old_result.as_mut_ptr()))?;
+ old_result.assume_init()
+ };
+
+ Ok(Itimerspec {
+ it_interval: Timespec {
+ tv_sec: old_result
+ .it_interval
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: old_result.it_interval.tv_nsec as _,
+ },
+ it_value: Timespec {
+ tv_sec: old_result
+ .it_interval
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: old_result.it_interval.tv_nsec as _,
+ },
+ })
+}
diff --git a/src/backend/libc/time/types.rs b/src/backend/libc/time/types.rs
new file mode 100644
index 0000000..5254c6b
--- /dev/null
+++ b/src/backend/libc/time/types.rs
@@ -0,0 +1,177 @@
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+use crate::backend::c;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+use crate::timespec::LibcTimespec;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+use crate::timespec::Timespec;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+use bitflags::bitflags;
+
+/// `struct itimerspec` for use with [`timerfd_gettime`] and
+/// [`timerfd_settime`].
+///
+/// [`timerfd_gettime`]: crate::time::timerfd_gettime
+/// [`timerfd_settime`]: crate::time::timerfd_settime
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(not(fix_y2038))]
+pub type Itimerspec = c::itimerspec;
+
+/// `struct itimerspec` for use with [`timerfd_gettime`] and
+/// [`timerfd_settime`].
+///
+/// [`timerfd_gettime`]: crate::time::timerfd_gettime
+/// [`timerfd_settime`]: crate::time::timerfd_settime
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct Itimerspec {
+ /// The interval of an interval timer.
+ pub it_interval: Timespec,
+ /// Time remaining in the current interval.
+ pub it_value: Timespec,
+}
+
+/// On most platforms, `LibcItimerspec` is just `Itimerspec`.
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(not(fix_y2038))]
+pub(crate) type LibcItimerspec = Itimerspec;
+
+/// On 32-bit glibc platforms, `LibcTimespec` differs from `Timespec`, so we
+/// define our own struct, with bidirectional `From` impls.
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub(crate) struct LibcItimerspec {
+ pub it_interval: LibcTimespec,
+ pub it_value: LibcTimespec,
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+impl From<LibcItimerspec> for Itimerspec {
+ #[inline]
+ fn from(t: LibcItimerspec) -> Self {
+ Self {
+ it_interval: t.it_interval.into(),
+ it_value: t.it_value.into(),
+ }
+ }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[cfg(fix_y2038)]
+impl From<Itimerspec> for LibcItimerspec {
+ #[inline]
+ fn from(t: Itimerspec) -> Self {
+ Self {
+ it_interval: t.it_interval.into(),
+ it_value: t.it_value.into(),
+ }
+ }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+bitflags! {
+ /// `TFD_*` flags for use with [`timerfd_create`].
+ ///
+ /// [`timerfd_create`]: crate::time::timerfd_create
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct TimerfdFlags: u32 {
+ /// `TFD_NONBLOCK`
+ #[doc(alias = "TFD_NONBLOCK")]
+ const NONBLOCK = bitcast!(c::TFD_NONBLOCK);
+
+ /// `TFD_CLOEXEC`
+ #[doc(alias = "TFD_CLOEXEC")]
+ const CLOEXEC = bitcast!(c::TFD_CLOEXEC);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+bitflags! {
+ /// `TFD_TIMER_*` flags for use with [`timerfd_settime`].
+ ///
+ /// [`timerfd_settime`]: crate::time::timerfd_settime
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct TimerfdTimerFlags: u32 {
+ /// `TFD_TIMER_ABSTIME`
+ #[doc(alias = "TFD_TIMER_ABSTIME")]
+ const ABSTIME = bitcast!(c::TFD_TIMER_ABSTIME);
+
+ /// `TFD_TIMER_CANCEL_ON_SET`
+ #[cfg(linux_kernel)]
+ #[doc(alias = "TFD_TIMER_CANCEL_ON_SET")]
+ const CANCEL_ON_SET = bitcast!(c::TFD_TIMER_CANCEL_ON_SET);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `CLOCK_*` constants for use with [`timerfd_create`].
+///
+/// [`timerfd_create`]: crate::time::timerfd_create
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(u32)]
+#[non_exhaustive]
+pub enum TimerfdClockId {
+ /// `CLOCK_REALTIME`—A clock that tells the “real” time.
+ ///
+ /// This is a clock that tells the amount of time elapsed since the Unix
+ /// epoch, 1970-01-01T00:00:00Z. The clock is externally settable, so it is
+ /// not monotonic. Successive reads may see decreasing times, so it isn't
+ /// reliable for measuring durations.
+ #[doc(alias = "CLOCK_REALTIME")]
+ Realtime = bitcast!(c::CLOCK_REALTIME),
+
+ /// `CLOCK_MONOTONIC`—A clock that tells an abstract time.
+ ///
+ /// Unlike `Realtime`, this clock is not based on a fixed known epoch, so
+ /// individual times aren't meaningful. However, since it isn't settable,
+ /// it is reliable for measuring durations.
+ ///
+ /// This clock does not advance while the system is suspended; see
+ /// `Boottime` for a clock that does.
+ #[doc(alias = "CLOCK_MONOTONIC")]
+ Monotonic = bitcast!(c::CLOCK_MONOTONIC),
+
+ /// `CLOCK_BOOTTIME`—Like `Monotonic`, but advances while suspended.
+ ///
+ /// This clock is similar to `Monotonic`, but does advance while the system
+ /// is suspended.
+ #[doc(alias = "CLOCK_BOOTTIME")]
+ Boottime = bitcast!(c::CLOCK_BOOTTIME),
+
+ /// `CLOCK_REALTIME_ALARM`—Like `Realtime`, but wakes a suspended system.
+ ///
+ /// This clock is like `Realtime`, but can wake up a suspended system.
+ ///
+ /// Use of this clock requires the `CAP_WAKE_ALARM` Linux capability.
+ #[doc(alias = "CLOCK_REALTIME_ALARM")]
+ RealtimeAlarm = bitcast!(c::CLOCK_REALTIME_ALARM),
+
+ /// `CLOCK_BOOTTIME_ALARM`—Like `Boottime`, but wakes a suspended system.
+ ///
+ /// This clock is like `Boottime`, but can wake up a suspended system.
+ ///
+ /// Use of this clock requires the `CAP_WAKE_ALARM` Linux capability.
+ #[doc(alias = "CLOCK_BOOTTIME_ALARM")]
+ BoottimeAlarm = bitcast!(c::CLOCK_BOOTTIME_ALARM),
+}
+
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[test]
+fn test_types() {
+ assert_eq_size!(TimerfdFlags, c::c_int);
+ assert_eq_size!(TimerfdTimerFlags, c::c_int);
+}
diff --git a/src/backend/libc/ugid/mod.rs b/src/backend/libc/ugid/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/libc/ugid/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/libc/ugid/syscalls.rs b/src/backend/libc/ugid/syscalls.rs
new file mode 100644
index 0000000..0d3f622
--- /dev/null
+++ b/src/backend/libc/ugid/syscalls.rs
@@ -0,0 +1,42 @@
+use crate::backend::c;
+use crate::ugid::{Gid, Uid};
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn getuid() -> Uid {
+ unsafe {
+ let uid = c::getuid();
+ Uid::from_raw(uid)
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn geteuid() -> Uid {
+ unsafe {
+ let uid = c::geteuid();
+ Uid::from_raw(uid)
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn getgid() -> Gid {
+ unsafe {
+ let gid = c::getgid();
+ Gid::from_raw(gid)
+ }
+}
+
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub(crate) fn getegid() -> Gid {
+ unsafe {
+ let gid = c::getegid();
+ Gid::from_raw(gid)
+ }
+}
diff --git a/src/backend/libc/winsock_c.rs b/src/backend/libc/winsock_c.rs
new file mode 100644
index 0000000..ee2704a
--- /dev/null
+++ b/src/backend/libc/winsock_c.rs
@@ -0,0 +1,59 @@
+//! Adapt the Winsock API to resemble a POSIX-style libc API.
+
+#![allow(unused_imports)]
+#![allow(non_camel_case_types)]
+#![allow(dead_code)]
+
+use windows_sys::Win32::Networking::WinSock;
+
+// Define the basic C types. With Rust 1.64, we can use these from `core::ffi`.
+pub(crate) type c_schar = i8;
+pub(crate) type c_uchar = u8;
+pub(crate) type c_short = i16;
+pub(crate) type c_ushort = u16;
+pub(crate) type c_int = i32;
+pub(crate) type c_uint = u32;
+pub(crate) type c_longlong = i64;
+pub(crate) type c_ulonglong = u64;
+pub(crate) type ssize_t = isize;
+pub(crate) type c_char = i8;
+pub(crate) type c_long = i32;
+pub(crate) type c_ulong = u32;
+pub(crate) use core::ffi::c_void;
+
+// windows-sys declares these constants as u16. For better compatibility
+// with Unix-family APIs, redeclare them as u32.
+pub(crate) const AF_INET: i32 = WinSock::AF_INET as _;
+pub(crate) const AF_INET6: i32 = WinSock::AF_INET6 as _;
+pub(crate) const AF_UNSPEC: i32 = WinSock::AF_UNSPEC as _;
+
+// Include the contents of `WinSock`, renaming as needed to match POSIX.
+//
+// Use `WSA_E_CANCELLED` for `ECANCELED` instead of `WSAECANCELLED`, because
+// `WSAECANCELLED` will be removed in the future.
+// <https://docs.microsoft.com/en-us/windows/win32/api/ws2spi/nc-ws2spi-lpnsplookupserviceend#remarks>
+pub(crate) use WinSock::{
+ closesocket as close, ioctlsocket as ioctl, WSAPoll as poll, ADDRESS_FAMILY as sa_family_t,
+ ADDRINFOA as addrinfo, IN6_ADDR as in6_addr, IN_ADDR as in_addr, IPV6_MREQ as ipv6_mreq,
+ IP_MREQ as ip_mreq, LINGER as linger, SD_BOTH as SHUT_RDWR, SD_RECEIVE as SHUT_RD,
+ SD_SEND as SHUT_WR, SOCKADDR as sockaddr, SOCKADDR_IN as sockaddr_in,
+ SOCKADDR_IN6 as sockaddr_in6, SOCKADDR_STORAGE as sockaddr_storage, WSAEACCES as EACCES,
+ WSAEADDRINUSE as EADDRINUSE, WSAEADDRNOTAVAIL as EADDRNOTAVAIL,
+ WSAEAFNOSUPPORT as EAFNOSUPPORT, WSAEALREADY as EALREADY, WSAEBADF as EBADF,
+ WSAECONNABORTED as ECONNABORTED, WSAECONNREFUSED as ECONNREFUSED, WSAECONNRESET as ECONNRESET,
+ WSAEDESTADDRREQ as EDESTADDRREQ, WSAEDISCON as EDISCON, WSAEDQUOT as EDQUOT,
+ WSAEFAULT as EFAULT, WSAEHOSTDOWN as EHOSTDOWN, WSAEHOSTUNREACH as EHOSTUNREACH,
+ WSAEINPROGRESS as EINPROGRESS, WSAEINTR as EINTR, WSAEINVAL as EINVAL,
+ WSAEINVALIDPROCTABLE as EINVALIDPROCTABLE, WSAEINVALIDPROVIDER as EINVALIDPROVIDER,
+ WSAEISCONN as EISCONN, WSAELOOP as ELOOP, WSAEMFILE as EMFILE, WSAEMSGSIZE as EMSGSIZE,
+ WSAENAMETOOLONG as ENAMETOOLONG, WSAENETDOWN as ENETDOWN, WSAENETRESET as ENETRESET,
+ WSAENETUNREACH as ENETUNREACH, WSAENOBUFS as ENOBUFS, WSAENOMORE as ENOMORE,
+ WSAENOPROTOOPT as ENOPROTOOPT, WSAENOTCONN as ENOTCONN, WSAENOTEMPTY as ENOTEMPTY,
+ WSAENOTSOCK as ENOTSOCK, WSAEOPNOTSUPP as EOPNOTSUPP, WSAEPFNOSUPPORT as EPFNOSUPPORT,
+ WSAEPROCLIM as EPROCLIM, WSAEPROTONOSUPPORT as EPROTONOSUPPORT, WSAEPROTOTYPE as EPROTOTYPE,
+ WSAEPROVIDERFAILEDINIT as EPROVIDERFAILEDINIT, WSAEREFUSED as EREFUSED, WSAEREMOTE as EREMOTE,
+ WSAESHUTDOWN as ESHUTDOWN, WSAESOCKTNOSUPPORT as ESOCKTNOSUPPORT, WSAESTALE as ESTALE,
+ WSAETIMEDOUT as ETIMEDOUT, WSAETOOMANYREFS as ETOOMANYREFS, WSAEUSERS as EUSERS,
+ WSAEWOULDBLOCK as EWOULDBLOCK, WSAEWOULDBLOCK as EAGAIN, WSAPOLLFD as pollfd,
+ WSA_E_CANCELLED as ECANCELED, *,
+};