summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcin Radomski <dextero@google.com>2024-03-13 15:43:53 +0000
committerMarcin Radomski <dextero@google.com>2024-03-26 10:52:32 +0000
commit2cdd3b557becb514725fe73a29c47655055f4a21 (patch)
tree28c6341452f5a95c362427b7b018f88638b552fa
parent355a3c4fa18e071a632e2be5f7690272f44e0a6a (diff)
downloadrustix-2cdd3b557becb514725fe73a29c47655055f4a21.tar.gz
Import 'rustix' crateupstream
Request Document: go/android-rust-importing-crates For CL Reviewers: go/android3p#cl-review For Build Team: go/ab-third-party-imports Bug: http://b/328180148 Test: m librustix Change-Id: I322283d1d8b635c047a30712ba8c02337365ffa6
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp41
-rw-r--r--CODE_OF_CONDUCT.md49
-rw-r--r--CONTRIBUTING.md27
-rw-r--r--COPYRIGHT29
-rw-r--r--Cargo.lock1043
-rw-r--r--Cargo.toml270
-rw-r--r--Cargo.toml.orig246
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-Apache-2.0_WITH_LLVM-exception220
-rw-r--r--LICENSE-MIT23
-rw-r--r--METADATA21
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--ORG_CODE_OF_CONDUCT.md143
-rw-r--r--OWNERS7
-rw-r--r--README.md196
-rw-r--r--SECURITY.md29
-rw-r--r--benches/mod.rs217
-rw-r--r--build.rs250
-rw-r--r--cargo_embargo.json11
-rw-r--r--patches/0001-Make-linux-raw-sys-dependency-optional.patch144
-rw-r--r--patches/0002-Make-mm-feature-compile-without-linux-raw-sys.patch66
-rw-r--r--patches/0003-Make-fs-feature-compile-without-linux-raw-sys.patch145
-rw-r--r--patches/0004-Don-t-rename-errno-to-libc_errno.patch250
-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
-rw-r--r--src/backend/linux_raw/arch/aarch64.rs268
-rw-r--r--src/backend/linux_raw/arch/arm.rs265
-rw-r--r--src/backend/linux_raw/arch/mips.rs543
-rw-r--r--src/backend/linux_raw/arch/mips32r6.rs543
-rw-r--r--src/backend/linux_raw/arch/mips64.rs466
-rw-r--r--src/backend/linux_raw/arch/mips64r6.rs470
-rw-r--r--src/backend/linux_raw/arch/mod.rs317
-rw-r--r--src/backend/linux_raw/arch/powerpc64.rs413
-rw-r--r--src/backend/linux_raw/arch/riscv64.rs265
-rw-r--r--src/backend/linux_raw/arch/thumb.rs322
-rw-r--r--src/backend/linux_raw/arch/x86.rs489
-rw-r--r--src/backend/linux_raw/arch/x86_64.rs293
-rw-r--r--src/backend/linux_raw/c.rs319
-rw-r--r--src/backend/linux_raw/conv.rs1019
-rw-r--r--src/backend/linux_raw/event/epoll.rs470
-rw-r--r--src/backend/linux_raw/event/mod.rs4
-rw-r--r--src/backend/linux_raw/event/poll_fd.rs98
-rw-r--r--src/backend/linux_raw/event/syscalls.rs152
-rw-r--r--src/backend/linux_raw/event/types.rs21
-rw-r--r--src/backend/linux_raw/fs/dir.rs315
-rw-r--r--src/backend/linux_raw/fs/inotify.rs118
-rw-r--r--src/backend/linux_raw/fs/makedev.rs19
-rw-r--r--src/backend/linux_raw/fs/mod.rs13
-rw-r--r--src/backend/linux_raw/fs/syscalls.rs1660
-rw-r--r--src/backend/linux_raw/fs/types.rs742
-rw-r--r--src/backend/linux_raw/io/errno.rs553
-rw-r--r--src/backend/linux_raw/io/mod.rs3
-rw-r--r--src/backend/linux_raw/io/syscalls.rs379
-rw-r--r--src/backend/linux_raw/io/types.rs57
-rw-r--r--src/backend/linux_raw/io_uring/mod.rs1
-rw-r--r--src/backend/linux_raw/io_uring/syscalls.rs62
-rw-r--r--src/backend/linux_raw/mm/mod.rs2
-rw-r--r--src/backend/linux_raw/mm/syscalls.rs237
-rw-r--r--src/backend/linux_raw/mm/types.rs295
-rw-r--r--src/backend/linux_raw/mod.rs117
-rw-r--r--src/backend/linux_raw/mount/mod.rs2
-rw-r--r--src/backend/linux_raw/mount/syscalls.rs239
-rw-r--r--src/backend/linux_raw/mount/types.rs332
-rw-r--r--src/backend/linux_raw/net/addr.rs178
-rw-r--r--src/backend/linux_raw/net/mod.rs9
-rw-r--r--src/backend/linux_raw/net/msghdr.rs171
-rw-r--r--src/backend/linux_raw/net/netdevice.rs68
-rw-r--r--src/backend/linux_raw/net/read_sockaddr.rs221
-rw-r--r--src/backend/linux_raw/net/send_recv.rs60
-rw-r--r--src/backend/linux_raw/net/sockopt.rs1047
-rw-r--r--src/backend/linux_raw/net/syscalls.rs1051
-rw-r--r--src/backend/linux_raw/net/write_sockaddr.rs82
-rw-r--r--src/backend/linux_raw/param/auxv.rs565
-rw-r--r--src/backend/linux_raw/param/init.rs171
-rw-r--r--src/backend/linux_raw/param/libc_auxv.rs175
-rw-r--r--src/backend/linux_raw/param/mod.rs15
-rw-r--r--src/backend/linux_raw/pid/mod.rs1
-rw-r--r--src/backend/linux_raw/pid/syscalls.rs17
-rw-r--r--src/backend/linux_raw/pipe/mod.rs2
-rw-r--r--src/backend/linux_raw/pipe/syscalls.rs135
-rw-r--r--src/backend/linux_raw/pipe/types.rs80
-rw-r--r--src/backend/linux_raw/prctl/mod.rs1
-rw-r--r--src/backend/linux_raw/prctl/syscalls.rs21
-rw-r--r--src/backend/linux_raw/process/cpu_set.rs46
-rw-r--r--src/backend/linux_raw/process/mod.rs4
-rw-r--r--src/backend/linux_raw/process/syscalls.rs603
-rw-r--r--src/backend/linux_raw/process/types.rs104
-rw-r--r--src/backend/linux_raw/process/wait.rs68
-rw-r--r--src/backend/linux_raw/pty/mod.rs1
-rw-r--r--src/backend/linux_raw/pty/syscalls.rs43
-rw-r--r--src/backend/linux_raw/rand/mod.rs2
-rw-r--r--src/backend/linux_raw/rand/syscalls.rs19
-rw-r--r--src/backend/linux_raw/rand/types.rs20
-rw-r--r--src/backend/linux_raw/reg.rs259
-rw-r--r--src/backend/linux_raw/runtime/mod.rs2
-rw-r--r--src/backend/linux_raw/runtime/syscalls.rs318
-rw-r--r--src/backend/linux_raw/runtime/tls.rs98
-rw-r--r--src/backend/linux_raw/shm/mod.rs2
-rw-r--r--src/backend/linux_raw/shm/syscalls.rs47
-rw-r--r--src/backend/linux_raw/shm/types.rs30
-rw-r--r--src/backend/linux_raw/system/mod.rs2
-rw-r--r--src/backend/linux_raw/system/syscalls.rs49
-rw-r--r--src/backend/linux_raw/system/types.rs4
-rw-r--r--src/backend/linux_raw/termios/mod.rs1
-rw-r--r--src/backend/linux_raw/termios/syscalls.rs330
-rw-r--r--src/backend/linux_raw/thread/futex.rs45
-rw-r--r--src/backend/linux_raw/thread/mod.rs2
-rw-r--r--src/backend/linux_raw/thread/syscalls.rs347
-rw-r--r--src/backend/linux_raw/time/mod.rs3
-rw-r--r--src/backend/linux_raw/time/syscalls.rs257
-rw-r--r--src/backend/linux_raw/time/types.rs100
-rw-r--r--src/backend/linux_raw/ugid/mod.rs1
-rw-r--r--src/backend/linux_raw/ugid/syscalls.rs66
-rw-r--r--src/backend/linux_raw/vdso.rs313
-rw-r--r--src/backend/linux_raw/vdso_wrappers.rs608
-rw-r--r--src/bitcast.rs34
-rw-r--r--src/buffer.rs21
-rw-r--r--src/check_types.rs91
-rw-r--r--src/clockid.rs164
-rw-r--r--src/cstr.rs77
-rw-r--r--src/event/eventfd.rs20
-rw-r--r--src/event/kqueue.rs449
-rw-r--r--src/event/mod.rs29
-rw-r--r--src/event/pause.rs31
-rw-r--r--src/event/poll.rs32
-rw-r--r--src/event/port.rs151
-rw-r--r--src/ffi.rs15
-rw-r--r--src/fs/abs.rs301
-rw-r--r--src/fs/at.rs472
-rw-r--r--src/fs/constants.rs7
-rw-r--r--src/fs/copy_file_range.rs20
-rw-r--r--src/fs/cwd.rs39
-rw-r--r--src/fs/dir.rs5
-rw-r--r--src/fs/fadvise.rs18
-rw-r--r--src/fs/fcntl.rs112
-rw-r--r--src/fs/fcntl_apple.rs66
-rw-r--r--src/fs/fcopyfile.rs88
-rw-r--r--src/fs/fd.rs357
-rw-r--r--src/fs/getpath.rs14
-rw-r--r--src/fs/id.rs1
-rw-r--r--src/fs/ioctl.rs165
-rw-r--r--src/fs/makedev.rs35
-rw-r--r--src/fs/memfd_create.rs18
-rw-r--r--src/fs/mod.rs178
-rw-r--r--src/fs/mount.rs55
-rw-r--r--src/fs/openat2.rs23
-rw-r--r--src/fs/raw_dir.rs237
-rw-r--r--src/fs/seek_from.rs53
-rw-r--r--src/fs/sendfile.rs19
-rw-r--r--src/fs/statx.rs135
-rw-r--r--src/fs/sync.rs14
-rw-r--r--src/fs/xattr.rs202
-rw-r--r--src/io/close.rs55
-rw-r--r--src/io/dup.rs124
-rw-r--r--src/io/errno.rs72
-rw-r--r--src/io/fcntl.rs141
-rw-r--r--src/io/ioctl.rs77
-rw-r--r--src/io/is_read_write.rs19
-rw-r--r--src/io/mod.rs32
-rw-r--r--src/io/read_write.rs312
-rw-r--r--src/io_uring.rs1527
-rw-r--r--src/ioctl/bsd.rs27
-rw-r--r--src/ioctl/linux.rs118
-rw-r--r--src/ioctl/mod.rs357
-rw-r--r--src/ioctl/patterns.rs256
-rw-r--r--src/lib.rs395
-rw-r--r--src/maybe_polyfill/no_std/io/mod.rs107
-rw-r--r--src/maybe_polyfill/no_std/mod.rs16
-rw-r--r--src/maybe_polyfill/no_std/net/ip_addr.rs2068
-rw-r--r--src/maybe_polyfill/no_std/net/mod.rs6
-rw-r--r--src/maybe_polyfill/no_std/net/socket_addr.rs641
-rw-r--r--src/maybe_polyfill/no_std/os/fd/mod.rs25
-rw-r--r--src/maybe_polyfill/no_std/os/fd/owned.rs294
-rw-r--r--src/maybe_polyfill/no_std/os/fd/raw.rs164
-rw-r--r--src/maybe_polyfill/no_std/os/mod.rs4
-rw-r--r--src/maybe_polyfill/no_std/os/windows/io/mod.rs5
-rw-r--r--src/maybe_polyfill/no_std/os/windows/io/raw.rs71
-rw-r--r--src/maybe_polyfill/no_std/os/windows/io/socket.rs199
-rw-r--r--src/maybe_polyfill/no_std/os/windows/mod.rs19
-rw-r--r--src/maybe_polyfill/std/mod.rs43
-rw-r--r--src/mm/madvise.rs49
-rw-r--r--src/mm/mmap.rs409
-rw-r--r--src/mm/mod.rs15
-rw-r--r--src/mm/msync.rs46
-rw-r--r--src/mm/userfaultfd.rs30
-rw-r--r--src/mount/fsopen.rs219
-rw-r--r--src/mount/mod.rs19
-rw-r--r--src/mount/mount_unmount.rs175
-rw-r--r--src/mount/types.rs1
-rw-r--r--src/net/mod.rs33
-rw-r--r--src/net/netdevice.rs99
-rw-r--r--src/net/send_recv/mod.rs403
-rw-r--r--src/net/send_recv/msg.rs988
-rw-r--r--src/net/socket.rs769
-rw-r--r--src/net/socket_addr_any.rs122
-rw-r--r--src/net/socketpair.rs36
-rw-r--r--src/net/sockopt.rs1482
-rw-r--r--src/net/types.rs1788
-rw-r--r--src/net/wsa.rs49
-rw-r--r--src/param/auxv.rs87
-rw-r--r--src/param/init.rs23
-rw-r--r--src/param/mod.rs14
-rw-r--r--src/path/arg.rs1099
-rw-r--r--src/path/dec_int.rs120
-rw-r--r--src/path/mod.rs12
-rw-r--r--src/pid.rs104
-rw-r--r--src/pipe.rs220
-rw-r--r--src/prctl.rs71
-rw-r--r--src/process/chdir.rs97
-rw-r--r--src/process/chroot.rs16
-rw-r--r--src/process/exit.rs36
-rw-r--r--src/process/id.rs232
-rw-r--r--src/process/ioctl.rs52
-rw-r--r--src/process/kill.rs97
-rw-r--r--src/process/membarrier.rs92
-rw-r--r--src/process/mod.rs80
-rw-r--r--src/process/pidfd.rs30
-rw-r--r--src/process/pidfd_getfd.rs56
-rw-r--r--src/process/prctl.rs1151
-rw-r--r--src/process/priority.rs132
-rw-r--r--src/process/procctl.rs530
-rw-r--r--src/process/rlimit.rs53
-rw-r--r--src/process/sched.rs125
-rw-r--r--src/process/sched_yield.rs16
-rw-r--r--src/process/umask.rs21
-rw-r--r--src/process/wait.rs365
-rw-r--r--src/procfs.rs520
-rw-r--r--src/pty.rs207
-rw-r--r--src/rand/getrandom.rs43
-rw-r--r--src/rand/mod.rs7
-rw-r--r--src/runtime.rs581
-rw-r--r--src/shm.rs40
-rw-r--r--src/signal.rs250
-rw-r--r--src/static_assertions.rs36
-rw-r--r--src/stdio.rs511
-rw-r--r--src/system.rs220
-rw-r--r--src/termios/ioctl.rs53
-rw-r--r--src/termios/mod.rs27
-rw-r--r--src/termios/tc.rs209
-rw-r--r--src/termios/tty.rs84
-rw-r--r--src/termios/types.rs1450
-rw-r--r--src/thread/clock.rs100
-rw-r--r--src/thread/futex.rs38
-rw-r--r--src/thread/id.rs115
-rw-r--r--src/thread/libcap.rs185
-rw-r--r--src/thread/mod.rs30
-rw-r--r--src/thread/prctl.rs1007
-rw-r--r--src/thread/setns.rs137
-rw-r--r--src/time/clock.rs108
-rw-r--r--src/time/mod.rs11
-rw-r--r--src/time/timerfd.rs41
-rw-r--r--src/timespec.rs115
-rw-r--r--src/ugid.rs98
-rw-r--r--src/utils.rs82
-rw-r--r--src/weak.rs288
329 files changed, 68005 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..47c3436
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "8a7ff3cf72a1ca1ae60d7ee37454c67aba81d678"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..261671f
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,41 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file as changes will be overridden on upgrade.
+
+// TODO: Add license.
+rust_library {
+ name: "librustix",
+ host_supported: true,
+ crate_name: "rustix",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.38.31",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ features: [
+ "alloc",
+ "errno",
+ "event",
+ "fs",
+ "libc",
+ "mm",
+ "std",
+ "use-libc",
+ "use-libc-auxv",
+ ],
+ cfgs: [
+ "libc",
+ "linux_kernel",
+ "linux_like",
+ "static_assertions",
+ ],
+ rustlibs: [
+ "libbitflags",
+ "liberrno",
+ "liblibc",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..32ff028
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,49 @@
+# Contributor Covenant Code of Conduct
+
+*Note*: this Code of Conduct pertains to individuals' behavior. Please also see the [Organizational Code of Conduct][OCoC].
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Bytecode Alliance CoC team at [report@bytecodealliance.org](mailto:report@bytecodealliance.org). The CoC team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The CoC team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the Bytecode Alliance's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[OCoC]: https://github.com/bytecodealliance/rustix/blob/main/ORG_CODE_OF_CONDUCT.md
+[homepage]: https://www.contributor-covenant.org
+[version]: https://www.contributor-covenant.org/version/1/4/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..fca1cf1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+# Contributing to rustix
+
+Rustix is a [Bytecode Alliance] project. It follows the Bytecode Alliance's
+[Code of Conduct] and [Organizational Code of Conduct].
+
+## Testing
+
+To keep compile times low, most features in rustix's API are behind cargo
+features. A special feature, `all-apis` enables all APIs, which is useful
+for testing.
+
+```console
+cargo test --features=all-apis
+```
+
+And, rustix has two backends, linux_raw and libc, and only one is used in
+any given build. To test with the libc backend explicitly, additionally
+enable the `use-libc` feature:
+
+```console
+cargo test --features=all-apis,use-libc
+```
+
+Beyond that, rustix's CI tests many targets and configurations. Asking for
+help is always welcome, and it's especially encouraged when the issue is
+getting all the `cfg`s lined up to get everything compiling on all the
+configurations on CI.
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 0000000..a841525
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,29 @@
+Short version for non-lawyers:
+
+`rustix` is triple-licensed under Apache 2.0 with the LLVM Exception,
+Apache 2.0, and MIT terms.
+
+
+Longer version:
+
+Copyrights in the `rustix` project are retained by their contributors.
+No copyright assignment is required to contribute to the `rustix`
+project.
+
+Some files include code derived from Rust's `libstd`; see the comments in
+the code for details.
+
+Except as otherwise noted (below and/or in individual files), `rustix`
+is licensed under:
+
+ - the Apache License, Version 2.0, with the LLVM Exception
+ <LICENSE-Apache-2.0_WITH_LLVM-exception> or
+ <http://llvm.org/foundation/relicensing/LICENSE.txt>
+ - the Apache License, Version 2.0
+ <LICENSE-APACHE> or
+ <http://www.apache.org/licenses/LICENSE-2.0>,
+ - or the MIT license
+ <LICENSE-MIT> or
+ <http://opensource.org/licenses/MIT>,
+
+at your option.
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f871427
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1043 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ciborium"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "bitflags 1.3.2",
+ "clap_lex",
+ "indexmap",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "compiler_builtins"
+version = "0.1.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3686cc48897ce1950aa70fd595bd2dc9f767a3c4cca4cd17b2cb52a2d37e6eb4"
+dependencies = [
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "criterion"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
+dependencies = [
+ "anes",
+ "atty",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "ctor"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if",
+ "hashbrown 0.14.3",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+dependencies = [
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "plotters"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustc-std-workspace-alloc"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff66d57013a5686e1917ed6a025d54dd591fcda71a41fe07edf4d16726aefa86"
+
+[[package]]
+name = "rustc-std-workspace-core"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c"
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.31"
+dependencies = [
+ "bitflags 2.4.1",
+ "compiler_builtins",
+ "criterion",
+ "ctor",
+ "errno",
+ "flate2",
+ "itoa",
+ "libc",
+ "linux-raw-sys",
+ "memoffset",
+ "once_cell",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+ "serial_test",
+ "static_assertions",
+ "tempfile",
+ "windows-sys",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serial_test"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d"
+dependencies = [
+ "dashmap",
+ "futures",
+ "lazy_static",
+ "log",
+ "parking_lot",
+ "serial_test_derive",
+]
+
+[[package]]
+name = "serial_test_derive"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix 0.38.28",
+ "windows-sys",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
+
+[[package]]
+name = "web-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..19269d4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,270 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.63"
+name = "rustix"
+version = "0.38.31"
+authors = [
+ "Dan Gohman <dev@sunfishcode.online>",
+ "Jakub Konka <kubkon@jakubkonka.com>",
+]
+include = [
+ "src",
+ "build.rs",
+ "Cargo.toml",
+ "COPYRIGHT",
+ "LICENSE*",
+ "/*.md",
+ "benches",
+]
+description = "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls"
+documentation = "https://docs.rs/rustix"
+readme = "README.md"
+keywords = [
+ "api",
+ "file",
+ "network",
+ "safe",
+ "syscall",
+]
+categories = [
+ "os::unix-apis",
+ "date-and-time",
+ "filesystem",
+ "network-programming",
+]
+license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
+repository = "https://github.com/bytecodealliance/rustix"
+
+[package.metadata.docs.rs]
+features = ["all-apis"]
+rustdoc-args = [
+ "--cfg",
+ "doc_cfg",
+]
+targets = [
+ "x86_64-unknown-linux-gnu",
+ "i686-unknown-linux-gnu",
+ "x86_64-apple-darwin",
+ "x86_64-pc-windows-msvc",
+ "x86_64-unknown-freebsd",
+ "x86_64-unknown-openbsd",
+ "x86_64-unknown-netbsd",
+ "x86_64-unknown-dragonfly",
+ "x86_64-unknown-illumos",
+ "x86_64-unknown-redox",
+ "x86_64-unknown-haiku",
+ "wasm32-unknown-emscripten",
+ "wasm32-wasi",
+]
+
+[[bench]]
+name = "mod"
+harness = false
+
+[dependencies.alloc]
+version = "1.0.0"
+optional = true
+package = "rustc-std-workspace-alloc"
+
+[dependencies.bitflags]
+version = "2.4.0"
+default-features = false
+
+[dependencies.compiler_builtins]
+version = "0.1.49"
+optional = true
+
+[dependencies.core]
+version = "1.0.0"
+optional = true
+package = "rustc-std-workspace-core"
+
+[dependencies.itoa]
+version = "1.0.1"
+optional = true
+default-features = false
+
+[dev-dependencies.flate2]
+version = "1.0"
+
+[dev-dependencies.libc]
+version = "0.2.152"
+
+[dev-dependencies.errno]
+version = "0.3.8"
+default-features = false
+
+[dev-dependencies.memoffset]
+version = "0.9.0"
+
+[dev-dependencies.serial_test]
+version = "2.0.0"
+
+[dev-dependencies.static_assertions]
+version = "1.1.0"
+
+[dev-dependencies.tempfile]
+version = "3.5.0"
+
+[features]
+all-apis = [
+ "event",
+ "fs",
+ "io_uring",
+ "mm",
+ "mount",
+ "net",
+ "param",
+ "pipe",
+ "process",
+ "procfs",
+ "pty",
+ "rand",
+ "runtime",
+ "shm",
+ "stdio",
+ "system",
+ "termios",
+ "thread",
+ "time",
+]
+alloc = []
+cc = []
+default = [
+ "std",
+ "use-libc-auxv",
+]
+event = []
+fs = []
+io_uring = [
+ "event",
+ "fs",
+ "net",
+ "linux-raw-sys/io_uring",
+]
+linux_4_11 = []
+linux_latest = ["linux_4_11"]
+mm = []
+mount = []
+net = [
+ "linux-raw-sys/net",
+ "linux-raw-sys/netlink",
+ "linux-raw-sys/if_ether",
+ "linux-raw-sys/xdp",
+]
+param = ["fs"]
+pipe = []
+process = ["linux-raw-sys/prctl"]
+procfs = [
+ "once_cell",
+ "itoa",
+ "fs",
+]
+pty = [
+ "itoa",
+ "fs",
+]
+rand = []
+runtime = ["linux-raw-sys/prctl"]
+rustc-dep-of-std = [
+ "dep:core",
+ "dep:alloc",
+ "dep:compiler_builtins",
+ "linux-raw-sys/rustc-dep-of-std",
+ "bitflags/rustc-dep-of-std",
+ "compiler_builtins?/rustc-dep-of-std",
+]
+shm = ["fs"]
+std = [
+ "bitflags/std",
+ "alloc",
+ "libc?/std",
+ "errno?/std",
+]
+stdio = []
+system = ["linux-raw-sys/system"]
+termios = []
+thread = ["linux-raw-sys/prctl"]
+time = []
+use-explicitly-provided-auxv = []
+use-libc = [
+ "errno",
+ "libc",
+]
+use-libc-auxv = []
+
+[target."cfg(all(any(target_os = \"android\", target_os = \"linux\"), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))".dependencies.linux-raw-sys]
+version = "0.4.12"
+features = [
+ "general",
+ "ioctl",
+ "no_std",
+]
+default-features = false
+optional = true
+
+[target."cfg(all(criterion, not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dev-dependencies.criterion]
+version = "0.4"
+
+[target."cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))".dependencies.libc]
+version = "0.2.152"
+features = ["extra_traits"]
+optional = true
+default-features = false
+
+[target."cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))".dependencies.errno]
+version = "0.3.8"
+optional = true
+default-features = false
+
+[target."cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))".dependencies.linux-raw-sys]
+version = "0.4.12"
+features = [
+ "general",
+ "errno",
+ "ioctl",
+ "no_std",
+ "elf",
+]
+default-features = false
+optional = true
+
+[target."cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))".dependencies.libc]
+version = "0.2.152"
+features = ["extra_traits"]
+default-features = false
+
+[target."cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))".dependencies.errno]
+version = "0.3.8"
+default-features = false
+
+[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies.once_cell]
+version = "1.5.2"
+optional = true
+
+[target."cfg(windows)".dependencies.errno]
+version = "0.3.8"
+default-features = false
+
+[target."cfg(windows)".dependencies.windows-sys]
+version = "0.52.0"
+features = [
+ "Win32_Foundation",
+ "Win32_Networking_WinSock",
+ "Win32_NetworkManagement_IpHelper",
+ "Win32_System_Threading",
+]
+
+[target."cfg(windows)".dev-dependencies.ctor]
+version = "0.2.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..4592ae9
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,246 @@
+[package]
+name = "rustix"
+version = "0.38.31"
+authors = [
+ "Dan Gohman <dev@sunfishcode.online>",
+ "Jakub Konka <kubkon@jakubkonka.com>",
+]
+description = "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls"
+documentation = "https://docs.rs/rustix"
+license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
+repository = "https://github.com/bytecodealliance/rustix"
+edition = "2021"
+keywords = ["api", "file", "network", "safe", "syscall"]
+categories = ["os::unix-apis", "date-and-time", "filesystem", "network-programming"]
+include = ["src", "build.rs", "Cargo.toml", "COPYRIGHT", "LICENSE*", "/*.md", "benches"]
+rust-version = "1.63"
+
+[dependencies]
+bitflags = { version = "2.4.0", default-features = false }
+itoa = { version = "1.0.1", default-features = false, optional = true }
+
+# Special dependencies used in rustc-dep-of-std mode.
+core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
+alloc = { version = "1.0.0", optional = true, package = "rustc-std-workspace-alloc" }
+compiler_builtins = { version = '0.1.49', optional = true }
+
+# The procfs feature needs once_cell.
+# With Rust 1.70.0, we can switch to `core::cell::OnceCell`.
+[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
+once_cell = { version = "1.5.2", optional = true }
+
+# Dependencies for platforms where linux_raw is supported, in addition to libc:
+#
+# On Linux on selected architectures, the linux_raw backend is supported, in
+# addition to the libc backend. The linux_raw backend is used by default. The
+# libc backend can be selected via adding `--cfg=rustix_use_libc` to
+# `RUSTFLAGS` or enabling the `use-libc` cargo feature.
+# Enabling only the libc backend will limit available functionality.
+[target.'cfg(all(not(rustix_use_libc), not(miri), target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64"))))'.dependencies]
+linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"], optional = true }
+errno = { version = "0.3.8", default-features = false, optional = true }
+libc = { version = "0.2.152", default-features = false, features = ["extra_traits"], optional = true }
+
+# Dependencies for platforms where only libc is supported:
+#
+# On all other Unix-family platforms, and under Miri, we always use the libc
+# backend, so enable its dependencies unconditionally.
+[target.'cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64")))))))'.dependencies]
+errno = { version = "0.3.8", default-features = false }
+libc = { version = "0.2.152", default-features = false, features = ["extra_traits"] }
+
+# Additional dependencies for Linux with the libc backend:
+#
+# Some syscalls do not have libc wrappers, such as in `io_uring`. For these,
+# the libc backend uses the linux-raw-sys ABI and `libc::syscall`.
+[target.'cfg(all(any(target_os = "android", target_os = "linux"), any(rustix_use_libc, miri, not(all(target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64")))))))'.dependencies]
+linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "ioctl", "no_std"], optional = true }
+
+# For the libc backend on Windows, use the Winsock API in windows-sys.
+[target.'cfg(windows)'.dependencies.windows-sys]
+version = "0.52.0"
+features = [
+ "Win32_Foundation",
+ "Win32_Networking_WinSock",
+ "Win32_NetworkManagement_IpHelper",
+ "Win32_System_Threading"
+]
+
+# For the libc backend on Windows, also use the errno crate, which has Windows
+# support.
+[target.'cfg(windows)'.dependencies.errno]
+version = "0.3.8"
+default-features = false
+
+[dev-dependencies]
+tempfile = "3.5.0"
+libc = "0.2.152"
+errno = { version = "0.3.8", default-features = false }
+serial_test = "2.0.0"
+memoffset = "0.9.0"
+flate2 = "1.0"
+static_assertions = "1.1.0"
+
+[target.'cfg(all(criterion, not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies]
+criterion = "0.4"
+
+[target.'cfg(windows)'.dev-dependencies]
+ctor = "0.2.0"
+
+# Add Criterion configuration, as described here:
+# <https://bheisler.github.io/criterion.rs/book/getting_started.html#step-1---add-dependency-to-cargotoml>
+[[bench]]
+name = "mod"
+harness = false
+
+[package.metadata.docs.rs]
+features = ["all-apis"]
+rustdoc-args = ["--cfg", "doc_cfg"]
+targets = [
+ "x86_64-unknown-linux-gnu",
+ "i686-unknown-linux-gnu",
+ "x86_64-apple-darwin",
+ "x86_64-pc-windows-msvc",
+ "x86_64-unknown-freebsd",
+ "x86_64-unknown-openbsd",
+ "x86_64-unknown-netbsd",
+ "x86_64-unknown-dragonfly",
+ "x86_64-unknown-illumos",
+ "x86_64-unknown-redox",
+ "x86_64-unknown-haiku",
+ "wasm32-unknown-emscripten",
+ "wasm32-wasi",
+]
+
+[features]
+
+# By default, use `std` and use libc for aux values.
+#
+# It turns out to be bizarrely awkward to obtain the aux values reliably and
+# efficiently on Linux from anywhere other than libc. We can do it, but most
+# users are better served by just using libc for this.
+default = ["std", "use-libc-auxv"]
+
+# This enables use of std. Disabling this enables `#![no_std]`, and requires
+# Rust 1.64 or newer.
+std = ["bitflags/std", "alloc", "libc?/std", "errno?/std"]
+
+# Enable this to request the libc backend.
+use-libc = ["errno", "libc"]
+
+# Enable `rustix::event::*`.
+event = []
+
+# Enable `rustix::fs::*`.
+fs = []
+
+# Enable `rustix::io_uring::*` (on platforms that support it).
+io_uring = ["event", "fs", "net", "linux-raw-sys/io_uring"]
+
+# Enable `rustix::mount::*`.
+mount = []
+
+# Enable `rustix::net::*`.
+net = ["linux-raw-sys/net", "linux-raw-sys/netlink", "linux-raw-sys/if_ether", "linux-raw-sys/xdp"]
+
+# Enable `rustix::thread::*`.
+thread = ["linux-raw-sys/prctl"]
+
+# Enable `rustix::process::*`.
+process = ["linux-raw-sys/prctl"]
+
+# Enable `rustix::shm::*`.
+shm = ["fs"]
+
+# Enable `rustix::time::*`.
+time = []
+
+# Enable `rustix::param::*`.
+param = ["fs"]
+
+# Enable this to enable `rustix::io::proc_self_*` (on Linux) and `ttyname`.
+procfs = ["once_cell", "itoa", "fs"]
+
+# Enable `rustix::pty::*`.
+pty = ["itoa", "fs"]
+
+# Enable `rustix::termios::*`.
+termios = []
+
+# Enable `rustix::mm::*`.
+mm = []
+
+# Enable `rustix::pipe::*`.
+pipe = []
+
+# Enable `rustix::rand::*`.
+rand = []
+
+# Enable `rustix::stdio::*`.
+stdio = []
+
+# Enable `rustix::system::*`.
+system = ["linux-raw-sys/system"]
+
+# Enable `rustix::runtime::*`. This API is undocumented and unstable and
+# experimental and not intended for general-purpose use.
+runtime = ["linux-raw-sys/prctl"]
+
+# Enable all API features.
+all-apis = [
+ "event",
+ "fs",
+ "io_uring",
+ "mm",
+ "mount",
+ "net",
+ "param",
+ "pipe",
+ "process",
+ "procfs",
+ "pty",
+ "rand",
+ "runtime",
+ "shm",
+ "stdio",
+ "system",
+ "termios",
+ "thread",
+ "time",
+]
+
+# When using the linux_raw backend, should we use libc for reading the aux
+# vectors, instead of reading them ourselves from /proc/self/auxv?
+use-libc-auxv = []
+
+# Enable "use-explicitly-provided-auxv" mode, with a public
+# `rustix::param::init` function that must be called before anything else in
+# rustix. This is unstable and experimental and not intended for
+# general-purpose use.
+use-explicitly-provided-auxv = []
+
+# OS compatibility features
+
+# Optimize for Linux 4.11 or later
+linux_4_11 = []
+
+# Enable all optimizations for the latest Linux versions.
+linux_latest = ["linux_4_11"]
+
+# Enable features which depend on the Rust global allocator, such as functions
+# that return owned strings or `Vec`s.
+alloc = []
+
+# This is used in the port of std to rustix. This is experimental and not meant
+# for regular use.
+rustc-dep-of-std = [
+ "dep:core",
+ "dep:alloc",
+ "dep:compiler_builtins",
+ "linux-raw-sys/rustc-dep-of-std",
+ "bitflags/rustc-dep-of-std",
+ "compiler_builtins?/rustc-dep-of-std",
+]
+
+# Obsolete and deprecated.
+cc = []
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE \ No newline at end of file
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-Apache-2.0_WITH_LLVM-exception b/LICENSE-Apache-2.0_WITH_LLVM-exception
new file mode 100644
index 0000000..f9d8195
--- /dev/null
+++ b/LICENSE-Apache-2.0_WITH_LLVM-exception
@@ -0,0 +1,220 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+--- LLVM Exceptions to the Apache 2.0 License ----
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into an Object form of such source code, you
+may redistribute such embedded portions in such Object form without complying
+with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
+
+In addition, if you combine or link compiled forms of this Software with
+software that is licensed under the GPLv2 ("Combined Software") and if a
+court of competent jurisdiction determines that the patent provision (Section
+3), the indemnity provision (Section 9) or other Section of the License
+conflicts with the conditions of the GPLv2, you may retroactively and
+prospectively choose to deem waived or otherwise exclude such Section(s) of
+the License, but only in their entirety and only with respect to the Combined
+Software.
+
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..67bb26c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,21 @@
+name: "rustix"
+description: "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "rustix"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/rustix/rustix-0.38.31.crate"
+ primary_source: true
+ }
+ version: "0.38.31"
+ # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 3
+ day: 13
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/ORG_CODE_OF_CONDUCT.md b/ORG_CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..6f4fb3f
--- /dev/null
+++ b/ORG_CODE_OF_CONDUCT.md
@@ -0,0 +1,143 @@
+# Bytecode Alliance Organizational Code of Conduct (OCoC)
+
+*Note*: this Code of Conduct pertains to organizations' behavior. Please also see the [Individual Code of Conduct](CODE_OF_CONDUCT.md).
+
+## Preamble
+
+The Bytecode Alliance (BA) welcomes involvement from organizations,
+including commercial organizations. This document is an
+*organizational* code of conduct, intended particularly to provide
+guidance to commercial organizations. It is distinct from the
+[Individual Code of Conduct (ICoC)](CODE_OF_CONDUCT.md), and does not
+replace the ICoC. This OCoC applies to any group of people acting in
+concert as a BA member or as a participant in BA activities, whether
+or not that group is formally incorporated in some jurisdiction.
+
+The code of conduct described below is not a set of rigid rules, and
+we did not write it to encompass every conceivable scenario that might
+arise. For example, it is theoretically possible there would be times
+when asserting patents is in the best interest of the BA community as
+a whole. In such instances, consult with the BA, strive for
+consensus, and interpret these rules with an intent that is generous
+to the community the BA serves.
+
+While we may revise these guidelines from time to time based on
+real-world experience, overall they are based on a simple principle:
+
+*Bytecode Alliance members should observe the distinction between
+ public community functions and private functions — especially
+ commercial ones — and should ensure that the latter support, or at
+ least do not harm, the former.*
+
+## Guidelines
+
+ * **Do not cause confusion about Wasm standards or interoperability.**
+
+ Having an interoperable WebAssembly core is a high priority for
+ the BA, and members should strive to preserve that core. It is fine
+ to develop additional non-standard features or APIs, but they
+ should always be clearly distinguished from the core interoperable
+ Wasm.
+
+ Treat the WebAssembly name and any BA-associated names with
+ respect, and follow BA trademark and branding guidelines. If you
+ distribute a customized version of software originally produced by
+ the BA, or if you build a product or service using BA-derived
+ software, use names that clearly distinguish your work from the
+ original. (You should still provide proper attribution to the
+ original, of course, wherever such attribution would normally be
+ given.)
+
+ Further, do not use the WebAssembly name or BA-associated names in
+ other public namespaces in ways that could cause confusion, e.g.,
+ in company names, names of commercial service offerings, domain
+ names, publicly-visible social media accounts or online service
+ accounts, etc. It may sometimes be reasonable, however, to
+ register such a name in a new namespace and then immediately donate
+ control of that account to the BA, because that would help the project
+ maintain its identity.
+
+ For further guidance, see the BA Trademark and Branding Policy
+ [TODO: create policy, then insert link].
+
+ * **Do not restrict contributors.** If your company requires
+ employees or contractors to sign non-compete agreements, those
+ agreements must not prevent people from participating in the BA or
+ contributing to related projects.
+
+ This does not mean that all non-compete agreements are incompatible
+ with this code of conduct. For example, a company may restrict an
+ employee's ability to solicit the company's customers. However, an
+ agreement must not block any form of technical or social
+ participation in BA activities, including but not limited to the
+ implementation of particular features.
+
+ The accumulation of experience and expertise in individual persons,
+ who are ultimately free to direct their energy and attention as
+ they decide, is one of the most important drivers of progress in
+ open source projects. A company that limits this freedom may hinder
+ the success of the BA's efforts.
+
+ * **Do not use patents as offensive weapons.** If any BA participant
+ prevents the adoption or development of BA technologies by
+ asserting its patents, that undermines the purpose of the
+ coalition. The collaboration fostered by the BA cannot include
+ members who act to undermine its work.
+
+ * **Practice responsible disclosure** for security vulnerabilities.
+ Use designated, non-public reporting channels to disclose technical
+ vulnerabilities, and give the project a reasonable period to
+ respond, remediate, and patch. [TODO: optionally include the
+ security vulnerability reporting URL here.]
+
+ Vulnerability reporters may patch their company's own offerings, as
+ long as that patching does not significantly delay the reporting of
+ the vulnerability. Vulnerability information should never be used
+ for unilateral commercial advantage. Vendors may legitimately
+ compete on the speed and reliability with which they deploy
+ security fixes, but withholding vulnerability information damages
+ everyone in the long run by risking harm to the BA project's
+ reputation and to the security of all users.
+
+ * **Respect the letter and spirit of open source practice.** While
+ there is not space to list here all possible aspects of standard
+ open source practice, some examples will help show what we mean:
+
+ * Abide by all applicable open source license terms. Do not engage
+ in copyright violation or misattribution of any kind.
+
+ * Do not claim others' ideas or designs as your own.
+
+ * When others engage in publicly visible work (e.g., an upcoming
+ demo that is coordinated in a public issue tracker), do not
+ unilaterally announce early releases or early demonstrations of
+ that work ahead of their schedule in order to secure private
+ advantage (such as marketplace advantage) for yourself.
+
+ The BA reserves the right to determine what constitutes good open
+ source practices and to take action as it deems appropriate to
+ encourage, and if necessary enforce, such practices.
+
+## Enforcement
+
+Instances of organizational behavior in violation of the OCoC may
+be reported by contacting the Bytecode Alliance CoC team at
+[report@bytecodealliance.org](mailto:report@bytecodealliance.org). The
+CoC team will review and investigate all complaints, and will respond
+in a way that it deems appropriate to the circumstances. The CoC team
+is obligated to maintain confidentiality with regard to the reporter of
+an incident. Further details of specific enforcement policies may be
+posted separately.
+
+When the BA deems an organization in violation of this OCoC, the BA
+will, at its sole discretion, determine what action to take. The BA
+will decide what type, degree, and duration of corrective action is
+needed, if any, before a violating organization can be considered for
+membership (if it was not already a member) or can have its membership
+reinstated (if it was a member and the BA canceled its membership due
+to the violation).
+
+In practice, the BA's first approach will be to start a conversation,
+with punitive enforcement used only as a last resort. Violations
+often turn out to be unintentional and swiftly correctable with all
+parties acting in good faith.
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..697f117
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
+
+dextero@google.com
+vill@google.com
+nputikhin@google.com
+istvannador@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9f437b1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,196 @@
+<div align="center">
+ <h1><code>rustix</code></h1>
+
+ <p>
+ <strong>Safe Rust bindings to POSIX/Unix/Linux/Winsock syscalls</strong>
+ </p>
+
+ <strong>A <a href="https://bytecodealliance.org/">Bytecode Alliance</a> project</strong>
+
+ <p>
+ <a href="https://github.com/bytecodealliance/rustix/actions?query=workflow%3ACI"><img src="https://github.com/bytecodealliance/rustix/workflows/CI/badge.svg" alt="Github Actions CI Status" /></a>
+ <a href="https://bytecodealliance.zulipchat.com/#narrow/stream/206238-general"><img src="https://img.shields.io/badge/zulip-join_chat-brightgreen.svg" alt="zulip chat" /></a>
+ <a href="https://crates.io/crates/rustix"><img src="https://img.shields.io/crates/v/rustix.svg" alt="crates.io page" /></a>
+ <a href="https://docs.rs/rustix"><img src="https://docs.rs/rustix/badge.svg" alt="docs.rs docs" /></a>
+ </p>
+</div>
+
+`rustix` provides efficient memory-safe and [I/O-safe] wrappers to POSIX-like,
+Unix-like, Linux, and Winsock syscall-like APIs, with configurable backends.
+It uses Rust references, slices, and return values instead of raw pointers, and
+[I/O safety types] instead of raw file descriptors, providing memory safety,
+[I/O safety], and [provenance]. It uses `Result`s for reporting errors,
+[`bitflags`] instead of bare integer flags, an [`Arg`] trait with optimizations
+to efficiently accept any Rust string type, and several other efficient
+conveniences.
+
+`rustix` is low-level and, and while the `net` API supports [Windows Sockets 2]
+(Winsock), the rest of the APIs do not support Windows; for higher-level and
+more portable APIs built on this functionality, see the [`cap-std`], [`memfd`],
+[`timerfd`], and [`io-streams`] crates, for example.
+
+`rustix` currently has two backends available:
+
+ * linux_raw, which uses raw Linux system calls and vDSO calls, and is
+ supported on Linux on x86-64, x86, aarch64, riscv64gc, powerpc64le,
+ arm (v5 onwards), mipsel, and mips64el, with stable, nightly, and 1.63 Rust.
+ - By being implemented entirely in Rust, avoiding `libc`, `errno`, and pthread
+ cancellation, and employing some specialized optimizations, most functions
+ compile down to very efficient code, which can often be fully inlined into
+ user code.
+ - Most functions in `linux_raw` preserve memory, I/O safety, and pointer
+ provenance all the way down to the syscalls.
+
+ * libc, which uses the [`libc`] crate which provides bindings to native `libc`
+ libraries on Unix-family platforms, and [`windows-sys`] for Winsock on
+ Windows, and is portable to many OS's.
+
+The linux_raw backend is enabled by default on platforms which support it. To
+enable the libc backend instead, either enable the "use-libc" cargo feature,
+or set the `RUSTFLAGS` environment variable to `--cfg=rustix_use_libc` when
+building.
+
+## Cargo features
+
+The modules [`rustix::io`], [`rustix::fd`], and [`rustix::ffi`] are enabled
+by default. The rest of the API is conditional with cargo feature flags:
+
+| Name | Description |
+| ---------- | -------------------------------------------------------------- |
+| `event` | [`rustix::event`]—Polling and event operations. |
+| `fs` | [`rustix::fs`]—Filesystem operations. |
+| `io_uring` | [`rustix::io_uring`]—Linux io_uring. |
+| `mm` | [`rustix::mm`]—Memory map operations. |
+| `mount` | [`rustix::mount`]—Linux mount API. |
+| `net` | [`rustix::net`]—Network-related operations. |
+| `param` | [`rustix::param`]—Process parameters. |
+| `pipe` | [`rustix::pipe`]—Pipe operations. |
+| `process` | [`rustix::process`]—Process-associated operations. |
+| `procfs` | [`rustix::procfs`]—Utilities for reading `/proc` on Linux. |
+| `pty` | [`rustix::pty`]—Pseudoterminal operations. |
+| `rand` | [`rustix::rand`]—Random-related operations. |
+| `shm` | [`rustix::shm`]—POSIX shared memory. |
+| `stdio` | [`rustix::stdio`]—Stdio-related operations. |
+| `system` | [`rustix::system`]—System-related operations. |
+| `termios` | [`rustix::termios`]—Terminal I/O stream operations. |
+| `thread` | [`rustix::thread`]—Thread-associated operations. |
+| `time` | [`rustix::time`]—Time-related operations. |
+| | |
+| `use-libc` | Enable the libc backend. |
+
+[`rustix::event`]: https://docs.rs/rustix/*/rustix/event/index.html
+[`rustix::fs`]: https://docs.rs/rustix/*/rustix/fs/index.html
+[`rustix::io_uring`]: https://docs.rs/rustix/*/rustix/io_uring/index.html
+[`rustix::mm`]: https://docs.rs/rustix/*/rustix/mm/index.html
+[`rustix::mount`]: https://docs.rs/rustix/*/rustix/mount/index.html
+[`rustix::net`]: https://docs.rs/rustix/*/rustix/net/index.html
+[`rustix::param`]: https://docs.rs/rustix/*/rustix/param/index.html
+[`rustix::pipe`]: https://docs.rs/rustix/*/rustix/pipe/index.html
+[`rustix::process`]: https://docs.rs/rustix/*/rustix/process/index.html
+[`rustix::procfs`]: https://docs.rs/rustix/*/rustix/procfs/index.html
+[`rustix::pty`]: https://docs.rs/rustix/*/rustix/pty/index.html
+[`rustix::rand`]: https://docs.rs/rustix/*/rustix/rand/index.html
+[`rustix::shm`]: https://docs.rs/rustix/*/rustix/shm/index.html
+[`rustix::stdio`]: https://docs.rs/rustix/*/rustix/stdio/index.html
+[`rustix::system`]: https://docs.rs/rustix/*/rustix/system/index.html
+[`rustix::termios`]: https://docs.rs/rustix/*/rustix/termios/index.html
+[`rustix::thread`]: https://docs.rs/rustix/*/rustix/thread/index.html
+[`rustix::time`]: https://docs.rs/rustix/*/rustix/time/index.html
+[`rustix::io`]: https://docs.rs/rustix/*/rustix/io/index.html
+[`rustix::fd`]: https://docs.rs/rustix/*/rustix/fd/index.html
+[`rustix::ffi`]: https://docs.rs/rustix/*/rustix/ffi/index.html
+
+## 64-bit Large File Support (LFS) and Year 2038 (y2038) support
+
+`rustix` automatically uses 64-bit APIs when available, and avoids exposing
+32-bit APIs that would have the year-2038 problem or fail to support large
+files. For instance, `rustix::fstatvfs` calls `fstatvfs64`, and returns a
+struct that's 64-bit even on 32-bit platforms.
+
+## Similar crates
+
+`rustix` is similar to [`nix`], [`simple_libc`], [`unix`], [`nc`], [`uapi`],
+and [`rusl`]. `rustix` is architected for [I/O safety] with most APIs using
+[`OwnedFd`] and [`AsFd`] to manipulate file descriptors rather than `File` or
+even `c_int`, and supporting multiple backends so that it can use direct
+syscalls while still being usable on all platforms `libc` supports. Like `nix`,
+`rustix` has an optimized and flexible filename argument mechanism that allows
+users to use a variety of string types, including non-UTF-8 string types.
+
+[`relibc`] is a similar project which aims to be a full "libc", including
+C-compatible interfaces and higher-level C/POSIX standard-library
+functionality; `rustix` just aims to provide safe and idiomatic Rust interfaces
+to low-level syscalls. `relibc` also doesn't tend to support features not
+supported on Redox, such as `*at` functions like `openat`, which are important
+features for `rustix`.
+
+`rustix` has its own code for making direct syscalls, similar to the
+[`syscall`], [`sc`], and [`scall`] crates, using the Rust `asm!` macro.
+`rustix` can also use Linux's vDSO mechanism to optimize Linux `clock_gettime`
+on all architectures, and all Linux system calls on x86. And `rustix`'s
+syscalls report errors using an optimized `Errno` type.
+
+`rustix`'s `*at` functions are similar to the [`openat`] crate, but `rustix`
+provides them as free functions rather than associated functions of a `Dir`
+type. `rustix`'s `CWD` constant exposes the special `AT_FDCWD` value in a safe
+way, so users don't need to open `.` to get a current-directory handle.
+
+`rustix`'s `openat2` function is similar to the [`openat2`] crate, but uses I/O
+safety types rather than `RawFd`. `rustix` does not provide dynamic feature
+detection, so users must handle the [`NOSYS`] error themselves.
+
+`rustix`'s `termios` module is similar to the [`termios`] crate, but uses I/O
+safety types rather than `RawFd`, and the flags parameters to functions such as
+`tcsetattr` are `enum`s rather than bare integers. And, rustix calls its
+`tcgetattr` function `tcgetattr`, rather than `Termios::from_fd`.
+
+## Minimum Supported Rust Version (MSRV)
+
+This crate currently works on the version of [Rust on Debian stable], which is
+currently [Rust 1.63]. This policy may change in the future, in minor version
+releases, so users using a fixed version of Rust should pin to a specific
+version of this crate.
+
+## Minimum Linux Version
+
+On Linux platforms, rustix requires at least Linux 3.2. This is at most the
+oldest Linux version supported by:
+ - [any current Rust target], or
+ - [kernel.org] at the time of rustix's [MSRV] release.
+The specifics of this policy may change in the future, but we intend it to
+always reflect “very old” Linux versions.
+
+[MSRV]: #minimum-supported-rust-version-msrv
+[Rust 1.63]: https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html
+[any current Rust target]: https://doc.rust-lang.org/nightly/rustc/platform-support.html
+[kernel.org]: https://www.kernel.org/releases.html
+[Rust on Debian stable]: https://packages.debian.org/stable/rust/rustc
+[Windows Sockets 2]: https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2
+[`nix`]: https://crates.io/crates/nix
+[`unix`]: https://crates.io/crates/unix
+[`nc`]: https://crates.io/crates/nc
+[`simple_libc`]: https://crates.io/crates/simple_libc
+[`uapi`]: https://crates.io/crates/uapi
+[`rusl`]: https://lib.rs/crates/rusl
+[`relibc`]: https://gitlab.redox-os.org/redox-os/relibc
+[`syscall`]: https://crates.io/crates/syscall
+[`sc`]: https://crates.io/crates/sc
+[`scall`]: https://crates.io/crates/scall
+[`openat`]: https://crates.io/crates/openat
+[`openat2`]: https://crates.io/crates/openat2
+[I/O safety types]: https://doc.rust-lang.org/stable/std/os/fd/index.html#structs
+[`termios`]: https://crates.io/crates/termios
+[`libc`]: https://crates.io/crates/libc
+[`windows-sys`]: https://crates.io/crates/windows-sys
+[`cap-std`]: https://crates.io/crates/cap-std
+[`memfd`]: https://crates.io/crates/memfd
+[`timerfd`]: https://crates.io/crates/timerfd
+[`io-streams`]: https://crates.io/crates/io-streams
+[`bitflags`]: https://crates.io/crates/bitflags
+[`Arg`]: https://docs.rs/rustix/*/rustix/path/trait.Arg.html
+[I/O-safe]: https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md
+[I/O safety]: https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md
+[provenance]: https://github.com/rust-lang/rust/issues/95228
+[`OwnedFd`]: https://doc.rust-lang.org/stable/std/os/fd/struct.OwnedFd.html
+[`AsFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.AsFd.html
+[`NOSYS`]: https://docs.rs/rustix/*/rustix/io/struct.Errno.html#associatedconstant.NOSYS
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..3513b9c
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,29 @@
+# Security Policy
+
+Building secure foundations for software development is at the core of what we do in the Bytecode Alliance. Contributions of external security researchers are a vital part of that.
+
+## Scope
+
+If you believe you've found a security issue in any website, service, or software owned or operated by the Bytecode Alliance, we encourage you to notify us.
+
+## How to Submit a Report
+
+To submit a vulnerability report to the Bytecode Alliance, please contact us at [security@bytecodealliance.org](mailto:security@bytecodealliance.org). Your submission will be reviewed and validated by a member of our security team.
+
+## Safe Harbor
+
+The Bytecode Alliance supports safe harbor for security researchers who:
+
+* Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our services.
+* Only interact with accounts you own or with explicit permission of the account holder. If you do encounter Personally Identifiable Information (PII) contact us immediately, do not proceed with access, and immediately purge any local information.
+* Provide us with a reasonable amount of time to resolve vulnerabilities prior to any disclosure to the public or a third-party.
+
+We will consider activities conducted consistent with this policy to constitute "authorized" conduct and will not pursue civil action or initiate a complaint to law enforcement. We will help to the extent we can if legal action is initiated by a third party against you.
+
+Please submit a report to us before engaging in conduct that may be inconsistent with or unaddressed by this policy.
+
+## Preferences
+
+* Please provide detailed reports with reproducible steps and a clearly defined impact.
+* Submit one vulnerability per report.
+* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
diff --git a/benches/mod.rs b/benches/mod.rs
new file mode 100644
index 0000000..cd3cb32
--- /dev/null
+++ b/benches/mod.rs
@@ -0,0 +1,217 @@
+//! Benchmarks for rustix.
+//!
+//! To enable these benchmarks, add `--cfg=criterion` to RUSTFLAGS and enable
+//! the "fs", "time", and "process" cargo features.
+//!
+//! ```sh
+//! RUSTFLAGS=--cfg=criterion cargo bench --features=fs,time,process,stdio
+//! ```
+
+#[cfg(any(
+ not(criterion),
+ not(feature = "fs"),
+ not(feature = "process"),
+ not(feature = "time"),
+ not(feature = "stdio"),
+ windows,
+ target_os = "emscripten",
+ target_os = "redox",
+ target_os = "wasi",
+))]
+fn main() {
+ unimplemented!(
+ "Add --cfg=criterion to RUSTFLAGS and enable the \"fs\", \"time\", \"process\", and \"stdio\" cargo \
+ features."
+ )
+}
+
+#[cfg(not(any(
+ not(criterion),
+ not(feature = "fs"),
+ not(feature = "process"),
+ not(feature = "time"),
+ not(feature = "stdio"),
+ windows,
+ target_os = "emscripten",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+use criterion::{criterion_group, criterion_main};
+
+#[cfg(not(any(
+ not(criterion),
+ not(feature = "fs"),
+ not(feature = "process"),
+ not(feature = "time"),
+ not(feature = "stdio"),
+ windows,
+ target_os = "emscripten",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+mod suite {
+ use criterion::Criterion;
+
+ pub(super) fn simple_statat(c: &mut Criterion) {
+ use rustix::fs::{statat, AtFlags, CWD};
+
+ c.bench_function("simple statat", |b| {
+ b.iter(|| {
+ statat(CWD, "/", AtFlags::empty()).unwrap();
+ })
+ });
+ }
+
+ pub(super) fn simple_statat_libc(c: &mut Criterion) {
+ c.bench_function("simple statat libc", |b| {
+ b.iter(|| {
+ let mut s = std::mem::MaybeUninit::<libc::stat>::uninit();
+ unsafe {
+ assert_eq!(
+ libc::fstatat(
+ libc::AT_FDCWD,
+ std::ffi::CString::new("/").unwrap().as_c_str().as_ptr() as _,
+ s.as_mut_ptr(),
+ 0
+ ),
+ 0
+ );
+ }
+ })
+ });
+ }
+
+ pub(super) fn simple_statat_libc_cstr(c: &mut Criterion) {
+ c.bench_function("simple statat libc cstr", |b| {
+ b.iter(|| {
+ let mut s = std::mem::MaybeUninit::<libc::stat>::uninit();
+ unsafe {
+ assert_eq!(
+ libc::fstatat(
+ libc::AT_FDCWD,
+ rustix::cstr!("/").as_ptr() as _,
+ s.as_mut_ptr(),
+ 0
+ ),
+ 0
+ );
+ }
+ })
+ });
+ }
+
+ pub(super) fn simple_statat_cstr(c: &mut Criterion) {
+ use rustix::fs::{statat, AtFlags, CWD};
+
+ c.bench_function("simple statat cstr", |b| {
+ b.iter(|| {
+ statat(CWD, rustix::cstr!("/"), AtFlags::empty()).unwrap();
+ })
+ });
+ }
+
+ pub(super) fn simple_fstat(c: &mut Criterion) {
+ use rustix::fs::fstat;
+
+ c.bench_function("simple fstat", |b| {
+ b.iter(|| {
+ fstat(rustix::stdio::stdin()).unwrap();
+ })
+ });
+ }
+
+ pub(super) fn simple_fstat_libc(c: &mut Criterion) {
+ c.bench_function("simple fstat libc", |b| {
+ b.iter(|| {
+ let mut s = std::mem::MaybeUninit::<libc::stat>::uninit();
+ unsafe {
+ assert_eq!(libc::fstat(libc::STDIN_FILENO, s.as_mut_ptr()), 0);
+ }
+ })
+ });
+ }
+
+ #[cfg(not(target_os = "wasi"))]
+ pub(super) fn simple_clock_gettime(c: &mut Criterion) {
+ use rustix::time::{clock_gettime, ClockId};
+
+ c.bench_function("simple clock_gettime", |b| {
+ b.iter(|| {
+ let _ = clock_gettime(ClockId::Monotonic);
+ })
+ });
+ }
+
+ #[cfg(not(target_os = "wasi"))]
+ pub(super) fn simple_clock_gettime_libc(c: &mut Criterion) {
+ c.bench_function("simple clock_gettime libc", |b| {
+ b.iter(|| {
+ let mut s = std::mem::MaybeUninit::<libc::timespec>::uninit();
+ unsafe {
+ assert_eq!(
+ libc::clock_gettime(libc::CLOCK_MONOTONIC, s.as_mut_ptr()),
+ 0
+ );
+ let _ = s.assume_init();
+ }
+ })
+ });
+ }
+
+ #[cfg(not(target_os = "wasi"))]
+ pub(super) fn simple_getpid(c: &mut Criterion) {
+ use rustix::process::getpid;
+
+ c.bench_function("simple getpid", |b| {
+ b.iter(|| {
+ let _ = getpid();
+ })
+ });
+ }
+
+ #[cfg(not(target_os = "wasi"))]
+ pub(super) fn simple_getpid_libc(c: &mut Criterion) {
+ c.bench_function("simple getpid libc", |b| {
+ b.iter(|| unsafe {
+ let _ = libc::getpid();
+ })
+ });
+ }
+}
+
+#[cfg(not(any(
+ not(criterion),
+ not(feature = "fs"),
+ not(feature = "process"),
+ not(feature = "time"),
+ not(feature = "stdio"),
+ windows,
+ target_os = "emscripten",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+criterion_group!(
+ benches,
+ suite::simple_statat,
+ suite::simple_statat_libc,
+ suite::simple_statat_libc_cstr,
+ suite::simple_statat_cstr,
+ suite::simple_fstat,
+ suite::simple_fstat_libc,
+ suite::simple_clock_gettime,
+ suite::simple_clock_gettime_libc,
+ suite::simple_getpid,
+ suite::simple_getpid_libc
+);
+#[cfg(not(any(
+ not(criterion),
+ not(feature = "fs"),
+ not(feature = "process"),
+ not(feature = "time"),
+ not(feature = "stdio"),
+ windows,
+ target_os = "emscripten",
+ target_os = "redox",
+ target_os = "wasi",
+)))]
+criterion_main!(benches);
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..d95f9a9
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,250 @@
+use std::env::var;
+use std::io::Write;
+
+/// The directory for inline asm.
+const ASM_PATH: &str = "src/backend/linux_raw/arch";
+
+fn main() {
+ // Don't rerun this on changes other than build.rs, as we only depend on
+ // the rustc version.
+ println!("cargo:rerun-if-changed=build.rs");
+
+ // Gather target information.
+ let arch = var("CARGO_CFG_TARGET_ARCH").unwrap();
+ let env = var("CARGO_CFG_TARGET_ENV").unwrap();
+ let inline_asm_name = format!("{}/{}.rs", ASM_PATH, arch);
+ let inline_asm_name_present = std::fs::metadata(inline_asm_name).is_ok();
+ let os = var("CARGO_CFG_TARGET_OS").unwrap();
+ let pointer_width = var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap();
+ let endian = var("CARGO_CFG_TARGET_ENDIAN").unwrap();
+
+ // Check for special target variants.
+ let is_x32 = arch == "x86_64" && pointer_width == "32";
+ let is_arm64_ilp32 = arch == "aarch64" && pointer_width == "32";
+ let is_powerpc64be = arch == "powerpc64" && endian == "big";
+ let is_mipseb = (arch == "mips" || arch == "mips32r6") && endian == "big";
+ let is_mips64eb = arch.contains("mips64") && endian == "big";
+ let is_unsupported_abi = is_x32 || is_arm64_ilp32 || is_powerpc64be || is_mipseb || is_mips64eb;
+
+ // Check for `--features=use-libc`. This allows crate users to enable the
+ // libc backend.
+ let feature_use_libc = var("CARGO_FEATURE_USE_LIBC").is_ok();
+
+ // Check for `RUSTFLAGS=--cfg=rustix_use_libc`. This allows end users to
+ // enable the libc backend even if rustix is depended on transitively.
+ let cfg_use_libc = var("CARGO_CFG_RUSTIX_USE_LIBC").is_ok();
+
+ // Check for `--features=rustc-dep-of-std`.
+ let rustc_dep_of_std = var("CARGO_FEATURE_RUSTC_DEP_OF_STD").is_ok();
+
+ // Check for eg. `RUSTFLAGS=--cfg=rustix_use_experimental_features`. This
+ // is a rustc flag rather than a cargo feature flag because it's
+ // experimental and not something we want accidentally enabled via
+ // `--all-features`.
+ let rustix_use_experimental_features =
+ var("CARGO_CFG_RUSTIX_USE_EXPERIMENTAL_FEATURES").is_ok();
+
+ // Check for eg. `RUSTFLAGS=--cfg=rustix_use_experimental_asm`. This is a
+ // rustc flag rather than a cargo feature flag because it's experimental
+ // and not something we want accidentally enabled via `--all-features`.
+ let rustix_use_experimental_asm = var("CARGO_CFG_RUSTIX_USE_EXPERIMENTAL_ASM").is_ok();
+
+ // Miri doesn't support inline asm, and has builtin support for recognizing
+ // libc FFI calls, so if we're running under miri, use the libc backend.
+ let miri = var("CARGO_CFG_MIRI").is_ok();
+
+ // If experimental features are enabled, auto-detect and use available
+ // features.
+ if rustc_dep_of_std {
+ use_feature("rustc_attrs");
+ use_feature("core_intrinsics");
+ } else if rustix_use_experimental_features {
+ use_feature_or_nothing("rustc_attrs");
+ use_feature_or_nothing("core_intrinsics");
+ }
+
+ // Features needed only in no-std configurations.
+ #[cfg(not(feature = "std"))]
+ {
+ use_feature_or_nothing("core_c_str");
+ use_feature_or_nothing("core_ffi_c");
+ use_feature_or_nothing("alloc_c_string");
+ use_feature_or_nothing("alloc_ffi");
+ }
+
+ // Feature needed for testing.
+ if use_static_assertions() {
+ use_feature("static_assertions");
+ }
+
+ // WASI support can utilize wasi_ext if present.
+ if os == "wasi" {
+ use_feature_or_nothing("wasi_ext");
+ }
+
+ // If the libc backend is requested, or if we're not on a platform for
+ // which we have linux_raw support, use the libc backend.
+ //
+ // For now Android uses the libc backend; in theory it could use the
+ // linux_raw backend, but to do that we'll need to figure out how to
+ // install the toolchain for it.
+ let libc = feature_use_libc
+ || cfg_use_libc
+ || os != "linux"
+ || !inline_asm_name_present
+ || is_unsupported_abi
+ || miri
+ || ((arch == "powerpc64" || arch.starts_with("mips")) && !rustix_use_experimental_asm);
+ if libc {
+ // Use the libc backend.
+ use_feature("libc");
+ } else {
+ // Use the linux_raw backend.
+ use_feature("linux_raw");
+ if rustix_use_experimental_asm {
+ use_feature("asm_experimental_arch");
+ }
+ }
+
+ // Detect whether the compiler requires us to use thumb mode on ARM.
+ if arch == "arm" && use_thumb_mode() {
+ use_feature("thumb_mode");
+ }
+
+ // Rust's libc crate groups some OS's together which have similar APIs;
+ // create similarly-named features to make `cfg` tests more concise.
+ let freebsdlike = os == "freebsd" || os == "dragonfly";
+ if freebsdlike {
+ use_feature("freebsdlike");
+ }
+ let netbsdlike = os == "openbsd" || os == "netbsd";
+ if netbsdlike {
+ use_feature("netbsdlike");
+ }
+ let apple = os == "macos" || os == "ios" || os == "tvos" || os == "watchos";
+ if apple {
+ use_feature("apple");
+ }
+ if os == "linux" || os == "l4re" || os == "android" || os == "emscripten" {
+ use_feature("linux_like");
+ }
+ if os == "solaris" || os == "illumos" {
+ use_feature("solarish");
+ }
+ if apple || freebsdlike || netbsdlike {
+ use_feature("bsd");
+ }
+
+ // Add some additional common target combinations.
+
+ // Android and "regular" Linux both use the Linux kernel.
+ if os == "android" || os == "linux" {
+ use_feature("linux_kernel");
+ }
+
+ // These platforms have a 32-bit `time_t`.
+ if libc
+ && (arch == "arm"
+ || arch == "mips"
+ || arch == "sparc"
+ || arch == "x86"
+ || (arch == "wasm32" && os == "emscripten"))
+ && (apple
+ || os == "android"
+ || os == "emscripten"
+ || os == "haiku"
+ || env == "gnu"
+ || (env == "musl" && arch == "x86"))
+ {
+ use_feature("fix_y2038");
+ }
+
+ println!("cargo:rerun-if-env-changed=CARGO_CFG_RUSTIX_USE_EXPERIMENTAL_ASM");
+ println!("cargo:rerun-if-env-changed=CARGO_CFG_RUSTIX_USE_LIBC");
+
+ // Rerun this script if any of our features or configuration flags change,
+ // or if the toolchain we used for feature detection changes.
+ println!("cargo:rerun-if-env-changed=CARGO_FEATURE_USE_LIBC");
+ println!("cargo:rerun-if-env-changed=CARGO_FEATURE_RUSTC_DEP_OF_STD");
+ println!("cargo:rerun-if-env-changed=CARGO_CFG_MIRI");
+}
+
+fn use_static_assertions() -> bool {
+ // `offset_from` was made const in Rust 1.65.
+ can_compile("const unsafe fn foo(p: *const u8) -> isize { p.offset_from(p) }")
+}
+
+fn use_thumb_mode() -> bool {
+ // In thumb mode, r7 is reserved.
+ !can_compile("pub unsafe fn f() { core::arch::asm!(\"udf #16\", in(\"r7\") 0); }")
+}
+
+fn use_feature_or_nothing(feature: &str) {
+ if has_feature(feature) {
+ use_feature(feature);
+ }
+}
+
+fn use_feature(feature: &str) {
+ println!("cargo:rustc-cfg={}", feature);
+}
+
+/// Test whether the rustc at `var("RUSTC")` supports the given feature.
+fn has_feature(feature: &str) -> bool {
+ can_compile(format!(
+ "#![allow(stable_features)]\n#![feature({})]",
+ feature
+ ))
+}
+
+/// Test whether the rustc at `var("RUSTC")` can compile the given code.
+fn can_compile<T: AsRef<str>>(test: T) -> bool {
+ use std::process::Stdio;
+
+ let out_dir = var("OUT_DIR").unwrap();
+ let rustc = var("RUSTC").unwrap();
+ let target = var("TARGET").unwrap();
+
+ // Use `RUSTC_WRAPPER` if it's set, unless it's set to an empty string, as
+ // documented [here].
+ // [here]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads
+ let wrapper = var("RUSTC_WRAPPER")
+ .ok()
+ .and_then(|w| if w.is_empty() { None } else { Some(w) });
+
+ let mut cmd = if let Some(wrapper) = wrapper {
+ let mut cmd = std::process::Command::new(wrapper);
+ // The wrapper's first argument is supposed to be the path to rustc.
+ cmd.arg(rustc);
+ cmd
+ } else {
+ std::process::Command::new(rustc)
+ };
+
+ cmd.arg("--crate-type=rlib") // Don't require `main`.
+ .arg("--emit=metadata") // Do as little as possible but still parse.
+ .arg("--target")
+ .arg(target)
+ .arg("--out-dir")
+ .arg(out_dir); // Put the output somewhere inconsequential.
+
+ // If Cargo wants to set RUSTFLAGS, use that.
+ if let Ok(rustflags) = var("CARGO_ENCODED_RUSTFLAGS") {
+ if !rustflags.is_empty() {
+ for arg in rustflags.split('\x1f') {
+ cmd.arg(arg);
+ }
+ }
+ }
+
+ let mut child = cmd
+ .arg("-") // Read from stdin.
+ .stdin(Stdio::piped()) // Stdin is a pipe.
+ .stderr(Stdio::null()) // Errors from feature detection aren't interesting and can be confusing.
+ .spawn()
+ .unwrap();
+
+ writeln!(child.stdin.take().unwrap(), "{}", test.as_ref()).unwrap();
+
+ child.wait().unwrap().success()
+}
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..c6a6330
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,11 @@
+{
+ "features": [
+ "alloc",
+ "std",
+ "use-libc-auxv",
+ "use-libc",
+ "mm",
+ "fs",
+ "event"
+ ]
+}
diff --git a/patches/0001-Make-linux-raw-sys-dependency-optional.patch b/patches/0001-Make-linux-raw-sys-dependency-optional.patch
new file mode 100644
index 0000000..0b40834
--- /dev/null
+++ b/patches/0001-Make-linux-raw-sys-dependency-optional.patch
@@ -0,0 +1,144 @@
+From 91eed6a7cfe8fa56777cccda65527d4a9ea25801 Mon Sep 17 00:00:00 2001
+From: Marcin Radomski <marcin@mradomski.pl>
+Date: Fri, 15 Mar 2024 23:08:44 +0100
+Subject: [PATCH 1/3] Make linux-raw-sys dependency optional
+
+See b/331344966 for status of the efforts to upstream this.
+
+Replace minimal set of cfg(linux_kernel) with
+cfg(feature = "linux-raw-sys") to keep the build working.
+
+With this, quite a few features just work:
+
+ cargo build --no-default-features --features \
+ use-libc,use-libc-auxv,std,event,mount,time,pipe,rand,stdio
+---
+ Cargo.toml | 4 ++--
+ Cargo.toml.orig | 5 +++--
+ src/backend/libc/io/syscalls.rs | 6 +++---
+ src/backend/libc/io/types.rs | 2 +-
+ src/io/read_write.rs | 6 +++---
+ 4 files changed, 10 insertions(+), 9 deletions(-)
+
+diff --git a/Cargo.toml b/Cargo.toml
+index 8184720b..8f091a9a 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -213,6 +213,7 @@ features = [
+ "no_std",
+ ]
+ default-features = false
++optional = true
+
+ [target."cfg(all(criterion, not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dev-dependencies.criterion]
+ version = "0.4"
+@@ -239,6 +240,7 @@ features = [
+ "elf",
+ ]
+ default-features = false
++optional = true
+
+ [target."cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))".dependencies.libc]
+ version = "0.2.152"
+diff --git a/Cargo.toml.orig b/Cargo.toml.orig
+index 59d4673f..715f7e8f 100644
+--- a/Cargo.toml.orig
++++ b/Cargo.toml.orig
+@@ -35,8 +35,9 @@ once_cell = { version = "1.5.2", optional = true }
+ # addition to the libc backend. The linux_raw backend is used by default. The
+ # libc backend can be selected via adding `--cfg=rustix_use_libc` to
+ # `RUSTFLAGS` or enabling the `use-libc` cargo feature.
++# Enabling only the libc backend will limit available functionality.
+ [target.'cfg(all(not(rustix_use_libc), not(miri), target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64"))))'.dependencies]
+-linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"] }
++linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"], optional = true }
+ libc_errno = { package = "errno", version = "0.3.8", default-features = false, optional = true }
+ libc = { version = "0.2.152", default-features = false, features = ["extra_traits"], optional = true }
+
+@@ -53,7 +54,7 @@ libc = { version = "0.2.153", default-features = false, features = ["extra_trait
+ # Some syscalls do not have libc wrappers, such as in `io_uring`. For these,
+ # the libc backend uses the linux-raw-sys ABI and `libc::syscall`.
+ [target.'cfg(all(any(target_os = "android", target_os = "linux"), any(rustix_use_libc, miri, not(all(target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64")))))))'.dependencies]
+-linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "ioctl", "no_std"] }
++linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "ioctl", "no_std"], optional = true }
+
+ # For the libc backend on Windows, use the Winsock API in windows-sys.
+ [target.'cfg(windows)'.dependencies.windows-sys]
+diff --git a/src/backend/libc/io/syscalls.rs b/src/backend/libc/io/syscalls.rs
+index e28e6bef..da88f54b 100644
+--- a/src/backend/libc/io/syscalls.rs
++++ b/src/backend/libc/io/syscalls.rs
+@@ -13,7 +13,7 @@ use crate::fd::{AsFd, BorrowedFd, OwnedFd, RawFd};
+ target_os = "wasi"
+ )))]
+ use crate::io::DupFlags;
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ use crate::io::ReadWriteFlags;
+ use crate::io::{self, FdFlags};
+ use crate::ioctl::{IoctlOutput, RawOpcode};
+@@ -139,7 +139,7 @@ pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], offset: u64) ->
+ }
+ }
+
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ pub(crate) fn preadv2(
+ fd: BorrowedFd<'_>,
+ bufs: &mut [IoSliceMut<'_>],
+@@ -159,7 +159,7 @@ pub(crate) fn preadv2(
+ }
+ }
+
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ pub(crate) fn pwritev2(
+ fd: BorrowedFd<'_>,
+ bufs: &[IoSlice<'_>],
+diff --git a/src/backend/libc/io/types.rs b/src/backend/libc/io/types.rs
+index 510206f9..a86dc261 100644
+--- a/src/backend/libc/io/types.rs
++++ b/src/backend/libc/io/types.rs
+@@ -17,7 +17,7 @@ bitflags! {
+ }
+ }
+
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ bitflags! {
+ /// `RWF_*` constants for use with [`preadv2`] and [`pwritev2`].
+ ///
+diff --git a/src/io/read_write.rs b/src/io/read_write.rs
+index 2ed9dd71..d5296fa4 100644
+--- a/src/io/read_write.rs
++++ b/src/io/read_write.rs
+@@ -11,7 +11,7 @@ use core::mem::MaybeUninit;
+ #[cfg(not(windows))]
+ pub use crate::maybe_polyfill::io::{IoSlice, IoSliceMut};
+
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ pub use backend::io::types::ReadWriteFlags;
+
+ /// `read(fd, buf)`—Reads from a stream.
+@@ -281,7 +281,7 @@ pub fn pwritev<Fd: AsFd>(fd: Fd, bufs: &[IoSlice<'_>], offset: u64) -> io::Resul
+ /// - [Linux]
+ ///
+ /// [Linux]: https://man7.org/linux/man-pages/man2/preadv2.2.html
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ #[inline]
+ pub fn preadv2<Fd: AsFd>(
+ fd: Fd,
+@@ -300,7 +300,7 @@ pub fn preadv2<Fd: AsFd>(
+ /// - [Linux]
+ ///
+ /// [Linux]: https://man7.org/linux/man-pages/man2/pwritev2.2.html
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ #[inline]
+ pub fn pwritev2<Fd: AsFd>(
+ fd: Fd,
+--
+2.44.0.291.gc1ea87d7ee-goog
+
diff --git a/patches/0002-Make-mm-feature-compile-without-linux-raw-sys.patch b/patches/0002-Make-mm-feature-compile-without-linux-raw-sys.patch
new file mode 100644
index 0000000..06648b0
--- /dev/null
+++ b/patches/0002-Make-mm-feature-compile-without-linux-raw-sys.patch
@@ -0,0 +1,66 @@
+From 1b0c4fc87447429dba781e7424fd08e5cb9ec1f8 Mon Sep 17 00:00:00 2001
+From: Marcin Radomski <marcin@mradomski.pl>
+Date: Fri, 15 Mar 2024 23:08:48 +0100
+Subject: [PATCH 2/3] Make `mm` feature compile without linux-raw-sys
+
+See b/331344966 for status of the efforts to upstream this.
+
+This makes a handful of features unavailable:
+
+- MprotectFlags::{SEM,BTI,MTE,SAO,ADI}
+- MapFlags::hugetlb_with_size_log2
+
+Tested with:
+
+ cargo build --no-default-features --features \
+ use-libc,use-libc-auxv,std,event,mount,time,pipe,rand,stdio,mm
+---
+ src/backend/libc/mm/types.rs | 10 +++++-----
+ src/mm/mmap.rs | 2 +-
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/src/backend/libc/mm/types.rs b/src/backend/libc/mm/types.rs
+index a4aa3e23..afd9ec0c 100644
+--- a/src/backend/libc/mm/types.rs
++++ b/src/backend/libc/mm/types.rs
+@@ -44,19 +44,19 @@ bitflags! {
+ #[cfg(linux_kernel)]
+ const GROWSDOWN = bitcast!(c::PROT_GROWSDOWN);
+ /// `PROT_SEM`
+- #[cfg(linux_kernel)]
++ #[cfg(feature = "linux-raw-sys")]
+ const SEM = linux_raw_sys::general::PROT_SEM;
+ /// `PROT_BTI`
+- #[cfg(all(linux_kernel, target_arch = "aarch64"))]
++ #[cfg(all(feature = "linux-raw-sys", target_arch = "aarch64"))]
+ const BTI = linux_raw_sys::general::PROT_BTI;
+ /// `PROT_MTE`
+- #[cfg(all(linux_kernel, target_arch = "aarch64"))]
++ #[cfg(all(feature = "linux-raw-sys", target_arch = "aarch64"))]
+ const MTE = linux_raw_sys::general::PROT_MTE;
+ /// `PROT_SAO`
+- #[cfg(all(linux_kernel, any(target_arch = "powerpc", target_arch = "powerpc64")))]
++ #[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(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64")))]
++ #[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>
+diff --git a/src/mm/mmap.rs b/src/mm/mmap.rs
+index 3f6523fd..9413340a 100644
+--- a/src/mm/mmap.rs
++++ b/src/mm/mmap.rs
+@@ -32,7 +32,7 @@ impl MapFlags {
+ /// let f = MapFlags::hugetlb_with_size_log2(30).unwrap();
+ /// assert_eq!(f, MapFlags::HUGETLB | MapFlags::HUGE_1GB);
+ /// ```
+- #[cfg(linux_kernel)]
++ #[cfg(feature = "linux-raw-sys")]
+ pub const fn hugetlb_with_size_log2(huge_page_size_log2: u32) -> Option<Self> {
+ use linux_raw_sys::general::{MAP_HUGETLB, MAP_HUGE_SHIFT};
+ if 16 <= huge_page_size_log2 && huge_page_size_log2 <= 63 {
+--
+2.44.0.291.gc1ea87d7ee-goog
+
diff --git a/patches/0003-Make-fs-feature-compile-without-linux-raw-sys.patch b/patches/0003-Make-fs-feature-compile-without-linux-raw-sys.patch
new file mode 100644
index 0000000..2c57443
--- /dev/null
+++ b/patches/0003-Make-fs-feature-compile-without-linux-raw-sys.patch
@@ -0,0 +1,145 @@
+From 267eb85d72adb51cb9a891f302fc7d56eb0f9ef6 Mon Sep 17 00:00:00 2001
+From: Marcin Radomski <marcin@mradomski.pl>
+Date: Fri, 15 Mar 2024 23:21:48 +0100
+Subject: [PATCH 3/3] Make fs feature compile without linux-raw-sys
+
+See b/331344966 for status of the efforts to upstream this.
+
+This makes some features unavailable:
+
+- ext4_ioc_resize_fs
+- ioctl_getflags
+- ioctl_setflags
+- RawDir
+
+But also features fs, shm, param and pty become partially usable without
+linux-raw-sys.
+
+Tested with:
+
+ cargo build --no-default-features --features \
+ use-libc,use-libc-auxv,std,event,mount,time,pipe,rand,stdio,mm,shm,param,pty
+---
+ src/backend/libc/fs/mod.rs | 4 ++--
+ src/backend/libc/fs/syscalls.rs | 11 +++++++++--
+ src/fs/ioctl.rs | 8 ++++----
+ src/fs/mod.rs | 8 ++++----
+ 4 files changed, 19 insertions(+), 12 deletions(-)
+
+diff --git a/src/backend/libc/fs/mod.rs b/src/backend/libc/fs/mod.rs
+index c17e8636..d342f3a2 100644
+--- a/src/backend/libc/fs/mod.rs
++++ b/src/backend/libc/fs/mod.rs
+@@ -15,9 +15,9 @@ pub(crate) mod syscalls;
+ pub(crate) mod types;
+
+ // TODO: Fix linux-raw-sys to define ioctl codes for sparc.
+-#[cfg(all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64")))]
++#[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(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
++#[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
+index 593b234a..134d66ff 100644
+--- a/src/backend/libc/fs/syscalls.rs
++++ b/src/backend/libc/fs/syscalls.rs
+@@ -1729,7 +1729,7 @@ pub(crate) fn memfd_create(name: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd
+ unsafe { ret_owned_fd(memfd_create(c_str(name), bitflags_bits!(flags))) }
+ }
+
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ pub(crate) fn openat2(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+@@ -1946,8 +1946,15 @@ pub(crate) fn statx(
+ // [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(any(target_os = "android", target_env = "musl"))]
++ #[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);
+ }
+diff --git a/src/fs/ioctl.rs b/src/fs/ioctl.rs
+index 490f183f..ad2fe4fa 100644
+--- a/src/fs/ioctl.rs
++++ b/src/fs/ioctl.rs
+@@ -58,7 +58,7 @@ pub fn ioctl_ficlone<Fd: AsFd, SrcFd: AsFd>(fd: Fd, src_fd: SrcFd) -> io::Result
+ }
+
+ /// `ioctl(fd, EXT4_IOC_RESIZE_FS, blocks)`—Resize ext4 filesystem on fd.
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ #[inline]
+ #[doc(alias = "EXT4_IOC_RESIZE_FS")]
+ pub fn ext4_ioc_resize_fs<Fd: AsFd>(fd: Fd, blocks: u64) -> io::Result<()> {
+@@ -93,7 +93,7 @@ unsafe impl ioctl::Ioctl for Ficlone<'_> {
+ }
+ }
+
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ bitflags! {
+ /// `FS_*` constants for use with [`ioctl_getflags`][crate::io::ioctl::ioctl_getflags].
+ pub struct IFlags: c::c_uint {
+@@ -131,7 +131,7 @@ bitflags! {
+ /// `ioctl(fd, FS_IOC_GETFLAGS)`—Returns the [inode flags] attributes
+ ///
+ /// [inode flags]: https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ #[inline]
+ #[doc(alias = "FS_IOC_GETFLAGS")]
+ pub fn ioctl_getflags<Fd: AsFd>(fd: Fd) -> io::Result<IFlags> {
+@@ -148,7 +148,7 @@ pub fn ioctl_getflags<Fd: AsFd>(fd: Fd) -> io::Result<IFlags> {
+ /// `ioctl(fd, FS_IOC_SETFLAGS)`—Modify the [inode flags] attributes
+ ///
+ /// [inode flags]: https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ #[inline]
+ #[doc(alias = "FS_IOC_SETFLAGS")]
+ pub fn ioctl_setflags<Fd: AsFd>(fd: Fd, flags: IFlags) -> io::Result<()> {
+diff --git a/src/fs/mod.rs b/src/fs/mod.rs
+index 27d35701..b14ac803 100644
+--- a/src/fs/mod.rs
++++ b/src/fs/mod.rs
+@@ -47,9 +47,9 @@ mod memfd_create;
+ #[cfg(linux_kernel)]
+ #[cfg(feature = "fs")]
+ mod mount;
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ mod openat2;
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ mod raw_dir;
+ mod seek_from;
+ #[cfg(target_os = "linux")]
+@@ -115,9 +115,9 @@ pub use memfd_create::memfd_create;
+ #[cfg(linux_kernel)]
+ #[cfg(feature = "fs")]
+ pub use mount::*;
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ pub use openat2::openat2;
+-#[cfg(linux_kernel)]
++#[cfg(feature = "linux-raw-sys")]
+ pub use raw_dir::{RawDir, RawDirEntry};
+ pub use seek_from::SeekFrom;
+ #[cfg(target_os = "linux")]
+--
+2.44.0.291.gc1ea87d7ee-goog
+
diff --git a/patches/0004-Don-t-rename-errno-to-libc_errno.patch b/patches/0004-Don-t-rename-errno-to-libc_errno.patch
new file mode 100644
index 0000000..fda3781
--- /dev/null
+++ b/patches/0004-Don-t-rename-errno-to-libc_errno.patch
@@ -0,0 +1,250 @@
+From f4d0faa256057dc56647ff734069bdffa52e487a Mon Sep 17 00:00:00 2001
+From: Marcin Radomski <marcin@mradomski.pl>
+Date: Wed, 20 Mar 2024 16:56:28 +0000
+Subject: [PATCH] Don't rename errno to libc_errno
+
+See b/331344966 for status of the efforts to upstream this.
+
+Change-Id: Ied4890f9a6217c627624daa33d98b1bb78081bba
+---
+ Cargo.toml | 16 ++++++----------
+ Cargo.toml.orig | 13 ++++++-------
+ src/backend/libc/event/syscalls.rs | 2 ++
+ src/backend/libc/fs/dir.rs | 2 +-
+ src/backend/libc/fs/syscalls.rs | 2 ++
+ src/backend/libc/io/errno.rs | 2 +-
+ src/backend/libc/io/syscalls.rs | 2 +-
+ src/backend/libc/process/syscalls.rs | 2 ++
+ src/backend/libc/pty/syscalls.rs | 2 ++
+ src/weak.rs | 2 ++
+ 10 files changed, 25 insertions(+), 20 deletions(-)
+
+diff --git a/Cargo.toml b/Cargo.toml
+index 8f091a9a..19269d40 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -101,10 +101,9 @@ version = "1.0"
+ [dev-dependencies.libc]
+ version = "0.2.152"
+
+-[dev-dependencies.libc_errno]
++[dev-dependencies.errno]
+ version = "0.3.8"
+ default-features = false
+-package = "errno"
+
+ [dev-dependencies.memoffset]
+ version = "0.9.0"
+@@ -191,7 +190,7 @@ std = [
+ "bitflags/std",
+ "alloc",
+ "libc?/std",
+- "libc_errno?/std",
++ "errno?/std",
+ ]
+ stdio = []
+ system = ["linux-raw-sys/system"]
+@@ -200,7 +199,7 @@ thread = ["linux-raw-sys/prctl"]
+ time = []
+ use-explicitly-provided-auxv = []
+ use-libc = [
+- "libc_errno",
++ "errno",
+ "libc",
+ ]
+ use-libc-auxv = []
+@@ -224,11 +223,10 @@ features = ["extra_traits"]
+ optional = true
+ default-features = false
+
+-[target."cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))".dependencies.libc_errno]
++[target."cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))".dependencies.errno]
+ version = "0.3.8"
+ optional = true
+ default-features = false
+-package = "errno"
+
+ [target."cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))".dependencies.linux-raw-sys]
+ version = "0.4.12"
+@@ -247,19 +245,17 @@ version = "0.2.152"
+ features = ["extra_traits"]
+ default-features = false
+
+-[target."cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))".dependencies.libc_errno]
++[target."cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))".dependencies.errno]
+ version = "0.3.8"
+ default-features = false
+-package = "errno"
+
+ [target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies.once_cell]
+ version = "1.5.2"
+ optional = true
+
+-[target."cfg(windows)".dependencies.libc_errno]
++[target."cfg(windows)".dependencies.errno]
+ version = "0.3.8"
+ default-features = false
+-package = "errno"
+
+ [target."cfg(windows)".dependencies.windows-sys]
+ version = "0.52.0"
+diff --git a/Cargo.toml.orig b/Cargo.toml.orig
+index ffde0dba..4592ae97 100644
+--- a/Cargo.toml.orig
++++ b/Cargo.toml.orig
+@@ -38,7 +38,7 @@ once_cell = { version = "1.5.2", optional = true }
+ # Enabling only the libc backend will limit available functionality.
+ [target.'cfg(all(not(rustix_use_libc), not(miri), target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64"))))'.dependencies]
+ linux-raw-sys = { version = "0.4.12", default-features = false, features = ["general", "errno", "ioctl", "no_std", "elf"], optional = true }
+-libc_errno = { package = "errno", version = "0.3.8", default-features = false, optional = true }
++errno = { version = "0.3.8", default-features = false, optional = true }
+ libc = { version = "0.2.152", default-features = false, features = ["extra_traits"], optional = true }
+
+ # Dependencies for platforms where only libc is supported:
+@@ -46,7 +46,7 @@ libc = { version = "0.2.152", default-features = false, features = ["extra_trait
+ # On all other Unix-family platforms, and under Miri, we always use the libc
+ # backend, so enable its dependencies unconditionally.
+ [target.'cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = "linux", target_endian = "little", any(target_arch = "arm", all(target_arch = "aarch64", target_pointer_width = "64"), target_arch = "riscv64", all(rustix_use_experimental_asm, target_arch = "powerpc64"), all(rustix_use_experimental_asm, target_arch = "mips"), all(rustix_use_experimental_asm, target_arch = "mips32r6"), all(rustix_use_experimental_asm, target_arch = "mips64"), all(rustix_use_experimental_asm, target_arch = "mips64r6"), target_arch = "x86", all(target_arch = "x86_64", target_pointer_width = "64")))))))'.dependencies]
+-libc_errno = { package = "errno", version = "0.3.8", default-features = false }
++errno = { version = "0.3.8", default-features = false }
+ libc = { version = "0.2.152", default-features = false, features = ["extra_traits"] }
+
+ # Additional dependencies for Linux with the libc backend:
+@@ -68,15 +68,14 @@ features = [
+
+ # For the libc backend on Windows, also use the errno crate, which has Windows
+ # support.
+-[target.'cfg(windows)'.dependencies.libc_errno]
++[target.'cfg(windows)'.dependencies.errno]
+ version = "0.3.8"
+-package = "errno"
+ default-features = false
+
+ [dev-dependencies]
+ tempfile = "3.5.0"
+ libc = "0.2.152"
+-libc_errno = { package = "errno", version = "0.3.8", default-features = false }
++errno = { version = "0.3.8", default-features = false }
+ serial_test = "2.0.0"
+ memoffset = "0.9.0"
+ flate2 = "1.0"
+@@ -124,10 +123,10 @@ default = ["std", "use-libc-auxv"]
+
+ # This enables use of std. Disabling this enables `#![no_std]`, and requires
+ # Rust 1.64 or newer.
+-std = ["bitflags/std", "alloc", "libc?/std", "libc_errno?/std"]
++std = ["bitflags/std", "alloc", "libc?/std", "errno?/std"]
+
+ # Enable this to request the libc backend.
+-use-libc = ["libc_errno", "libc"]
++use-libc = ["errno", "libc"]
+
+ # Enable `rustix::event::*`.
+ event = []
+diff --git a/src/backend/libc/event/syscalls.rs b/src/backend/libc/event/syscalls.rs
+index 725ec825..c3c5245e 100644
+--- a/src/backend/libc/event/syscalls.rs
++++ b/src/backend/libc/event/syscalls.rs
+@@ -25,6 +25,8 @@ 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",
+diff --git a/src/backend/libc/fs/dir.rs b/src/backend/libc/fs/dir.rs
+index 6c827432..e3d4dfd1 100644
+--- a/src/backend/libc/fs/dir.rs
++++ b/src/backend/libc/fs/dir.rs
+@@ -36,7 +36,7 @@ use c::readdir as libc_readdir;
+ use c::readdir64 as libc_readdir;
+ use core::fmt;
+ use core::ptr::NonNull;
+-use libc_errno::{errno, set_errno, Errno};
++use errno::{errno, set_errno, Errno};
+
+ /// `DIR*`
+ pub struct Dir {
+diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs
+index 446601ac..4b3bff1d 100644
+--- a/src/backend/libc/fs/syscalls.rs
++++ b/src/backend/libc/fs/syscalls.rs
+@@ -89,6 +89,8 @@ use {
+ 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))]
+diff --git a/src/backend/libc/io/errno.rs b/src/backend/libc/io/errno.rs
+index 805ea679..8d3d00ac 100644
+--- a/src/backend/libc/io/errno.rs
++++ b/src/backend/libc/io/errno.rs
+@@ -4,7 +4,7 @@
+ //! `errno` value.
+
+ use crate::backend::c;
+-use libc_errno::errno;
++use errno::errno;
+
+ /// `errno`—An error code.
+ ///
+diff --git a/src/backend/libc/io/syscalls.rs b/src/backend/libc/io/syscalls.rs
+index da88f54b..1a5e1fbd 100644
+--- a/src/backend/libc/io/syscalls.rs
++++ b/src/backend/libc/io/syscalls.rs
+@@ -19,7 +19,7 @@ use crate::io::{self, FdFlags};
+ use crate::ioctl::{IoctlOutput, RawOpcode};
+ use core::cmp::min;
+ #[cfg(all(feature = "fs", feature = "net"))]
+-use libc_errno::errno;
++use errno::errno;
+ #[cfg(not(target_os = "espidf"))]
+ use {
+ crate::backend::MAX_IOV,
+diff --git a/src/backend/libc/process/syscalls.rs b/src/backend/libc/process/syscalls.rs
+index 46fd1821..8d931b95 100644
+--- a/src/backend/libc/process/syscalls.rs
++++ b/src/backend/libc/process/syscalls.rs
+@@ -72,6 +72,8 @@ 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 {
+diff --git a/src/backend/libc/pty/syscalls.rs b/src/backend/libc/pty/syscalls.rs
+index 2395efde..85d2255b 100644
+--- a/src/backend/libc/pty/syscalls.rs
++++ b/src/backend/libc/pty/syscalls.rs
+@@ -18,6 +18,8 @@ use {
+ #[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> {
+diff --git a/src/weak.rs b/src/weak.rs
+index 3cda8e6c..56be05ce 100644
+--- a/src/weak.rs
++++ b/src/weak.rs
+@@ -38,6 +38,8 @@ use core::ptr::null_mut;
+ use core::sync::atomic::{self, AtomicPtr, Ordering};
+ use core::{marker, mem};
+
++use errno as libc_errno;
++
+ const NULL: *mut c_void = null_mut();
+ const INVALID: *mut c_void = 1 as *mut c_void;
+
+--
+2.44.0.396.g6e790dbe36-goog
+
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, *,
+};
diff --git a/src/backend/linux_raw/arch/aarch64.rs b/src/backend/linux_raw/arch/aarch64.rs
new file mode 100644
index 0000000..d4cf247
--- /dev/null
+++ b/src/backend/linux_raw/arch/aarch64.rs
@@ -0,0 +1,268 @@
+//! aarch64 Linux system calls.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[cfg(target_pointer_width = "32")]
+compile_error!("arm64-ilp32 is not supported yet");
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ lateout("x0") r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ in("x0") a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ in("x3") a3.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ in("x3") a3.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ in("x3") a3.to_asm(),
+ in("x4") a4.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ in("x3") a3.to_asm(),
+ in("x4") a4.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ in("x3") a3.to_asm(),
+ in("x4") a4.to_asm(),
+ in("x5") a5.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("x8") nr.to_asm(),
+ inlateout("x0") a0.to_asm() => r0,
+ in("x1") a1.to_asm(),
+ in("x2") a2.to_asm(),
+ in("x3") a3.to_asm(),
+ in("x4") a4.to_asm(),
+ in("x5") a5.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/arch/arm.rs b/src/backend/linux_raw/arch/arm.rs
new file mode 100644
index 0000000..77c1f82
--- /dev/null
+++ b/src/backend/linux_raw/arch/arm.rs
@@ -0,0 +1,265 @@
+//! arm Linux system calls.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ lateout("r0") r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ in("r0") a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ in("r5") a5.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "svc 0",
+ in("r7") nr.to_asm(),
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ in("r5") a5.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/arch/mips.rs b/src/backend/linux_raw/arch/mips.rs
new file mode 100644
index 0000000..37932e0
--- /dev/null
+++ b/src/backend/linux_raw/arch/mips.rs
@@ -0,0 +1,543 @@
+//! mipsel Linux system calls.
+//!
+//! On mipsel, Linux indicates success or failure using `$a3` rather
+//! than by returning a negative error code as most other architectures do.
+//!
+//! Mips-family platforms have a special calling convention for `__NR_pipe`,
+//! however we use `__NR_pipe2` instead to avoid having to implement it.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, A6, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "syscall",
+ in("$2" /*$v0*/) nr.to_asm(),
+ in("$4" /*$a0*/) a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "sw {}, 20($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ in(reg) a5.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "sw {}, 20($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ in(reg) a5.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall7_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+ a6: ArgReg<'_, A6>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "sw {}, 20($sp)",
+ "sw {}, 24($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ in(reg) a5.to_asm(),
+ in(reg) a6.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
diff --git a/src/backend/linux_raw/arch/mips32r6.rs b/src/backend/linux_raw/arch/mips32r6.rs
new file mode 100644
index 0000000..c2d9244
--- /dev/null
+++ b/src/backend/linux_raw/arch/mips32r6.rs
@@ -0,0 +1,543 @@
+//! mipsisa32r6el Linux system calls.
+//!
+//! On mipsisa32r6el, Linux indicates success or failure using `$a3` rather
+//! than by returning a negative error code as most other architectures do.
+//!
+//! Mips-family platforms have a special calling convention for `__NR_pipe`,
+//! however we use `__NR_pipe2` instead to avoid having to implement it.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, A6, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "syscall",
+ in("$2" /*$v0*/) nr.to_asm(),
+ in("$4" /*$a0*/) a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "sw {}, 20($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ in(reg) a5.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "sw {}, 20($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ in(reg) a5.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall7_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+ a6: ArgReg<'_, A6>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ ".set noat",
+ "subu $sp, 32",
+ "sw {}, 16($sp)",
+ "sw {}, 20($sp)",
+ "sw {}, 24($sp)",
+ "syscall",
+ "addu $sp, 32",
+ ".set at",
+ in(reg) a4.to_asm(),
+ in(reg) a5.to_asm(),
+ in(reg) a6.to_asm(),
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$t0*/) _,
+ lateout("$9" /*$t1*/) _,
+ lateout("$10" /*$t2*/) _,
+ lateout("$11" /*$t3*/) _,
+ lateout("$12" /*$t4*/) _,
+ lateout("$13" /*$t5*/) _,
+ lateout("$14" /*$t6*/) _,
+ lateout("$15" /*$t7*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
diff --git a/src/backend/linux_raw/arch/mips64.rs b/src/backend/linux_raw/arch/mips64.rs
new file mode 100644
index 0000000..244daf3
--- /dev/null
+++ b/src/backend/linux_raw/arch/mips64.rs
@@ -0,0 +1,466 @@
+//! mips64el Linux system calls.
+//!
+//! On mips64el, Linux indicates success or failure using `$a3` (`$7`) rather
+//! than by returning a negative error code as most other architectures do.
+//!
+//! Mips-family platforms have a special calling convention for `__NR_pipe`,
+//! however we use `__NR_pipe2` instead to avoid having to implement it.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "syscall",
+ in("$2" /*$v0*/) nr.to_asm(),
+ in("$4" /*$a0*/) a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ inlateout("$9" /*$a5*/) a5.to_asm() => _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ inlateout("$9" /*$a5*/) a5.to_asm() => _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
diff --git a/src/backend/linux_raw/arch/mips64r6.rs b/src/backend/linux_raw/arch/mips64r6.rs
new file mode 100644
index 0000000..8c06d9e
--- /dev/null
+++ b/src/backend/linux_raw/arch/mips64r6.rs
@@ -0,0 +1,470 @@
+//! mipsisa64r6el Linux system calls.
+//!
+//! On mipsisa64r6el, Linux indicates success or failure using `$a3` (`$7`)
+//! rather than by returning a negative error code as most other architectures
+//! do.
+//!
+//! Mips-family platforms have a special calling convention for `__NR_pipe`,
+//! however we use `__NR_pipe2` instead to avoid having to implement it.
+//!
+//! Note that MIPS R6 inline assembly currently doesn't differ from MIPS,
+//! because no explicit call of R6-only or R2-only instructions exist here.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "syscall",
+ in("$2" /*$v0*/) nr.to_asm(),
+ in("$4" /*$a0*/) a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ lateout("$7" /*$a3*/) err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ lateout("$8" /*$a4*/) _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ lateout("$9" /*$a5*/) _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ inlateout("$9" /*$a5*/) a5.to_asm() => _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let x0;
+ let err: usize;
+ asm!(
+ "syscall",
+ inlateout("$2" /*$v0*/) nr.to_asm() => x0,
+ in("$4" /*$a0*/) a0.to_asm(),
+ in("$5" /*$a1*/) a1.to_asm(),
+ in("$6" /*$a2*/) a2.to_asm(),
+ inlateout("$7" /*$a3*/) a3.to_asm() => err,
+ inlateout("$8" /*$a4*/) a4.to_asm() => _,
+ inlateout("$9" /*$a5*/) a5.to_asm() => _,
+ lateout("$10" /*$a6*/) _,
+ lateout("$11" /*$a7*/) _,
+ lateout("$12" /*$t0*/) _,
+ lateout("$13" /*$t1*/) _,
+ lateout("$14" /*$t2*/) _,
+ lateout("$15" /*$t3*/) _,
+ lateout("$24" /*$t8*/) _,
+ lateout("$25" /*$t9*/) _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(if err != 0 {
+ (x0 as usize).wrapping_neg() as *mut _
+ } else {
+ x0
+ })
+}
diff --git a/src/backend/linux_raw/arch/mod.rs b/src/backend/linux_raw/arch/mod.rs
new file mode 100644
index 0000000..ac9e25f
--- /dev/null
+++ b/src/backend/linux_raw/arch/mod.rs
@@ -0,0 +1,317 @@
+//! Architecture-specific syscall code.
+//!
+//! This module also has a `choose` submodule which chooses a scheme and is
+//! what most of the `rustix` syscalls use.
+//!
+//! Compilers should really have intrinsics for making system calls. They're
+//! much like regular calls, with custom calling conventions, and calling
+//! conventions are otherwise the compiler's job. But for now, use inline asm.
+//!
+//! The calling conventions for Linux syscalls are [documented here].
+//!
+//! [documented here]: https://man7.org/linux/man-pages/man2/syscall.2.html
+//!
+//! # Safety
+//!
+//! This contains the inline `asm` statements performing the syscall
+//! instructions.
+
+#![allow(unsafe_code)]
+#![cfg_attr(not(feature = "all-apis"), allow(unused_imports))]
+// We'll use as many arguments as syscalls need.
+#![allow(clippy::too_many_arguments)]
+
+// These functions always use the machine's syscall instruction, even when it
+// isn't the fastest option available.
+#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
+#[cfg_attr(all(target_arch = "arm", not(thumb_mode)), path = "arm.rs")]
+#[cfg_attr(all(target_arch = "arm", thumb_mode), path = "thumb.rs")]
+#[cfg_attr(target_arch = "mips", path = "mips.rs")]
+#[cfg_attr(target_arch = "mips32r6", path = "mips32r6.rs")]
+#[cfg_attr(target_arch = "mips64", path = "mips64.rs")]
+#[cfg_attr(target_arch = "mips64r6", path = "mips64r6.rs")]
+#[cfg_attr(target_arch = "powerpc64", path = "powerpc64.rs")]
+#[cfg_attr(target_arch = "riscv64", path = "riscv64.rs")]
+#[cfg_attr(target_arch = "x86", path = "x86.rs")]
+#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
+pub(in crate::backend) mod asm;
+
+// On most architectures, the architecture syscall instruction is fast, so use
+// it directly.
+#[cfg(any(
+ target_arch = "arm",
+ target_arch = "aarch64",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "powerpc64",
+ target_arch = "riscv64",
+ target_arch = "x86_64",
+))]
+pub(in crate::backend) use self::asm as choose;
+
+// On 32-bit x86, use vDSO wrappers for all syscalls. We could use the
+// architecture syscall instruction (`int 0x80`), but the vDSO kernel_vsyscall
+// mechanism is much faster.
+#[cfg(target_arch = "x86")]
+pub(in crate::backend) use super::vdso_wrappers::x86_via_vdso as choose;
+
+// This would be the code for always using `int 0x80` on 32-bit x86.
+//#[cfg(target_arch = "x86")]
+//pub(in crate::backend) use self::asm as choose;
+
+// Macros for invoking system calls.
+//
+// These factor out:
+// - Calling `nr` on the syscall number to convert it into `SyscallNumber`.
+// - Calling `.into()` on each of the arguments to convert them into `ArgReg`.
+// - Qualifying the `syscall*` and `__NR_*` identifiers.
+// - Counting the number of arguments.
+macro_rules! syscall {
+ ($nr:ident) => {
+ $crate::backend::arch::choose::syscall0($crate::backend::reg::nr(
+ linux_raw_sys::general::$nr,
+ ))
+ };
+
+ ($nr:ident, $a0:expr) => {
+ $crate::backend::arch::choose::syscall1(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr) => {
+ $crate::backend::arch::choose::syscall2(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr) => {
+ $crate::backend::arch::choose::syscall3(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {
+ $crate::backend::arch::choose::syscall4(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
+ $crate::backend::arch::choose::syscall5(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr) => {
+ $crate::backend::arch::choose::syscall6(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ $a5.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr) => {
+ $crate::backend::arch::choose::syscall7(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ $a5.into(),
+ $a6.into(),
+ )
+ };
+}
+
+// Macro to invoke a syscall that always uses direct assembly, rather than the
+// vDSO. Useful when still finding the vDSO.
+#[allow(unused_macros)]
+macro_rules! syscall_always_asm {
+ ($nr:ident) => {
+ $crate::backend::arch::asm::syscall0($crate::backend::reg::nr(linux_raw_sys::general::$nr))
+ };
+
+ ($nr:ident, $a0:expr) => {
+ $crate::backend::arch::asm::syscall1(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr) => {
+ $crate::backend::arch::asm::syscall2(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr) => {
+ $crate::backend::arch::asm::syscall3(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {
+ $crate::backend::arch::asm::syscall4(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
+ $crate::backend::arch::asm::syscall5(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr) => {
+ $crate::backend::arch::asm::syscall6(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ $a5.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr) => {
+ $crate::backend::arch::asm::syscall7(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ $a5.into(),
+ $a6.into(),
+ )
+ };
+}
+
+/// Like `syscall`, but adds the `readonly` attribute to the inline asm, which
+/// indicates that the syscall does not mutate any memory.
+macro_rules! syscall_readonly {
+ ($nr:ident) => {
+ $crate::backend::arch::choose::syscall0_readonly($crate::backend::reg::nr(
+ linux_raw_sys::general::$nr,
+ ))
+ };
+
+ ($nr:ident, $a0:expr) => {
+ $crate::backend::arch::choose::syscall1_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr) => {
+ $crate::backend::arch::choose::syscall2_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr) => {
+ $crate::backend::arch::choose::syscall3_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {
+ $crate::backend::arch::choose::syscall4_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {
+ $crate::backend::arch::choose::syscall5_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr) => {
+ $crate::backend::arch::choose::syscall6_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ $a5.into(),
+ )
+ };
+
+ ($nr:ident, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr) => {
+ $crate::backend::arch::choose::syscall7_readonly(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ $a1.into(),
+ $a2.into(),
+ $a3.into(),
+ $a4.into(),
+ $a5.into(),
+ $a6.into(),
+ )
+ };
+}
+
+/// Like `syscall`, but indicates that the syscall does not return.
+#[cfg(feature = "runtime")]
+macro_rules! syscall_noreturn {
+ ($nr:ident, $a0:expr) => {
+ $crate::backend::arch::choose::syscall1_noreturn(
+ $crate::backend::reg::nr(linux_raw_sys::general::$nr),
+ $a0.into(),
+ )
+ };
+}
diff --git a/src/backend/linux_raw/arch/powerpc64.rs b/src/backend/linux_raw/arch/powerpc64.rs
new file mode 100644
index 0000000..14866c2
--- /dev/null
+++ b/src/backend/linux_raw/arch/powerpc64.rs
@@ -0,0 +1,413 @@
+//! powerpc64le Linux system calls.
+//!
+//! On powerpc64le, Linux indicates success or failure using `cr0.SO` rather
+//! than by returning a negative error code as most other architectures do. In
+//! theory we could immediately translate this into a `Result`, and it'd save a
+//! few branches. And in theory we could have specialized sequences for use
+//! with syscalls that are known to never fail. However, those would require
+//! more extensive changes in rustix's platform-independent code. For now, we
+//! check the flag and negate the error value to make PowerPC64 look like other
+//! architectures.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ lateout("r3") r0,
+ lateout("r4") _,
+ lateout("r5") _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ lateout("r4") _,
+ lateout("r5") _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ lateout("r4") _,
+ lateout("r5") _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "sc",
+ in("r0") nr.to_asm(),
+ in("r3") a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ lateout("r5") _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ lateout("r5") _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ lateout("r6") _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ inlateout("r6") a3.to_asm() => _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ inlateout("r6") a3.to_asm() => _,
+ lateout("r7") _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ inlateout("r6") a3.to_asm() => _,
+ inlateout("r7") a4.to_asm() => _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ inlateout("r6") a3.to_asm() => _,
+ inlateout("r7") a4.to_asm() => _,
+ lateout("r8") _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ inlateout("r6") a3.to_asm() => _,
+ inlateout("r7") a4.to_asm() => _,
+ inlateout("r8") a5.to_asm() => _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "sc",
+ "bns 0f",
+ "neg 3, 3",
+ "0:",
+ inlateout("r0") nr.to_asm() => _,
+ inlateout("r3") a0.to_asm() => r0,
+ inlateout("r4") a1.to_asm() => _,
+ inlateout("r5") a2.to_asm() => _,
+ inlateout("r6") a3.to_asm() => _,
+ inlateout("r7") a4.to_asm() => _,
+ inlateout("r8") a5.to_asm() => _,
+ lateout("r9") _,
+ lateout("r10") _,
+ lateout("r11") _,
+ lateout("r12") _,
+ lateout("cr0") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/arch/riscv64.rs b/src/backend/linux_raw/arch/riscv64.rs
new file mode 100644
index 0000000..7b8533d
--- /dev/null
+++ b/src/backend/linux_raw/arch/riscv64.rs
@@ -0,0 +1,265 @@
+//! riscv64 Linux system calls.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ lateout("a0") r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ in("a0") a0.to_asm(),
+ options(nostack, noreturn)
+ );
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ in("a3") a3.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ in("a3") a3.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ in("a3") a3.to_asm(),
+ in("a4") a4.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ in("a3") a3.to_asm(),
+ in("a4") a4.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ in("a3") a3.to_asm(),
+ in("a4") a4.to_asm(),
+ in("a5") a5.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "ecall",
+ in("a7") nr.to_asm(),
+ inlateout("a0") a0.to_asm() => r0,
+ in("a1") a1.to_asm(),
+ in("a2") a2.to_asm(),
+ in("a3") a3.to_asm(),
+ in("a4") a4.to_asm(),
+ in("a5") a5.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/arch/thumb.rs b/src/backend/linux_raw/arch/thumb.rs
new file mode 100644
index 0000000..73f9c1c
--- /dev/null
+++ b/src/backend/linux_raw/arch/thumb.rs
@@ -0,0 +1,322 @@
+//! arm Linux system calls, using thumb-mode.
+//!
+//! In thumb-mode, r7 is the frame pointer and is not permitted to be used in
+//! an inline asm operand, so we have to use a different register and copy it
+//! into r7 inside the inline asm.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ lateout("r0") r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "mov r7, {nr}",
+ "svc 0",
+ nr = in(reg) nr.to_asm(),
+ in("r0") a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ in("r5") a5.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "mov {tmp}, r7",
+ "mov r7, {nr}",
+ "svc 0",
+ "mov r7, {tmp}",
+ nr = in(reg) nr.to_asm(),
+ tmp = out(reg) _,
+ inlateout("r0") a0.to_asm() => r0,
+ in("r1") a1.to_asm(),
+ in("r2") a2.to_asm(),
+ in("r3") a3.to_asm(),
+ in("r4") a4.to_asm(),
+ in("r5") a5.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/arch/x86.rs b/src/backend/linux_raw/arch/x86.rs
new file mode 100644
index 0000000..e789181
--- /dev/null
+++ b/src/backend/linux_raw/arch/x86.rs
@@ -0,0 +1,489 @@
+//! 32-bit x86 Linux system calls.
+//!
+//! There are two forms; `indirect_*` which take a callee, which allow calling
+//! through the vDSO when possible, and plain forms, which use the `int 0x80`
+//! instruction.
+//!
+//! Most `rustix` syscalls use the vsyscall mechanism rather than going using
+//! `int 0x80` sequences, as vsyscall is much faster.
+//!
+//! Syscalls made with `int 0x80` preserve the flags register, while syscalls
+//! made using vsyscall do not.
+
+#![allow(dead_code)]
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use crate::backend::vdso_wrappers::SyscallType;
+use core::arch::asm;
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall0(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "call {callee}",
+ callee = in(reg) callee,
+ inlateout("eax") nr.to_asm() => r0,
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall1(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "call {callee}",
+ callee = in(reg) callee,
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall1_noreturn(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> ! {
+ asm!(
+ "call {callee}",
+ callee = in(reg) callee,
+ in("eax") nr.to_asm(),
+ in("ebx") a0.to_asm(),
+ options(noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall2(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "call {callee}",
+ callee = in(reg) callee,
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall3(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "call {callee}",
+ callee = in(reg) callee,
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall4(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ // a3 should go in esi, but `asm!` won't let us use it as an operand.
+ // Temporarily swap it into place, and then swap it back afterward.
+ //
+ // We hard-code the callee operand to use edi instead of `in(reg)` because
+ // even though we can't name esi as an operand, the compiler can use esi to
+ // satisfy `in(reg)`.
+ asm!(
+ "xchg esi, {a3}",
+ "call edi",
+ "xchg esi, {a3}",
+ a3 = in(reg) a3.to_asm(),
+ in("edi") callee,
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall5(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ // Oof. a3 should go in esi, and `asm!` won't let us use that register as
+ // an operand. And we can't request stack slots. And there are no other
+ // registers free. Use eax as a temporary pointer to a slice, since it gets
+ // clobbered as the return value anyway.
+ asm!(
+ "push esi",
+ "push [eax + 0]",
+ "mov esi, [eax + 4]",
+ "mov eax, [eax + 8]",
+ "call [esp]",
+ "pop esi",
+ "pop esi",
+ inout("eax") &[callee as _, a3.to_asm(), nr.to_asm()] => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ in("edi") a4.to_asm(),
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn indirect_syscall6(
+ callee: SyscallType,
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ // Oof again. a3 should go in esi, and a5 should go in ebp, and `asm!`
+ // won't let us use either of those registers as operands. And we can't
+ // request stack slots. And there are no other registers free. Use eax as a
+ // temporary pointer to a slice, since it gets clobbered as the return
+ // value anyway.
+ //
+ // This is another reason that syscalls should be compiler intrinsics
+ // rather than inline asm.
+ asm!(
+ "push ebp",
+ "push esi",
+ "push [eax + 0]",
+ "mov esi, [eax + 4]",
+ "mov ebp, [eax + 8]",
+ "mov eax, [eax + 12]",
+ "call [esp]",
+ "pop esi",
+ "pop esi",
+ "pop ebp",
+ inout("eax") &[callee as _, a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ in("edi") a4.to_asm(),
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "int $$0x80",
+ in("eax") nr.to_asm(),
+ in("ebx") a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "int $$0x80",
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ // a3 should go in esi, but `asm!` won't let us use it as an operand.
+ // Temporarily swap it into place, and then swap it back afterward.
+ asm!(
+ "xchg esi, {a3}",
+ "int $$0x80",
+ "xchg esi, {a3}",
+ a3 = in(reg) a3.to_asm(),
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "xchg esi, {a3}",
+ "int $$0x80",
+ "xchg esi, {a3}",
+ a3 = in(reg) a3.to_asm(),
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ // As in `syscall4`, use xchg to handle a3. a4 should go in edi, and we can
+ // use that register as an operand. Unlike in `indirect_syscall5`, we don't
+ // have a `callee` operand taking up a register, so we have enough
+ // registers and don't need to use a slice.
+ asm!(
+ "xchg esi, {a3}",
+ "int $$0x80",
+ "xchg esi, {a3}",
+ a3 = in(reg) a3.to_asm(),
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ in("edi") a4.to_asm(),
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ // See the comments in `syscall5`.
+ asm!(
+ "xchg esi, {a3}",
+ "int $$0x80",
+ "xchg esi, {a3}",
+ a3 = in(reg) a3.to_asm(),
+ inlateout("eax") nr.to_asm() => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ in("edi") a4.to_asm(),
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ // See the comments in `indirect_syscall6`.
+ asm!(
+ "push ebp",
+ "push esi",
+ "mov esi, [eax + 0]",
+ "mov ebp, [eax + 4]",
+ "mov eax, [eax + 8]",
+ "int $$0x80",
+ "pop esi",
+ "pop ebp",
+ inout("eax") &[a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ in("edi") a4.to_asm(),
+ options(preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ // See the comments in `indirect_syscall6`.
+ asm!(
+ "push ebp",
+ "push esi",
+ "mov esi, [eax + 0]",
+ "mov ebp, [eax + 4]",
+ "mov eax, [eax + 8]",
+ "int $$0x80",
+ "pop esi",
+ "pop ebp",
+ inout("eax") &[a3.to_asm(), a5.to_asm(), nr.to_asm()] => r0,
+ in("ebx") a0.to_asm(),
+ in("ecx") a1.to_asm(),
+ in("edx") a2.to_asm(),
+ in("edi") a4.to_asm(),
+ options(preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/arch/x86_64.rs b/src/backend/linux_raw/arch/x86_64.rs
new file mode 100644
index 0000000..62f35d9
--- /dev/null
+++ b/src/backend/linux_raw/arch/x86_64.rs
@@ -0,0 +1,293 @@
+//! x86-64 Linux system calls.
+
+use crate::backend::reg::{
+ ArgReg, FromAsm, RetReg, SyscallNumber, ToAsm, A0, A1, A2, A3, A4, A5, R0,
+};
+use core::arch::asm;
+
+#[cfg(target_pointer_width = "32")]
+compile_error!("x32 is not yet supported");
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
+ asm!(
+ "syscall",
+ in("rax") nr.to_asm(),
+ in("rdi") a0.to_asm(),
+ options(nostack, noreturn)
+ )
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall2_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall3_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ in("r10") a3.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall4_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ in("r10") a3.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ in("r10") a3.to_asm(),
+ in("r8") a4.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall5_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ in("r10") a3.to_asm(),
+ in("r8") a4.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ in("r10") a3.to_asm(),
+ in("r8") a4.to_asm(),
+ in("r9") a5.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags)
+ );
+ FromAsm::from_asm(r0)
+}
+
+#[inline]
+pub(in crate::backend) unsafe fn syscall6_readonly(
+ nr: SyscallNumber<'_>,
+ a0: ArgReg<'_, A0>,
+ a1: ArgReg<'_, A1>,
+ a2: ArgReg<'_, A2>,
+ a3: ArgReg<'_, A3>,
+ a4: ArgReg<'_, A4>,
+ a5: ArgReg<'_, A5>,
+) -> RetReg<R0> {
+ let r0;
+ asm!(
+ "syscall",
+ inlateout("rax") nr.to_asm() => r0,
+ in("rdi") a0.to_asm(),
+ in("rsi") a1.to_asm(),
+ in("rdx") a2.to_asm(),
+ in("r10") a3.to_asm(),
+ in("r8") a4.to_asm(),
+ in("r9") a5.to_asm(),
+ lateout("rcx") _,
+ lateout("r11") _,
+ options(nostack, preserves_flags, readonly)
+ );
+ FromAsm::from_asm(r0)
+}
diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs
new file mode 100644
index 0000000..b2cd5bd
--- /dev/null
+++ b/src/backend/linux_raw/c.rs
@@ -0,0 +1,319 @@
+//! Adapt the Linux API to resemble a POSIX-style libc API.
+//!
+//! The linux_raw backend doesn't use actual libc; this just defines certain
+//! types that are convenient to have defined.
+
+#![allow(unused_imports)]
+#![allow(non_camel_case_types)]
+
+pub(crate) type size_t = usize;
+pub(crate) use linux_raw_sys::ctypes::*;
+pub(crate) use linux_raw_sys::errno::EINVAL;
+pub(crate) use linux_raw_sys::ioctl::{FIONBIO, FIONREAD};
+// Import the kernel's `uid_t` and `gid_t` if they're 32-bit.
+#[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))]
+pub(crate) use linux_raw_sys::general::{__kernel_gid_t as gid_t, __kernel_uid_t as uid_t};
+pub(crate) use linux_raw_sys::general::{
+ __kernel_pid_t as pid_t, __kernel_time64_t as time_t, __kernel_timespec as timespec, iovec,
+ O_CLOEXEC, O_NOCTTY, O_NONBLOCK, O_RDWR,
+};
+
+#[cfg(feature = "event")]
+#[cfg(test)]
+pub(crate) use linux_raw_sys::general::epoll_event;
+
+#[cfg(any(
+ feature = "fs",
+ all(
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+ )
+))]
+pub(crate) use linux_raw_sys::general::{
+ AT_FDCWD, NFS_SUPER_MAGIC, O_LARGEFILE, PROC_SUPER_MAGIC, UTIME_NOW, UTIME_OMIT, XATTR_CREATE,
+ XATTR_REPLACE,
+};
+
+pub(crate) use linux_raw_sys::ioctl::{BLKPBSZGET, BLKSSZGET, FICLONE};
+#[cfg(target_pointer_width = "32")]
+pub(crate) use linux_raw_sys::ioctl::{FS_IOC32_GETFLAGS, FS_IOC32_SETFLAGS};
+#[cfg(target_pointer_width = "64")]
+pub(crate) use linux_raw_sys::ioctl::{FS_IOC_GETFLAGS, FS_IOC_SETFLAGS};
+
+#[cfg(feature = "io_uring")]
+pub(crate) use linux_raw_sys::{general::open_how, io_uring::*};
+
+#[cfg(feature = "net")]
+pub(crate) use linux_raw_sys::{
+ cmsg_macros::*,
+ general::{O_CLOEXEC as SOCK_CLOEXEC, O_NONBLOCK as SOCK_NONBLOCK},
+ if_ether::*,
+ net::{
+ linger, msghdr, sockaddr, sockaddr_in, sockaddr_in6, sockaddr_un, socklen_t, AF_DECnet,
+ __kernel_sa_family_t as sa_family_t, __kernel_sockaddr_storage as sockaddr_storage,
+ cmsghdr, in6_addr, in_addr, ip_mreq, ip_mreq_source, ip_mreqn, ipv6_mreq, AF_APPLETALK,
+ AF_ASH, AF_ATMPVC, AF_ATMSVC, AF_AX25, AF_BLUETOOTH, AF_BRIDGE, AF_CAN, AF_ECONET,
+ AF_IEEE802154, AF_INET, AF_INET6, AF_IPX, AF_IRDA, AF_ISDN, AF_IUCV, AF_KEY, AF_LLC,
+ AF_NETBEUI, AF_NETLINK, AF_NETROM, AF_PACKET, AF_PHONET, AF_PPPOX, AF_RDS, AF_ROSE,
+ AF_RXRPC, AF_SECURITY, AF_SNA, AF_TIPC, AF_UNIX, AF_UNSPEC, AF_WANPIPE, AF_X25, AF_XDP,
+ IP6T_SO_ORIGINAL_DST, IPPROTO_FRAGMENT, IPPROTO_ICMPV6, IPPROTO_MH, IPPROTO_ROUTING,
+ IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_FREEBIND, IPV6_MULTICAST_HOPS,
+ IPV6_MULTICAST_LOOP, IPV6_RECVTCLASS, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY,
+ IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP,
+ IP_FREEBIND, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL,
+ MSG_CMSG_CLOEXEC, MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR, MSG_ERRQUEUE,
+ MSG_MORE, MSG_NOSIGNAL, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, SCM_CREDENTIALS,
+ SCM_RIGHTS, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET,
+ SOCK_STREAM, SOL_SOCKET, SOL_XDP, SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE, SO_DOMAIN,
+ SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_ORIGINAL_DST,
+ SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVTIMEO_NEW, SO_RCVTIMEO_NEW as SO_RCVTIMEO,
+ SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF, SO_SNDTIMEO_NEW,
+ SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_CONGESTION, TCP_CORK,
+ TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY, TCP_QUICKACK,
+ TCP_THIN_LINEAR_TIMEOUTS, TCP_USER_TIMEOUT,
+ },
+ netlink::*,
+ xdp::{
+ sockaddr_xdp, xdp_desc, xdp_mmap_offsets, xdp_mmap_offsets_v1, xdp_options,
+ xdp_ring_offset, xdp_ring_offset_v1, xdp_statistics, xdp_statistics_v1, xdp_umem_reg,
+ xdp_umem_reg_v1, XDP_COPY, XDP_MMAP_OFFSETS, XDP_OPTIONS, XDP_OPTIONS_ZEROCOPY,
+ XDP_PGOFF_RX_RING, XDP_PGOFF_TX_RING, XDP_PKT_CONTD, XDP_RING_NEED_WAKEUP, XDP_RX_RING,
+ XDP_SHARED_UMEM, XDP_STATISTICS, XDP_TX_RING, XDP_UMEM_COMPLETION_RING, XDP_UMEM_FILL_RING,
+ XDP_UMEM_PGOFF_COMPLETION_RING, XDP_UMEM_PGOFF_FILL_RING, XDP_UMEM_REG,
+ XDP_UMEM_UNALIGNED_CHUNK_FLAG, XDP_USE_NEED_WAKEUP, XDP_USE_SG, XDP_ZEROCOPY,
+ XSK_UNALIGNED_BUF_ADDR_MASK, XSK_UNALIGNED_BUF_OFFSET_SHIFT,
+ },
+};
+
+// Cast away bindgen's `enum` type to make these consistent with the other
+// `setsockopt`/`getsockopt` level values.
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_IP: u32 = linux_raw_sys::net::IPPROTO_IP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_ICMP: u32 = linux_raw_sys::net::IPPROTO_ICMP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_IGMP: u32 = linux_raw_sys::net::IPPROTO_IGMP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_IPIP: u32 = linux_raw_sys::net::IPPROTO_IPIP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_TCP: u32 = linux_raw_sys::net::IPPROTO_TCP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_EGP: u32 = linux_raw_sys::net::IPPROTO_EGP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_PUP: u32 = linux_raw_sys::net::IPPROTO_PUP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_UDP: u32 = linux_raw_sys::net::IPPROTO_UDP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_IDP: u32 = linux_raw_sys::net::IPPROTO_IDP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_TP: u32 = linux_raw_sys::net::IPPROTO_TP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_DCCP: u32 = linux_raw_sys::net::IPPROTO_DCCP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_IPV6: u32 = linux_raw_sys::net::IPPROTO_IPV6 as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_RSVP: u32 = linux_raw_sys::net::IPPROTO_RSVP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_GRE: u32 = linux_raw_sys::net::IPPROTO_GRE as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_ESP: u32 = linux_raw_sys::net::IPPROTO_ESP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_AH: u32 = linux_raw_sys::net::IPPROTO_AH as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_MTP: u32 = linux_raw_sys::net::IPPROTO_MTP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_BEETPH: u32 = linux_raw_sys::net::IPPROTO_BEETPH as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_ENCAP: u32 = linux_raw_sys::net::IPPROTO_ENCAP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_PIM: u32 = linux_raw_sys::net::IPPROTO_PIM as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_COMP: u32 = linux_raw_sys::net::IPPROTO_COMP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_SCTP: u32 = linux_raw_sys::net::IPPROTO_SCTP as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_UDPLITE: u32 = linux_raw_sys::net::IPPROTO_UDPLITE as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_MPLS: u32 = linux_raw_sys::net::IPPROTO_MPLS as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_ETHERNET: u32 = linux_raw_sys::net::IPPROTO_ETHERNET as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_RAW: u32 = linux_raw_sys::net::IPPROTO_RAW as _;
+#[cfg(feature = "net")]
+pub(crate) const IPPROTO_MPTCP: u32 = linux_raw_sys::net::IPPROTO_MPTCP as _;
+
+#[cfg(any(feature = "process", feature = "runtime"))]
+pub(crate) use linux_raw_sys::general::siginfo_t;
+
+#[cfg(any(feature = "process", feature = "runtime"))]
+pub(crate) const EXIT_SUCCESS: c_int = 0;
+#[cfg(any(feature = "process", feature = "runtime"))]
+pub(crate) const EXIT_FAILURE: c_int = 1;
+#[cfg(feature = "process")]
+pub(crate) const EXIT_SIGNALED_SIGABRT: c_int = 128 + linux_raw_sys::general::SIGABRT as c_int;
+#[cfg(feature = "runtime")]
+pub(crate) const CLONE_CHILD_SETTID: c_int = linux_raw_sys::general::CLONE_CHILD_SETTID as c_int;
+
+#[cfg(feature = "process")]
+pub(crate) use linux_raw_sys::{
+ general::{
+ CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED,
+ O_NONBLOCK as PIDFD_NONBLOCK, P_ALL, P_PGID, P_PID, P_PIDFD,
+ },
+ ioctl::TIOCSCTTY,
+};
+
+#[cfg(feature = "pty")]
+pub(crate) use linux_raw_sys::ioctl::TIOCGPTPEER;
+
+#[cfg(feature = "termios")]
+pub(crate) use linux_raw_sys::{
+ general::{
+ cc_t, speed_t, tcflag_t, termios, termios2, winsize, B0, B1000000, B110, B115200, B1152000,
+ B1200, B134, B150, B1500000, B1800, B19200, B200, B2000000, B230400, B2400, B2500000, B300,
+ B3000000, B3500000, B38400, B4000000, B460800, B4800, B50, B500000, B57600, B576000, B600,
+ B75, B921600, B9600, BOTHER, BRKINT, BS0, BS1, BSDLY, CBAUD, CBAUDEX, CIBAUD, CLOCAL,
+ CMSPAR, CR0, CR1, CR2, CR3, CRDLY, CREAD, CRTSCTS, CS5, CS6, CS7, CS8, CSIZE, CSTOPB, ECHO,
+ ECHOCTL, ECHOE, ECHOK, ECHOKE, ECHONL, ECHOPRT, EXTA, EXTB, EXTPROC, FF0, FF1, FFDLY,
+ FLUSHO, HUPCL, IBSHIFT, ICANON, ICRNL, IEXTEN, IGNBRK, IGNCR, IGNPAR, IMAXBEL, INLCR,
+ INPCK, ISIG, ISTRIP, IUCLC, IUTF8, IXANY, IXOFF, IXON, NCCS, NL0, NL1, NLDLY, NOFLSH,
+ OCRNL, OFDEL, OFILL, OLCUC, ONLCR, ONLRET, ONOCR, OPOST, PARENB, PARMRK, PARODD, PENDIN,
+ TAB0, TAB1, TAB2, TAB3, TABDLY, TCIFLUSH, TCIOFF, TCIOFLUSH, TCION, TCOFLUSH, TCOOFF,
+ TCOON, TCSADRAIN, TCSAFLUSH, TCSANOW, TOSTOP, VDISCARD, VEOF, VEOL, VEOL2, VERASE, VINTR,
+ VKILL, VLNEXT, VMIN, VQUIT, VREPRINT, VSTART, VSTOP, VSUSP, VSWTC, VT0, VT1, VTDLY, VTIME,
+ VWERASE, XCASE, XTABS,
+ },
+ ioctl::{TCGETS2, TCSETS2, TCSETSF2, TCSETSW2, TIOCEXCL, TIOCNXCL},
+};
+
+// On MIPS, `TCSANOW` et al have `TCSETS` added to them, so we need it to
+// subtract it out.
+#[cfg(all(
+ feature = "termios",
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )
+))]
+pub(crate) use linux_raw_sys::ioctl::TCSETS;
+
+// Define our own `uid_t` and `gid_t` if the kernel's versions are not 32-bit.
+#[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))]
+pub(crate) type uid_t = u32;
+#[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))]
+pub(crate) type gid_t = u32;
+
+// Bindgen infers `u32` for many of these macro types which meant to be
+// used with `c_int` in the C APIs, so cast them to `c_int`.
+
+// Convert the signal constants from `u32` to `c_int`.
+pub(crate) const SIGHUP: c_int = linux_raw_sys::general::SIGHUP as _;
+pub(crate) const SIGINT: c_int = linux_raw_sys::general::SIGINT as _;
+pub(crate) const SIGQUIT: c_int = linux_raw_sys::general::SIGQUIT as _;
+pub(crate) const SIGILL: c_int = linux_raw_sys::general::SIGILL as _;
+pub(crate) const SIGTRAP: c_int = linux_raw_sys::general::SIGTRAP as _;
+pub(crate) const SIGABRT: c_int = linux_raw_sys::general::SIGABRT as _;
+pub(crate) const SIGBUS: c_int = linux_raw_sys::general::SIGBUS as _;
+pub(crate) const SIGFPE: c_int = linux_raw_sys::general::SIGFPE as _;
+pub(crate) const SIGKILL: c_int = linux_raw_sys::general::SIGKILL as _;
+pub(crate) const SIGUSR1: c_int = linux_raw_sys::general::SIGUSR1 as _;
+pub(crate) const SIGSEGV: c_int = linux_raw_sys::general::SIGSEGV as _;
+pub(crate) const SIGUSR2: c_int = linux_raw_sys::general::SIGUSR2 as _;
+pub(crate) const SIGPIPE: c_int = linux_raw_sys::general::SIGPIPE as _;
+pub(crate) const SIGALRM: c_int = linux_raw_sys::general::SIGALRM as _;
+pub(crate) const SIGTERM: c_int = linux_raw_sys::general::SIGTERM as _;
+#[cfg(not(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+)))]
+pub(crate) const SIGSTKFLT: c_int = linux_raw_sys::general::SIGSTKFLT as _;
+pub(crate) const SIGCHLD: c_int = linux_raw_sys::general::SIGCHLD as _;
+pub(crate) const SIGCONT: c_int = linux_raw_sys::general::SIGCONT as _;
+pub(crate) const SIGSTOP: c_int = linux_raw_sys::general::SIGSTOP as _;
+pub(crate) const SIGTSTP: c_int = linux_raw_sys::general::SIGTSTP as _;
+pub(crate) const SIGTTIN: c_int = linux_raw_sys::general::SIGTTIN as _;
+pub(crate) const SIGTTOU: c_int = linux_raw_sys::general::SIGTTOU as _;
+pub(crate) const SIGURG: c_int = linux_raw_sys::general::SIGURG as _;
+pub(crate) const SIGXCPU: c_int = linux_raw_sys::general::SIGXCPU as _;
+pub(crate) const SIGXFSZ: c_int = linux_raw_sys::general::SIGXFSZ as _;
+pub(crate) const SIGVTALRM: c_int = linux_raw_sys::general::SIGVTALRM as _;
+pub(crate) const SIGPROF: c_int = linux_raw_sys::general::SIGPROF as _;
+pub(crate) const SIGWINCH: c_int = linux_raw_sys::general::SIGWINCH as _;
+pub(crate) const SIGIO: c_int = linux_raw_sys::general::SIGIO as _;
+pub(crate) const SIGPWR: c_int = linux_raw_sys::general::SIGPWR as _;
+pub(crate) const SIGSYS: c_int = linux_raw_sys::general::SIGSYS as _;
+#[cfg(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 _;
+
+#[cfg(feature = "stdio")]
+pub(crate) const STDIN_FILENO: c_int = linux_raw_sys::general::STDIN_FILENO as _;
+#[cfg(feature = "stdio")]
+pub(crate) const STDOUT_FILENO: c_int = linux_raw_sys::general::STDOUT_FILENO as _;
+#[cfg(feature = "stdio")]
+pub(crate) const STDERR_FILENO: c_int = linux_raw_sys::general::STDERR_FILENO as _;
+
+pub(crate) const PIPE_BUF: usize = linux_raw_sys::general::PIPE_BUF as _;
+
+pub(crate) const CLOCK_MONOTONIC: c_int = linux_raw_sys::general::CLOCK_MONOTONIC as _;
+pub(crate) const CLOCK_REALTIME: c_int = linux_raw_sys::general::CLOCK_REALTIME as _;
+pub(crate) const CLOCK_MONOTONIC_RAW: c_int = linux_raw_sys::general::CLOCK_MONOTONIC_RAW as _;
+pub(crate) const CLOCK_MONOTONIC_COARSE: c_int =
+ linux_raw_sys::general::CLOCK_MONOTONIC_COARSE as _;
+pub(crate) const CLOCK_REALTIME_COARSE: c_int = linux_raw_sys::general::CLOCK_REALTIME_COARSE as _;
+pub(crate) const CLOCK_THREAD_CPUTIME_ID: c_int =
+ linux_raw_sys::general::CLOCK_THREAD_CPUTIME_ID as _;
+pub(crate) const CLOCK_PROCESS_CPUTIME_ID: c_int =
+ linux_raw_sys::general::CLOCK_PROCESS_CPUTIME_ID as _;
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+pub(crate) const CLOCK_BOOTTIME: c_int = linux_raw_sys::general::CLOCK_BOOTTIME as _;
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+pub(crate) const CLOCK_BOOTTIME_ALARM: c_int = linux_raw_sys::general::CLOCK_BOOTTIME_ALARM as _;
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+pub(crate) const CLOCK_TAI: c_int = linux_raw_sys::general::CLOCK_TAI as _;
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+pub(crate) const CLOCK_REALTIME_ALARM: c_int = linux_raw_sys::general::CLOCK_REALTIME_ALARM as _;
+
+#[cfg(feature = "system")]
+mod reboot_symbols {
+ use super::c_int;
+
+ pub(crate) const LINUX_REBOOT_MAGIC1: c_int = linux_raw_sys::general::LINUX_REBOOT_MAGIC1 as _;
+ pub(crate) const LINUX_REBOOT_MAGIC2: c_int = linux_raw_sys::general::LINUX_REBOOT_MAGIC2 as _;
+
+ pub(crate) const LINUX_REBOOT_CMD_RESTART: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_RESTART as _;
+ pub(crate) const LINUX_REBOOT_CMD_HALT: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_HALT as _;
+ pub(crate) const LINUX_REBOOT_CMD_CAD_ON: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_CAD_ON as _;
+ pub(crate) const LINUX_REBOOT_CMD_CAD_OFF: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_CAD_OFF as _;
+ pub(crate) const LINUX_REBOOT_CMD_POWER_OFF: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_POWER_OFF as _;
+ pub(crate) const LINUX_REBOOT_CMD_SW_SUSPEND: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_SW_SUSPEND as _;
+ pub(crate) const LINUX_REBOOT_CMD_KEXEC: c_int =
+ linux_raw_sys::general::LINUX_REBOOT_CMD_KEXEC as _;
+}
+#[cfg(feature = "system")]
+pub(crate) use reboot_symbols::*;
diff --git a/src/backend/linux_raw/conv.rs b/src/backend/linux_raw/conv.rs
new file mode 100644
index 0000000..f0de45a
--- /dev/null
+++ b/src/backend/linux_raw/conv.rs
@@ -0,0 +1,1019 @@
+//! Convert values to [`ArgReg`] and from [`RetReg`].
+//!
+//! System call arguments and return values are all communicated with inline
+//! asm and FFI as `*mut Opaque`. To protect these raw pointers from escaping
+//! or being accidentally misused as they travel through the code, we wrap them
+//! in [`ArgReg`] and [`RetReg`] structs. This file provides `From`
+//! implementations and explicit conversion functions for converting values
+//! into and out of these wrapper structs.
+//!
+//! # Safety
+//!
+//! Some of this code is `unsafe` in order to work with raw file descriptors,
+//! and some is `unsafe` to interpret the values in a `RetReg`.
+#![allow(unsafe_code)]
+
+use super::c;
+use super::fd::{AsRawFd, BorrowedFd, FromRawFd, RawFd};
+#[cfg(any(feature = "event", feature = "runtime"))]
+use super::io::errno::try_decode_error;
+#[cfg(target_pointer_width = "64")]
+use super::io::errno::try_decode_u64;
+#[cfg(not(debug_assertions))]
+use super::io::errno::{
+ decode_c_int_infallible, decode_c_uint_infallible, decode_usize_infallible,
+};
+use super::io::errno::{
+ try_decode_c_int, try_decode_c_uint, try_decode_raw_fd, try_decode_usize, try_decode_void,
+ try_decode_void_star,
+};
+use super::reg::{raw_arg, ArgNumber, ArgReg, RetReg, R0};
+#[cfg(feature = "time")]
+use super::time::types::TimerfdClockId;
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+use crate::clockid::ClockId;
+use crate::fd::OwnedFd;
+use crate::ffi::CStr;
+use crate::io;
+#[cfg(any(feature = "process", feature = "runtime", feature = "termios"))]
+use crate::pid::Pid;
+#[cfg(feature = "process")]
+use crate::process::Resource;
+#[cfg(any(feature = "process", feature = "runtime"))]
+use crate::signal::Signal;
+use crate::utils::{as_mut_ptr, as_ptr};
+use core::mem::MaybeUninit;
+use core::ptr::null_mut;
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+use linux_raw_sys::general::__kernel_clockid_t;
+#[cfg(target_pointer_width = "64")]
+use linux_raw_sys::general::__kernel_loff_t;
+#[cfg(feature = "net")]
+use linux_raw_sys::net::socklen_t;
+
+/// Convert `SYS_*` constants for socketcall.
+#[cfg(target_arch = "x86")]
+#[inline]
+pub(super) fn x86_sys<'a, Num: ArgNumber>(sys: u32) -> ArgReg<'a, Num> {
+ pass_usize(sys as usize)
+}
+
+/// Pass the "low" half of the endian-specific memory encoding of a `u64`, for
+/// 32-bit architectures.
+#[cfg(target_pointer_width = "32")]
+#[inline]
+pub(super) fn lo<'a, Num: ArgNumber>(x: u64) -> ArgReg<'a, Num> {
+ #[cfg(target_endian = "little")]
+ let x = x >> 32;
+ #[cfg(target_endian = "big")]
+ let x = x & 0xffff_ffff;
+
+ pass_usize(x as usize)
+}
+
+/// Pass the "high" half of the endian-specific memory encoding of a `u64`, for
+/// 32-bit architectures.
+#[cfg(target_pointer_width = "32")]
+#[inline]
+pub(super) fn hi<'a, Num: ArgNumber>(x: u64) -> ArgReg<'a, Num> {
+ #[cfg(target_endian = "little")]
+ let x = x & 0xffff_ffff;
+ #[cfg(target_endian = "big")]
+ let x = x >> 32;
+
+ pass_usize(x as usize)
+}
+
+/// Pass a zero, or null, argument.
+#[inline]
+pub(super) fn zero<'a, Num: ArgNumber>() -> ArgReg<'a, Num> {
+ raw_arg(null_mut())
+}
+
+/// Pass the `mem::size_of` of a type.
+#[inline]
+pub(super) fn size_of<'a, T: Sized, Num: ArgNumber>() -> ArgReg<'a, Num> {
+ pass_usize(core::mem::size_of::<T>())
+}
+
+/// Pass an arbitrary `usize` value.
+///
+/// For passing pointers, use `void_star` or other functions which take a raw
+/// pointer instead of casting to `usize`, so that provenance is preserved.
+#[inline]
+pub(super) fn pass_usize<'a, Num: ArgNumber>(t: usize) -> ArgReg<'a, Num> {
+ raw_arg(t as *mut _)
+}
+
+impl<'a, Num: ArgNumber, T> From<*mut T> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(c: *mut T) -> ArgReg<'a, Num> {
+ raw_arg(c.cast())
+ }
+}
+
+impl<'a, Num: ArgNumber, T> From<*const T> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(c: *const T) -> ArgReg<'a, Num> {
+ let mut_ptr = c as *mut T;
+ raw_arg(mut_ptr.cast())
+ }
+}
+
+impl<'a, Num: ArgNumber> From<&'a CStr> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(c: &'a CStr) -> Self {
+ let mut_ptr = c.as_ptr() as *mut u8;
+ raw_arg(mut_ptr.cast())
+ }
+}
+
+impl<'a, Num: ArgNumber> From<Option<&'a CStr>> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(t: Option<&'a CStr>) -> Self {
+ raw_arg(match t {
+ Some(s) => {
+ let mut_ptr = s.as_ptr() as *mut u8;
+ mut_ptr.cast()
+ }
+ None => null_mut(),
+ })
+ }
+}
+
+/// Pass a borrowed file-descriptor argument.
+impl<'a, Num: ArgNumber> From<BorrowedFd<'a>> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(fd: BorrowedFd<'a>) -> Self {
+ // SAFETY: `BorrowedFd` ensures that the file descriptor is valid, and
+ // the lifetime parameter on the resulting `ArgReg` ensures that the
+ // result is bounded by the `BorrowedFd`'s lifetime.
+ unsafe { raw_fd(fd.as_raw_fd()) }
+ }
+}
+
+/// Pass a raw file-descriptor argument. Most users should use [`ArgReg::from`]
+/// instead, to preserve I/O safety as long as possible.
+///
+/// # Safety
+///
+/// `fd` must be a valid open file descriptor.
+#[inline]
+pub(super) unsafe fn raw_fd<'a, Num: ArgNumber>(fd: RawFd) -> ArgReg<'a, Num> {
+ // Use `no_fd` when passing `-1` is intended.
+ #[cfg(feature = "fs")]
+ debug_assert!(fd == crate::fs::CWD.as_raw_fd() || fd >= 0);
+
+ // Don't pass the `io_uring_register_files_skip` sentry value this way.
+ #[cfg(feature = "io_uring")]
+ debug_assert_ne!(
+ fd,
+ crate::io_uring::io_uring_register_files_skip().as_raw_fd()
+ );
+
+ // Linux doesn't look at the high bits beyond the `c_int`, so use
+ // zero-extension rather than sign-extension because it's a smaller
+ // instruction.
+ let fd: c::c_int = fd;
+ pass_usize(fd as c::c_uint as usize)
+}
+
+/// Deliberately pass `-1` to a file-descriptor argument, for system calls
+/// like `mmap` where this indicates the argument is omitted.
+#[inline]
+pub(super) fn no_fd<'a, Num: ArgNumber>() -> ArgReg<'a, Num> {
+ pass_usize(!0_usize)
+}
+
+#[inline]
+pub(super) fn slice_just_addr<T: Sized, Num: ArgNumber>(v: &[T]) -> ArgReg<'_, Num> {
+ let mut_ptr = v.as_ptr() as *mut T;
+ raw_arg(mut_ptr.cast())
+}
+
+#[inline]
+pub(super) fn slice_just_addr_mut<T: Sized, Num: ArgNumber>(v: &mut [T]) -> ArgReg<'_, Num> {
+ raw_arg(v.as_mut_ptr().cast())
+}
+
+#[inline]
+pub(super) fn slice<T: Sized, Num0: ArgNumber, Num1: ArgNumber>(
+ v: &[T],
+) -> (ArgReg<'_, Num0>, ArgReg<'_, Num1>) {
+ (slice_just_addr(v), pass_usize(v.len()))
+}
+
+#[inline]
+pub(super) fn slice_mut<T: Sized, Num0: ArgNumber, Num1: ArgNumber>(
+ v: &mut [T],
+) -> (ArgReg<'_, Num0>, ArgReg<'_, Num1>) {
+ (raw_arg(v.as_mut_ptr().cast()), pass_usize(v.len()))
+}
+
+#[inline]
+pub(super) fn by_ref<T: Sized, Num: ArgNumber>(t: &T) -> ArgReg<'_, Num> {
+ let mut_ptr = as_ptr(t) as *mut T;
+ raw_arg(mut_ptr.cast())
+}
+
+#[inline]
+pub(super) fn by_mut<T: Sized, Num: ArgNumber>(t: &mut T) -> ArgReg<'_, Num> {
+ raw_arg(as_mut_ptr(t).cast())
+}
+
+/// Convert an optional mutable reference into a `usize` for passing to a
+/// syscall.
+#[inline]
+pub(super) fn opt_mut<T: Sized, Num: ArgNumber>(t: Option<&mut T>) -> ArgReg<'_, Num> {
+ // This optimizes into the equivalent of `transmute(t)`, and has the
+ // advantage of not requiring `unsafe`.
+ match t {
+ Some(t) => by_mut(t),
+ None => raw_arg(null_mut()),
+ }
+}
+
+/// Convert an optional immutable reference into a `usize` for passing to a
+/// syscall.
+#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+#[inline]
+pub(super) fn opt_ref<T: Sized, Num: ArgNumber>(t: Option<&T>) -> ArgReg<'_, Num> {
+ // This optimizes into the equivalent of `transmute(t)`, and has the
+ // advantage of not requiring `unsafe`.
+ match t {
+ Some(t) => by_ref(t),
+ None => raw_arg(null_mut()),
+ }
+}
+
+/// Convert a `c_int` into an `ArgReg`.
+///
+/// Be sure to use `raw_fd` to pass `RawFd` values.
+#[inline]
+pub(super) fn c_int<'a, Num: ArgNumber>(i: c::c_int) -> ArgReg<'a, Num> {
+ pass_usize(i as usize)
+}
+
+/// Convert a `c_uint` into an `ArgReg`.
+#[inline]
+pub(super) fn c_uint<'a, Num: ArgNumber>(i: c::c_uint) -> ArgReg<'a, Num> {
+ pass_usize(i as usize)
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline]
+pub(super) fn loff_t<'a, Num: ArgNumber>(i: __kernel_loff_t) -> ArgReg<'a, Num> {
+ pass_usize(i as usize)
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline]
+pub(super) fn loff_t_from_u64<'a, Num: ArgNumber>(i: u64) -> ArgReg<'a, Num> {
+ // `loff_t` is signed, but syscalls which expect `loff_t` return `EINVAL`
+ // if it's outside the signed `i64` range, so we can silently cast.
+ pass_usize(i as usize)
+}
+
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+impl<'a, Num: ArgNumber> From<ClockId> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(i: ClockId) -> Self {
+ pass_usize(i as __kernel_clockid_t as usize)
+ }
+}
+
+#[cfg(feature = "time")]
+impl<'a, Num: ArgNumber> From<TimerfdClockId> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(i: TimerfdClockId) -> Self {
+ pass_usize(i as __kernel_clockid_t as usize)
+ }
+}
+
+#[cfg(feature = "net")]
+#[inline]
+pub(super) fn socklen_t<'a, Num: ArgNumber>(i: socklen_t) -> ArgReg<'a, Num> {
+ pass_usize(i as usize)
+}
+
+#[cfg(any(
+ feature = "fs",
+ all(
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+ )
+))]
+pub(crate) mod fs {
+ use super::*;
+ use crate::fs::{FileType, Mode, OFlags};
+ #[cfg(target_pointer_width = "32")]
+ use linux_raw_sys::general::O_LARGEFILE;
+
+ impl<'a, Num: ArgNumber> From<Mode> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(mode: Mode) -> Self {
+ pass_usize(mode.bits() as usize)
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<(Mode, FileType)> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(pair: (Mode, FileType)) -> Self {
+ pass_usize(pair.0.as_raw_mode() as usize | pair.1.as_raw_mode() as usize)
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::AtFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::AtFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::XattrFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::XattrFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::inotify::CreateFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::inotify::CreateFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::inotify::WatchFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::inotify::WatchFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::MemfdFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::MemfdFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::RenameFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::RenameFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::StatxFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::StatxFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ #[inline]
+ fn oflags_bits(oflags: OFlags) -> c::c_uint {
+ let mut bits = oflags.bits();
+ // Add `O_LARGEFILE`, unless `O_PATH` is set, as Linux returns `EINVAL`
+ // when both are set.
+ if !oflags.contains(OFlags::PATH) {
+ bits |= O_LARGEFILE;
+ }
+ bits
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[inline]
+ const fn oflags_bits(oflags: OFlags) -> c::c_uint {
+ oflags.bits()
+ }
+
+ impl<'a, Num: ArgNumber> From<OFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(oflags: OFlags) -> Self {
+ pass_usize(oflags_bits(oflags) as usize)
+ }
+ }
+
+ /// Convert an `OFlags` into a `u64` for use in the `open_how` struct.
+ #[inline]
+ pub(crate) fn oflags_for_open_how(oflags: OFlags) -> u64 {
+ u64::from(oflags_bits(oflags))
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::FallocateFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::FallocateFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::Advice> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(advice: crate::fs::Advice) -> Self {
+ c_uint(advice as c::c_uint)
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::SealFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::fs::SealFlags) -> Self {
+ c_uint(flags.bits())
+ }
+ }
+
+ impl<'a, Num: ArgNumber> From<crate::fs::Access> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(access: crate::fs::Access) -> Self {
+ c_uint(access.bits())
+ }
+ }
+}
+
+#[cfg(any(feature = "fs", feature = "mount"))]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::MountFlagsArg> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::MountFlagsArg) -> Self {
+ c_uint(flags.0)
+ }
+}
+
+// When the deprecated "fs" aliases are removed, we can remove the "fs"
+// here too.
+#[cfg(any(feature = "fs", feature = "mount"))]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::UnmountFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::UnmountFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::mount::FsConfigCmd> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(cmd: crate::mount::FsConfigCmd) -> Self {
+ c_uint(cmd as c::c_uint)
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::FsOpenFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::FsOpenFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::FsMountFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::FsMountFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::MountAttrFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::MountAttrFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::OpenTreeFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::OpenTreeFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::FsPickFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::FsPickFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mount")]
+impl<'a, Num: ArgNumber> From<crate::backend::mount::types::MoveMountFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mount::types::MoveMountFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+impl<'a, Num: ArgNumber> From<crate::io::FdFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::io::FdFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "pipe")]
+impl<'a, Num: ArgNumber> From<crate::pipe::PipeFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::pipe::PipeFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "pipe")]
+impl<'a, Num: ArgNumber> From<crate::pipe::SpliceFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::pipe::SpliceFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+impl<'a, Num: ArgNumber> From<crate::io::DupFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::io::DupFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+impl<'a, Num: ArgNumber> From<crate::io::ReadWriteFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::io::ReadWriteFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "process")]
+impl<'a, Num: ArgNumber> From<crate::process::PidfdFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::process::PidfdFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "pty")]
+impl<'a, Num: ArgNumber> From<crate::pty::OpenptFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::pty::OpenptFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "thread")]
+impl<'a, Num: ArgNumber> From<crate::thread::UnshareFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::thread::UnshareFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "event")]
+impl<'a, Num: ArgNumber> From<crate::event::EventfdFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::event::EventfdFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "event")]
+impl<'a, Num: ArgNumber> From<crate::event::epoll::CreateFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::event::epoll::CreateFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::ProtFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::ProtFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::MsyncFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::MsyncFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::MremapFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::MremapFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::MlockFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::MlockFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::MlockAllFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::MlockAllFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::MapFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::MapFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::MprotectFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::MprotectFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "mm")]
+impl<'a, Num: ArgNumber> From<crate::backend::mm::types::UserfaultfdFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::backend::mm::types::UserfaultfdFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "process")]
+impl<'a, Num: ArgNumber> From<crate::backend::process::types::MembarrierCommand>
+ for ArgReg<'a, Num>
+{
+ #[inline]
+ fn from(cmd: crate::backend::process::types::MembarrierCommand) -> Self {
+ c_uint(cmd as u32)
+ }
+}
+
+#[cfg(feature = "process")]
+impl<'a, Num: ArgNumber> From<crate::process::Cpuid> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(cpuid: crate::process::Cpuid) -> Self {
+ c_uint(cpuid.as_raw())
+ }
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline]
+pub(super) fn dev_t<'a, Num: ArgNumber>(dev: u64) -> ArgReg<'a, Num> {
+ pass_usize(dev as usize)
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline]
+pub(super) fn dev_t<'a, Num: ArgNumber>(dev: u64) -> io::Result<ArgReg<'a, Num>> {
+ Ok(pass_usize(dev.try_into().map_err(|_err| io::Errno::INVAL)?))
+}
+
+/// Convert a `Resource` into a syscall argument.
+#[cfg(feature = "process")]
+impl<'a, Num: ArgNumber> From<Resource> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(resource: Resource) -> Self {
+ c_uint(resource as c::c_uint)
+ }
+}
+
+#[cfg(any(feature = "process", feature = "runtime", feature = "termios"))]
+impl<'a, Num: ArgNumber> From<Pid> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(pid: Pid) -> Self {
+ pass_usize(pid.as_raw_nonzero().get() as usize)
+ }
+}
+
+#[cfg(feature = "process")]
+#[inline]
+pub(super) fn negative_pid<'a, Num: ArgNumber>(pid: Pid) -> ArgReg<'a, Num> {
+ pass_usize(pid.as_raw_nonzero().get().wrapping_neg() as usize)
+}
+
+#[cfg(any(feature = "process", feature = "runtime"))]
+impl<'a, Num: ArgNumber> From<Signal> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(sig: Signal) -> Self {
+ pass_usize(sig as usize)
+ }
+}
+
+#[cfg(feature = "io_uring")]
+impl<'a, Num: ArgNumber> From<crate::io_uring::IoringEnterFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::io_uring::IoringEnterFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "time")]
+impl<'a, Num: ArgNumber> From<crate::time::TimerfdFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::time::TimerfdFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "time")]
+impl<'a, Num: ArgNumber> From<crate::time::TimerfdTimerFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::time::TimerfdTimerFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "rand")]
+impl<'a, Num: ArgNumber> From<crate::rand::GetRandomFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::rand::GetRandomFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<crate::net::RecvFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::net::RecvFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<crate::net::SendFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::net::SendFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<crate::net::SocketFlags> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::net::SocketFlags) -> Self {
+ c_uint(flags.bits())
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<crate::net::AddressFamily> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(family: crate::net::AddressFamily) -> Self {
+ c_uint(family.0.into())
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<(crate::net::SocketType, crate::net::SocketFlags)>
+ for ArgReg<'a, Num>
+{
+ #[inline]
+ fn from(pair: (crate::net::SocketType, crate::net::SocketFlags)) -> Self {
+ c_uint(pair.0 .0 | pair.1.bits())
+ }
+}
+
+#[cfg(feature = "thread")]
+impl<'a, Num: ArgNumber> From<(crate::thread::FutexOperation, crate::thread::FutexFlags)>
+ for ArgReg<'a, Num>
+{
+ #[inline]
+ fn from(pair: (crate::thread::FutexOperation, crate::thread::FutexFlags)) -> Self {
+ c_uint(pair.0 as u32 | pair.1.bits())
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<crate::net::SocketType> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(type_: crate::net::SocketType) -> Self {
+ c_uint(type_.0)
+ }
+}
+
+#[cfg(feature = "net")]
+impl<'a, Num: ArgNumber> From<Option<crate::net::Protocol>> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(protocol: Option<crate::net::Protocol>) -> Self {
+ c_uint(match protocol {
+ Some(p) => p.0.get(),
+ None => 0,
+ })
+ }
+}
+
+impl<'a, Num: ArgNumber, T> From<&'a mut MaybeUninit<T>> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(t: &'a mut MaybeUninit<T>) -> Self {
+ raw_arg(t.as_mut_ptr().cast())
+ }
+}
+
+impl<'a, Num: ArgNumber, T> From<&'a mut [MaybeUninit<T>]> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(t: &'a mut [MaybeUninit<T>]) -> Self {
+ raw_arg(t.as_mut_ptr().cast())
+ }
+}
+
+#[cfg(any(feature = "process", feature = "thread"))]
+impl<'a, Num: ArgNumber> From<crate::ugid::Uid> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(t: crate::ugid::Uid) -> Self {
+ c_uint(t.as_raw())
+ }
+}
+
+#[cfg(any(feature = "process", feature = "thread"))]
+impl<'a, Num: ArgNumber> From<crate::ugid::Gid> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(t: crate::ugid::Gid) -> Self {
+ c_uint(t.as_raw())
+ }
+}
+
+#[cfg(feature = "runtime")]
+impl<'a, Num: ArgNumber> From<crate::runtime::How> for ArgReg<'a, Num> {
+ #[inline]
+ fn from(flags: crate::runtime::How) -> Self {
+ c_uint(flags as u32)
+ }
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns `()` on
+/// success.
+///
+/// # Safety
+///
+/// The caller must ensure that this is the return value of a syscall which
+/// just returns 0 on success.
+#[inline]
+pub(super) unsafe fn ret(raw: RetReg<R0>) -> io::Result<()> {
+ try_decode_void(raw)
+}
+
+/// Convert a `usize` returned from a syscall that doesn't return on success.
+///
+/// # Safety
+///
+/// The caller must ensure that this is the return value of a syscall which
+/// doesn't return on success.
+#[cfg(any(feature = "event", feature = "runtime"))]
+#[inline]
+pub(super) unsafe fn ret_error(raw: RetReg<R0>) -> io::Errno {
+ try_decode_error(raw)
+}
+
+/// Convert a `usize` returned from a syscall that effectively always returns
+/// `()`.
+///
+/// # Safety
+///
+/// The caller must ensure that this is the return value of a syscall which
+/// always returns `()`.
+#[inline]
+pub(super) unsafe fn ret_infallible(raw: RetReg<R0>) {
+ #[cfg(debug_assertions)]
+ {
+ try_decode_void(raw).unwrap()
+ }
+ #[cfg(not(debug_assertions))]
+ drop(raw);
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns a
+/// `c_int` on success.
+#[inline]
+pub(super) fn ret_c_int(raw: RetReg<R0>) -> io::Result<c::c_int> {
+ try_decode_c_int(raw)
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns a
+/// `c_uint` on success.
+#[inline]
+pub(super) fn ret_c_uint(raw: RetReg<R0>) -> io::Result<c::c_uint> {
+ try_decode_c_uint(raw)
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns a `u64`
+/// on success.
+#[cfg(target_pointer_width = "64")]
+#[inline]
+pub(super) fn ret_u64(raw: RetReg<R0>) -> io::Result<u64> {
+ try_decode_u64(raw)
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns a
+/// `usize` on success.
+#[inline]
+pub(super) fn ret_usize(raw: RetReg<R0>) -> io::Result<usize> {
+ try_decode_usize(raw)
+}
+
+/// Convert a `usize` returned from a syscall that effectively always
+/// returns a `usize`.
+///
+/// # Safety
+///
+/// This function must only be used with return values from infallible
+/// syscalls.
+#[inline]
+pub(super) unsafe fn ret_usize_infallible(raw: RetReg<R0>) -> usize {
+ #[cfg(debug_assertions)]
+ {
+ try_decode_usize(raw).unwrap()
+ }
+ #[cfg(not(debug_assertions))]
+ {
+ decode_usize_infallible(raw)
+ }
+}
+
+/// Convert a `c_int` returned from a syscall that effectively always
+/// returns a `c_int`.
+///
+/// # Safety
+///
+/// This function must only be used with return values from infallible
+/// syscalls.
+#[inline]
+pub(super) unsafe fn ret_c_int_infallible(raw: RetReg<R0>) -> c::c_int {
+ #[cfg(debug_assertions)]
+ {
+ try_decode_c_int(raw).unwrap()
+ }
+ #[cfg(not(debug_assertions))]
+ {
+ decode_c_int_infallible(raw)
+ }
+}
+
+/// Convert a `c_uint` returned from a syscall that effectively always
+/// returns a `c_uint`.
+///
+/// # Safety
+///
+/// This function must only be used with return values from infallible
+/// syscalls.
+#[inline]
+pub(super) unsafe fn ret_c_uint_infallible(raw: RetReg<R0>) -> c::c_uint {
+ #[cfg(debug_assertions)]
+ {
+ try_decode_c_uint(raw).unwrap()
+ }
+ #[cfg(not(debug_assertions))]
+ {
+ decode_c_uint_infallible(raw)
+ }
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns an
+/// `OwnedFd` on success.
+///
+/// # Safety
+///
+/// The caller must ensure that this is the return value of a syscall which
+/// returns an owned file descriptor.
+#[inline]
+pub(super) unsafe fn ret_owned_fd(raw: RetReg<R0>) -> io::Result<OwnedFd> {
+ let raw_fd = try_decode_raw_fd(raw)?;
+ Ok(crate::backend::fd::OwnedFd::from_raw_fd(raw_fd))
+}
+
+/// Convert the return value of `dup2` and `dup3`.
+///
+/// When these functions succeed, they return the same value as their second
+/// argument, so we don't construct a new `OwnedFd`.
+///
+/// # Safety
+///
+/// The caller must ensure that this is the return value of a syscall which
+/// returns a file descriptor.
+#[inline]
+pub(super) unsafe fn ret_discarded_fd(raw: RetReg<R0>) -> io::Result<()> {
+ let _raw_fd = try_decode_raw_fd(raw)?;
+ Ok(())
+}
+
+/// Convert a `usize` returned from a syscall that effectively returns a
+/// `*mut c_void` on success.
+#[inline]
+pub(super) fn ret_void_star(raw: RetReg<R0>) -> io::Result<*mut c::c_void> {
+ try_decode_void_star(raw)
+}
diff --git a/src/backend/linux_raw/event/epoll.rs b/src/backend/linux_raw/event/epoll.rs
new file mode 100644
index 0000000..3d5787b
--- /dev/null
+++ b/src/backend/linux_raw/event/epoll.rs
@@ -0,0 +1,470 @@
+//! 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() {}
+//! ```
+
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use crate::backend::event::syscalls;
+use crate::fd::{AsFd, AsRawFd, OwnedFd};
+use crate::io;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+use bitflags::bitflags;
+use core::ffi::c_void;
+use core::hash::{Hash, Hasher};
+use core::slice;
+
+bitflags! {
+ /// `EPOLL_*` for use with [`new`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CreateFlags: c::c_uint {
+ /// `EPOLL_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::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 = linux_raw_sys::general::EPOLLIN as u32;
+
+ /// `EPOLLOUT`
+ const OUT = linux_raw_sys::general::EPOLLOUT as u32;
+
+ /// `EPOLLPRI`
+ const PRI = linux_raw_sys::general::EPOLLPRI as u32;
+
+ /// `EPOLLERR`
+ const ERR = linux_raw_sys::general::EPOLLERR as u32;
+
+ /// `EPOLLHUP`
+ const HUP = linux_raw_sys::general::EPOLLHUP as u32;
+
+ /// `EPOLLRDNORM`
+ const RDNORM = linux_raw_sys::general::EPOLLRDNORM as u32;
+
+ /// `EPOLLRDBAND`
+ const RDBAND = linux_raw_sys::general::EPOLLRDBAND as u32;
+
+ /// `EPOLLWRNORM`
+ const WRNORM = linux_raw_sys::general::EPOLLWRNORM as u32;
+
+ /// `EPOLLWRBAND`
+ const WRBAND = linux_raw_sys::general::EPOLLWRBAND as u32;
+
+ /// `EPOLLMSG`
+ const MSG = linux_raw_sys::general::EPOLLMSG as u32;
+
+ /// `EPOLLRDHUP`
+ const RDHUP = linux_raw_sys::general::EPOLLRDHUP as u32;
+
+ /// `EPOLLET`
+ const ET = linux_raw_sys::general::EPOLLET as u32;
+
+ /// `EPOLLONESHOT`
+ const ONESHOT = linux_raw_sys::general::EPOLLONESHOT as u32;
+
+ /// `EPOLLWAKEUP`
+ const WAKEUP = linux_raw_sys::general::EPOLLWAKEUP as u32;
+
+ /// `EPOLLEXCLUSIVE`
+ const EXCLUSIVE = linux_raw_sys::general::EPOLLEXCLUSIVE as u32;
+
+ /// <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> {
+ syscalls::epoll_create(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")]
+#[inline]
+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.
+ unsafe {
+ syscalls::epoll_add(
+ epoll.as_fd(),
+ source.as_fd().as_raw_fd(),
+ &Event {
+ flags: event_flags,
+ data,
+ },
+ )
+ }
+}
+
+/// `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")]
+#[inline]
+pub fn modify(
+ 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.
+ unsafe {
+ let raw_fd = source.as_fd().as_raw_fd();
+ syscalls::epoll_mod(
+ epoll.as_fd(),
+ raw_fd,
+ &Event {
+ flags: event_flags,
+ data,
+ },
+ )
+ }
+}
+
+/// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in a
+/// given epoll object.
+#[doc(alias = "epoll_ctl")]
+#[inline]
+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();
+ syscalls::epoll_del(epoll.as_fd(), raw_fd)
+ }
+}
+
+/// `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")))]
+#[inline]
+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 = syscalls::epoll_wait(
+ epoll.as_fd(),
+ event_list.events[..].as_mut_ptr().cast(),
+ event_list.events.capacity(),
+ timeout,
+ )?;
+ event_list.events.set_len(nfds);
+ }
+
+ 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(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,
+}
+
+/// 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, data);
+}
diff --git a/src/backend/linux_raw/event/mod.rs b/src/backend/linux_raw/event/mod.rs
new file mode 100644
index 0000000..605de25
--- /dev/null
+++ b/src/backend/linux_raw/event/mod.rs
@@ -0,0 +1,4 @@
+pub mod epoll;
+pub(crate) mod poll_fd;
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/event/poll_fd.rs b/src/backend/linux_raw/event/poll_fd.rs
new file mode 100644
index 0000000..9de43f2
--- /dev/null
+++ b/src/backend/linux_raw/event/poll_fd.rs
@@ -0,0 +1,98 @@
+use crate::fd::{AsFd, BorrowedFd};
+use bitflags::bitflags;
+
+bitflags! {
+ /// `POLL*` flags for use with [`poll`].
+ ///
+ /// [`poll`]: crate::event::poll
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PollFlags: u16 {
+ /// `POLLIN`
+ const IN = linux_raw_sys::general::POLLIN as u16;
+ /// `POLLPRI`
+ const PRI = linux_raw_sys::general::POLLPRI as u16;
+ /// `POLLOUT`
+ const OUT = linux_raw_sys::general::POLLOUT as u16;
+ /// `POLLRDNORM`
+ const RDNORM = linux_raw_sys::general::POLLRDNORM as u16;
+ /// `POLLWRNORM`
+ const WRNORM = linux_raw_sys::general::POLLWRNORM as u16;
+ /// `POLLRDBAND`
+ const RDBAND = linux_raw_sys::general::POLLRDBAND as u16;
+ /// `POLLWRBAND`
+ const WRBAND = linux_raw_sys::general::POLLWRBAND as u16;
+ /// `POLLERR`
+ const ERR = linux_raw_sys::general::POLLERR as u16;
+ /// `POLLHUP`
+ const HUP = linux_raw_sys::general::POLLHUP as u16;
+ /// `POLLNVAL`
+ const NVAL = linux_raw_sys::general::POLLNVAL as u16;
+ /// `POLLRDHUP`
+ const RDHUP = linux_raw_sys::general::POLLRDHUP as u16;
+
+ /// <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")]
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct PollFd<'fd> {
+ pub(crate) fd: BorrowedFd<'fd>,
+ pub(crate) events: u16,
+ pub(crate) revents: u16,
+}
+
+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.fd = fd.as_fd();
+ }
+
+ /// Clears the ready events.
+ #[inline]
+ pub fn clear_revents(&mut self) {
+ self.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 {
+ fd,
+ events: events.bits(),
+ revents: 0,
+ }
+ }
+
+ /// 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.revents).unwrap()
+ }
+}
+
+impl<'fd> AsFd for PollFd<'fd> {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.fd.as_fd()
+ }
+}
diff --git a/src/backend/linux_raw/event/syscalls.rs b/src/backend/linux_raw/event/syscalls.rs
new file mode 100644
index 0000000..0ae7753
--- /dev/null
+++ b/src/backend/linux_raw/event/syscalls.rs
@@ -0,0 +1,152 @@
+//! linux_raw syscalls supporting `rustix::event`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+#[cfg(feature = "alloc")]
+use crate::backend::conv::pass_usize;
+use crate::backend::conv::{
+ by_ref, c_int, c_uint, raw_fd, ret, ret_error, ret_owned_fd, ret_usize, slice_mut, zero,
+};
+use crate::event::{epoll, EventfdFlags, PollFd};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+use linux_raw_sys::general::{EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD};
+#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+use {
+ crate::backend::conv::{opt_ref, size_of},
+ linux_raw_sys::general::{__kernel_timespec, kernel_sigset_t},
+};
+
+#[inline]
+pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
+ let (fds_addr_mut, fds_len) = slice_mut(fds);
+
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ unsafe {
+ let timeout = if timeout >= 0 {
+ Some(__kernel_timespec {
+ tv_sec: (timeout as i64) / 1000,
+ tv_nsec: (timeout as i64) % 1000 * 1_000_000,
+ })
+ } else {
+ None
+ };
+ ret_usize(syscall!(
+ __NR_ppoll,
+ fds_addr_mut,
+ fds_len,
+ opt_ref(timeout.as_ref()),
+ zero(),
+ size_of::<kernel_sigset_t, _>()
+ ))
+ }
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ ret_usize(syscall!(__NR_poll, fds_addr_mut, fds_len, c_int(timeout)))
+ }
+}
+
+#[inline]
+pub(crate) fn epoll_create(flags: epoll::CreateFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_epoll_create1, flags)) }
+}
+
+#[inline]
+pub(crate) unsafe fn epoll_add(
+ epfd: BorrowedFd<'_>,
+ fd: c::c_int,
+ event: &epoll::Event,
+) -> io::Result<()> {
+ ret(syscall_readonly!(
+ __NR_epoll_ctl,
+ epfd,
+ c_uint(EPOLL_CTL_ADD),
+ raw_fd(fd),
+ by_ref(event)
+ ))
+}
+
+#[inline]
+pub(crate) unsafe fn epoll_mod(
+ epfd: BorrowedFd<'_>,
+ fd: c::c_int,
+ event: &epoll::Event,
+) -> io::Result<()> {
+ ret(syscall_readonly!(
+ __NR_epoll_ctl,
+ epfd,
+ c_uint(EPOLL_CTL_MOD),
+ raw_fd(fd),
+ by_ref(event)
+ ))
+}
+
+#[inline]
+pub(crate) unsafe fn epoll_del(epfd: BorrowedFd<'_>, fd: c::c_int) -> io::Result<()> {
+ ret(syscall_readonly!(
+ __NR_epoll_ctl,
+ epfd,
+ c_uint(EPOLL_CTL_DEL),
+ raw_fd(fd),
+ zero()
+ ))
+}
+
+#[cfg(feature = "alloc")]
+#[inline]
+pub(crate) fn epoll_wait(
+ epfd: BorrowedFd<'_>,
+ events: *mut epoll::Event,
+ num_events: usize,
+ timeout: c::c_int,
+) -> io::Result<usize> {
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ ret_usize(syscall!(
+ __NR_epoll_wait,
+ epfd,
+ events,
+ pass_usize(num_events),
+ c_int(timeout)
+ ))
+ }
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ unsafe {
+ ret_usize(syscall!(
+ __NR_epoll_pwait,
+ epfd,
+ events,
+ pass_usize(num_events),
+ c_int(timeout),
+ zero()
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_eventfd2, c_uint(initval), flags)) }
+}
+
+#[inline]
+pub(crate) fn pause() {
+ unsafe {
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ let error = ret_error(syscall_readonly!(
+ __NR_ppoll,
+ zero(),
+ zero(),
+ zero(),
+ zero()
+ ));
+
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ let error = ret_error(syscall_readonly!(__NR_pause));
+
+ debug_assert_eq!(error, io::Errno::INTR);
+ }
+}
diff --git a/src/backend/linux_raw/event/types.rs b/src/backend/linux_raw/event/types.rs
new file mode 100644
index 0000000..01611f6
--- /dev/null
+++ b/src/backend/linux_raw/event/types.rs
@@ -0,0 +1,21 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `EFD_*` flags for use with [`eventfd`].
+ ///
+ /// [`eventfd`]: crate::event::eventfd
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct EventfdFlags: c::c_uint {
+ /// `EFD_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::EFD_CLOEXEC;
+ /// `EFD_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::EFD_NONBLOCK;
+ /// `EFD_SEMAPHORE`
+ const SEMAPHORE = linux_raw_sys::general::EFD_SEMAPHORE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/linux_raw/fs/dir.rs b/src/backend/linux_raw/fs/dir.rs
new file mode 100644
index 0000000..a8c9a55
--- /dev/null
+++ b/src/backend/linux_raw/fs/dir.rs
@@ -0,0 +1,315 @@
+use crate::fd::{AsFd, BorrowedFd, OwnedFd};
+use crate::ffi::{CStr, CString};
+use crate::fs::{
+ fcntl_getfl, fstat, fstatfs, fstatvfs, openat, FileType, Mode, OFlags, Stat, StatFs, StatVfs,
+};
+use crate::io;
+#[cfg(feature = "process")]
+use crate::process::fchdir;
+use crate::utils::as_ptr;
+use alloc::borrow::ToOwned;
+use alloc::vec::Vec;
+use core::fmt;
+use core::mem::size_of;
+use linux_raw_sys::general::{linux_dirent64, SEEK_SET};
+
+/// `DIR*`
+pub struct Dir {
+ /// The `OwnedFd` that we read directory entries from.
+ fd: OwnedFd,
+
+ /// Have we seen any errors in this iteration?
+ any_errors: bool,
+
+ /// Should we rewind the stream on the next iteration?
+ rewind: bool,
+
+ /// The buffer for `linux_dirent64` entries.
+ buf: Vec<u8>,
+
+ /// Where we are in the buffer.
+ pos: usize,
+}
+
+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> {
+ Ok(Self {
+ fd,
+ any_errors: false,
+ rewind: false,
+ buf: Vec::new(),
+ pos: 0,
+ })
+ }
+
+ /// 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]
+ fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
+ let flags = fcntl_getfl(fd)?;
+ let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?;
+
+ Ok(Self {
+ fd: fd_for_dir,
+ any_errors: false,
+ rewind: false,
+ buf: Vec::new(),
+ pos: 0,
+ })
+ }
+
+ /// `rewinddir(self)`
+ #[inline]
+ pub fn rewind(&mut self) {
+ self.any_errors = false;
+ self.rewind = true;
+ self.pos = self.buf.len();
+ }
+
+ /// `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;
+ }
+
+ // If a rewind was requested, seek to the beginning.
+ if self.rewind {
+ self.rewind = false;
+ match io::retry_on_intr(|| {
+ crate::backend::fs::syscalls::_seek(self.fd.as_fd(), 0, SEEK_SET)
+ }) {
+ Ok(_) => (),
+ Err(err) => {
+ self.any_errors = true;
+ return Some(Err(err));
+ }
+ }
+ }
+
+ // Compute linux_dirent64 field offsets.
+ let z = linux_dirent64 {
+ d_ino: 0_u64,
+ d_off: 0_i64,
+ d_type: 0_u8,
+ d_reclen: 0_u16,
+ d_name: Default::default(),
+ };
+ let base = as_ptr(&z) as usize;
+ let offsetof_d_reclen = (as_ptr(&z.d_reclen) as usize) - base;
+ let offsetof_d_name = (as_ptr(&z.d_name) as usize) - base;
+ let offsetof_d_ino = (as_ptr(&z.d_ino) as usize) - base;
+ let offsetof_d_type = (as_ptr(&z.d_type) as usize) - base;
+
+ // Test if we need more entries, and if so, read more.
+ if self.buf.len() - self.pos < size_of::<linux_dirent64>() {
+ match self.read_more()? {
+ Ok(()) => (),
+ Err(err) => return Some(Err(err)),
+ }
+ }
+
+ // We successfully read an entry. Extract the fields.
+ let pos = self.pos;
+
+ // Do an unaligned u16 load.
+ let d_reclen = u16::from_ne_bytes([
+ self.buf[pos + offsetof_d_reclen],
+ self.buf[pos + offsetof_d_reclen + 1],
+ ]);
+ assert!(self.buf.len() - pos >= d_reclen as usize);
+ self.pos += d_reclen as usize;
+
+ // Read the NUL-terminated name from the `d_name` field. Without
+ // `unsafe`, we need to scan for the NUL twice: once to obtain a size
+ // for the slice, and then once within `CStr::from_bytes_with_nul`.
+ let name_start = pos + offsetof_d_name;
+ let name_len = self.buf[name_start..]
+ .iter()
+ .position(|x| *x == b'\0')
+ .unwrap();
+ let name = CStr::from_bytes_with_nul(&self.buf[name_start..][..=name_len]).unwrap();
+ let name = name.to_owned();
+ assert!(name.as_bytes().len() <= self.buf.len() - name_start);
+
+ // Do an unaligned u64 load.
+ let d_ino = u64::from_ne_bytes([
+ self.buf[pos + offsetof_d_ino],
+ self.buf[pos + offsetof_d_ino + 1],
+ self.buf[pos + offsetof_d_ino + 2],
+ self.buf[pos + offsetof_d_ino + 3],
+ self.buf[pos + offsetof_d_ino + 4],
+ self.buf[pos + offsetof_d_ino + 5],
+ self.buf[pos + offsetof_d_ino + 6],
+ self.buf[pos + offsetof_d_ino + 7],
+ ]);
+
+ let d_type = self.buf[pos + offsetof_d_type];
+
+ // Check that our types correspond to the `linux_dirent64` types.
+ let _ = linux_dirent64 {
+ d_ino,
+ d_off: 0,
+ d_type,
+ d_reclen,
+ d_name: Default::default(),
+ };
+
+ Some(Ok(DirEntry {
+ d_ino,
+ d_type,
+ name,
+ }))
+ }
+
+ #[must_use]
+ fn read_more(&mut self) -> Option<io::Result<()>> {
+ // The first few times we're called, we allocate a relatively small
+ // buffer, because many directories are small. If we're called more,
+ // use progressively larger allocations, up to a fixed maximum.
+ //
+ // The specific sizes and policy here have not been tuned in detail yet
+ // and may need to be adjusted. In doing so, we should be careful to
+ // avoid unbounded buffer growth. This buffer only exists to share the
+ // cost of a `getdents` call over many entries, so if it gets too big,
+ // cache and heap usage will outweigh the benefit. And ultimately,
+ // directories can contain more entries than we can allocate contiguous
+ // memory for, so we'll always need to cap the size at some point.
+ if self.buf.len() < 1024 * size_of::<linux_dirent64>() {
+ self.buf.reserve(32 * size_of::<linux_dirent64>());
+ }
+ self.buf.resize(self.buf.capacity(), 0);
+ let nread = match io::retry_on_intr(|| {
+ crate::backend::fs::syscalls::getdents(self.fd.as_fd(), &mut self.buf)
+ }) {
+ Ok(nread) => nread,
+ Err(io::Errno::NOENT) => {
+ self.any_errors = true;
+ return None;
+ }
+ Err(err) => {
+ self.any_errors = true;
+ return Some(Err(err));
+ }
+ };
+ self.buf.resize(nread, 0);
+ self.pos = 0;
+ if nread == 0 {
+ None
+ } else {
+ Some(Ok(()))
+ }
+ }
+
+ /// `fstat(self)`
+ #[inline]
+ pub fn stat(&self) -> io::Result<Stat> {
+ fstat(&self.fd)
+ }
+
+ /// `fstatfs(self)`
+ #[inline]
+ pub fn statfs(&self) -> io::Result<StatFs> {
+ fstatfs(&self.fd)
+ }
+
+ /// `fstatvfs(self)`
+ #[inline]
+ pub fn statvfs(&self) -> io::Result<StatVfs> {
+ fstatvfs(&self.fd)
+ }
+
+ /// `fchdir(self)`
+ #[cfg(feature = "process")]
+ #[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
+ #[inline]
+ pub fn chdir(&self) -> io::Result<()> {
+ fchdir(&self.fd)
+ }
+}
+
+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 {
+ f.debug_struct("Dir").field("fd", &self.fd).finish()
+ }
+}
+
+/// `struct dirent`
+#[derive(Debug)]
+pub struct DirEntry {
+ d_ino: u64,
+ d_type: u8,
+ 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.
+ #[inline]
+ pub fn file_type(&self) -> FileType {
+ FileType::from_dirent_d_type(self.d_type)
+ }
+
+ /// Return the inode number of this directory entry.
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ self.d_ino
+ }
+}
+
+#[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 `getdents64` to fail.
+ crate::io::dup2(&file_fd, &mut dir.fd).unwrap();
+
+ assert!(matches!(dir.next(), Some(Err(_))));
+ assert!(dir.next().is_none());
+}
diff --git a/src/backend/linux_raw/fs/inotify.rs b/src/backend/linux_raw/fs/inotify.rs
new file mode 100644
index 0000000..851335b
--- /dev/null
+++ b/src/backend/linux_raw/fs/inotify.rs
@@ -0,0 +1,118 @@
+//! inotify support for working with inotifies
+
+use crate::backend::c;
+use crate::backend::fs::syscalls;
+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: c::c_uint {
+ /// `IN_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::IN_CLOEXEC;
+ /// `IN_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::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: c::c_uint {
+ /// `IN_ACCESS`
+ const ACCESS = linux_raw_sys::general::IN_ACCESS;
+ /// `IN_ATTRIB`
+ const ATTRIB = linux_raw_sys::general::IN_ATTRIB;
+ /// `IN_CLOSE_NOWRITE`
+ const CLOSE_NOWRITE = linux_raw_sys::general::IN_CLOSE_NOWRITE;
+ /// `IN_CLOSE_WRITE`
+ const CLOSE_WRITE = linux_raw_sys::general::IN_CLOSE_WRITE;
+ /// `IN_CREATE`
+ const CREATE = linux_raw_sys::general::IN_CREATE;
+ /// `IN_DELETE`
+ const DELETE = linux_raw_sys::general::IN_DELETE;
+ /// `IN_DELETE_SELF`
+ const DELETE_SELF = linux_raw_sys::general::IN_DELETE_SELF;
+ /// `IN_MODIFY`
+ const MODIFY = linux_raw_sys::general::IN_MODIFY;
+ /// `IN_MOVE_SELF`
+ const MOVE_SELF = linux_raw_sys::general::IN_MOVE_SELF;
+ /// `IN_MOVED_FROM`
+ const MOVED_FROM = linux_raw_sys::general::IN_MOVED_FROM;
+ /// `IN_MOVED_TO`
+ const MOVED_TO = linux_raw_sys::general::IN_MOVED_TO;
+ /// `IN_OPEN`
+ const OPEN = linux_raw_sys::general::IN_OPEN;
+
+ /// `IN_CLOSE`
+ const CLOSE = linux_raw_sys::general::IN_CLOSE;
+ /// `IN_MOVE`
+ const MOVE = linux_raw_sys::general::IN_MOVE;
+ /// `IN_ALL_EVENTS`
+ const ALL_EVENTS = linux_raw_sys::general::IN_ALL_EVENTS;
+
+ /// `IN_DONT_FOLLOW`
+ const DONT_FOLLOW = linux_raw_sys::general::IN_DONT_FOLLOW;
+ /// `IN_EXCL_UNLINK`
+ const EXCL_UNLINK = linux_raw_sys::general::IN_EXCL_UNLINK;
+ /// `IN_MASK_ADD`
+ const MASK_ADD = linux_raw_sys::general::IN_MASK_ADD;
+ /// `IN_MASK_CREATE`
+ const MASK_CREATE = linux_raw_sys::general::IN_MASK_CREATE;
+ /// `IN_ONESHOT`
+ const ONESHOT = linux_raw_sys::general::IN_ONESHOT;
+ /// `IN_ONLYDIR`
+ const ONLYDIR = linux_raw_sys::general::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")]
+#[inline]
+pub fn inotify_init(flags: CreateFlags) -> io::Result<OwnedFd> {
+ syscalls::inotify_init1(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.
+#[inline]
+pub fn inotify_add_watch<P: crate::path::Arg>(
+ inot: BorrowedFd<'_>,
+ path: P,
+ flags: WatchFlags,
+) -> io::Result<i32> {
+ path.into_with_c_str(|path| syscalls::inotify_add_watch(inot, path, flags))
+}
+
+/// `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")]
+#[inline]
+pub fn inotify_remove_watch(inot: BorrowedFd<'_>, wd: i32) -> io::Result<()> {
+ syscalls::inotify_rm_watch(inot, wd)
+}
diff --git a/src/backend/linux_raw/fs/makedev.rs b/src/backend/linux_raw/fs/makedev.rs
new file mode 100644
index 0000000..284ba2f
--- /dev/null
+++ b/src/backend/linux_raw/fs/makedev.rs
@@ -0,0 +1,19 @@
+use crate::fs::Dev;
+
+#[inline]
+pub(crate) fn makedev(maj: u32, min: u32) -> Dev {
+ ((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)
+}
+
+#[inline]
+pub(crate) fn major(dev: Dev) -> u32 {
+ (((dev >> 31 >> 1) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff)) as u32
+}
+
+#[inline]
+pub(crate) fn minor(dev: Dev) -> u32 {
+ (((dev >> 12) & 0xffff_ff00) | (dev & 0x0000_00ff)) as u32
+}
diff --git a/src/backend/linux_raw/fs/mod.rs b/src/backend/linux_raw/fs/mod.rs
new file mode 100644
index 0000000..9f53c5d
--- /dev/null
+++ b/src/backend/linux_raw/fs/mod.rs
@@ -0,0 +1,13 @@
+#[cfg(feature = "alloc")]
+pub(crate) mod dir;
+pub mod inotify;
+pub(crate) mod makedev;
+pub(crate) mod syscalls;
+pub(crate) mod types;
+
+// TODO: Fix linux-raw-sys to define ioctl codes for sparc.
+#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))]
+pub(crate) const EXT4_IOC_RESIZE_FS: u32 = 0x8008_6610;
+
+#[cfg(not(any(target_arch = "sparc", target_arch = "sparc64")))]
+pub(crate) use linux_raw_sys::ioctl::EXT4_IOC_RESIZE_FS;
diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs
new file mode 100644
index 0000000..1265493
--- /dev/null
+++ b/src/backend/linux_raw/fs/syscalls.rs
@@ -0,0 +1,1660 @@
+//! linux_raw syscalls supporting `rustix::fs`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code)]
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+use crate::backend::conv::fs::oflags_for_open_how;
+#[cfg(any(
+ not(feature = "linux_4_11"),
+ target_arch = "aarch64",
+ target_arch = "riscv64",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+))]
+use crate::backend::conv::zero;
+use crate::backend::conv::{
+ by_ref, c_int, c_uint, dev_t, opt_mut, pass_usize, raw_fd, ret, ret_c_int, ret_c_uint,
+ ret_infallible, ret_owned_fd, ret_usize, size_of, slice, slice_mut,
+};
+#[cfg(target_pointer_width = "64")]
+use crate::backend::conv::{loff_t, loff_t_from_u64, ret_u64};
+#[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "riscv64",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_pointer_width = "32",
+))]
+use crate::fd::AsFd;
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::ffi::CStr;
+#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+use crate::fs::CWD;
+use crate::fs::{
+ inotify, Access, Advice, AtFlags, FallocateFlags, FileType, FlockOperation, Gid, MemfdFlags,
+ Mode, OFlags, RenameFlags, ResolveFlags, SealFlags, SeekFrom, Stat, StatFs, StatVfs,
+ StatVfsMountFlags, StatxFlags, Timestamps, Uid, XattrFlags,
+};
+use crate::io;
+use core::mem::MaybeUninit;
+#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+use linux_raw_sys::general::stat as linux_stat64;
+use linux_raw_sys::general::{
+ __kernel_fsid_t, open_how, statx, AT_EACCESS, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_NOFOLLOW,
+ F_ADD_SEALS, F_GETFL, F_GET_SEALS, F_SETFL, SEEK_CUR, SEEK_DATA, SEEK_END, SEEK_HOLE, SEEK_SET,
+ STATX__RESERVED,
+};
+#[cfg(target_pointer_width = "32")]
+use {
+ crate::backend::conv::{hi, lo, slice_just_addr},
+ linux_raw_sys::general::stat64 as linux_stat64,
+ linux_raw_sys::general::timespec as __kernel_old_timespec,
+};
+
+#[inline]
+pub(crate) fn open(path: &CStr, flags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
+ // Always enable support for large files.
+ let flags = flags | OFlags::from_bits_retain(c::O_LARGEFILE);
+
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ {
+ openat(CWD.as_fd(), path, flags, mode)
+ }
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(__NR_open, path, flags, mode))
+ }
+}
+
+#[inline]
+pub(crate) fn openat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: OFlags,
+ mode: Mode,
+) -> io::Result<OwnedFd> {
+ // Always enable support for large files.
+ let flags = flags | OFlags::from_bits_retain(c::O_LARGEFILE);
+
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_openat, dirfd, path, flags, mode)) }
+}
+
+#[inline]
+pub(crate) fn openat2(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ mut flags: OFlags,
+ mode: Mode,
+ resolve: ResolveFlags,
+) -> io::Result<OwnedFd> {
+ // Enable support for large files, but not with `O_PATH` because
+ // `openat2` doesn't like those flags together.
+ if !flags.contains(OFlags::PATH) {
+ flags |= OFlags::from_bits_retain(c::O_LARGEFILE);
+ }
+
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_openat2,
+ dirfd,
+ path,
+ by_ref(&open_how {
+ flags: oflags_for_open_how(flags),
+ mode: u64::from(mode.bits()),
+ resolve: resolve.bits(),
+ }),
+ size_of::<open_how, _>()
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn chmod(path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fchmodat,
+ raw_fd(AT_FDCWD),
+ path,
+ mode
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn chmodat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ mode: Mode,
+ flags: AtFlags,
+) -> io::Result<()> {
+ if flags == AtFlags::SYMLINK_NOFOLLOW {
+ return Err(io::Errno::OPNOTSUPP);
+ }
+ if !flags.is_empty() {
+ return Err(io::Errno::INVAL);
+ }
+ unsafe { ret(syscall_readonly!(__NR_fchmodat, dirfd, path, mode)) }
+}
+
+#[inline]
+pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_fchmod, fd, mode)) }
+}
+
+#[inline]
+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(syscall_readonly!(
+ __NR_fchownat,
+ dirfd,
+ path,
+ c_uint(ow),
+ c_uint(gr),
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn chown(path: &CStr, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
+ // Most architectures have a `chown` syscall.
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
+ ret(syscall_readonly!(__NR_chown, path, c_uint(ow), c_uint(gr)))
+ }
+
+ // Aarch64 and RISC-V don't, so use `fchownat`.
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ unsafe {
+ let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
+ ret(syscall_readonly!(
+ __NR_fchownat,
+ raw_fd(AT_FDCWD),
+ path,
+ c_uint(ow),
+ c_uint(gr),
+ zero()
+ ))
+ }
+}
+
+#[inline]
+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(syscall_readonly!(__NR_fchown, fd, c_uint(ow), c_uint(gr)))
+ }
+}
+
+#[inline]
+pub(crate) fn mknodat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ file_type: FileType,
+ mode: Mode,
+ dev: u64,
+) -> io::Result<()> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_mknodat,
+ dirfd,
+ path,
+ (mode, file_type),
+ dev_t(dev)?
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_mknodat,
+ dirfd,
+ path,
+ (mode, file_type),
+ dev_t(dev)
+ ))
+ }
+}
+
+#[inline]
+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.
+ (SEEK_SET, pos as i64)
+ }
+ SeekFrom::End(offset) => (SEEK_END, offset),
+ SeekFrom::Current(offset) => (SEEK_CUR, offset),
+ SeekFrom::Data(offset) => (SEEK_DATA, offset),
+ SeekFrom::Hole(offset) => (SEEK_HOLE, offset),
+ };
+ _seek(fd, offset, whence)
+}
+
+#[inline]
+pub(crate) fn _seek(fd: BorrowedFd<'_>, offset: i64, whence: c::c_uint) -> io::Result<u64> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ let mut result = MaybeUninit::<u64>::uninit();
+ ret(syscall!(
+ __NR__llseek,
+ fd,
+ // Don't use the hi/lo functions here because Linux's llseek
+ // takes its 64-bit argument differently from everything else.
+ pass_usize((offset >> 32) as usize),
+ pass_usize(offset as usize),
+ &mut result,
+ c_uint(whence)
+ ))?;
+ Ok(result.assume_init())
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_u64(syscall_readonly!(
+ __NR_lseek,
+ fd,
+ loff_t(offset),
+ c_uint(whence)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> {
+ _seek(fd, 0, SEEK_CUR).map(|x| x as u64)
+}
+
+#[inline]
+pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> {
+ // <https://github.com/torvalds/linux/blob/fcadab740480e0e0e9fa9bd272acd409884d431a/arch/arm64/kernel/sys32.c#L81-L83>
+ #[cfg(all(
+ target_pointer_width = "32",
+ any(
+ target_arch = "arm",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "powerpc"
+ ),
+ ))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ftruncate64,
+ fd,
+ zero(),
+ hi(length),
+ lo(length)
+ ))
+ }
+ #[cfg(all(
+ target_pointer_width = "32",
+ not(any(
+ target_arch = "arm",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "powerpc"
+ )),
+ ))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ftruncate64,
+ fd,
+ hi(length),
+ lo(length)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ftruncate,
+ fd,
+ loff_t_from_u64(length)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fallocate(
+ fd: BorrowedFd<'_>,
+ mode: FallocateFlags,
+ offset: u64,
+ len: u64,
+) -> io::Result<()> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fallocate,
+ fd,
+ mode,
+ hi(offset),
+ lo(offset),
+ hi(len),
+ lo(len)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fallocate,
+ fd,
+ mode,
+ loff_t_from_u64(offset),
+ loff_t_from_u64(len)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fadvise(fd: BorrowedFd<'_>, pos: u64, len: u64, advice: Advice) -> io::Result<()> {
+ // On ARM, the arguments are reordered so that the len and pos argument
+ // pairs are aligned. And ARM has a custom syscall code for this.
+ #[cfg(target_arch = "arm")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_arm_fadvise64_64,
+ fd,
+ advice,
+ hi(pos),
+ lo(pos),
+ hi(len),
+ lo(len)
+ ))
+ }
+
+ // On powerpc, the arguments are reordered as on ARM.
+ #[cfg(target_arch = "powerpc")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fadvise64_64,
+ fd,
+ advice,
+ hi(pos),
+ lo(pos),
+ hi(len),
+ lo(len)
+ ))
+ }
+
+ // On mips, the arguments are not reordered, and padding is inserted
+ // instead to ensure alignment.
+ #[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fadvise64,
+ fd,
+ zero(),
+ hi(pos),
+ lo(pos),
+ hi(len),
+ lo(len),
+ advice
+ ))
+ }
+
+ // For all other 32-bit architectures, use `fadvise64_64` so that we get a
+ // 64-bit length.
+ #[cfg(all(
+ target_pointer_width = "32",
+ not(any(
+ target_arch = "arm",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "powerpc"
+ )),
+ ))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fadvise64_64,
+ fd,
+ hi(pos),
+ lo(pos),
+ hi(len),
+ lo(len),
+ advice
+ ))
+ }
+
+ // On 64-bit architectures, use `fadvise64` which is sufficient.
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fadvise64,
+ fd,
+ loff_t_from_u64(pos),
+ loff_t_from_u64(len),
+ advice
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_fsync, fd)) }
+}
+
+#[inline]
+pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_fdatasync, fd)) }
+}
+
+#[inline]
+pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_flock,
+ fd,
+ c_uint(operation as c::c_uint)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn syncfs(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_syncfs, fd)) }
+}
+
+#[inline]
+pub(crate) fn sync() {
+ unsafe { ret_infallible(syscall_readonly!(__NR_sync)) }
+}
+
+#[inline]
+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(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),
+ }
+ }
+
+ #[cfg(all(
+ target_pointer_width = "64",
+ not(target_arch = "mips64"),
+ not(target_arch = "mips64r6")
+ ))]
+ unsafe {
+ let mut result = MaybeUninit::<Stat>::uninit();
+ ret(syscall!(__NR_fstat, fd, &mut result))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+))]
+fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {
+ let mut result = MaybeUninit::<linux_stat64>::uninit();
+
+ #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+ unsafe {
+ ret(syscall!(__NR_fstat, fd, &mut result))?;
+ stat_to_stat(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall!(__NR_fstat64, fd, &mut result))?;
+ stat_to_stat(result.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn stat(path: &CStr) -> io::Result<Stat> {
+ // See the comments in `fstat` about using `crate::fs::statx` here.
+ #[cfg(any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ ))]
+ {
+ match crate::fs::statx(
+ crate::fs::CWD.as_fd(),
+ path,
+ AtFlags::empty(),
+ StatxFlags::BASIC_STATS,
+ ) {
+ Ok(x) => statx_to_stat(x),
+ Err(io::Errno::NOSYS) => stat_old(path),
+ Err(err) => Err(err),
+ }
+ }
+
+ #[cfg(all(
+ target_pointer_width = "64",
+ not(target_arch = "mips64"),
+ not(target_arch = "mips64r6"),
+ ))]
+ unsafe {
+ let mut result = MaybeUninit::<Stat>::uninit();
+ ret(syscall!(
+ __NR_newfstatat,
+ raw_fd(AT_FDCWD),
+ path,
+ &mut result,
+ c_uint(0)
+ ))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+))]
+fn stat_old(path: &CStr) -> io::Result<Stat> {
+ let mut result = MaybeUninit::<linux_stat64>::uninit();
+
+ #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+ unsafe {
+ ret(syscall!(
+ __NR_newfstatat,
+ raw_fd(AT_FDCWD),
+ path,
+ &mut result,
+ c_uint(0)
+ ))?;
+ stat_to_stat(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall!(
+ __NR_fstatat64,
+ raw_fd(AT_FDCWD),
+ path,
+ &mut result,
+ c_uint(0)
+ ))?;
+ stat_to_stat(result.assume_init())
+ }
+}
+
+#[inline]
+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(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),
+ }
+ }
+
+ #[cfg(all(
+ target_pointer_width = "64",
+ not(target_arch = "mips64"),
+ not(target_arch = "mips64r6"),
+ ))]
+ unsafe {
+ let mut result = MaybeUninit::<Stat>::uninit();
+ ret(syscall!(__NR_newfstatat, dirfd, path, &mut result, flags))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+))]
+fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
+ let mut result = MaybeUninit::<linux_stat64>::uninit();
+
+ #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+ unsafe {
+ ret(syscall!(__NR_newfstatat, dirfd, path, &mut result, flags))?;
+ stat_to_stat(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall!(__NR_fstatat64, dirfd, path, &mut result, flags))?;
+ stat_to_stat(result.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn lstat(path: &CStr) -> io::Result<Stat> {
+ // See the comments in `fstat` about using `crate::fs::statx` here.
+ #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))]
+ {
+ match crate::fs::statx(
+ crate::fs::CWD.as_fd(),
+ path,
+ AtFlags::SYMLINK_NOFOLLOW,
+ StatxFlags::BASIC_STATS,
+ ) {
+ Ok(x) => statx_to_stat(x),
+ Err(io::Errno::NOSYS) => lstat_old(path),
+ Err(err) => Err(err),
+ }
+ }
+
+ #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))]
+ unsafe {
+ let mut result = MaybeUninit::<Stat>::uninit();
+ ret(syscall!(
+ __NR_newfstatat,
+ raw_fd(AT_FDCWD),
+ path,
+ &mut result,
+ c_uint(AT_SYMLINK_NOFOLLOW)
+ ))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(any(target_pointer_width = "32", target_arch = "mips64"))]
+fn lstat_old(path: &CStr) -> io::Result<Stat> {
+ let mut result = MaybeUninit::<linux_stat64>::uninit();
+
+ #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+ unsafe {
+ ret(syscall!(
+ __NR_newfstatat,
+ raw_fd(AT_FDCWD),
+ path,
+ &mut result,
+ c_uint(AT_SYMLINK_NOFOLLOW)
+ ))?;
+ stat_to_stat(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall!(
+ __NR_fstatat64,
+ raw_fd(AT_FDCWD),
+ path,
+ &mut result,
+ c_uint(AT_SYMLINK_NOFOLLOW)
+ ))?;
+ stat_to_stat(result.assume_init())
+ }
+}
+
+/// Convert from a Linux `statx` value to rustix's `Stat`.
+#[cfg(any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+))]
+#[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),
+ 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),
+ 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.into(),
+ st_mtime: bitcast!(i64::from(x.stx_mtime.tv_sec)),
+ st_mtime_nsec: x.stx_mtime.tv_nsec.into(),
+ st_ctime: bitcast!(i64::from(x.stx_ctime.tv_sec)),
+ st_ctime_nsec: x.stx_ctime.tv_nsec.into(),
+ st_ino: x.stx_ino.into(),
+ })
+}
+
+/// Convert from a Linux `stat64` value to rustix's `Stat`.
+#[cfg(target_pointer_width = "32")]
+#[allow(deprecated)] // for `st_[amc]time` u64->i64 transition
+fn stat_to_stat(s64: linux_raw_sys::general::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 `stat` value to rustix's `Stat`.
+#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+fn stat_to_stat(s: linux_raw_sys::general::stat) -> io::Result<Stat> {
+ Ok(Stat {
+ st_dev: s.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_mode: s.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_nlink: s.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_uid: s.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_gid: s.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_rdev: s.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_size: s.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_blksize: s.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_blocks: s.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ st_atime: bitcast!(i64::from(s.st_atime)),
+ st_atime_nsec: s
+ .st_atime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_mtime: bitcast!(i64::from(s.st_mtime)),
+ st_mtime_nsec: s
+ .st_mtime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_ctime: bitcast!(i64::from(s.st_ctime)),
+ st_ctime_nsec: s
+ .st_ctime_nsec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ st_ino: s.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ })
+}
+
+#[inline]
+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/
+ if (mask.bits() & STATX__RESERVED) == STATX__RESERVED {
+ return Err(io::Errno::INVAL);
+ }
+ let mask = mask & StatxFlags::all();
+
+ unsafe {
+ let mut statx_buf = MaybeUninit::<statx>::uninit();
+ ret(syscall!(
+ __NR_statx,
+ dirfd,
+ path,
+ flags,
+ mask,
+ &mut statx_buf
+ ))?;
+ Ok(statx_buf.assume_init())
+ }
+}
+
+#[cfg(not(feature = "linux_4_11"))]
+#[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. This can use
+ // "readonly" because we don't pass it a buffer to mutate.
+ matches!(
+ ret(syscall_readonly!(
+ __NR_statx,
+ raw_fd(AT_FDCWD),
+ zero(),
+ zero(),
+ zero(),
+ zero()
+ )),
+ Err(io::Errno::FAULT)
+ )
+ }
+}
+
+#[inline]
+pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ let mut result = MaybeUninit::<StatFs>::uninit();
+ ret(syscall!(
+ __NR_fstatfs64,
+ fd,
+ size_of::<StatFs, _>(),
+ &mut result
+ ))?;
+ Ok(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ let mut result = MaybeUninit::<StatFs>::uninit();
+ ret(syscall!(__NR_fstatfs, fd, &mut result))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> {
+ // Linux doesn't have an `fstatvfs` syscall; we have to do `fstatfs` and
+ // translate the fields as best we can.
+ let statfs = fstatfs(fd)?;
+
+ Ok(statfs_to_statvfs(statfs))
+}
+
+#[inline]
+pub(crate) fn statfs(path: &CStr) -> io::Result<StatFs> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ let mut result = MaybeUninit::<StatFs>::uninit();
+ ret(syscall!(
+ __NR_statfs64,
+ path,
+ size_of::<StatFs, _>(),
+ &mut result
+ ))?;
+ Ok(result.assume_init())
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ let mut result = MaybeUninit::<StatFs>::uninit();
+ ret(syscall!(__NR_statfs, path, &mut result))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn statvfs(path: &CStr) -> io::Result<StatVfs> {
+ // Linux doesn't have a `statvfs` syscall; we have to do `statfs` and
+ // translate the fields as best we can.
+ let statfs = statfs(path)?;
+
+ Ok(statfs_to_statvfs(statfs))
+}
+
+fn statfs_to_statvfs(statfs: StatFs) -> StatVfs {
+ let __kernel_fsid_t { val } = statfs.f_fsid;
+ let [f_fsid_val0, f_fsid_val1]: [i32; 2] = val;
+
+ StatVfs {
+ f_bsize: statfs.f_bsize as u64,
+ f_frsize: if statfs.f_frsize != 0 {
+ statfs.f_frsize
+ } else {
+ statfs.f_bsize
+ } as u64,
+ f_blocks: statfs.f_blocks as u64,
+ f_bfree: statfs.f_bfree as u64,
+ f_bavail: statfs.f_bavail as u64,
+ f_files: statfs.f_files as u64,
+ f_ffree: statfs.f_ffree as u64,
+ f_favail: statfs.f_ffree as u64,
+ f_fsid: u64::from(f_fsid_val0 as u32) | u64::from(f_fsid_val1 as u32) << 32,
+ f_flag: StatVfsMountFlags::from_bits_retain(statfs.f_flags as u64),
+ f_namemax: statfs.f_namelen as u64,
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[inline]
+pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
+ let (buf_addr_mut, buf_len) = slice_mut(buf);
+ unsafe {
+ ret_usize(syscall!(
+ __NR_readlinkat,
+ raw_fd(AT_FDCWD),
+ path,
+ buf_addr_mut,
+ buf_len
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn readlinkat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ buf: &mut [MaybeUninit<u8>],
+) -> io::Result<usize> {
+ let (buf_addr_mut, buf_len) = slice_mut(buf);
+ unsafe {
+ ret_usize(syscall!(
+ __NR_readlinkat,
+ dirfd,
+ path,
+ buf_addr_mut,
+ buf_len
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret_c_uint(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETFL)))
+ .map(OFlags::from_bits_retain)
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_c_uint(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETFL))).map(OFlags::from_bits_retain)
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> {
+ // Always enable support for large files.
+ let flags = flags | OFlags::from_bits_retain(c::O_LARGEFILE);
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_SETFL), flags))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(__NR_fcntl, fd, c_uint(F_SETFL), flags))
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret_c_int(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GET_SEALS)))
+ .map(|seals| SealFlags::from_bits_retain(seals as u32))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_c_int(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GET_SEALS)))
+ .map(|seals| SealFlags::from_bits_retain(seals as u32))
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fcntl64,
+ fd,
+ c_uint(F_ADD_SEALS),
+ seals
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fcntl,
+ fd,
+ c_uint(F_ADD_SEALS),
+ seals
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
+ #[cfg(target_pointer_width = "64")]
+ use linux_raw_sys::general::{flock, F_SETLK, F_SETLKW};
+ #[cfg(target_pointer_width = "32")]
+ use linux_raw_sys::general::{flock64 as flock, F_SETLK64 as F_SETLK, F_SETLKW64 as F_SETLKW};
+ use linux_raw_sys::general::{F_RDLCK, F_UNLCK, F_WRLCK};
+
+ 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),
+ };
+
+ let lock = flock {
+ 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.
+ l_whence: SEEK_SET as _,
+ l_start: 0,
+ l_len: 0,
+
+ // Unused.
+ l_pid: 0,
+ };
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fcntl64,
+ fd,
+ c_uint(cmd),
+ by_ref(&lock)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fcntl,
+ fd,
+ c_uint(cmd),
+ by_ref(&lock)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn rename(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
+ #[cfg(target_arch = "riscv64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_renameat2,
+ raw_fd(AT_FDCWD),
+ old_path,
+ raw_fd(AT_FDCWD),
+ new_path,
+ c_uint(0)
+ ))
+ }
+ #[cfg(not(target_arch = "riscv64"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_renameat,
+ raw_fd(AT_FDCWD),
+ old_path,
+ raw_fd(AT_FDCWD),
+ new_path
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn renameat(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+) -> io::Result<()> {
+ #[cfg(target_arch = "riscv64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_renameat2,
+ old_dirfd,
+ old_path,
+ new_dirfd,
+ new_path,
+ c_uint(0)
+ ))
+ }
+ #[cfg(not(target_arch = "riscv64"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_renameat,
+ old_dirfd,
+ old_path,
+ new_dirfd,
+ new_path
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn renameat2(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: RenameFlags,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_renameat2,
+ old_dirfd,
+ old_path,
+ new_dirfd,
+ new_path,
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn unlink(path: &CStr) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_unlinkat,
+ raw_fd(AT_FDCWD),
+ path,
+ c_uint(0)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_unlinkat, dirfd, path, flags)) }
+}
+
+#[inline]
+pub(crate) fn rmdir(path: &CStr) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_unlinkat,
+ raw_fd(AT_FDCWD),
+ path,
+ c_uint(AT_REMOVEDIR)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn link(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_linkat,
+ raw_fd(AT_FDCWD),
+ old_path,
+ raw_fd(AT_FDCWD),
+ new_path,
+ c_uint(0)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn linkat(
+ old_dirfd: BorrowedFd<'_>,
+ old_path: &CStr,
+ new_dirfd: BorrowedFd<'_>,
+ new_path: &CStr,
+ flags: AtFlags,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_linkat,
+ old_dirfd,
+ old_path,
+ new_dirfd,
+ new_path,
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn symlink(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_symlinkat,
+ old_path,
+ raw_fd(AT_FDCWD),
+ new_path
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn symlinkat(old_path: &CStr, dirfd: BorrowedFd<'_>, new_path: &CStr) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_symlinkat, old_path, dirfd, new_path)) }
+}
+
+#[inline]
+pub(crate) fn mkdir(path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_mkdirat,
+ raw_fd(AT_FDCWD),
+ path,
+ mode
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn mkdirat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_mkdirat, dirfd, path, mode)) }
+}
+
+#[cfg(feature = "alloc")]
+#[inline]
+pub(crate) fn getdents(fd: BorrowedFd<'_>, dirent: &mut [u8]) -> io::Result<usize> {
+ let (dirent_addr_mut, dirent_len) = slice_mut(dirent);
+
+ unsafe { ret_usize(syscall!(__NR_getdents64, fd, dirent_addr_mut, dirent_len)) }
+}
+
+#[inline]
+pub(crate) fn getdents_uninit(
+ fd: BorrowedFd<'_>,
+ dirent: &mut [MaybeUninit<u8>],
+) -> io::Result<usize> {
+ let (dirent_addr_mut, dirent_len) = slice_mut(dirent);
+
+ unsafe { ret_usize(syscall!(__NR_getdents64, fd, dirent_addr_mut, dirent_len)) }
+}
+
+#[inline]
+pub(crate) fn utimensat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ _utimensat(dirfd, Some(path), times, flags)
+}
+
+#[inline]
+fn _utimensat(
+ dirfd: BorrowedFd<'_>,
+ path: Option<&CStr>,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // `utimensat_time64` was introduced in Linux 5.1. The old `utimensat`
+ // syscall is not y2038-compatible on 32-bit architectures.
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ match ret(syscall_readonly!(
+ __NR_utimensat_time64,
+ dirfd,
+ path,
+ by_ref(times),
+ flags
+ )) {
+ Err(io::Errno::NOSYS) => _utimensat_old(dirfd, path, times, flags),
+ otherwise => otherwise,
+ }
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_utimensat,
+ dirfd,
+ path,
+ by_ref(times),
+ flags
+ ))
+ }
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn _utimensat_old(
+ dirfd: BorrowedFd<'_>,
+ path: Option<&CStr>,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ let old_times = [
+ __kernel_old_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::INVAL)?,
+ },
+ __kernel_old_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::INVAL)?,
+ },
+ ];
+ // The length of the array is fixed and not passed into the syscall.
+ let old_times_addr = slice_just_addr(&old_times);
+ ret(syscall_readonly!(
+ __NR_utimensat,
+ dirfd,
+ path,
+ old_times_addr,
+ flags
+ ))
+}
+
+#[inline]
+pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
+ _utimensat(fd, None, times, AtFlags::empty())
+}
+
+#[inline]
+pub(crate) fn access(path: &CStr, access: Access) -> io::Result<()> {
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ {
+ accessat_noflags(CWD.as_fd(), path, access)
+ }
+
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ ret(syscall_readonly!(__NR_access, path, access))
+ }
+}
+
+pub(crate) fn accessat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ access: Access,
+ flags: AtFlags,
+) -> io::Result<()> {
+ if !flags
+ .difference(AtFlags::EACCESS | AtFlags::SYMLINK_NOFOLLOW)
+ .is_empty()
+ {
+ return Err(io::Errno::INVAL);
+ }
+
+ // Linux's `faccessat` syscall doesn't have a flags argument, so if we have
+ // any flags, use the newer `faccessat2` introduced in Linux 5.8 which
+ // does. Unless we're on Android where using newer system calls can cause
+ // seccomp to abort the process.
+ #[cfg(not(target_os = "android"))]
+ if !flags.is_empty() {
+ unsafe {
+ match ret(syscall_readonly!(
+ __NR_faccessat2,
+ dirfd,
+ path,
+ access,
+ flags
+ )) {
+ Ok(()) => return Ok(()),
+ Err(io::Errno::NOSYS) => {}
+ Err(other) => return Err(other),
+ }
+ }
+ }
+
+ // Linux's `faccessat` doesn't have a flags parameter. If we have
+ // `AT_EACCESS` and we're not setuid or setgid, we can emulate it.
+ if flags.is_empty()
+ || (flags.bits() == AT_EACCESS
+ && crate::backend::ugid::syscalls::getuid()
+ == crate::backend::ugid::syscalls::geteuid()
+ && crate::backend::ugid::syscalls::getgid()
+ == crate::backend::ugid::syscalls::getegid())
+ {
+ return accessat_noflags(dirfd, path, access);
+ }
+
+ Err(io::Errno::NOSYS)
+}
+
+#[inline]
+fn accessat_noflags(dirfd: BorrowedFd<'_>, path: &CStr, access: Access) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_faccessat, dirfd, path, access)) }
+}
+
+#[inline]
+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> {
+ unsafe {
+ ret_usize(syscall!(
+ __NR_copy_file_range,
+ fd_in,
+ opt_mut(off_in),
+ fd_out,
+ opt_mut(off_out),
+ pass_usize(len),
+ c_uint(0)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn memfd_create(name: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_memfd_create, name, flags)) }
+}
+
+#[inline]
+pub(crate) fn sendfile(
+ out_fd: BorrowedFd<'_>,
+ in_fd: BorrowedFd<'_>,
+ offset: Option<&mut u64>,
+ count: usize,
+) -> io::Result<usize> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret_usize(syscall!(
+ __NR_sendfile64,
+ out_fd,
+ in_fd,
+ opt_mut(offset),
+ pass_usize(count)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_usize(syscall!(
+ __NR_sendfile,
+ out_fd,
+ in_fd,
+ opt_mut(offset),
+ pass_usize(count)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn inotify_init1(flags: inotify::CreateFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_inotify_init1, flags)) }
+}
+
+#[inline]
+pub(crate) fn inotify_add_watch(
+ infd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: inotify::WatchFlags,
+) -> io::Result<i32> {
+ unsafe { ret_c_int(syscall_readonly!(__NR_inotify_add_watch, infd, path, flags)) }
+}
+
+#[inline]
+pub(crate) fn inotify_rm_watch(infd: BorrowedFd<'_>, wfd: i32) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_inotify_rm_watch, infd, c_int(wfd))) }
+}
+
+#[inline]
+pub(crate) fn getxattr(path: &CStr, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
+ let (value_addr_mut, value_len) = slice_mut(value);
+ unsafe {
+ ret_usize(syscall!(
+ __NR_getxattr,
+ path,
+ name,
+ value_addr_mut,
+ value_len
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn lgetxattr(path: &CStr, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
+ let (value_addr_mut, value_len) = slice_mut(value);
+ unsafe {
+ ret_usize(syscall!(
+ __NR_lgetxattr,
+ path,
+ name,
+ value_addr_mut,
+ value_len
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fgetxattr(fd: BorrowedFd<'_>, name: &CStr, value: &mut [u8]) -> io::Result<usize> {
+ let (value_addr_mut, value_len) = slice_mut(value);
+ unsafe {
+ ret_usize(syscall!(
+ __NR_fgetxattr,
+ fd,
+ name,
+ value_addr_mut,
+ value_len
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn setxattr(
+ path: &CStr,
+ name: &CStr,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ let (value_addr, value_len) = slice(value);
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_setxattr,
+ path,
+ name,
+ value_addr,
+ value_len,
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn lsetxattr(
+ path: &CStr,
+ name: &CStr,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ let (value_addr, value_len) = slice(value);
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_lsetxattr,
+ path,
+ name,
+ value_addr,
+ value_len,
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn fsetxattr(
+ fd: BorrowedFd<'_>,
+ name: &CStr,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ let (value_addr, value_len) = slice(value);
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsetxattr,
+ fd,
+ name,
+ value_addr,
+ value_len,
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn listxattr(path: &CStr, list: &mut [c::c_char]) -> io::Result<usize> {
+ let (list_addr_mut, list_len) = slice_mut(list);
+ unsafe { ret_usize(syscall!(__NR_listxattr, path, list_addr_mut, list_len)) }
+}
+
+#[inline]
+pub(crate) fn llistxattr(path: &CStr, list: &mut [c::c_char]) -> io::Result<usize> {
+ let (list_addr_mut, list_len) = slice_mut(list);
+ unsafe { ret_usize(syscall!(__NR_llistxattr, path, list_addr_mut, list_len)) }
+}
+
+#[inline]
+pub(crate) fn flistxattr(fd: BorrowedFd<'_>, list: &mut [c::c_char]) -> io::Result<usize> {
+ let (list_addr_mut, list_len) = slice_mut(list);
+ unsafe { ret_usize(syscall!(__NR_flistxattr, fd, list_addr_mut, list_len)) }
+}
+
+#[inline]
+pub(crate) fn removexattr(path: &CStr, name: &CStr) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_removexattr, path, name)) }
+}
+
+#[inline]
+pub(crate) fn lremovexattr(path: &CStr, name: &CStr) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_lremovexattr, path, name)) }
+}
+
+#[inline]
+pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_fremovexattr, fd, name)) }
+}
+
+#[test]
+fn test_sizes() {
+ assert_eq_size!(linux_raw_sys::general::__kernel_loff_t, u64);
+
+ // Assert that `Timestamps` has the expected layout.
+ assert_eq_size!([linux_raw_sys::general::__kernel_timespec; 2], Timestamps);
+}
diff --git a/src/backend/linux_raw/fs/types.rs b/src/backend/linux_raw/fs/types.rs
new file mode 100644
index 0000000..c109a6f
--- /dev/null
+++ b/src/backend/linux_raw/fs/types.rs
@@ -0,0 +1,742 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+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_uint {
+ /// `R_OK`
+ const READ_OK = linux_raw_sys::general::R_OK;
+
+ /// `W_OK`
+ const WRITE_OK = linux_raw_sys::general::W_OK;
+
+ /// `X_OK`
+ const EXEC_OK = linux_raw_sys::general::X_OK;
+
+ /// `F_OK`
+ const EXISTS = linux_raw_sys::general::F_OK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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: c::c_uint {
+ /// `AT_SYMLINK_NOFOLLOW`
+ const SYMLINK_NOFOLLOW = linux_raw_sys::general::AT_SYMLINK_NOFOLLOW;
+
+ /// `AT_EACCESS`
+ const EACCESS = linux_raw_sys::general::AT_EACCESS;
+
+ /// `AT_REMOVEDIR`
+ const REMOVEDIR = linux_raw_sys::general::AT_REMOVEDIR;
+
+ /// `AT_SYMLINK_FOLLOW`
+ const SYMLINK_FOLLOW = linux_raw_sys::general::AT_SYMLINK_FOLLOW;
+
+ /// `AT_NO_AUTOMOUNT`
+ const NO_AUTOMOUNT = linux_raw_sys::general::AT_NO_AUTOMOUNT;
+
+ /// `AT_EMPTY_PATH`
+ const EMPTY_PATH = linux_raw_sys::general::AT_EMPTY_PATH;
+
+ /// `AT_STATX_SYNC_AS_STAT`
+ const STATX_SYNC_AS_STAT = linux_raw_sys::general::AT_STATX_SYNC_AS_STAT;
+
+ /// `AT_STATX_FORCE_SYNC`
+ const STATX_FORCE_SYNC = linux_raw_sys::general::AT_STATX_FORCE_SYNC;
+
+ /// `AT_STATX_DONT_SYNC`
+ const STATX_DONT_SYNC = linux_raw_sys::general::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`
+ const RWXU = linux_raw_sys::general::S_IRWXU;
+
+ /// `S_IRUSR`
+ const RUSR = linux_raw_sys::general::S_IRUSR;
+
+ /// `S_IWUSR`
+ const WUSR = linux_raw_sys::general::S_IWUSR;
+
+ /// `S_IXUSR`
+ const XUSR = linux_raw_sys::general::S_IXUSR;
+
+ /// `S_IRWXG`
+ const RWXG = linux_raw_sys::general::S_IRWXG;
+
+ /// `S_IRGRP`
+ const RGRP = linux_raw_sys::general::S_IRGRP;
+
+ /// `S_IWGRP`
+ const WGRP = linux_raw_sys::general::S_IWGRP;
+
+ /// `S_IXGRP`
+ const XGRP = linux_raw_sys::general::S_IXGRP;
+
+ /// `S_IRWXO`
+ const RWXO = linux_raw_sys::general::S_IRWXO;
+
+ /// `S_IROTH`
+ const ROTH = linux_raw_sys::general::S_IROTH;
+
+ /// `S_IWOTH`
+ const WOTH = linux_raw_sys::general::S_IWOTH;
+
+ /// `S_IXOTH`
+ const XOTH = linux_raw_sys::general::S_IXOTH;
+
+ /// `S_ISUID`
+ const SUID = linux_raw_sys::general::S_ISUID;
+
+ /// `S_ISGID`
+ const SGID = linux_raw_sys::general::S_ISGID;
+
+ /// `S_ISVTX`
+ const SVTX = linux_raw_sys::general::S_ISVTX;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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()
+ }
+}
+
+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)
+ }
+}
+
+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: c::c_uint {
+ /// `O_ACCMODE`
+ const ACCMODE = linux_raw_sys::general::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 = linux_raw_sys::general::O_RDONLY |
+ linux_raw_sys::general::O_WRONLY |
+ linux_raw_sys::general::O_RDWR;
+
+ /// `O_APPEND`
+ const APPEND = linux_raw_sys::general::O_APPEND;
+
+ /// `O_CREAT`
+ #[doc(alias = "CREAT")]
+ const CREATE = linux_raw_sys::general::O_CREAT;
+
+ /// `O_DIRECTORY`
+ const DIRECTORY = linux_raw_sys::general::O_DIRECTORY;
+
+ /// `O_DSYNC`.
+ const DSYNC = linux_raw_sys::general::O_SYNC;
+
+ /// `O_EXCL`
+ const EXCL = linux_raw_sys::general::O_EXCL;
+
+ /// `O_FSYNC`.
+ const FSYNC = linux_raw_sys::general::O_SYNC;
+
+ /// `O_NOFOLLOW`
+ const NOFOLLOW = linux_raw_sys::general::O_NOFOLLOW;
+
+ /// `O_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::O_NONBLOCK;
+
+ /// `O_RDONLY`
+ const RDONLY = linux_raw_sys::general::O_RDONLY;
+
+ /// `O_WRONLY`
+ const WRONLY = linux_raw_sys::general::O_WRONLY;
+
+ /// `O_RDWR`
+ ///
+ /// This is not equal to `RDONLY | WRONLY`. It's a distinct flag.
+ const RDWR = linux_raw_sys::general::O_RDWR;
+
+ /// `O_NOCTTY`
+ const NOCTTY = linux_raw_sys::general::O_NOCTTY;
+
+ /// `O_RSYNC`.
+ const RSYNC = linux_raw_sys::general::O_SYNC;
+
+ /// `O_SYNC`
+ const SYNC = linux_raw_sys::general::O_SYNC;
+
+ /// `O_TRUNC`
+ const TRUNC = linux_raw_sys::general::O_TRUNC;
+
+ /// `O_PATH`
+ const PATH = linux_raw_sys::general::O_PATH;
+
+ /// `O_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::O_CLOEXEC;
+
+ /// `O_TMPFILE`
+ const TMPFILE = linux_raw_sys::general::O_TMPFILE;
+
+ /// `O_NOATIME`
+ const NOATIME = linux_raw_sys::general::O_NOATIME;
+
+ /// `O_DIRECT`
+ const DIRECT = linux_raw_sys::general::O_DIRECT;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::RESOLVE_NO_XDEV as u64;
+
+ /// `RESOLVE_NO_MAGICLINKS`
+ const NO_MAGICLINKS = linux_raw_sys::general::RESOLVE_NO_MAGICLINKS as u64;
+
+ /// `RESOLVE_NO_SYMLINKS`
+ const NO_SYMLINKS = linux_raw_sys::general::RESOLVE_NO_SYMLINKS as u64;
+
+ /// `RESOLVE_BENEATH`
+ const BENEATH = linux_raw_sys::general::RESOLVE_BENEATH as u64;
+
+ /// `RESOLVE_IN_ROOT`
+ const IN_ROOT = linux_raw_sys::general::RESOLVE_IN_ROOT as u64;
+
+ /// `RESOLVE_CACHED` (since Linux 5.12)
+ const CACHED = linux_raw_sys::general::RESOLVE_CACHED as u64;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::RENAME_EXCHANGE;
+
+ /// `RENAME_NOREPLACE`
+ const NOREPLACE = linux_raw_sys::general::RENAME_NOREPLACE;
+
+ /// `RENAME_WHITEOUT`
+ const WHITEOUT = linux_raw_sys::general::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 = linux_raw_sys::general::S_IFREG as isize,
+
+ /// `S_IFDIR`
+ Directory = linux_raw_sys::general::S_IFDIR as isize,
+
+ /// `S_IFLNK`
+ Symlink = linux_raw_sys::general::S_IFLNK as isize,
+
+ /// `S_IFIFO`
+ #[doc(alias = "IFO")]
+ Fifo = linux_raw_sys::general::S_IFIFO as isize,
+
+ /// `S_IFSOCK`
+ Socket = linux_raw_sys::general::S_IFSOCK as isize,
+
+ /// `S_IFCHR`
+ CharacterDevice = linux_raw_sys::general::S_IFCHR as isize,
+
+ /// `S_IFBLK`
+ BlockDevice = linux_raw_sys::general::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 & linux_raw_sys::general::S_IFMT {
+ linux_raw_sys::general::S_IFREG => Self::RegularFile,
+ linux_raw_sys::general::S_IFDIR => Self::Directory,
+ linux_raw_sys::general::S_IFLNK => Self::Symlink,
+ linux_raw_sys::general::S_IFIFO => Self::Fifo,
+ linux_raw_sys::general::S_IFSOCK => Self::Socket,
+ linux_raw_sys::general::S_IFCHR => Self::CharacterDevice,
+ linux_raw_sys::general::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 => linux_raw_sys::general::S_IFREG,
+ Self::Directory => linux_raw_sys::general::S_IFDIR,
+ Self::Symlink => linux_raw_sys::general::S_IFLNK,
+ Self::Fifo => linux_raw_sys::general::S_IFIFO,
+ Self::Socket => linux_raw_sys::general::S_IFSOCK,
+ Self::CharacterDevice => linux_raw_sys::general::S_IFCHR,
+ Self::BlockDevice => linux_raw_sys::general::S_IFBLK,
+ Self::Unknown => linux_raw_sys::general::S_IFMT,
+ }
+ }
+
+ /// Construct a `FileType` from the `d_type` field of a `c::dirent`.
+ #[inline]
+ pub(crate) const fn from_dirent_d_type(d_type: u8) -> Self {
+ match d_type as u32 {
+ linux_raw_sys::general::DT_REG => Self::RegularFile,
+ linux_raw_sys::general::DT_DIR => Self::Directory,
+ linux_raw_sys::general::DT_LNK => Self::Symlink,
+ linux_raw_sys::general::DT_SOCK => Self::Socket,
+ linux_raw_sys::general::DT_FIFO => Self::Fifo,
+ linux_raw_sys::general::DT_CHR => Self::CharacterDevice,
+ linux_raw_sys::general::DT_BLK => Self::BlockDevice,
+ // linux_raw_sys::general::DT_UNKNOWN |
+ _ => Self::Unknown,
+ }
+ }
+}
+
+/// `POSIX_FADV_*` constants for use with [`fadvise`].
+///
+/// [`fadvise`]: crate::fs::fadvise
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub enum Advice {
+ /// `POSIX_FADV_NORMAL`
+ Normal = linux_raw_sys::general::POSIX_FADV_NORMAL,
+
+ /// `POSIX_FADV_SEQUENTIAL`
+ Sequential = linux_raw_sys::general::POSIX_FADV_SEQUENTIAL,
+
+ /// `POSIX_FADV_RANDOM`
+ Random = linux_raw_sys::general::POSIX_FADV_RANDOM,
+
+ /// `POSIX_FADV_NOREUSE`
+ NoReuse = linux_raw_sys::general::POSIX_FADV_NOREUSE,
+
+ /// `POSIX_FADV_WILLNEED`
+ WillNeed = linux_raw_sys::general::POSIX_FADV_WILLNEED,
+
+ /// `POSIX_FADV_DONTNEED`
+ DontNeed = linux_raw_sys::general::POSIX_FADV_DONTNEED,
+}
+
+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 = linux_raw_sys::general::MFD_CLOEXEC;
+
+ /// `MFD_ALLOW_SEALING`
+ const ALLOW_SEALING = linux_raw_sys::general::MFD_ALLOW_SEALING;
+
+ /// `MFD_HUGETLB` (since Linux 4.14)
+ const HUGETLB = linux_raw_sys::general::MFD_HUGETLB;
+
+ /// `MFD_HUGE_64KB`
+ const HUGE_64KB = linux_raw_sys::general::MFD_HUGE_64KB;
+ /// `MFD_HUGE_512JB`
+ const HUGE_512KB = linux_raw_sys::general::MFD_HUGE_512KB;
+ /// `MFD_HUGE_1MB`
+ const HUGE_1MB = linux_raw_sys::general::MFD_HUGE_1MB;
+ /// `MFD_HUGE_2MB`
+ const HUGE_2MB = linux_raw_sys::general::MFD_HUGE_2MB;
+ /// `MFD_HUGE_8MB`
+ const HUGE_8MB = linux_raw_sys::general::MFD_HUGE_8MB;
+ /// `MFD_HUGE_16MB`
+ const HUGE_16MB = linux_raw_sys::general::MFD_HUGE_16MB;
+ /// `MFD_HUGE_32MB`
+ const HUGE_32MB = linux_raw_sys::general::MFD_HUGE_32MB;
+ /// `MFD_HUGE_256MB`
+ const HUGE_256MB = linux_raw_sys::general::MFD_HUGE_256MB;
+ /// `MFD_HUGE_512MB`
+ const HUGE_512MB = linux_raw_sys::general::MFD_HUGE_512MB;
+ /// `MFD_HUGE_1GB`
+ const HUGE_1GB = linux_raw_sys::general::MFD_HUGE_1GB;
+ /// `MFD_HUGE_2GB`
+ const HUGE_2GB = linux_raw_sys::general::MFD_HUGE_2GB;
+ /// `MFD_HUGE_16GB`
+ const HUGE_16GB = linux_raw_sys::general::MFD_HUGE_16GB;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::F_SEAL_SEAL;
+ /// `F_SEAL_SHRINK`.
+ const SHRINK = linux_raw_sys::general::F_SEAL_SHRINK;
+ /// `F_SEAL_GROW`.
+ const GROW = linux_raw_sys::general::F_SEAL_GROW;
+ /// `F_SEAL_WRITE`.
+ const WRITE = linux_raw_sys::general::F_SEAL_WRITE;
+ /// `F_SEAL_FUTURE_WRITE` (since Linux 5.1)
+ const FUTURE_WRITE = linux_raw_sys::general::F_SEAL_FUTURE_WRITE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::STATX_TYPE;
+
+ /// `STATX_MODE`
+ const MODE = linux_raw_sys::general::STATX_MODE;
+
+ /// `STATX_NLINK`
+ const NLINK = linux_raw_sys::general::STATX_NLINK;
+
+ /// `STATX_UID`
+ const UID = linux_raw_sys::general::STATX_UID;
+
+ /// `STATX_GID`
+ const GID = linux_raw_sys::general::STATX_GID;
+
+ /// `STATX_ATIME`
+ const ATIME = linux_raw_sys::general::STATX_ATIME;
+
+ /// `STATX_MTIME`
+ const MTIME = linux_raw_sys::general::STATX_MTIME;
+
+ /// `STATX_CTIME`
+ const CTIME = linux_raw_sys::general::STATX_CTIME;
+
+ /// `STATX_INO`
+ const INO = linux_raw_sys::general::STATX_INO;
+
+ /// `STATX_SIZE`
+ const SIZE = linux_raw_sys::general::STATX_SIZE;
+
+ /// `STATX_BLOCKS`
+ const BLOCKS = linux_raw_sys::general::STATX_BLOCKS;
+
+ /// `STATX_BASIC_STATS`
+ const BASIC_STATS = linux_raw_sys::general::STATX_BASIC_STATS;
+
+ /// `STATX_BTIME`
+ const BTIME = linux_raw_sys::general::STATX_BTIME;
+
+ /// `STATX_MNT_ID` (since Linux 5.8)
+ const MNT_ID = linux_raw_sys::general::STATX_MNT_ID;
+
+ /// `STATX_DIOALIGN` (since Linux 6.1)
+ const DIOALIGN = linux_raw_sys::general::STATX_DIOALIGN;
+
+ /// `STATX_ALL`
+ const ALL = linux_raw_sys::general::STATX_ALL;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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`
+ const KEEP_SIZE = linux_raw_sys::general::FALLOC_FL_KEEP_SIZE;
+ /// `FALLOC_FL_PUNCH_HOLE`
+ const PUNCH_HOLE = linux_raw_sys::general::FALLOC_FL_PUNCH_HOLE;
+ /// `FALLOC_FL_NO_HIDE_STALE`
+ const NO_HIDE_STALE = linux_raw_sys::general::FALLOC_FL_NO_HIDE_STALE;
+ /// `FALLOC_FL_COLLAPSE_RANGE`
+ const COLLAPSE_RANGE = linux_raw_sys::general::FALLOC_FL_COLLAPSE_RANGE;
+ /// `FALLOC_FL_ZERO_RANGE`
+ const ZERO_RANGE = linux_raw_sys::general::FALLOC_FL_ZERO_RANGE;
+ /// `FALLOC_FL_INSERT_RANGE`
+ const INSERT_RANGE = linux_raw_sys::general::FALLOC_FL_INSERT_RANGE;
+ /// `FALLOC_FL_UNSHARE_RANGE`
+ const UNSHARE_RANGE = linux_raw_sys::general::FALLOC_FL_UNSHARE_RANGE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `ST_*` constants for use with [`StatVfs`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct StatVfsMountFlags: u64 {
+ /// `ST_MANDLOCK`
+ const MANDLOCK = linux_raw_sys::general::MS_MANDLOCK as u64;
+
+ /// `ST_NOATIME`
+ const NOATIME = linux_raw_sys::general::MS_NOATIME as u64;
+
+ /// `ST_NODEV`
+ const NODEV = linux_raw_sys::general::MS_NODEV as u64;
+
+ /// `ST_NODIRATIME`
+ const NODIRATIME = linux_raw_sys::general::MS_NODIRATIME as u64;
+
+ /// `ST_NOEXEC`
+ const NOEXEC = linux_raw_sys::general::MS_NOEXEC as u64;
+
+ /// `ST_NOSUID`
+ const NOSUID = linux_raw_sys::general::MS_NOSUID as u64;
+
+ /// `ST_RDONLY`
+ const RDONLY = linux_raw_sys::general::MS_RDONLY as u64;
+
+ /// `ST_RELATIME`
+ const RELATIME = linux_raw_sys::general::MS_RELATIME as u64;
+
+ /// `ST_SYNCHRONOUS`
+ const SYNCHRONOUS = linux_raw_sys::general::MS_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
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum FlockOperation {
+ /// `LOCK_SH`
+ LockShared = linux_raw_sys::general::LOCK_SH,
+ /// `LOCK_EX`
+ LockExclusive = linux_raw_sys::general::LOCK_EX,
+ /// `LOCK_UN`
+ Unlock = linux_raw_sys::general::LOCK_UN,
+ /// `LOCK_SH | LOCK_NB`
+ NonBlockingLockShared = linux_raw_sys::general::LOCK_SH | linux_raw_sys::general::LOCK_NB,
+ /// `LOCK_EX | LOCK_NB`
+ NonBlockingLockExclusive = linux_raw_sys::general::LOCK_EX | linux_raw_sys::general::LOCK_NB,
+ /// `LOCK_UN | LOCK_NB`
+ NonBlockingUnlock = linux_raw_sys::general::LOCK_UN | linux_raw_sys::general::LOCK_NB,
+}
+
+/// `struct stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+// On 32-bit, and mips64, 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(any(
+ target_pointer_width = "32",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+))]
+#[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 stat` for use with [`statat`] and [`fstat`].
+///
+/// [`statat`]: crate::fs::statat
+/// [`fstat`]: crate::fs::fstat
+#[cfg(all(
+ target_pointer_width = "64",
+ not(target_arch = "mips64"),
+ not(target_arch = "mips64r6")
+))]
+pub type Stat = linux_raw_sys::general::stat;
+
+/// `struct statfs` for use with [`statfs`] and [`fstatfs`].
+///
+/// [`statfs`]: crate::fs::statfs
+/// [`fstatfs`]: crate::fs::fstatfs
+#[allow(clippy::module_name_repetitions)]
+pub type StatFs = linux_raw_sys::general::statfs64;
+
+/// `struct statvfs` for use with [`statvfs`] and [`fstatvfs`].
+///
+/// [`statvfs`]: crate::fs::statvfs
+/// [`fstatvfs`]: crate::fs::fstatvfs
+#[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
+pub type Statx = linux_raw_sys::general::statx;
+
+/// `struct statx_timestamp` for use with [`Statx`].
+pub type StatxTimestamp = linux_raw_sys::general::statx_timestamp;
+
+/// `mode_t`
+#[cfg(not(any(
+ target_arch = "x86",
+ target_arch = "sparc",
+ target_arch = "avr",
+ target_arch = "arm",
+)))]
+pub type RawMode = linux_raw_sys::general::__kernel_mode_t;
+
+/// `mode_t`
+#[cfg(any(
+ target_arch = "x86",
+ target_arch = "sparc",
+ target_arch = "avr",
+ target_arch = "arm",
+))]
+// Don't use `__kernel_mode_t` since it's `u16` which differs from `st_size`.
+pub type RawMode = c::c_uint;
+
+/// `dev_t`
+// Within the kernel the dev_t is 32-bit, but userspace uses a 64-bit field.
+pub type Dev = u64;
+
+/// `__fsword_t`
+#[cfg(not(any(target_arch = "mips64", target_arch = "mips64r6")))]
+pub type FsWord = linux_raw_sys::general::__fsword_t;
+
+/// `__fsword_t`
+#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+pub type FsWord = i64;
diff --git a/src/backend/linux_raw/io/errno.rs b/src/backend/linux_raw/io/errno.rs
new file mode 100644
index 0000000..bc40e9a
--- /dev/null
+++ b/src/backend/linux_raw/io/errno.rs
@@ -0,0 +1,553 @@
+//! The `rustix` `Errno` type.
+//!
+//! This type holds an OS error code, which conceptually corresponds to an
+//! `errno` value.
+//!
+//! # Safety
+//!
+//! Linux uses error codes in `-4095..0`; we use rustc attributes to describe
+//! this restricted range of values.
+#![allow(unsafe_code)]
+#![cfg_attr(not(rustc_attrs), allow(unused_unsafe))]
+
+use crate::backend::c;
+use crate::backend::fd::RawFd;
+use crate::backend::reg::{RetNumber, RetReg};
+use crate::io;
+use linux_raw_sys::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)]
+// Linux returns negated error codes, and we leave them in negated form, so
+// error codes are in `-4095..0`.
+#[cfg_attr(rustc_attrs, rustc_layout_scalar_valid_range_start(0xf001))]
+#[cfg_attr(rustc_attrs, rustc_layout_scalar_valid_range_end(0xffff))]
+pub struct Errno(u16);
+
+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| {
+ // `std::io::Error` could theoretically have arbitrary OS error
+ // values, so check that they're in Linux's range.
+ if (1..4096).contains(&raw) {
+ Some(Self::from_errno(raw as u32))
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Extract the raw OS error number from this error.
+ #[inline]
+ pub const fn raw_os_error(self) -> i32 {
+ (self.0 as i16 as i32).wrapping_neg()
+ }
+
+ /// Construct an `Errno` from a raw OS error number.
+ #[inline]
+ pub const fn from_raw_os_error(raw: i32) -> Self {
+ Self::from_errno(raw as u32)
+ }
+
+ /// Convert from a C `errno` value (which is positive) to an `Errno`.
+ const fn from_errno(raw: u32) -> Self {
+ // We store error values in negated form, so that we don't have to
+ // negate them after every syscall.
+ let encoded = raw.wrapping_neg() as u16;
+
+ // TODO: Use Range::contains, once that's `const`.
+ assert!(encoded >= 0xf001);
+
+ // SAFETY: Linux syscalls return negated error values in the range
+ // `-4095..0`, which we just asserted.
+ unsafe { Self(encoded) }
+ }
+}
+
+/// Check for an error from the result of a syscall which encodes a
+/// `c::c_int` on success.
+#[inline]
+pub(in crate::backend) fn try_decode_c_int<Num: RetNumber>(
+ raw: RetReg<Num>,
+) -> io::Result<c::c_int> {
+ if raw.is_in_range(-4095..0) {
+ // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
+ // in that range.
+ return Err(unsafe { Errno(raw.decode_error_code()) });
+ }
+
+ Ok(raw.decode_c_int())
+}
+
+/// Check for an error from the result of a syscall which encodes a
+/// `c::c_uint` on success.
+#[inline]
+pub(in crate::backend) fn try_decode_c_uint<Num: RetNumber>(
+ raw: RetReg<Num>,
+) -> io::Result<c::c_uint> {
+ if raw.is_in_range(-4095..0) {
+ // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
+ // in that range.
+ return Err(unsafe { Errno(raw.decode_error_code()) });
+ }
+
+ Ok(raw.decode_c_uint())
+}
+
+/// Check for an error from the result of a syscall which encodes a `usize` on
+/// success.
+#[inline]
+pub(in crate::backend) fn try_decode_usize<Num: RetNumber>(raw: RetReg<Num>) -> io::Result<usize> {
+ if raw.is_in_range(-4095..0) {
+ // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
+ // in that range.
+ return Err(unsafe { Errno(raw.decode_error_code()) });
+ }
+
+ Ok(raw.decode_usize())
+}
+
+/// Check for an error from the result of a syscall which encodes a
+/// `*mut c_void` on success.
+#[inline]
+pub(in crate::backend) fn try_decode_void_star<Num: RetNumber>(
+ raw: RetReg<Num>,
+) -> io::Result<*mut c::c_void> {
+ if raw.is_in_range(-4095..0) {
+ // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
+ // in that range.
+ return Err(unsafe { Errno(raw.decode_error_code()) });
+ }
+
+ Ok(raw.decode_void_star())
+}
+
+/// Check for an error from the result of a syscall which encodes a
+/// `u64` on success.
+#[cfg(target_pointer_width = "64")]
+#[inline]
+pub(in crate::backend) fn try_decode_u64<Num: RetNumber>(raw: RetReg<Num>) -> io::Result<u64> {
+ if raw.is_in_range(-4095..0) {
+ // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
+ // in that range.
+ return Err(unsafe { Errno(raw.decode_error_code()) });
+ }
+
+ Ok(raw.decode_u64())
+}
+
+/// Check for an error from the result of a syscall which encodes a file
+/// descriptor on success.
+///
+/// # Safety
+///
+/// This must only be used with syscalls which return file descriptors on
+/// success.
+#[inline]
+pub(in crate::backend) unsafe fn try_decode_raw_fd<Num: RetNumber>(
+ raw: RetReg<Num>,
+) -> io::Result<RawFd> {
+ // Instead of using `check_result` here, we just check for negative, since
+ // this function is only used for system calls which return file
+ // descriptors, and this produces smaller code.
+ if raw.is_negative() {
+ debug_assert!(raw.is_in_range(-4095..0));
+
+ // Tell the optimizer that we know the value is in the error range.
+ // This helps it avoid unnecessary integer conversions.
+ #[cfg(core_intrinsics)]
+ {
+ core::intrinsics::assume(raw.is_in_range(-4095..0));
+ }
+
+ return Err(Errno(raw.decode_error_code()));
+ }
+
+ Ok(raw.decode_raw_fd())
+}
+
+/// Check for an error from the result of a syscall which encodes no value on
+/// success. On success, return the unconsumed `raw` value.
+///
+/// # Safety
+///
+/// This must only be used with syscalls which return no value on success.
+#[inline]
+pub(in crate::backend) unsafe fn try_decode_void<Num: RetNumber>(
+ raw: RetReg<Num>,
+) -> io::Result<()> {
+ // Instead of using `check_result` here, we just check for zero, since this
+ // function is only used for system calls which have no other return value,
+ // and this produces smaller code.
+ if raw.is_nonzero() {
+ debug_assert!(raw.is_in_range(-4095..0));
+
+ // Tell the optimizer that we know the value is in the error range.
+ // This helps it avoid unnecessary integer conversions.
+ #[cfg(core_intrinsics)]
+ {
+ core::intrinsics::assume(raw.is_in_range(-4095..0));
+ }
+
+ return Err(Errno(raw.decode_error_code()));
+ }
+
+ raw.decode_void();
+
+ Ok(())
+}
+
+/// Check for an error from the result of a syscall which does not return on
+/// success. On success, return the unconsumed `raw` value.
+///
+/// # Safety
+///
+/// This must only be used with syscalls which do not return on success.
+#[cfg(any(feature = "event", feature = "runtime"))]
+#[inline]
+pub(in crate::backend) unsafe fn try_decode_error<Num: RetNumber>(raw: RetReg<Num>) -> io::Errno {
+ debug_assert!(raw.is_in_range(-4095..0));
+
+ // Tell the optimizer that we know the value is in the error range.
+ // This helps it avoid unnecessary integer conversions.
+ #[cfg(core_intrinsics)]
+ {
+ core::intrinsics::assume(raw.is_in_range(-4095..0));
+ }
+
+ Errno(raw.decode_error_code())
+}
+
+/// Return the contained `usize` value.
+#[cfg(not(debug_assertions))]
+#[inline]
+pub(in crate::backend) fn decode_usize_infallible<Num: RetNumber>(raw: RetReg<Num>) -> usize {
+ raw.decode_usize()
+}
+
+/// Return the contained `c_int` value.
+#[cfg(not(debug_assertions))]
+#[inline]
+pub(in crate::backend) fn decode_c_int_infallible<Num: RetNumber>(raw: RetReg<Num>) -> c::c_int {
+ raw.decode_c_int()
+}
+
+/// Return the contained `c_uint` value.
+#[cfg(not(debug_assertions))]
+#[inline]
+pub(in crate::backend) fn decode_c_uint_infallible<Num: RetNumber>(raw: RetReg<Num>) -> c::c_uint {
+ raw.decode_c_uint()
+}
+
+impl Errno {
+ /// `EACCES`
+ #[doc(alias = "ACCES")]
+ pub const ACCESS: Self = Self::from_errno(errno::EACCES);
+ /// `EADDRINUSE`
+ pub const ADDRINUSE: Self = Self::from_errno(errno::EADDRINUSE);
+ /// `EADDRNOTAVAIL`
+ pub const ADDRNOTAVAIL: Self = Self::from_errno(errno::EADDRNOTAVAIL);
+ /// `EADV`
+ pub const ADV: Self = Self::from_errno(errno::EADV);
+ /// `EAFNOSUPPORT`
+ pub const AFNOSUPPORT: Self = Self::from_errno(errno::EAFNOSUPPORT);
+ /// `EAGAIN`
+ pub const AGAIN: Self = Self::from_errno(errno::EAGAIN);
+ /// `EALREADY`
+ pub const ALREADY: Self = Self::from_errno(errno::EALREADY);
+ /// `EBADE`
+ pub const BADE: Self = Self::from_errno(errno::EBADE);
+ /// `EBADF`
+ pub const BADF: Self = Self::from_errno(errno::EBADF);
+ /// `EBADFD`
+ pub const BADFD: Self = Self::from_errno(errno::EBADFD);
+ /// `EBADMSG`
+ pub const BADMSG: Self = Self::from_errno(errno::EBADMSG);
+ /// `EBADR`
+ pub const BADR: Self = Self::from_errno(errno::EBADR);
+ /// `EBADRQC`
+ pub const BADRQC: Self = Self::from_errno(errno::EBADRQC);
+ /// `EBADSLT`
+ pub const BADSLT: Self = Self::from_errno(errno::EBADSLT);
+ /// `EBFONT`
+ pub const BFONT: Self = Self::from_errno(errno::EBFONT);
+ /// `EBUSY`
+ pub const BUSY: Self = Self::from_errno(errno::EBUSY);
+ /// `ECANCELED`
+ pub const CANCELED: Self = Self::from_errno(errno::ECANCELED);
+ /// `ECHILD`
+ pub const CHILD: Self = Self::from_errno(errno::ECHILD);
+ /// `ECHRNG`
+ pub const CHRNG: Self = Self::from_errno(errno::ECHRNG);
+ /// `ECOMM`
+ pub const COMM: Self = Self::from_errno(errno::ECOMM);
+ /// `ECONNABORTED`
+ pub const CONNABORTED: Self = Self::from_errno(errno::ECONNABORTED);
+ /// `ECONNREFUSED`
+ pub const CONNREFUSED: Self = Self::from_errno(errno::ECONNREFUSED);
+ /// `ECONNRESET`
+ pub const CONNRESET: Self = Self::from_errno(errno::ECONNRESET);
+ /// `EDEADLK`
+ pub const DEADLK: Self = Self::from_errno(errno::EDEADLK);
+ /// `EDEADLOCK`
+ pub const DEADLOCK: Self = Self::from_errno(errno::EDEADLOCK);
+ /// `EDESTADDRREQ`
+ pub const DESTADDRREQ: Self = Self::from_errno(errno::EDESTADDRREQ);
+ /// `EDOM`
+ pub const DOM: Self = Self::from_errno(errno::EDOM);
+ /// `EDOTDOT`
+ pub const DOTDOT: Self = Self::from_errno(errno::EDOTDOT);
+ /// `EDQUOT`
+ pub const DQUOT: Self = Self::from_errno(errno::EDQUOT);
+ /// `EEXIST`
+ pub const EXIST: Self = Self::from_errno(errno::EEXIST);
+ /// `EFAULT`
+ pub const FAULT: Self = Self::from_errno(errno::EFAULT);
+ /// `EFBIG`
+ pub const FBIG: Self = Self::from_errno(errno::EFBIG);
+ /// `EHOSTDOWN`
+ pub const HOSTDOWN: Self = Self::from_errno(errno::EHOSTDOWN);
+ /// `EHOSTUNREACH`
+ pub const HOSTUNREACH: Self = Self::from_errno(errno::EHOSTUNREACH);
+ /// `EHWPOISON`
+ pub const HWPOISON: Self = Self::from_errno(errno::EHWPOISON);
+ /// `EIDRM`
+ pub const IDRM: Self = Self::from_errno(errno::EIDRM);
+ /// `EILSEQ`
+ pub const ILSEQ: Self = Self::from_errno(errno::EILSEQ);
+ /// `EINPROGRESS`
+ pub const INPROGRESS: Self = Self::from_errno(errno::EINPROGRESS);
+ /// `EINTR`.
+ ///
+ /// For a convenient way to retry system calls that exit with `INTR`, use
+ /// [`retry_on_intr`].
+ ///
+ /// [`retry_on_intr`]: io::retry_on_intr
+ pub const INTR: Self = Self::from_errno(errno::EINTR);
+ /// `EINVAL`
+ pub const INVAL: Self = Self::from_errno(errno::EINVAL);
+ /// `EIO`
+ pub const IO: Self = Self::from_errno(errno::EIO);
+ /// `EISCONN`
+ pub const ISCONN: Self = Self::from_errno(errno::EISCONN);
+ /// `EISDIR`
+ pub const ISDIR: Self = Self::from_errno(errno::EISDIR);
+ /// `EISNAM`
+ pub const ISNAM: Self = Self::from_errno(errno::EISNAM);
+ /// `EKEYEXPIRED`
+ pub const KEYEXPIRED: Self = Self::from_errno(errno::EKEYEXPIRED);
+ /// `EKEYREJECTED`
+ pub const KEYREJECTED: Self = Self::from_errno(errno::EKEYREJECTED);
+ /// `EKEYREVOKED`
+ pub const KEYREVOKED: Self = Self::from_errno(errno::EKEYREVOKED);
+ /// `EL2HLT`
+ pub const L2HLT: Self = Self::from_errno(errno::EL2HLT);
+ /// `EL2NSYNC`
+ pub const L2NSYNC: Self = Self::from_errno(errno::EL2NSYNC);
+ /// `EL3HLT`
+ pub const L3HLT: Self = Self::from_errno(errno::EL3HLT);
+ /// `EL3RST`
+ pub const L3RST: Self = Self::from_errno(errno::EL3RST);
+ /// `ELIBACC`
+ pub const LIBACC: Self = Self::from_errno(errno::ELIBACC);
+ /// `ELIBBAD`
+ pub const LIBBAD: Self = Self::from_errno(errno::ELIBBAD);
+ /// `ELIBEXEC`
+ pub const LIBEXEC: Self = Self::from_errno(errno::ELIBEXEC);
+ /// `ELIBMAX`
+ pub const LIBMAX: Self = Self::from_errno(errno::ELIBMAX);
+ /// `ELIBSCN`
+ pub const LIBSCN: Self = Self::from_errno(errno::ELIBSCN);
+ /// `ELNRNG`
+ pub const LNRNG: Self = Self::from_errno(errno::ELNRNG);
+ /// `ELOOP`
+ pub const LOOP: Self = Self::from_errno(errno::ELOOP);
+ /// `EMEDIUMTYPE`
+ pub const MEDIUMTYPE: Self = Self::from_errno(errno::EMEDIUMTYPE);
+ /// `EMFILE`
+ pub const MFILE: Self = Self::from_errno(errno::EMFILE);
+ /// `EMLINK`
+ pub const MLINK: Self = Self::from_errno(errno::EMLINK);
+ /// `EMSGSIZE`
+ pub const MSGSIZE: Self = Self::from_errno(errno::EMSGSIZE);
+ /// `EMULTIHOP`
+ pub const MULTIHOP: Self = Self::from_errno(errno::EMULTIHOP);
+ /// `ENAMETOOLONG`
+ pub const NAMETOOLONG: Self = Self::from_errno(errno::ENAMETOOLONG);
+ /// `ENAVAIL`
+ pub const NAVAIL: Self = Self::from_errno(errno::ENAVAIL);
+ /// `ENETDOWN`
+ pub const NETDOWN: Self = Self::from_errno(errno::ENETDOWN);
+ /// `ENETRESET`
+ pub const NETRESET: Self = Self::from_errno(errno::ENETRESET);
+ /// `ENETUNREACH`
+ pub const NETUNREACH: Self = Self::from_errno(errno::ENETUNREACH);
+ /// `ENFILE`
+ pub const NFILE: Self = Self::from_errno(errno::ENFILE);
+ /// `ENOANO`
+ pub const NOANO: Self = Self::from_errno(errno::ENOANO);
+ /// `ENOBUFS`
+ pub const NOBUFS: Self = Self::from_errno(errno::ENOBUFS);
+ /// `ENOCSI`
+ pub const NOCSI: Self = Self::from_errno(errno::ENOCSI);
+ /// `ENODATA`
+ #[doc(alias = "NOATTR")]
+ pub const NODATA: Self = Self::from_errno(errno::ENODATA);
+ /// `ENODEV`
+ pub const NODEV: Self = Self::from_errno(errno::ENODEV);
+ /// `ENOENT`
+ pub const NOENT: Self = Self::from_errno(errno::ENOENT);
+ /// `ENOEXEC`
+ pub const NOEXEC: Self = Self::from_errno(errno::ENOEXEC);
+ /// `ENOKEY`
+ pub const NOKEY: Self = Self::from_errno(errno::ENOKEY);
+ /// `ENOLCK`
+ pub const NOLCK: Self = Self::from_errno(errno::ENOLCK);
+ /// `ENOLINK`
+ pub const NOLINK: Self = Self::from_errno(errno::ENOLINK);
+ /// `ENOMEDIUM`
+ pub const NOMEDIUM: Self = Self::from_errno(errno::ENOMEDIUM);
+ /// `ENOMEM`
+ pub const NOMEM: Self = Self::from_errno(errno::ENOMEM);
+ /// `ENOMSG`
+ pub const NOMSG: Self = Self::from_errno(errno::ENOMSG);
+ /// `ENONET`
+ pub const NONET: Self = Self::from_errno(errno::ENONET);
+ /// `ENOPKG`
+ pub const NOPKG: Self = Self::from_errno(errno::ENOPKG);
+ /// `ENOPROTOOPT`
+ pub const NOPROTOOPT: Self = Self::from_errno(errno::ENOPROTOOPT);
+ /// `ENOSPC`
+ pub const NOSPC: Self = Self::from_errno(errno::ENOSPC);
+ /// `ENOSR`
+ pub const NOSR: Self = Self::from_errno(errno::ENOSR);
+ /// `ENOSTR`
+ pub const NOSTR: Self = Self::from_errno(errno::ENOSTR);
+ /// `ENOSYS`
+ pub const NOSYS: Self = Self::from_errno(errno::ENOSYS);
+ /// `ENOTBLK`
+ pub const NOTBLK: Self = Self::from_errno(errno::ENOTBLK);
+ /// `ENOTCONN`
+ pub const NOTCONN: Self = Self::from_errno(errno::ENOTCONN);
+ /// `ENOTDIR`
+ pub const NOTDIR: Self = Self::from_errno(errno::ENOTDIR);
+ /// `ENOTEMPTY`
+ pub const NOTEMPTY: Self = Self::from_errno(errno::ENOTEMPTY);
+ /// `ENOTNAM`
+ pub const NOTNAM: Self = Self::from_errno(errno::ENOTNAM);
+ /// `ENOTRECOVERABLE`
+ pub const NOTRECOVERABLE: Self = Self::from_errno(errno::ENOTRECOVERABLE);
+ /// `ENOTSOCK`
+ pub const NOTSOCK: Self = Self::from_errno(errno::ENOTSOCK);
+ /// `ENOTSUP`
+ // On Linux, `ENOTSUP` has the same value as `EOPNOTSUPP`.
+ pub const NOTSUP: Self = Self::from_errno(errno::EOPNOTSUPP);
+ /// `ENOTTY`
+ pub const NOTTY: Self = Self::from_errno(errno::ENOTTY);
+ /// `ENOTUNIQ`
+ pub const NOTUNIQ: Self = Self::from_errno(errno::ENOTUNIQ);
+ /// `ENXIO`
+ pub const NXIO: Self = Self::from_errno(errno::ENXIO);
+ /// `EOPNOTSUPP`
+ pub const OPNOTSUPP: Self = Self::from_errno(errno::EOPNOTSUPP);
+ /// `EOVERFLOW`
+ pub const OVERFLOW: Self = Self::from_errno(errno::EOVERFLOW);
+ /// `EOWNERDEAD`
+ pub const OWNERDEAD: Self = Self::from_errno(errno::EOWNERDEAD);
+ /// `EPERM`
+ pub const PERM: Self = Self::from_errno(errno::EPERM);
+ /// `EPFNOSUPPORT`
+ pub const PFNOSUPPORT: Self = Self::from_errno(errno::EPFNOSUPPORT);
+ /// `EPIPE`
+ pub const PIPE: Self = Self::from_errno(errno::EPIPE);
+ /// `EPROTO`
+ pub const PROTO: Self = Self::from_errno(errno::EPROTO);
+ /// `EPROTONOSUPPORT`
+ pub const PROTONOSUPPORT: Self = Self::from_errno(errno::EPROTONOSUPPORT);
+ /// `EPROTOTYPE`
+ pub const PROTOTYPE: Self = Self::from_errno(errno::EPROTOTYPE);
+ /// `ERANGE`
+ pub const RANGE: Self = Self::from_errno(errno::ERANGE);
+ /// `EREMCHG`
+ pub const REMCHG: Self = Self::from_errno(errno::EREMCHG);
+ /// `EREMOTE`
+ pub const REMOTE: Self = Self::from_errno(errno::EREMOTE);
+ /// `EREMOTEIO`
+ pub const REMOTEIO: Self = Self::from_errno(errno::EREMOTEIO);
+ /// `ERESTART`
+ pub const RESTART: Self = Self::from_errno(errno::ERESTART);
+ /// `ERFKILL`
+ pub const RFKILL: Self = Self::from_errno(errno::ERFKILL);
+ /// `EROFS`
+ pub const ROFS: Self = Self::from_errno(errno::EROFS);
+ /// `ESHUTDOWN`
+ pub const SHUTDOWN: Self = Self::from_errno(errno::ESHUTDOWN);
+ /// `ESOCKTNOSUPPORT`
+ pub const SOCKTNOSUPPORT: Self = Self::from_errno(errno::ESOCKTNOSUPPORT);
+ /// `ESPIPE`
+ pub const SPIPE: Self = Self::from_errno(errno::ESPIPE);
+ /// `ESRCH`
+ pub const SRCH: Self = Self::from_errno(errno::ESRCH);
+ /// `ESRMNT`
+ pub const SRMNT: Self = Self::from_errno(errno::ESRMNT);
+ /// `ESTALE`
+ pub const STALE: Self = Self::from_errno(errno::ESTALE);
+ /// `ESTRPIPE`
+ pub const STRPIPE: Self = Self::from_errno(errno::ESTRPIPE);
+ /// `ETIME`
+ pub const TIME: Self = Self::from_errno(errno::ETIME);
+ /// `ETIMEDOUT`
+ pub const TIMEDOUT: Self = Self::from_errno(errno::ETIMEDOUT);
+ /// `E2BIG`
+ #[doc(alias = "2BIG")]
+ pub const TOOBIG: Self = Self::from_errno(errno::E2BIG);
+ /// `ETOOMANYREFS`
+ pub const TOOMANYREFS: Self = Self::from_errno(errno::ETOOMANYREFS);
+ /// `ETXTBSY`
+ pub const TXTBSY: Self = Self::from_errno(errno::ETXTBSY);
+ /// `EUCLEAN`
+ pub const UCLEAN: Self = Self::from_errno(errno::EUCLEAN);
+ /// `EUNATCH`
+ pub const UNATCH: Self = Self::from_errno(errno::EUNATCH);
+ /// `EUSERS`
+ pub const USERS: Self = Self::from_errno(errno::EUSERS);
+ /// `EWOULDBLOCK`
+ pub const WOULDBLOCK: Self = Self::from_errno(errno::EWOULDBLOCK);
+ /// `EXDEV`
+ pub const XDEV: Self = Self::from_errno(errno::EXDEV);
+ /// `EXFULL`
+ pub const XFULL: Self = Self::from_errno(errno::EXFULL);
+}
diff --git a/src/backend/linux_raw/io/mod.rs b/src/backend/linux_raw/io/mod.rs
new file mode 100644
index 0000000..9477b9b
--- /dev/null
+++ b/src/backend/linux_raw/io/mod.rs
@@ -0,0 +1,3 @@
+pub(crate) mod errno;
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/io/syscalls.rs b/src/backend/linux_raw/io/syscalls.rs
new file mode 100644
index 0000000..c38f28f
--- /dev/null
+++ b/src/backend/linux_raw/io/syscalls.rs
@@ -0,0 +1,379 @@
+//! linux_raw syscalls supporting `rustix::io`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code)]
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+#[cfg(target_pointer_width = "64")]
+use crate::backend::conv::loff_t_from_u64;
+#[cfg(all(
+ target_pointer_width = "32",
+ any(target_arch = "arm", target_arch = "mips", target_arch = "mips32r6"),
+))]
+use crate::backend::conv::zero;
+use crate::backend::conv::{
+ c_uint, pass_usize, raw_fd, ret, ret_c_int, ret_c_uint, ret_discarded_fd, ret_owned_fd,
+ ret_usize, slice,
+};
+#[cfg(target_pointer_width = "32")]
+use crate::backend::conv::{hi, lo};
+use crate::backend::{c, MAX_IOV};
+use crate::fd::{AsFd, BorrowedFd, OwnedFd, RawFd};
+use crate::io::{self, DupFlags, FdFlags, IoSlice, IoSliceMut, ReadWriteFlags};
+use crate::ioctl::{IoctlOutput, RawOpcode};
+#[cfg(all(feature = "fs", feature = "net"))]
+use crate::net::{RecvFlags, SendFlags};
+use core::cmp;
+use linux_raw_sys::general::{F_DUPFD_CLOEXEC, F_GETFD, F_SETFD};
+
+#[inline]
+pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: *mut u8, len: usize) -> io::Result<usize> {
+ ret_usize(syscall!(__NR_read, fd, buf, pass_usize(len)))
+}
+
+#[inline]
+pub(crate) unsafe fn pread(
+ fd: BorrowedFd<'_>,
+ buf: *mut u8,
+ len: usize,
+ pos: u64,
+) -> io::Result<usize> {
+ // <https://github.com/torvalds/linux/blob/fcadab740480e0e0e9fa9bd272acd409884d431a/arch/arm64/kernel/sys32.c#L75>
+ #[cfg(all(
+ target_pointer_width = "32",
+ any(target_arch = "arm", target_arch = "mips", target_arch = "mips32r6"),
+ ))]
+ {
+ ret_usize(syscall!(
+ __NR_pread64,
+ fd,
+ buf,
+ pass_usize(len),
+ zero(),
+ hi(pos),
+ lo(pos)
+ ))
+ }
+ #[cfg(all(
+ target_pointer_width = "32",
+ not(any(target_arch = "arm", target_arch = "mips", target_arch = "mips32r6")),
+ ))]
+ {
+ ret_usize(syscall!(
+ __NR_pread64,
+ fd,
+ buf,
+ pass_usize(len),
+ hi(pos),
+ lo(pos)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ ret_usize(syscall!(
+ __NR_pread64,
+ fd,
+ buf,
+ pass_usize(len),
+ loff_t_from_u64(pos)
+ ))
+}
+
+#[inline]
+pub(crate) fn readv(fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+
+ unsafe { ret_usize(syscall!(__NR_readv, fd, bufs_addr, bufs_len)) }
+}
+
+#[inline]
+pub(crate) fn preadv(
+ fd: BorrowedFd<'_>,
+ bufs: &mut [IoSliceMut<'_>],
+ pos: u64,
+) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+
+ // Unlike the plain "p" functions, the "pv" functions pass their offset in
+ // an endian-independent way, and always in two registers.
+ unsafe {
+ ret_usize(syscall!(
+ __NR_preadv,
+ fd,
+ bufs_addr,
+ bufs_len,
+ pass_usize(pos as usize),
+ pass_usize((pos >> 32) as usize)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn preadv2(
+ fd: BorrowedFd<'_>,
+ bufs: &mut [IoSliceMut<'_>],
+ pos: u64,
+ flags: ReadWriteFlags,
+) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+
+ // Unlike the plain "p" functions, the "pv" functions pass their offset in
+ // an endian-independent way, and always in two registers.
+ unsafe {
+ ret_usize(syscall!(
+ __NR_preadv2,
+ fd,
+ bufs_addr,
+ bufs_len,
+ pass_usize(pos as usize),
+ pass_usize((pos >> 32) as usize),
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ unsafe { ret_usize(syscall_readonly!(__NR_write, fd, buf_addr, buf_len)) }
+}
+
+#[inline]
+pub(crate) fn pwrite(fd: BorrowedFd<'_>, buf: &[u8], pos: u64) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ // <https://github.com/torvalds/linux/blob/fcadab740480e0e0e9fa9bd272acd409884d431a/arch/arm64/kernel/sys32.c#L81-L83>
+ #[cfg(all(
+ target_pointer_width = "32",
+ any(target_arch = "arm", target_arch = "mips", target_arch = "mips32r6"),
+ ))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_pwrite64,
+ fd,
+ buf_addr,
+ buf_len,
+ zero(),
+ hi(pos),
+ lo(pos)
+ ))
+ }
+ #[cfg(all(
+ target_pointer_width = "32",
+ not(any(target_arch = "arm", target_arch = "mips", target_arch = "mips32r6")),
+ ))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_pwrite64,
+ fd,
+ buf_addr,
+ buf_len,
+ hi(pos),
+ lo(pos)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_pwrite64,
+ fd,
+ buf_addr,
+ buf_len,
+ loff_t_from_u64(pos)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+
+ unsafe { ret_usize(syscall_readonly!(__NR_writev, fd, bufs_addr, bufs_len)) }
+}
+
+#[inline]
+pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], pos: u64) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+
+ // Unlike the plain "p" functions, the "pv" functions pass their offset in
+ // an endian-independent way, and always in two registers.
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_pwritev,
+ fd,
+ bufs_addr,
+ bufs_len,
+ pass_usize(pos as usize),
+ pass_usize((pos >> 32) as usize)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn pwritev2(
+ fd: BorrowedFd<'_>,
+ bufs: &[IoSlice<'_>],
+ pos: u64,
+ flags: ReadWriteFlags,
+) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+
+ // Unlike the plain "p" functions, the "pv" functions pass their offset in
+ // an endian-independent way, and always in two registers.
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_pwritev2,
+ fd,
+ bufs_addr,
+ bufs_len,
+ pass_usize(pos as usize),
+ pass_usize((pos >> 32) as usize),
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn close(fd: RawFd) {
+ // See the documentation for [`io::close`] for why errors are ignored.
+ syscall_readonly!(__NR_close, raw_fd(fd)).decode_void();
+}
+
+#[inline]
+pub(crate) unsafe fn ioctl(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> io::Result<IoctlOutput> {
+ ret_c_int(syscall!(__NR_ioctl, fd, c_uint(request), arg))
+}
+
+#[inline]
+pub(crate) unsafe fn ioctl_readonly(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> io::Result<IoctlOutput> {
+ ret_c_int(syscall_readonly!(__NR_ioctl, fd, c_uint(request), arg))
+}
+
+#[cfg(all(feature = "fs", feature = "net"))]
+pub(crate) fn is_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> {
+ 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.
+ let mut buf = [core::mem::MaybeUninit::<u8>::uninit()];
+ match unsafe {
+ crate::backend::net::syscalls::recv(
+ fd,
+ buf.as_mut_ptr() as *mut u8,
+ 1,
+ RecvFlags::PEEK | RecvFlags::DONTWAIT,
+ )
+ } {
+ Ok(0) => read = false,
+ Err(err) => {
+ #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK`
+ match err {
+ io::Errno::AGAIN | io::Errno::WOULDBLOCK => (),
+ io::Errno::NOTSOCK => not_socket = true,
+ _ => return Err(err),
+ }
+ }
+ Ok(_) => (),
+ }
+ }
+ if write && !not_socket {
+ // Do a `send` with `DONTWAIT` for 0 bytes. An `EPIPE` indicates
+ // the write side is shut down.
+ #[allow(unreachable_patterns)] // `EAGAIN` equals `EWOULDBLOCK`
+ match crate::backend::net::syscalls::send(fd, &[], SendFlags::DONTWAIT) {
+ Err(io::Errno::AGAIN | io::Errno::WOULDBLOCK | io::Errno::NOTSOCK) => (),
+ Err(io::Errno::PIPE) => write = false,
+ Err(err) => return Err(err),
+ Ok(_) => (),
+ }
+ }
+ Ok((read, write))
+}
+
+#[inline]
+pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_dup, fd)) }
+}
+
+#[allow(clippy::needless_pass_by_ref_mut)]
+#[inline]
+pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> {
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ {
+ // 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.
+ dup3(fd, new, DupFlags::empty())
+ }
+
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ ret_discarded_fd(syscall_readonly!(__NR_dup2, fd, new.as_fd()))
+ }
+}
+
+#[allow(clippy::needless_pass_by_ref_mut)]
+#[inline]
+pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
+ unsafe { ret_discarded_fd(syscall_readonly!(__NR_dup3, fd, new.as_fd(), flags)) }
+}
+
+#[inline]
+pub(crate) fn fcntl_getfd(fd: BorrowedFd<'_>) -> io::Result<FdFlags> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret_c_uint(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETFD)))
+ .map(FdFlags::from_bits_retain)
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_c_uint(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETFD)))
+ .map(FdFlags::from_bits_retain)
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_setfd(fd: BorrowedFd<'_>, flags: FdFlags) -> io::Result<()> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_SETFD), flags))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(__NR_fcntl, fd, c_uint(F_SETFD), flags))
+ }
+}
+
+#[inline]
+pub(crate) fn fcntl_dupfd_cloexec(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_fcntl64,
+ fd,
+ c_uint(F_DUPFD_CLOEXEC),
+ raw_fd(min)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_fcntl,
+ fd,
+ c_uint(F_DUPFD_CLOEXEC),
+ raw_fd(min)
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/io/types.rs b/src/backend/linux_raw/io/types.rs
new file mode 100644
index 0000000..4b3dfc6
--- /dev/null
+++ b/src/backend/linux_raw/io/types.rs
@@ -0,0 +1,57 @@
+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: c::c_uint {
+ /// `FD_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::FD_CLOEXEC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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: c::c_uint {
+ /// `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;
+ }
+}
+
+bitflags! {
+ /// `O_*` constants for use with [`dup2`].
+ ///
+ /// [`dup2`]: crate::io::dup2
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct DupFlags: c::c_uint {
+ /// `O_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::O_CLOEXEC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/linux_raw/io_uring/mod.rs b/src/backend/linux_raw/io_uring/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/linux_raw/io_uring/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/io_uring/syscalls.rs b/src/backend/linux_raw/io_uring/syscalls.rs
new file mode 100644
index 0000000..d10cd13
--- /dev/null
+++ b/src/backend/linux_raw/io_uring/syscalls.rs
@@ -0,0 +1,62 @@
+//! linux_raw syscalls supporting `rustix::io_uring`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend::syscalls` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::{by_mut, c_uint, pass_usize, ret_c_uint, ret_owned_fd};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+use crate::io_uring::{io_uring_params, IoringEnterFlags, IoringRegisterOp};
+use core::ffi::c_void;
+
+#[inline]
+pub(crate) fn io_uring_setup(entries: u32, params: &mut io_uring_params) -> io::Result<OwnedFd> {
+ unsafe {
+ ret_owned_fd(syscall!(
+ __NR_io_uring_setup,
+ c_uint(entries),
+ by_mut(params)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn io_uring_register(
+ fd: BorrowedFd<'_>,
+ opcode: IoringRegisterOp,
+ arg: *const c_void,
+ nr_args: u32,
+) -> io::Result<u32> {
+ ret_c_uint(syscall_readonly!(
+ __NR_io_uring_register,
+ fd,
+ c_uint(opcode as u32),
+ arg,
+ c_uint(nr_args)
+ ))
+}
+
+#[inline]
+pub(crate) unsafe fn io_uring_enter(
+ fd: BorrowedFd<'_>,
+ to_submit: u32,
+ min_complete: u32,
+ flags: IoringEnterFlags,
+ arg: *const c_void,
+ size: usize,
+) -> io::Result<u32> {
+ // This is not `_readonly` because `io_uring_enter` waits for I/O to
+ // complete, and I/O could involve writing to memory buffers, which
+ // could be a side effect depended on by the caller.
+ ret_c_uint(syscall!(
+ __NR_io_uring_enter,
+ fd,
+ c_uint(to_submit),
+ c_uint(min_complete),
+ flags,
+ arg,
+ pass_usize(size)
+ ))
+}
diff --git a/src/backend/linux_raw/mm/mod.rs b/src/backend/linux_raw/mm/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/linux_raw/mm/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/mm/syscalls.rs b/src/backend/linux_raw/mm/syscalls.rs
new file mode 100644
index 0000000..361f111
--- /dev/null
+++ b/src/backend/linux_raw/mm/syscalls.rs
@@ -0,0 +1,237 @@
+//! linux_raw syscalls supporting `rustix::io`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code)]
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+use super::types::{
+ Advice, MapFlags, MlockAllFlags, MlockFlags, MprotectFlags, MremapFlags, MsyncFlags, ProtFlags,
+ UserfaultfdFlags,
+};
+use crate::backend::c;
+#[cfg(target_pointer_width = "64")]
+use crate::backend::conv::loff_t_from_u64;
+use crate::backend::conv::{c_uint, no_fd, pass_usize, ret, ret_owned_fd, ret_void_star};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+use linux_raw_sys::general::MAP_ANONYMOUS;
+
+#[inline]
+pub(crate) fn madvise(addr: *mut c::c_void, len: usize, advice: Advice) -> io::Result<()> {
+ unsafe {
+ ret(syscall!(
+ __NR_madvise,
+ addr,
+ pass_usize(len),
+ c_uint(advice as c::c_uint)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn msync(addr: *mut c::c_void, len: usize, flags: MsyncFlags) -> io::Result<()> {
+ ret(syscall!(__NR_msync, addr, pass_usize(len), flags))
+}
+
+/// # Safety
+///
+/// `mmap` is primarily unsafe due to the `addr` parameter, as anything working
+/// with memory pointed to by raw pointers is unsafe.
+#[inline]
+pub(crate) unsafe fn mmap(
+ addr: *mut c::c_void,
+ length: usize,
+ prot: ProtFlags,
+ flags: MapFlags,
+ fd: BorrowedFd<'_>,
+ offset: u64,
+) -> io::Result<*mut c::c_void> {
+ #[cfg(target_pointer_width = "32")]
+ {
+ ret_void_star(syscall!(
+ __NR_mmap2,
+ addr,
+ pass_usize(length),
+ prot,
+ flags,
+ fd,
+ (offset / 4096)
+ .try_into()
+ .map(pass_usize)
+ .map_err(|_| io::Errno::INVAL)?
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ {
+ ret_void_star(syscall!(
+ __NR_mmap,
+ addr,
+ pass_usize(length),
+ prot,
+ flags,
+ fd,
+ loff_t_from_u64(offset)
+ ))
+ }
+}
+
+/// # Safety
+///
+/// `mmap` is primarily unsafe due to the `addr` parameter, as anything working
+/// with memory pointed to by raw pointers is unsafe.
+#[inline]
+pub(crate) unsafe fn mmap_anonymous(
+ addr: *mut c::c_void,
+ length: usize,
+ prot: ProtFlags,
+ flags: MapFlags,
+) -> io::Result<*mut c::c_void> {
+ #[cfg(target_pointer_width = "32")]
+ {
+ ret_void_star(syscall!(
+ __NR_mmap2,
+ addr,
+ pass_usize(length),
+ prot,
+ c_uint(flags.bits() | MAP_ANONYMOUS),
+ no_fd(),
+ pass_usize(0)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ {
+ ret_void_star(syscall!(
+ __NR_mmap,
+ addr,
+ pass_usize(length),
+ prot,
+ c_uint(flags.bits() | MAP_ANONYMOUS),
+ no_fd(),
+ loff_t_from_u64(0)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn mprotect(
+ ptr: *mut c::c_void,
+ len: usize,
+ flags: MprotectFlags,
+) -> io::Result<()> {
+ ret(syscall!(__NR_mprotect, ptr, pass_usize(len), flags))
+}
+
+/// # Safety
+///
+/// `munmap` is primarily unsafe due to the `addr` parameter, as anything
+/// working with memory pointed to by raw pointers is unsafe.
+#[inline]
+pub(crate) unsafe fn munmap(addr: *mut c::c_void, length: usize) -> io::Result<()> {
+ ret(syscall!(__NR_munmap, addr, pass_usize(length)))
+}
+
+/// # Safety
+///
+/// `mremap` is primarily unsafe due to the `old_address` parameter, as
+/// anything working with memory pointed to by raw pointers is unsafe.
+#[inline]
+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> {
+ ret_void_star(syscall!(
+ __NR_mremap,
+ old_address,
+ pass_usize(old_size),
+ pass_usize(new_size),
+ flags
+ ))
+}
+
+/// # 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.
+#[inline]
+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> {
+ ret_void_star(syscall!(
+ __NR_mremap,
+ old_address,
+ pass_usize(old_size),
+ pass_usize(new_size),
+ flags,
+ new_address
+ ))
+}
+
+/// # 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(syscall!(__NR_mlock, addr, pass_usize(length)))
+}
+
+/// # Safety
+///
+/// `mlock_with` operates on raw pointers and may round out to the nearest page
+/// boundaries.
+#[inline]
+pub(crate) unsafe fn mlock_with(
+ addr: *mut c::c_void,
+ length: usize,
+ flags: MlockFlags,
+) -> io::Result<()> {
+ ret(syscall!(__NR_mlock2, addr, pass_usize(length), 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(syscall!(__NR_munlock, addr, pass_usize(length)))
+}
+
+#[inline]
+pub(crate) unsafe fn userfaultfd(flags: UserfaultfdFlags) -> io::Result<OwnedFd> {
+ ret_owned_fd(syscall_readonly!(__NR_userfaultfd, 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]
+pub(crate) fn mlockall(flags: MlockAllFlags) -> io::Result<()> {
+ // When `mlockall` is used with `MCL_ONFAULT | MCL_FUTURE`, the ordering
+ // of `mlockall` with respect to arbitrary loads may be significant,
+ // because if a load happens and evokes a fault before the `mlockall`,
+ // the memory doesn't get locked, but if the load and therefore
+ // the fault happens after, then the memory does get locked.
+ // So to be conservative in this regard, we use `syscall` instead
+ // of `syscall_readonly`
+ unsafe { ret(syscall!(__NR_mlockall, flags)) }
+}
+
+/// Unlocks all pages mapped into the address space of the calling process.
+#[inline]
+pub(crate) fn munlockall() -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_munlockall)) }
+}
diff --git a/src/backend/linux_raw/mm/types.rs b/src/backend/linux_raw/mm/types.rs
new file mode 100644
index 0000000..68898f5
--- /dev/null
+++ b/src/backend/linux_raw/mm/types.rs
@@ -0,0 +1,295 @@
+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 = linux_raw_sys::general::PROT_READ;
+ /// `PROT_WRITE`
+ const WRITE = linux_raw_sys::general::PROT_WRITE;
+ /// `PROT_EXEC`
+ const EXEC = linux_raw_sys::general::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 = linux_raw_sys::general::PROT_READ;
+ /// `PROT_WRITE`
+ const WRITE = linux_raw_sys::general::PROT_WRITE;
+ /// `PROT_EXEC`
+ const EXEC = linux_raw_sys::general::PROT_EXEC;
+ /// `PROT_GROWSUP`
+ const GROWSUP = linux_raw_sys::general::PROT_GROWSUP;
+ /// `PROT_GROWSDOWN`
+ const GROWSDOWN = linux_raw_sys::general::PROT_GROWSDOWN;
+ /// `PROT_SEM`
+ const SEM = linux_raw_sys::general::PROT_SEM;
+ /// `PROT_BTI`
+ #[cfg(target_arch = "aarch64")]
+ const BTI = linux_raw_sys::general::PROT_BTI;
+ /// `PROT_MTE`
+ #[cfg(target_arch = "aarch64")]
+ const MTE = linux_raw_sys::general::PROT_MTE;
+ /// `PROT_SAO`
+ #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
+ const SAO = linux_raw_sys::general::PROT_SAO;
+ /// `PROT_ADI`
+ #[cfg(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 = linux_raw_sys::general::MAP_SHARED;
+ /// `MAP_SHARED_VALIDATE` (since Linux 4.15)
+ const SHARED_VALIDATE = linux_raw_sys::general::MAP_SHARED_VALIDATE;
+ /// `MAP_PRIVATE`
+ const PRIVATE = linux_raw_sys::general::MAP_PRIVATE;
+ /// `MAP_DENYWRITE`
+ const DENYWRITE = linux_raw_sys::general::MAP_DENYWRITE;
+ /// `MAP_FIXED`
+ const FIXED = linux_raw_sys::general::MAP_FIXED;
+ /// `MAP_FIXED_NOREPLACE` (since Linux 4.17)
+ const FIXED_NOREPLACE = linux_raw_sys::general::MAP_FIXED_NOREPLACE;
+ /// `MAP_GROWSDOWN`
+ const GROWSDOWN = linux_raw_sys::general::MAP_GROWSDOWN;
+ /// `MAP_HUGETLB`
+ const HUGETLB = linux_raw_sys::general::MAP_HUGETLB;
+ /// `MAP_HUGE_2MB` (since Linux 3.8)
+ const HUGE_2MB = linux_raw_sys::general::MAP_HUGE_2MB;
+ /// `MAP_HUGE_1GB` (since Linux 3.8)
+ const HUGE_1GB = linux_raw_sys::general::MAP_HUGE_1GB;
+ /// `MAP_LOCKED`
+ const LOCKED = linux_raw_sys::general::MAP_LOCKED;
+ /// `MAP_NORESERVE`
+ const NORESERVE = linux_raw_sys::general::MAP_NORESERVE;
+ /// `MAP_POPULATE`
+ const POPULATE = linux_raw_sys::general::MAP_POPULATE;
+ /// `MAP_STACK`
+ const STACK = linux_raw_sys::general::MAP_STACK;
+ /// `MAP_SYNC` (since Linux 4.15)
+ #[cfg(not(any(target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6")))]
+ const SYNC = linux_raw_sys::general::MAP_SYNC;
+ /// `MAP_UNINITIALIZED`
+ #[cfg(not(any(target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6")))]
+ const UNINITIALIZED = linux_raw_sys::general::MAP_UNINITIALIZED;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::MREMAP_MAYMOVE;
+ /// `MREMAP_DONTUNMAP` (since Linux 5.7)
+ const DONTUNMAP = linux_raw_sys::general::MREMAP_DONTUNMAP;
+
+ /// <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 = linux_raw_sys::general::MS_SYNC;
+ /// `MS_ASYNC`—Specifies that an update be scheduled, but the call
+ /// returns immediately.
+ const ASYNC = linux_raw_sys::general::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 = linux_raw_sys::general::MS_INVALIDATE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::MLOCK_ONFAULT;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `POSIX_MADV_*` constants for use with [`madvise`].
+///
+/// [`madvise`]: crate::mm::madvise
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+#[non_exhaustive]
+pub enum Advice {
+ /// `POSIX_MADV_NORMAL`
+ Normal = linux_raw_sys::general::MADV_NORMAL,
+
+ /// `POSIX_MADV_SEQUENTIAL`
+ Sequential = linux_raw_sys::general::MADV_SEQUENTIAL,
+
+ /// `POSIX_MADV_RANDOM`
+ Random = linux_raw_sys::general::MADV_RANDOM,
+
+ /// `POSIX_MADV_WILLNEED`
+ WillNeed = linux_raw_sys::general::MADV_WILLNEED,
+
+ /// `MADV_DONTNEED`
+ LinuxDontNeed = linux_raw_sys::general::MADV_DONTNEED,
+
+ /// `MADV_FREE` (since Linux 4.5)
+ LinuxFree = linux_raw_sys::general::MADV_FREE,
+ /// `MADV_REMOVE`
+ LinuxRemove = linux_raw_sys::general::MADV_REMOVE,
+ /// `MADV_DONTFORK`
+ LinuxDontFork = linux_raw_sys::general::MADV_DONTFORK,
+ /// `MADV_DOFORK`
+ LinuxDoFork = linux_raw_sys::general::MADV_DOFORK,
+ /// `MADV_HWPOISON`
+ LinuxHwPoison = linux_raw_sys::general::MADV_HWPOISON,
+ /// `MADV_SOFT_OFFLINE`
+ #[cfg(not(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )))]
+ LinuxSoftOffline = linux_raw_sys::general::MADV_SOFT_OFFLINE,
+ /// `MADV_MERGEABLE`
+ LinuxMergeable = linux_raw_sys::general::MADV_MERGEABLE,
+ /// `MADV_UNMERGEABLE`
+ LinuxUnmergeable = linux_raw_sys::general::MADV_UNMERGEABLE,
+ /// `MADV_HUGEPAGE`
+ LinuxHugepage = linux_raw_sys::general::MADV_HUGEPAGE,
+ /// `MADV_NOHUGEPAGE`
+ LinuxNoHugepage = linux_raw_sys::general::MADV_NOHUGEPAGE,
+ /// `MADV_DONTDUMP` (since Linux 3.4)
+ LinuxDontDump = linux_raw_sys::general::MADV_DONTDUMP,
+ /// `MADV_DODUMP` (since Linux 3.4)
+ LinuxDoDump = linux_raw_sys::general::MADV_DODUMP,
+ /// `MADV_WIPEONFORK` (since Linux 4.14)
+ LinuxWipeOnFork = linux_raw_sys::general::MADV_WIPEONFORK,
+ /// `MADV_KEEPONFORK` (since Linux 4.14)
+ LinuxKeepOnFork = linux_raw_sys::general::MADV_KEEPONFORK,
+ /// `MADV_COLD` (since Linux 5.4)
+ LinuxCold = linux_raw_sys::general::MADV_COLD,
+ /// `MADV_PAGEOUT` (since Linux 5.4)
+ LinuxPageOut = linux_raw_sys::general::MADV_PAGEOUT,
+ /// `MADV_POPULATE_READ` (since Linux 5.14)
+ LinuxPopulateRead = linux_raw_sys::general::MADV_POPULATE_READ,
+ /// `MADV_POPULATE_WRITE` (since Linux 5.14)
+ LinuxPopulateWrite = linux_raw_sys::general::MADV_POPULATE_WRITE,
+ /// `MADV_DONTNEED_LOCKED` (since Linux 5.18)
+ LinuxDontneedLocked = linux_raw_sys::general::MADV_DONTNEED_LOCKED,
+}
+
+#[allow(non_upper_case_globals)]
+impl Advice {
+ /// `POSIX_MADV_DONTNEED`
+ ///
+ /// On Linux, this is mapped to `POSIX_MADV_NORMAL` because Linux's
+ /// `MADV_DONTNEED` differs from `POSIX_MADV_DONTNEED`. See `LinuxDontNeed`
+ /// for the Linux behavior.
+ pub const DontNeed: Self = Self::Normal;
+}
+
+bitflags! {
+ /// `O_*` flags for use with [`userfaultfd`].
+ ///
+ /// [`userfaultfd`]: crate::mm::userfaultfd
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UserfaultfdFlags: c::c_uint {
+ /// `O_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::O_CLOEXEC;
+ /// `O_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::O_NONBLOCK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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.
+ const ONFAULT = linux_raw_sys::general::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 = linux_raw_sys::general::MCL_FUTURE;
+ /// Lock all pages which are currently mapped into the address space of
+ /// the process.
+ const CURRENT = linux_raw_sys::general::MCL_CURRENT;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/linux_raw/mod.rs b/src/backend/linux_raw/mod.rs
new file mode 100644
index 0000000..0d4e533
--- /dev/null
+++ b/src/backend/linux_raw/mod.rs
@@ -0,0 +1,117 @@
+//! The linux_raw backend.
+//!
+//! This makes Linux syscalls directly, without going through libc.
+//!
+//! # Safety
+//!
+//! These files performs raw system calls, and sometimes passes them
+//! uninitialized memory buffers. The signatures in this file are currently
+//! manually maintained and must correspond with the signatures of the actual
+//! Linux syscalls.
+//!
+//! Some of this could be auto-generated from the Linux header file
+//! <linux/syscalls.h>, but we often need more information than it provides,
+//! such as which pointers are array slices, out parameters, or in-out
+//! parameters, which integers are owned or borrowed file descriptors, etc.
+
+#[macro_use]
+mod arch;
+mod conv;
+mod reg;
+#[cfg(any(feature = "time", feature = "process", target_arch = "x86"))]
+mod vdso;
+#[cfg(any(feature = "time", feature = "process", target_arch = "x86"))]
+mod vdso_wrappers;
+
+#[cfg(feature = "event")]
+pub(crate) mod event;
+#[cfg(any(
+ feature = "fs",
+ all(
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+ )
+))]
+pub(crate) mod fs;
+pub(crate) mod io;
+#[cfg(feature = "io_uring")]
+pub(crate) mod io_uring;
+#[cfg(feature = "mm")]
+pub(crate) mod mm;
+#[cfg(feature = "mount")]
+pub(crate) mod mount;
+#[cfg(all(feature = "fs", not(feature = "mount")))]
+pub(crate) mod mount; // for deprecated mount functions in "fs"
+#[cfg(feature = "net")]
+pub(crate) mod net;
+#[cfg(any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+))]
+pub(crate) mod param;
+#[cfg(feature = "pipe")]
+pub(crate) mod pipe;
+#[cfg(feature = "process")]
+pub(crate) mod process;
+#[cfg(feature = "pty")]
+pub(crate) mod pty;
+#[cfg(feature = "rand")]
+pub(crate) mod rand;
+#[cfg(feature = "runtime")]
+pub(crate) mod runtime;
+#[cfg(feature = "shm")]
+pub(crate) mod shm;
+#[cfg(feature = "system")]
+pub(crate) mod system;
+#[cfg(feature = "termios")]
+pub(crate) mod termios;
+#[cfg(feature = "thread")]
+pub(crate) mod thread;
+#[cfg(feature = "time")]
+pub(crate) mod time;
+
+pub(crate) mod fd {
+ pub use crate::maybe_polyfill::os::fd::{
+ AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd,
+ };
+}
+
+// The linux_raw backend doesn't use actual libc, so we define selected
+// libc-like definitions in a module called `c`.
+pub(crate) mod c;
+
+// Private modules used by multiple public modules.
+#[cfg(any(feature = "procfs", feature = "process", feature = "runtime"))]
+pub(crate) mod pid;
+#[cfg(any(feature = "process", feature = "thread"))]
+pub(crate) mod prctl;
+#[cfg(any(
+ feature = "fs",
+ feature = "process",
+ feature = "thread",
+ all(
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+ )
+))]
+pub(crate) mod ugid;
+
+/// The maximum number of buffers that can be passed into a vectored I/O system
+/// call on the current platform.
+const MAX_IOV: usize = linux_raw_sys::general::UIO_MAXIOV as usize;
diff --git a/src/backend/linux_raw/mount/mod.rs b/src/backend/linux_raw/mount/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/linux_raw/mount/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/mount/syscalls.rs b/src/backend/linux_raw/mount/syscalls.rs
new file mode 100644
index 0000000..5cb80cc
--- /dev/null
+++ b/src/backend/linux_raw/mount/syscalls.rs
@@ -0,0 +1,239 @@
+//! linux_raw syscalls supporting `rustix::mount`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code)]
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::ret;
+#[cfg(feature = "mount")]
+use crate::backend::conv::{ret_owned_fd, slice, zero};
+#[cfg(feature = "mount")]
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::ffi::CStr;
+use crate::io;
+
+#[inline]
+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(syscall_readonly!(
+ __NR_mount,
+ source,
+ target,
+ file_system_type,
+ flags,
+ data
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn unmount(target: &CStr, flags: super::types::UnmountFlags) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_umount2, target, flags)) }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsopen(fs_name: &CStr, flags: super::types::FsOpenFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_fsopen, fs_name, flags)) }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsmount(
+ fs_fd: BorrowedFd<'_>,
+ flags: super::types::FsMountFlags,
+ attr_flags: super::types::MountAttrFlags,
+) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_fsmount, fs_fd, flags, attr_flags)) }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn move_mount(
+ from_dfd: BorrowedFd<'_>,
+ from_pathname: &CStr,
+ to_dfd: BorrowedFd<'_>,
+ to_pathname: &CStr,
+ flags: super::types::MoveMountFlags,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_move_mount,
+ from_dfd,
+ from_pathname,
+ to_dfd,
+ to_pathname,
+ flags
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn open_tree(
+ dfd: BorrowedFd<'_>,
+ filename: &CStr,
+ flags: super::types::OpenTreeFlags,
+) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_open_tree, dfd, filename, flags)) }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fspick(
+ dfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: super::types::FsPickFlags,
+) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_fspick, dfd, path, flags)) }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_set_flag(fs_fd: BorrowedFd<'_>, key: &CStr) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::SetFlag,
+ key,
+ zero(),
+ zero()
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_set_string(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ value: &CStr,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::SetString,
+ key,
+ value,
+ zero()
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_set_binary(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ value: &[u8],
+) -> io::Result<()> {
+ let (value_addr, value_len) = slice(value);
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::SetBinary,
+ key,
+ value_addr,
+ value_len
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_set_fd(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::SetFd,
+ key,
+ zero(),
+ fd
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_set_path(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ path: &CStr,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::SetPath,
+ key,
+ path,
+ fd
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_set_path_empty(
+ fs_fd: BorrowedFd<'_>,
+ key: &CStr,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::SetPathEmpty,
+ key,
+ cstr!(""),
+ fd
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_create(fs_fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::Create,
+ zero(),
+ zero(),
+ zero()
+ ))
+ }
+}
+
+#[cfg(feature = "mount")]
+#[inline]
+pub(crate) fn fsconfig_reconfigure(fs_fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fsconfig,
+ fs_fd,
+ super::types::FsConfigCmd::Reconfigure,
+ zero(),
+ zero(),
+ zero()
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/mount/types.rs b/src/backend/linux_raw/mount/types.rs
new file mode 100644
index 0000000..43cb8c0
--- /dev/null
+++ b/src/backend/linux_raw/mount/types.rs
@@ -0,0 +1,332 @@
+use crate::backend::c;
+use bitflags::bitflags;
+
+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_uint {
+ /// `MS_BIND`
+ const BIND = linux_raw_sys::general::MS_BIND;
+
+ /// `MS_DIRSYNC`
+ const DIRSYNC = linux_raw_sys::general::MS_DIRSYNC;
+
+ /// `MS_LAZYTIME`
+ const LAZYTIME = linux_raw_sys::general::MS_LAZYTIME;
+
+ /// `MS_MANDLOCK`
+ #[doc(alias = "MANDLOCK")]
+ const PERMIT_MANDATORY_FILE_LOCKING = linux_raw_sys::general::MS_MANDLOCK;
+
+ /// `MS_NOATIME`
+ const NOATIME = linux_raw_sys::general::MS_NOATIME;
+
+ /// `MS_NODEV`
+ const NODEV = linux_raw_sys::general::MS_NODEV;
+
+ /// `MS_NODIRATIME`
+ const NODIRATIME = linux_raw_sys::general::MS_NODIRATIME;
+
+ /// `MS_NOEXEC`
+ const NOEXEC = linux_raw_sys::general::MS_NOEXEC;
+
+ /// `MS_NOSUID`
+ const NOSUID = linux_raw_sys::general::MS_NOSUID;
+
+ /// `MS_RDONLY`
+ const RDONLY = linux_raw_sys::general::MS_RDONLY;
+
+ /// `MS_REC`
+ const REC = linux_raw_sys::general::MS_REC;
+
+ /// `MS_RELATIME`
+ const RELATIME = linux_raw_sys::general::MS_RELATIME;
+
+ /// `MS_SILENT`
+ const SILENT = linux_raw_sys::general::MS_SILENT;
+
+ /// `MS_STRICTATIME`
+ const STRICTATIME = linux_raw_sys::general::MS_STRICTATIME;
+
+ /// `MS_SYNCHRONOUS`
+ const SYNCHRONOUS = linux_raw_sys::general::MS_SYNCHRONOUS;
+
+ /// `MS_NOSYMFOLLOW`
+ const NOSYMFOLLOW = linux_raw_sys::general::MS_NOSYMFOLLOW;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// `MNT_*` constants for use with [`unmount`].
+ ///
+ /// [`unmount`]: crate::mount::unmount
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UnmountFlags: c::c_uint {
+ /// `MNT_FORCE`
+ const FORCE = linux_raw_sys::general::MNT_FORCE;
+ /// `MNT_DETACH`
+ const DETACH = linux_raw_sys::general::MNT_DETACH;
+ /// `MNT_EXPIRE`
+ const EXPIRE = linux_raw_sys::general::MNT_EXPIRE;
+ /// `UMOUNT_NOFOLLOW`
+ const NOFOLLOW = linux_raw_sys::general::UMOUNT_NOFOLLOW;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+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 = linux_raw_sys::general::FSOPEN_CLOEXEC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+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 = linux_raw_sys::general::FSMOUNT_CLOEXEC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `FSCONFIG_*` constants for use with the `fsconfig` syscall.
+#[cfg(feature = "mount")]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub(crate) enum FsConfigCmd {
+ /// `FSCONFIG_SET_FLAG`
+ SetFlag = linux_raw_sys::general::fsconfig_command::FSCONFIG_SET_FLAG as u32,
+
+ /// `FSCONFIG_SET_STRING`
+ SetString = linux_raw_sys::general::fsconfig_command::FSCONFIG_SET_STRING as u32,
+
+ /// `FSCONFIG_SET_BINARY`
+ SetBinary = linux_raw_sys::general::fsconfig_command::FSCONFIG_SET_BINARY as u32,
+
+ /// `FSCONFIG_SET_PATH`
+ SetPath = linux_raw_sys::general::fsconfig_command::FSCONFIG_SET_PATH as u32,
+
+ /// `FSCONFIG_SET_PATH_EMPTY`
+ SetPathEmpty = linux_raw_sys::general::fsconfig_command::FSCONFIG_SET_PATH_EMPTY as u32,
+
+ /// `FSCONFIG_SET_FD`
+ SetFd = linux_raw_sys::general::fsconfig_command::FSCONFIG_SET_FD as u32,
+
+ /// `FSCONFIG_CMD_CREATE`
+ Create = linux_raw_sys::general::fsconfig_command::FSCONFIG_CMD_CREATE as u32,
+
+ /// `FSCONFIG_CMD_RECONFIGURE`
+ Reconfigure = linux_raw_sys::general::fsconfig_command::FSCONFIG_CMD_RECONFIGURE as u32,
+}
+
+#[cfg(feature = "mount")]
+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 = linux_raw_sys::general::MOUNT_ATTR_RDONLY;
+
+ /// `MOUNT_ATTR_NOSUID`
+ const MOUNT_ATTR_NOSUID = linux_raw_sys::general::MOUNT_ATTR_NOSUID;
+
+ /// `MOUNT_ATTR_NODEV`
+ const MOUNT_ATTR_NODEV = linux_raw_sys::general::MOUNT_ATTR_NODEV;
+
+ /// `MOUNT_ATTR_NOEXEC`
+ const MOUNT_ATTR_NOEXEC = linux_raw_sys::general::MOUNT_ATTR_NOEXEC;
+
+ /// `MOUNT_ATTR__ATIME`
+ const MOUNT_ATTR__ATIME = linux_raw_sys::general::MOUNT_ATTR__ATIME;
+
+ /// `MOUNT_ATTR_RELATIME`
+ const MOUNT_ATTR_RELATIME = linux_raw_sys::general::MOUNT_ATTR_RELATIME;
+
+ /// `MOUNT_ATTR_NOATIME`
+ const MOUNT_ATTR_NOATIME = linux_raw_sys::general::MOUNT_ATTR_NOATIME;
+
+ /// `MOUNT_ATTR_STRICTATIME`
+ const MOUNT_ATTR_STRICTATIME = linux_raw_sys::general::MOUNT_ATTR_STRICTATIME;
+
+ /// `MOUNT_ATTR_NODIRATIME`
+ const MOUNT_ATTR_NODIRATIME = linux_raw_sys::general::MOUNT_ATTR_NODIRATIME;
+
+ /// `MOUNT_ATTR_NOUSER`
+ const MOUNT_ATTR_IDMAP = linux_raw_sys::general::MOUNT_ATTR_IDMAP;
+
+ /// `MOUNT_ATTR__ATIME_FLAGS`
+ const MOUNT_ATTR_NOSYMFOLLOW = linux_raw_sys::general::MOUNT_ATTR_NOSYMFOLLOW;
+
+ /// `MOUNT_ATTR__ATIME_FLAGS`
+ const MOUNT_ATTR_SIZE_VER0 = linux_raw_sys::general::MOUNT_ATTR_SIZE_VER0;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+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 = linux_raw_sys::general::MOVE_MOUNT_F_SYMLINKS;
+
+ /// `MOVE_MOUNT_F_AUTOMOUNTS`
+ const MOVE_MOUNT_F_AUTOMOUNTS = linux_raw_sys::general::MOVE_MOUNT_F_AUTOMOUNTS;
+
+ /// `MOVE_MOUNT_F_EMPTY_PATH`
+ const MOVE_MOUNT_F_EMPTY_PATH = linux_raw_sys::general::MOVE_MOUNT_F_EMPTY_PATH;
+
+ /// `MOVE_MOUNT_T_SYMLINKS`
+ const MOVE_MOUNT_T_SYMLINKS = linux_raw_sys::general::MOVE_MOUNT_T_SYMLINKS;
+
+ /// `MOVE_MOUNT_T_AUTOMOUNTS`
+ const MOVE_MOUNT_T_AUTOMOUNTS = linux_raw_sys::general::MOVE_MOUNT_T_AUTOMOUNTS;
+
+ /// `MOVE_MOUNT_T_EMPTY_PATH`
+ const MOVE_MOUNT_T_EMPTY_PATH = linux_raw_sys::general::MOVE_MOUNT_T_EMPTY_PATH;
+
+ /// `MOVE_MOUNT__MASK`
+ const MOVE_MOUNT_SET_GROUP = linux_raw_sys::general::MOVE_MOUNT_SET_GROUP;
+
+ // TODO: add when Linux 6.5 is released
+ // /// `MOVE_MOUNT_BENEATH`
+ // const MOVE_MOUNT_BENEATH = linux_raw_sys::general::MOVE_MOUNT_BENEATH;
+
+ /// `MOVE_MOUNT__MASK`
+ const MOVE_MOUNT__MASK = linux_raw_sys::general::MOVE_MOUNT__MASK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+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 = linux_raw_sys::general::OPEN_TREE_CLONE;
+
+ /// `OPENTREE_CLOEXEC`
+ const OPEN_TREE_CLOEXEC = linux_raw_sys::general::OPEN_TREE_CLOEXEC;
+
+ /// `AT_EMPTY_PATH`
+ const AT_EMPTY_PATH = linux_raw_sys::general::AT_EMPTY_PATH;
+
+ /// `AT_NO_AUTOMOUNT`
+ const AT_NO_AUTOMOUNT = linux_raw_sys::general::AT_NO_AUTOMOUNT;
+
+ /// `AT_RECURSIVE`
+ const AT_RECURSIVE = linux_raw_sys::general::AT_RECURSIVE;
+
+ /// `AT_SYMLINK_NOFOLLOW`
+ const AT_SYMLINK_NOFOLLOW = linux_raw_sys::general::AT_SYMLINK_NOFOLLOW;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(feature = "mount")]
+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 = linux_raw_sys::general::FSPICK_CLOEXEC;
+
+ /// `FSPICK_SYMLINK_NOFOLLOW`
+ const FSPICK_SYMLINK_NOFOLLOW = linux_raw_sys::general::FSPICK_SYMLINK_NOFOLLOW;
+
+ /// `FSPICK_NO_AUTOMOUNT`
+ const FSPICK_NO_AUTOMOUNT = linux_raw_sys::general::FSPICK_NO_AUTOMOUNT;
+
+ /// `FSPICK_EMPTY_PATH`
+ const FSPICK_EMPTY_PATH = linux_raw_sys::general::FSPICK_EMPTY_PATH;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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_uint {
+ /// `MS_SILENT`
+ const SILENT = linux_raw_sys::general::MS_SILENT;
+ /// `MS_SHARED`
+ const SHARED = linux_raw_sys::general::MS_SHARED;
+ /// `MS_PRIVATE`
+ const PRIVATE = linux_raw_sys::general::MS_PRIVATE;
+ /// `MS_SLAVE`
+ const SLAVE = linux_raw_sys::general::MS_SLAVE;
+ /// `MS_UNBINDABLE`
+ const UNBINDABLE = linux_raw_sys::general::MS_UNBINDABLE;
+ /// `MS_REC`
+ const REC = linux_raw_sys::general::MS_REC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub(crate) struct InternalMountFlags: c::c_uint {
+ const REMOUNT = linux_raw_sys::general::MS_REMOUNT;
+ const MOVE = linux_raw_sys::general::MS_MOVE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[repr(transparent)]
+pub(crate) struct MountFlagsArg(pub(crate) c::c_uint);
diff --git a/src/backend/linux_raw/net/addr.rs b/src/backend/linux_raw/net/addr.rs
new file mode 100644
index 0000000..85ba875
--- /dev/null
+++ b/src/backend/linux_raw/net/addr.rs
@@ -0,0 +1,178 @@
+//! Socket address utilities.
+//!
+//! # Safety
+//!
+//! This file uses `CStr::from_bytes_with_nul_unchecked` on a string it knows
+//! to be NUL-terminated.
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use crate::ffi::CStr;
+use crate::{io, path};
+use core::cmp::Ordering;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::slice;
+
+/// `struct sockaddr_un`
+#[derive(Clone)]
+#[doc(alias = "sockaddr_un")]
+pub struct SocketAddrUnix {
+ pub(crate) unix: c::sockaddr_un,
+ len: c::socklen_t,
+}
+
+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 _;
+ }
+ let len = offsetof_sun_path() + bytes.len();
+ let len = len.try_into().unwrap();
+ Ok(Self { unix, len })
+ }
+
+ /// Construct a new abstract Unix-domain address from a byte slice.
+ #[inline]
+ pub fn new_abstract_name(name: &[u8]) -> io::Result<Self> {
+ let mut unix = Self::init();
+ let id = &mut unix.sun_path[1..];
+
+ // SAFETY: Convert `&mut [c_char]` to `&mut [u8]`.
+ let id = unsafe { slice::from_raw_parts_mut(id.as_mut_ptr().cast::<u8>(), id.len()) };
+
+ if let Some(id) = id.get_mut(..name.len()) {
+ id.copy_from_slice(name);
+ let len = offsetof_sun_path() + 1 + name.len();
+ let len = len.try_into().unwrap();
+ Ok(Self { unix, len })
+ } else {
+ Err(io::Errno::NAMETOOLONG)
+ }
+ }
+
+ const fn init() -> c::sockaddr_un {
+ c::sockaddr_un {
+ sun_family: c::AF_UNIX as _,
+ sun_path: [0; 108],
+ }
+ }
+
+ /// 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] as u8 != b'\0' {
+ let end = len as usize - offsetof_sun_path();
+ let bytes = &self.unix.sun_path[..end];
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len()) };
+
+ // SAFETY: `from_bytes_with_nul_unchecked` since the string is
+ // NUL-terminated.
+ unsafe { Some(CStr::from_bytes_with_nul_unchecked(bytes)) }
+ } else {
+ None
+ }
+ }
+
+ /// For an abstract address, return the identifier.
+ #[inline]
+ pub fn abstract_name(&self) -> Option<&[u8]> {
+ let len = self.len();
+ if len != 0 && self.unix.sun_path[0] as u8 == b'\0' {
+ let end = len as usize - offsetof_sun_path();
+ let bytes = &self.unix.sun_path[1..end];
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len()) };
+
+ Some(bytes)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub(crate) fn addr_len(&self) -> c::socklen_t {
+ self.len
+ }
+
+ #[inline]
+ pub(crate) fn len(&self) -> usize {
+ self.addr_len() as usize
+ }
+}
+
+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])
+ }
+}
+
+impl Eq for SocketAddrUnix {}
+
+impl PartialOrd for SocketAddrUnix {
+ #[inline]
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+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])
+ }
+}
+
+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)
+ }
+}
+
+impl fmt::Debug for SocketAddrUnix {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(path) = self.path() {
+ path.fmt(fmt)
+ } else if let Some(name) = self.abstract_name() {
+ name.fmt(fmt)
+ } else {
+ "(unnamed)".fmt(fmt)
+ }
+ }
+}
+
+/// `struct sockaddr_storage` as a raw struct.
+pub type SocketAddrStorage = c::sockaddr;
+
+/// Return the offset of the `sun_path` field of `sockaddr_un`.
+#[inline]
+pub(crate) fn offsetof_sun_path() -> usize {
+ let z = c::sockaddr_un {
+ sun_family: 0_u16,
+ sun_path: [0; 108],
+ };
+ (crate::utils::as_ptr(&z.sun_path) as usize) - (crate::utils::as_ptr(&z) as usize)
+}
diff --git a/src/backend/linux_raw/net/mod.rs b/src/backend/linux_raw/net/mod.rs
new file mode 100644
index 0000000..0387d8b
--- /dev/null
+++ b/src/backend/linux_raw/net/mod.rs
@@ -0,0 +1,9 @@
+pub(crate) mod addr;
+pub(crate) mod msghdr;
+#[cfg(linux_kernel)]
+pub(crate) mod netdevice;
+pub(crate) mod read_sockaddr;
+pub(crate) mod send_recv;
+pub(crate) mod sockopt;
+pub(crate) mod syscalls;
+pub(crate) mod write_sockaddr;
diff --git a/src/backend/linux_raw/net/msghdr.rs b/src/backend/linux_raw/net/msghdr.rs
new file mode 100644
index 0000000..3ccce04
--- /dev/null
+++ b/src/backend/linux_raw/net/msghdr.rs
@@ -0,0 +1,171 @@
+//! 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.
+
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+#[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, MaybeUninit};
+use core::ptr::null_mut;
+
+fn msg_iov_len(len: usize) -> c::size_t {
+ // This cast cannot overflow.
+ len as c::size_t
+}
+
+pub(crate) fn msg_control_len(len: usize) -> c::size_t {
+ // Same as above.
+ len as c::size_t
+}
+
+/// 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::c_int;
+ let mut msghdr = c::msghdr {
+ msg_name: name.as_mut_ptr().cast(),
+ msg_namelen: namelen,
+ msg_iov: iov.as_mut_ptr().cast(),
+ msg_iovlen: msg_iov_len(iov.len()),
+ msg_control: control.as_control_ptr().cast(),
+ msg_controllen: msg_control_len(control.control_len()),
+ msg_flags: 0,
+ };
+
+ 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(c::msghdr {
+ msg_name: null_mut(),
+ msg_namelen: 0,
+ msg_iov: iov.as_ptr() as _,
+ msg_iovlen: msg_iov_len(iov.len()),
+ msg_control: control.as_control_ptr().cast(),
+ msg_controllen: msg_control_len(control.control_len()),
+ msg_flags: 0,
+ })
+}
+
+/// 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(c::msghdr {
+ msg_name: as_ptr(&encoded) as _,
+ msg_namelen: size_of::<SocketAddrV4>() as _,
+ msg_iov: iov.as_ptr() as _,
+ msg_iovlen: msg_iov_len(iov.len()),
+ msg_control: control.as_control_ptr().cast(),
+ msg_controllen: msg_control_len(control.control_len()),
+ msg_flags: 0,
+ })
+}
+
+/// 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(c::msghdr {
+ msg_name: as_ptr(&encoded) as _,
+ msg_namelen: size_of::<SocketAddrV6>() as _,
+ msg_iov: iov.as_ptr() as _,
+ msg_iovlen: msg_iov_len(iov.len()),
+ msg_control: control.as_control_ptr().cast(),
+ msg_controllen: msg_control_len(control.control_len()),
+ msg_flags: 0,
+ })
+}
+
+/// Create a message header intended to send with a Unix address.
+pub(crate) fn with_unix_msghdr<R>(
+ addr: &crate::net::SocketAddrUnix,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+ f(c::msghdr {
+ msg_name: as_ptr(&addr.unix) as _,
+ msg_namelen: addr.addr_len() as _,
+ msg_iov: iov.as_ptr() as _,
+ msg_iovlen: msg_iov_len(iov.len()),
+ msg_control: control.as_control_ptr().cast(),
+ msg_controllen: msg_control_len(control.control_len()),
+ msg_flags: 0,
+ })
+}
+
+/// Create a message header intended to send with an XDP 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(c::msghdr {
+ msg_name: as_ptr(&encoded) as _,
+ msg_namelen: size_of::<SocketAddrXdp>() as _,
+ msg_iov: iov.as_ptr() as _,
+ msg_iovlen: msg_iov_len(iov.len()),
+ msg_control: control.as_control_ptr().cast(),
+ msg_controllen: msg_control_len(control.control_len()),
+ msg_flags: 0,
+ })
+}
+
+/// Create a zero-initialized message header struct value.
+pub(crate) fn zero_msghdr() -> c::msghdr {
+ c::msghdr {
+ msg_name: null_mut(),
+ msg_namelen: 0,
+ msg_iov: null_mut(),
+ msg_iovlen: 0,
+ msg_control: null_mut(),
+ msg_controllen: 0,
+ msg_flags: 0,
+ }
+}
diff --git a/src/backend/linux_raw/net/netdevice.rs b/src/backend/linux_raw/net/netdevice.rs
new file mode 100644
index 0000000..333d27c
--- /dev/null
+++ b/src/backend/linux_raw/net/netdevice.rs
@@ -0,0 +1,68 @@
+#![allow(unsafe_code)]
+
+use crate::backend::io::syscalls::ioctl;
+use crate::fd::AsFd;
+use crate::io;
+use core::slice;
+use core::str;
+use linux_raw_sys::ctypes::c_char;
+use linux_raw_sys::ioctl::SIOCGIFINDEX;
+#[cfg(feature = "alloc")]
+use linux_raw_sys::ioctl::SIOCGIFNAME;
+use linux_raw_sys::net::{ifreq, ifreq__bindgen_ty_1, ifreq__bindgen_ty_2, IFNAMSIZ};
+#[cfg(feature = "alloc")]
+use {alloc::borrow::ToOwned, alloc::string::String};
+
+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);
+ }
+ if if_name_bytes.contains(&0) {
+ return Err(io::Errno::NODEV);
+ }
+
+ // SAFETY: Convert `&[u8]` to `&[c_char]`.
+ let if_name_bytes = unsafe {
+ slice::from_raw_parts(if_name_bytes.as_ptr().cast::<c_char>(), if_name_bytes.len())
+ };
+
+ let mut ifreq = ifreq {
+ ifr_ifrn: ifreq__bindgen_ty_1 { ifrn_name: [0; 16] },
+ ifr_ifru: ifreq__bindgen_ty_2 { ifru_ivalue: 0 },
+ };
+ unsafe { ifreq.ifr_ifrn.ifrn_name[..if_name_bytes.len()].copy_from_slice(if_name_bytes) };
+
+ unsafe { ioctl(fd.as_fd(), SIOCGIFINDEX, &mut ifreq as *mut ifreq as _) }?;
+ let index = unsafe { ifreq.ifr_ifru.ifru_ivalue };
+ 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_ifrn: ifreq__bindgen_ty_1 { ifrn_name: [0; 16] },
+ ifr_ifru: ifreq__bindgen_ty_2 {
+ ifru_ivalue: index as _,
+ },
+ };
+
+ unsafe { ioctl(fd.as_fd(), SIOCGIFNAME, &mut ifreq as *mut ifreq as _) }?;
+
+ if let Some(nul_byte) = unsafe { ifreq.ifr_ifrn.ifrn_name }
+ .iter()
+ .position(|ch| *ch == 0)
+ {
+ let ifrn_name = unsafe { &ifreq.ifr_ifrn.ifrn_name[..nul_byte] };
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let ifrn_name =
+ unsafe { slice::from_raw_parts(ifrn_name.as_ptr().cast::<u8>(), ifrn_name.len()) };
+
+ str::from_utf8(ifrn_name)
+ .map_err(|_| io::Errno::ILSEQ)
+ .map(ToOwned::to_owned)
+ } else {
+ Err(io::Errno::INVAL)
+ }
+}
diff --git a/src/backend/linux_raw/net/read_sockaddr.rs b/src/backend/linux_raw/net/read_sockaddr.rs
new file mode 100644
index 0000000..23e1d64
--- /dev/null
+++ b/src/backend/linux_raw/net/read_sockaddr.rs
@@ -0,0 +1,221 @@
+//! 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.
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use crate::io;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::{SockaddrXdpFlags, SocketAddrXdp};
+use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddrAny, SocketAddrUnix, SocketAddrV4, SocketAddrV6};
+use core::mem::size_of;
+use core::slice;
+
+// This must match the header of `sockaddr`.
+#[repr(C)]
+struct sockaddr_header {
+ 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) -> u16 {
+ // Assert that we know the layout of `sockaddr`.
+ let _ = c::sockaddr {
+ __storage: c::sockaddr_storage {
+ __bindgen_anon_1: linux_raw_sys::net::__kernel_sockaddr_storage__bindgen_ty_1 {
+ __bindgen_anon_1:
+ linux_raw_sys::net::__kernel_sockaddr_storage__bindgen_ty_1__bindgen_ty_1 {
+ ss_family: 0_u16,
+ __data: [0; 126_usize],
+ },
+ },
+ },
+ };
+
+ (*storage.cast::<sockaddr_header>()).ss_family
+}
+
+/// 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.
+#[inline]
+pub(crate) unsafe fn initialize_family_to_unspec(storage: *mut c::sockaddr) {
+ (*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,
+ len: usize,
+) -> io::Result<SocketAddrAny> {
+ 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(decode.sin_addr.s_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>();
+ Ok(SocketAddrAny::V6(SocketAddrV6::new(
+ Ipv6Addr::from(decode.sin6_addr.in6_u.u6_addr8),
+ u16::from_be(decode.sin6_port),
+ u32::from_be(decode.sin6_flowinfo),
+ decode.sin6_scope_id,
+ )))
+ }
+ c::AF_UNIX => {
+ if len < offsetof_sun_path {
+ return Err(io::Errno::INVAL);
+ }
+ if len == offsetof_sun_path {
+ Ok(SocketAddrAny::Unix(SocketAddrUnix::new(&[][..])?))
+ } 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
+ if decode.sun_path[0] == 0 {
+ let bytes = &decode.sun_path[1..len - offsetof_sun_path];
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let bytes = slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len());
+
+ return SocketAddrUnix::new_abstract_name(bytes).map(SocketAddrAny::Unix);
+ }
+
+ // Otherwise we expect a NUL-terminated filesystem path.
+ let bytes = &decode.sun_path[..len - 1 - offsetof_sun_path];
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let bytes = slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len());
+
+ assert_eq!(decode.sun_path[len - 1 - offsetof_sun_path], 0);
+ Ok(SocketAddrAny::Unix(SocketAddrUnix::new(bytes)?))
+ }
+ }
+ #[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::NOTSUP),
+ }
+}
+
+/// 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,
+ len: usize,
+) -> Option<SocketAddrAny> {
+ if len == 0 {
+ None
+ } else {
+ Some(read_sockaddr_os(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, len: usize) -> SocketAddrAny {
+ let offsetof_sun_path = super::addr::offsetof_sun_path();
+
+ assert!(len >= size_of::<c::sa_family_t>());
+ match read_ss_family(storage).into() {
+ 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(decode.sin_addr.s_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(decode.sin6_addr.in6_u.u6_addr8),
+ u16::from_be(decode.sin6_port),
+ u32::from_be(decode.sin6_flowinfo),
+ decode.sin6_scope_id,
+ ))
+ }
+ 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
+ if decode.sun_path[0] == 0 {
+ let bytes = &decode.sun_path[1..len - offsetof_sun_path];
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let bytes = slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len());
+
+ return SocketAddrAny::Unix(SocketAddrUnix::new_abstract_name(bytes).unwrap());
+ }
+
+ // Otherwise we expect a NUL-terminated filesystem path.
+ assert_eq!(decode.sun_path[len - 1 - offsetof_sun_path], 0);
+
+ let bytes = &decode.sun_path[..len - 1 - offsetof_sun_path];
+
+ // SAFETY: Convert `&[c_char]` to `&[u8]`.
+ let bytes = slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len());
+
+ SocketAddrAny::Unix(SocketAddrUnix::new(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/linux_raw/net/send_recv.rs b/src/backend/linux_raw/net/send_recv.rs
new file mode 100644
index 0000000..d5cdd07
--- /dev/null
+++ b/src/backend/linux_raw/net/send_recv.rs
@@ -0,0 +1,60 @@
+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`
+ const CONFIRM = c::MSG_CONFIRM;
+ /// `MSG_DONTROUTE`
+ const DONTROUTE = c::MSG_DONTROUTE;
+ /// `MSG_DONTWAIT`
+ const DONTWAIT = c::MSG_DONTWAIT;
+ /// `MSG_EOT`
+ const EOT = c::MSG_EOR;
+ /// `MSG_MORE`
+ const MORE = c::MSG_MORE;
+ /// `MSG_NOSIGNAL`
+ const NOSIGNAL = c::MSG_NOSIGNAL;
+ /// `MSG_OOB`
+ const OOB = 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 {
+ /// `MSG_CMSG_CLOEXEC`
+ const CMSG_CLOEXEC = c::MSG_CMSG_CLOEXEC;
+ /// `MSG_DONTWAIT`
+ const DONTWAIT = c::MSG_DONTWAIT;
+ /// `MSG_ERRQUEUE`
+ const ERRQUEUE = c::MSG_ERRQUEUE;
+ /// `MSG_OOB`
+ const OOB = c::MSG_OOB;
+ /// `MSG_PEEK`
+ const PEEK = c::MSG_PEEK;
+ /// `MSG_TRUNC`
+ const TRUNC = c::MSG_TRUNC;
+ /// `MSG_WAITALL`
+ const WAITALL = c::MSG_WAITALL;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/linux_raw/net/sockopt.rs b/src/backend/linux_raw/net/sockopt.rs
new file mode 100644
index 0000000..d806603
--- /dev/null
+++ b/src/backend/linux_raw/net/sockopt.rs
@@ -0,0 +1,1047 @@
+//! linux_raw syscalls supporting `rustix::net::sockopt`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+use crate::backend::conv::{by_mut, c_uint, ret, socklen_t};
+use crate::fd::BorrowedFd;
+#[cfg(feature = "alloc")]
+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};
+use crate::net::{
+ AddressFamily, Ipv4Addr, Ipv6Addr, Protocol, RawProtocol, SocketAddrAny, SocketAddrStorage,
+ SocketAddrV4, SocketAddrV6, SocketType, UCred,
+};
+#[cfg(feature = "alloc")]
+use alloc::borrow::ToOwned;
+#[cfg(feature = "alloc")]
+use alloc::string::String;
+use core::mem::MaybeUninit;
+use core::time::Duration;
+use linux_raw_sys::general::{__kernel_old_timeval, __kernel_sock_timeval};
+#[cfg(target_os = "linux")]
+use linux_raw_sys::xdp::{xdp_mmap_offsets, xdp_statistics, xdp_statistics_v1};
+#[cfg(target_arch = "x86")]
+use {
+ crate::backend::conv::{slice_just_addr, x86_sys},
+ crate::backend::reg::{ArgReg, SocketArg},
+ linux_raw_sys::net::{SYS_GETSOCKOPT, SYS_SETSOCKOPT},
+};
+
+#[inline]
+fn getsockopt<T: Copy>(fd: BorrowedFd<'_>, level: u32, optname: u32) -> io::Result<T> {
+ let mut optlen: c::socklen_t = 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>::uninit();
+ getsockopt_raw(fd, level, optname, &mut value, &mut optlen)?;
+
+ assert_eq!(
+ optlen as usize,
+ core::mem::size_of::<T>(),
+ "unexpected getsockopt size"
+ );
+
+ unsafe { Ok(value.assume_init()) }
+}
+
+#[inline]
+fn getsockopt_raw<T>(
+ fd: BorrowedFd<'_>,
+ level: u32,
+ optname: u32,
+ value: &mut MaybeUninit<T>,
+ optlen: &mut c::socklen_t,
+) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall!(
+ __NR_getsockopt,
+ fd,
+ c_uint(level),
+ c_uint(optname),
+ value,
+ by_mut(optlen)
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_GETSOCKOPT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ c_uint(level),
+ c_uint(optname),
+ value.into(),
+ by_mut(optlen),
+ ])
+ ))
+ }
+}
+
+#[inline]
+fn setsockopt<T: Copy>(fd: BorrowedFd<'_>, level: u32, optname: u32, 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: u32,
+ optname: u32,
+ ptr: *const T,
+ optlen: c::socklen_t,
+) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_setsockopt,
+ fd,
+ c_uint(level),
+ c_uint(optname),
+ ptr,
+ socklen_t(optlen)
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SETSOCKOPT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ c_uint(level),
+ c_uint(optname),
+ ptr.into(),
+ socklen_t(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: c::c_int::from(linger.is_some()),
+ 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)))
+}
+
+#[inline]
+pub(crate) fn set_socket_passcred(fd: BorrowedFd<'_>, passcred: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_PASSCRED, from_bool(passcred))
+}
+
+#[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 time = duration_to_linux_sock_timeval(timeout)?;
+ let optname = match id {
+ Timeout::Recv => c::SO_RCVTIMEO_NEW,
+ Timeout::Send => c::SO_SNDTIMEO_NEW,
+ };
+ match setsockopt(fd, c::SOL_SOCKET, optname, time) {
+ Err(io::Errno::NOPROTOOPT) if c::SO_RCVTIMEO_NEW != c::SO_RCVTIMEO_OLD => {
+ set_socket_timeout_old(fd, id, timeout)
+ }
+ otherwise => otherwise,
+ }
+}
+
+/// Same as `set_socket_timeout` but uses `__kernel_old_timeval` instead of
+/// `__kernel_sock_timeval` and `_OLD` constants instead of `_NEW`.
+fn set_socket_timeout_old(
+ fd: BorrowedFd<'_>,
+ id: Timeout,
+ timeout: Option<Duration>,
+) -> io::Result<()> {
+ let time = duration_to_linux_old_timeval(timeout)?;
+ let optname = match id {
+ Timeout::Recv => c::SO_RCVTIMEO_OLD,
+ Timeout::Send => c::SO_SNDTIMEO_OLD,
+ };
+ setsockopt(fd, c::SOL_SOCKET, optname, time)
+}
+
+#[inline]
+pub(crate) fn get_socket_timeout(fd: BorrowedFd<'_>, id: Timeout) -> io::Result<Option<Duration>> {
+ let optname = match id {
+ Timeout::Recv => c::SO_RCVTIMEO_NEW,
+ Timeout::Send => c::SO_SNDTIMEO_NEW,
+ };
+ let time: __kernel_sock_timeval = match getsockopt(fd, c::SOL_SOCKET, optname) {
+ Err(io::Errno::NOPROTOOPT) if c::SO_RCVTIMEO_NEW != c::SO_RCVTIMEO_OLD => {
+ return get_socket_timeout_old(fd, id)
+ }
+ otherwise => otherwise?,
+ };
+ Ok(duration_from_linux_sock_timeval(time))
+}
+
+/// Same as `get_socket_timeout` but uses `__kernel_old_timeval` instead of
+/// `__kernel_sock_timeval` and `_OLD` constants instead of `_NEW`.
+fn get_socket_timeout_old(fd: BorrowedFd<'_>, id: Timeout) -> io::Result<Option<Duration>> {
+ let optname = match id {
+ Timeout::Recv => c::SO_RCVTIMEO_OLD,
+ Timeout::Send => c::SO_SNDTIMEO_OLD,
+ };
+ let time: __kernel_old_timeval = getsockopt(fd, c::SOL_SOCKET, optname)?;
+ Ok(duration_from_linux_old_timeval(time))
+}
+
+/// Convert a `__linux_sock_timeval` to a Rust `Option<Duration>`.
+#[inline]
+fn duration_from_linux_sock_timeval(time: __kernel_sock_timeval) -> Option<Duration> {
+ if time.tv_sec == 0 && time.tv_usec == 0 {
+ None
+ } else {
+ Some(Duration::from_secs(time.tv_sec as u64) + Duration::from_micros(time.tv_usec as u64))
+ }
+}
+
+/// Like `duration_from_linux` but uses Linux's old 32-bit
+/// `__kernel_old_timeval`.
+fn duration_from_linux_old_timeval(time: __kernel_old_timeval) -> Option<Duration> {
+ if time.tv_sec == 0 && time.tv_usec == 0 {
+ None
+ } else {
+ Some(Duration::from_secs(time.tv_sec as u64) + Duration::from_micros(time.tv_usec as u64))
+ }
+}
+
+/// Convert a Rust `Option<Duration>` to a `__kernel_sock_timeval`.
+#[inline]
+fn duration_to_linux_sock_timeval(timeout: Option<Duration>) -> io::Result<__kernel_sock_timeval> {
+ Ok(match timeout {
+ Some(timeout) => {
+ if timeout == Duration::ZERO {
+ return Err(io::Errno::INVAL);
+ }
+ // `subsec_micros` rounds down, so we use `subsec_nanos` and
+ // manually round up.
+ let mut timeout = __kernel_sock_timeval {
+ tv_sec: timeout.as_secs().try_into().unwrap_or(i64::MAX),
+ tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
+ };
+ if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
+ timeout.tv_usec = 1;
+ }
+ timeout
+ }
+ None => __kernel_sock_timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ },
+ })
+}
+
+/// Like `duration_to_linux` but uses Linux's old 32-bit
+/// `__kernel_old_timeval`.
+fn duration_to_linux_old_timeval(timeout: Option<Duration>) -> io::Result<__kernel_old_timeval> {
+ Ok(match timeout {
+ Some(timeout) => {
+ if timeout == Duration::ZERO {
+ return Err(io::Errno::INVAL);
+ }
+
+ // `subsec_micros` rounds down, so we use `subsec_nanos` and
+ // manually round up.
+ let mut timeout = __kernel_old_timeval {
+ tv_sec: timeout.as_secs().try_into().unwrap_or(c::c_long::MAX),
+ tv_usec: ((timeout.subsec_nanos() + 999) / 1000) as _,
+ };
+ if timeout.tv_sec == 0 && timeout.tv_usec == 0 {
+ timeout.tv_usec = 1;
+ }
+ timeout
+ }
+ None => __kernel_old_timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ },
+ })
+}
+
+#[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]
+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]
+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)
+}
+
+#[inline]
+pub(crate) fn set_socket_reuseport(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_socket_reuseport(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_REUSEPORT).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn get_socket_protocol(fd: BorrowedFd<'_>) -> io::Result<Option<Protocol>> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_PROTOCOL)
+ .map(|raw: u32| RawProtocol::new(raw).map(Protocol::from_raw))
+}
+
+#[inline]
+pub(crate) fn get_socket_cookie(fd: BorrowedFd<'_>) -> io::Result<u64> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_COOKIE)
+}
+
+#[inline]
+pub(crate) fn get_socket_incoming_cpu(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::SOL_SOCKET, c::SO_INCOMING_CPU)
+}
+
+#[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)
+}
+
+#[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)
+}
+
+#[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)
+}
+
+#[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<()> {
+ let mreq = to_ipv6mr(multiaddr, interface);
+ setsockopt(fd, c::IPPROTO_IPV6, c::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)
+}
+
+#[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<()> {
+ let mreq = to_ipv6mr(multiaddr, interface);
+ setsockopt(fd, c::IPPROTO_IPV6, c::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.into(),
+ None => -1,
+ };
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_UNICAST_HOPS, hops)
+}
+
+#[inline]
+pub(crate) fn set_ip_tos(fd: BorrowedFd<'_>, value: u8) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_TOS, i32::from(value))
+}
+
+#[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)
+}
+
+#[inline]
+pub(crate) fn set_ip_recvtos(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_RECVTOS, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_ip_recvtos(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_RECVTOS).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_recvtclass(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_ipv6_recvtclass(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_RECVTCLASS).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_ip_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_ip_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IP, c::IP_FREEBIND).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn set_ipv6_freebind(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_ipv6_freebind(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_IPV6, c::IPV6_FREEBIND).map(to_bool)
+}
+
+#[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!(),
+ }
+}
+
+#[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!(),
+ }
+}
+
+#[inline]
+pub(crate) fn set_ipv6_tclass(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_IPV6, c::IPV6_TCLASS, value)
+}
+
+#[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]
+pub(crate) fn set_tcp_keepcnt(fd: BorrowedFd<'_>, count: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPCNT, count)
+}
+
+#[inline]
+pub(crate) fn get_tcp_keepcnt(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPCNT)
+}
+
+#[inline]
+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, c::TCP_KEEPIDLE, secs)
+}
+
+#[inline]
+pub(crate) fn get_tcp_keepidle(fd: BorrowedFd<'_>) -> io::Result<Duration> {
+ let secs: c::c_uint = getsockopt(fd, c::IPPROTO_TCP, c::TCP_KEEPIDLE)?;
+ Ok(Duration::from_secs(secs as u64))
+}
+
+#[inline]
+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]
+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]
+pub(crate) fn set_tcp_user_timeout(fd: BorrowedFd<'_>, value: u32) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT, value)
+}
+
+#[inline]
+pub(crate) fn get_tcp_user_timeout(fd: BorrowedFd<'_>) -> io::Result<u32> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_USER_TIMEOUT)
+}
+
+#[inline]
+pub(crate) fn set_tcp_quickack(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_tcp_quickack(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_QUICKACK).map(to_bool)
+}
+
+#[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")]
+#[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(),
+ )
+ }
+}
+
+#[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),
+ )
+}
+
+#[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)
+}
+
+#[inline]
+pub(crate) fn set_tcp_cork(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
+ setsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK, from_bool(value))
+}
+
+#[inline]
+pub(crate) fn get_tcp_cork(fd: BorrowedFd<'_>) -> io::Result<bool> {
+ getsockopt(fd, c::IPPROTO_TCP, c::TCP_CORK).map(to_bool)
+}
+
+#[inline]
+pub(crate) fn get_socket_peercred(fd: BorrowedFd<'_>) -> io::Result<UCred> {
+ getsockopt(fd, c::SOL_SOCKET, linux_raw_sys::net::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),
+ }
+}
+
+#[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,
+ }
+}
+
+#[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).s_addr,
+ imr_interface: to_imr_addr(interface).s_addr,
+ imr_sourceaddr: to_imr_addr(sourceaddr).s_addr,
+ }
+}
+
+#[inline]
+fn to_imr_addr(addr: &Ipv4Addr) -> c::in_addr {
+ c::in_addr {
+ s_addr: 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_ifindex: to_ipv6mr_interface(interface),
+ }
+}
+
+#[inline]
+fn to_ipv6mr_multiaddr(multiaddr: &Ipv6Addr) -> c::in6_addr {
+ c::in6_addr {
+ in6_u: linux_raw_sys::net::in6_addr__bindgen_ty_1 {
+ u6_addr8: multiaddr.octets(),
+ },
+ }
+}
+
+#[inline]
+fn to_ipv6mr_interface(interface: u32) -> c::c_int {
+ interface as c::c_int
+}
+
+#[inline]
+fn from_bool(value: bool) -> c::c_uint {
+ c::c_uint::from(value)
+}
+
+#[inline]
+fn to_bool(value: c::c_uint) -> bool {
+ value != 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/linux_raw/net/syscalls.rs b/src/backend/linux_raw/net/syscalls.rs
new file mode 100644
index 0000000..4d4427a
--- /dev/null
+++ b/src/backend/linux_raw/net/syscalls.rs
@@ -0,0 +1,1051 @@
+//! linux_raw syscalls supporting `rustix::net`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+#[cfg(target_os = "linux")]
+use super::msghdr::with_xdp_msghdr;
+use super::msghdr::{
+ with_noaddr_msghdr, with_recv_msghdr, with_unix_msghdr, with_v4_msghdr, with_v6_msghdr,
+};
+use super::read_sockaddr::{initialize_family_to_unspec, maybe_read_sockaddr_os, read_sockaddr_os};
+use super::send_recv::{RecvFlags, SendFlags};
+#[cfg(target_os = "linux")]
+use super::write_sockaddr::encode_sockaddr_xdp;
+use super::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};
+use crate::backend::c;
+use crate::backend::conv::{
+ by_mut, by_ref, c_int, c_uint, pass_usize, ret, ret_owned_fd, ret_usize, size_of, slice,
+ socklen_t, zero,
+};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io::{self, IoSlice, IoSliceMut};
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+use crate::net::{
+ AddressFamily, Protocol, RecvAncillaryBuffer, RecvMsgReturn, SendAncillaryBuffer, Shutdown,
+ SocketAddrAny, SocketAddrUnix, SocketAddrV4, SocketAddrV6, SocketFlags, SocketType,
+};
+use c::{sockaddr, sockaddr_in, sockaddr_in6, socklen_t};
+use core::mem::MaybeUninit;
+#[cfg(target_arch = "x86")]
+use {
+ crate::backend::conv::{slice_just_addr, x86_sys},
+ crate::backend::reg::{ArgReg, SocketArg},
+ linux_raw_sys::net::{
+ SYS_ACCEPT, SYS_ACCEPT4, SYS_BIND, SYS_CONNECT, SYS_GETPEERNAME, SYS_GETSOCKNAME,
+ SYS_LISTEN, SYS_RECV, SYS_RECVFROM, SYS_RECVMSG, SYS_SEND, SYS_SENDMSG, SYS_SENDTO,
+ SYS_SHUTDOWN, SYS_SOCKET, SYS_SOCKETPAIR,
+ },
+};
+
+#[inline]
+pub(crate) fn socket(
+ family: AddressFamily,
+ type_: SocketType,
+ protocol: Option<Protocol>,
+) -> io::Result<OwnedFd> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(__NR_socket, family, type_, protocol))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SOCKET),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ family.into(),
+ type_.into(),
+ protocol.into(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn socket_with(
+ family: AddressFamily,
+ type_: SocketType,
+ flags: SocketFlags,
+ protocol: Option<Protocol>,
+) -> io::Result<OwnedFd> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_socket,
+ family,
+ (type_, flags),
+ protocol
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SOCKET),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ family.into(),
+ (type_, flags).into(),
+ protocol.into(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn socketpair(
+ family: AddressFamily,
+ type_: SocketType,
+ flags: SocketFlags,
+ protocol: Option<Protocol>,
+) -> io::Result<(OwnedFd, OwnedFd)> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(syscall!(
+ __NR_socketpair,
+ family,
+ (type_, flags),
+ protocol,
+ &mut result
+ ))?;
+ let [fd0, fd1] = result.assume_init();
+ Ok((fd0, fd1))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_SOCKETPAIR),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ family.into(),
+ (type_, flags).into(),
+ protocol.into(),
+ (&mut result).into(),
+ ])
+ ))?;
+ let [fd0, fd1] = result.assume_init();
+ Ok((fd0, fd1))
+ }
+}
+
+#[inline]
+pub(crate) fn accept(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let fd = ret_owned_fd(syscall_readonly!(__NR_accept, fd, zero(), zero()))?;
+ Ok(fd)
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let fd = ret_owned_fd(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_ACCEPT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[fd.into(), zero(), zero()])
+ ))?;
+ Ok(fd)
+ }
+}
+
+#[inline]
+pub(crate) fn accept_with(fd: BorrowedFd<'_>, flags: SocketFlags) -> io::Result<OwnedFd> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let fd = ret_owned_fd(syscall_readonly!(__NR_accept4, fd, zero(), zero(), flags))?;
+ Ok(fd)
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let fd = ret_owned_fd(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_ACCEPT4),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[fd.into(), zero(), zero(), flags.into()])
+ ))?;
+ Ok(fd)
+ }
+}
+
+#[inline]
+pub(crate) fn acceptfrom(fd: BorrowedFd<'_>) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ let fd = ret_owned_fd(syscall!(
+ __NR_accept,
+ fd,
+ &mut storage,
+ by_mut(&mut addrlen)
+ ))?;
+ Ok((
+ fd,
+ maybe_read_sockaddr_os(&storage.assume_init(), addrlen.try_into().unwrap()),
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ let fd = ret_owned_fd(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_ACCEPT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ (&mut storage).into(),
+ by_mut(&mut addrlen),
+ ])
+ ))?;
+ Ok((
+ fd,
+ maybe_read_sockaddr_os(&storage.assume_init(), addrlen.try_into().unwrap()),
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn acceptfrom_with(
+ fd: BorrowedFd<'_>,
+ flags: SocketFlags,
+) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ let fd = ret_owned_fd(syscall!(
+ __NR_accept4,
+ fd,
+ &mut storage,
+ by_mut(&mut addrlen),
+ flags
+ ))?;
+ Ok((
+ fd,
+ maybe_read_sockaddr_os(&storage.assume_init(), addrlen.try_into().unwrap()),
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ let fd = ret_owned_fd(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_ACCEPT4),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ (&mut storage).into(),
+ by_mut(&mut addrlen),
+ flags.into(),
+ ])
+ ))?;
+ Ok((
+ fd,
+ maybe_read_sockaddr_os(&storage.assume_init(), addrlen.try_into().unwrap()),
+ ))
+ }
+}
+
+#[inline]
+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| {
+ #[cfg(not(target_arch = "x86"))]
+ let result =
+ unsafe { ret_usize(syscall!(__NR_recvmsg, sockfd, by_mut(msghdr), msg_flags)) };
+
+ #[cfg(target_arch = "x86")]
+ let result = unsafe {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_RECVMSG),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ sockfd.into(),
+ by_mut(msghdr),
+ msg_flags.into(),
+ ])
+ ))
+ };
+
+ 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(msghdr.msg_flags),
+ }
+ })
+ })
+}
+
+#[inline]
+pub(crate) fn sendmsg(
+ sockfd: BorrowedFd<'_>,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ with_noaddr_msghdr(iov, control, |msghdr| {
+ #[cfg(not(target_arch = "x86"))]
+ let result =
+ unsafe { ret_usize(syscall!(__NR_sendmsg, sockfd, by_ref(&msghdr), msg_flags)) };
+
+ #[cfg(target_arch = "x86")]
+ let result = unsafe {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDMSG),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ sockfd.into(),
+ by_ref(&msghdr),
+ msg_flags.into()
+ ])
+ ))
+ };
+
+ result
+ })
+}
+
+#[inline]
+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| {
+ #[cfg(not(target_arch = "x86"))]
+ let result =
+ unsafe { ret_usize(syscall!(__NR_sendmsg, sockfd, by_ref(&msghdr), msg_flags)) };
+
+ #[cfg(target_arch = "x86")]
+ let result = unsafe {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDMSG),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ sockfd.into(),
+ by_ref(&msghdr),
+ msg_flags.into(),
+ ])
+ ))
+ };
+
+ result
+ })
+}
+
+#[inline]
+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| {
+ #[cfg(not(target_arch = "x86"))]
+ let result =
+ unsafe { ret_usize(syscall!(__NR_sendmsg, sockfd, by_ref(&msghdr), msg_flags)) };
+
+ #[cfg(target_arch = "x86")]
+ let result = unsafe {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDMSG),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ sockfd.into(),
+ by_ref(&msghdr),
+ msg_flags.into()
+ ])
+ ))
+ };
+
+ result
+ })
+}
+
+#[inline]
+pub(crate) fn sendmsg_unix(
+ sockfd: BorrowedFd<'_>,
+ addr: &SocketAddrUnix,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ msg_flags: SendFlags,
+) -> io::Result<usize> {
+ with_unix_msghdr(addr, iov, control, |msghdr| {
+ #[cfg(not(target_arch = "x86"))]
+ let result =
+ unsafe { ret_usize(syscall!(__NR_sendmsg, sockfd, by_ref(&msghdr), msg_flags)) };
+
+ #[cfg(target_arch = "x86")]
+ let result = unsafe {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDMSG),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ sockfd.into(),
+ by_ref(&msghdr),
+ msg_flags.into()
+ ])
+ ))
+ };
+
+ result
+ })
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+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| {
+ #[cfg(not(target_arch = "x86"))]
+ let result =
+ unsafe { ret_usize(syscall!(__NR_sendmsg, sockfd, by_ref(&msghdr), msg_flags)) };
+
+ #[cfg(target_arch = "x86")]
+ let result = unsafe {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDMSG),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ sockfd.into(),
+ by_ref(&msghdr),
+ msg_flags.into()
+ ])
+ ))
+ };
+
+ result
+ })
+}
+
+#[inline]
+pub(crate) fn shutdown(fd: BorrowedFd<'_>, how: Shutdown) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_shutdown,
+ fd,
+ c_uint(how as c::c_uint)
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SHUTDOWN),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[fd.into(), c_uint(how as c::c_uint)])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn send(fd: BorrowedFd<'_>, buf: &[u8], flags: SendFlags) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ #[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "riscv64",
+ target_arch = "x86",
+ target_arch = "x86_64",
+ )))]
+ unsafe {
+ ret_usize(syscall_readonly!(__NR_send, fd, buf_addr, buf_len, flags))
+ }
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "riscv64",
+ target_arch = "x86_64",
+ ))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_sendto,
+ fd,
+ buf_addr,
+ buf_len,
+ flags,
+ zero(),
+ zero()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SEND),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf_addr,
+ buf_len,
+ flags.into()
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn sendto_v4(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrV4,
+) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_sendto,
+ fd,
+ buf_addr,
+ buf_len,
+ flags,
+ by_ref(&encode_sockaddr_v4(addr)),
+ size_of::<sockaddr_in, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDTO),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf_addr,
+ buf_len,
+ flags.into(),
+ by_ref(&encode_sockaddr_v4(addr)),
+ size_of::<sockaddr_in, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn sendto_v6(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrV6,
+) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_sendto,
+ fd,
+ buf_addr,
+ buf_len,
+ flags,
+ by_ref(&encode_sockaddr_v6(addr)),
+ size_of::<sockaddr_in6, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDTO),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf_addr,
+ buf_len,
+ flags.into(),
+ by_ref(&encode_sockaddr_v6(addr)),
+ size_of::<sockaddr_in6, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn sendto_unix(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrUnix,
+) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_sendto,
+ fd,
+ buf_addr,
+ buf_len,
+ flags,
+ by_ref(&addr.unix),
+ socklen_t(addr.addr_len())
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDTO),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf_addr,
+ buf_len,
+ flags.into(),
+ by_ref(&addr.unix),
+ socklen_t(addr.addr_len()),
+ ])
+ ))
+ }
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn sendto_xdp(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrXdp,
+) -> io::Result<usize> {
+ let (buf_addr, buf_len) = slice(buf);
+
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_sendto,
+ fd,
+ buf_addr,
+ buf_len,
+ flags,
+ by_ref(&encode_sockaddr_xdp(addr)),
+ size_of::<c::sockaddr_xdp, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret_usize(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_SENDTO),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf_addr,
+ buf_len,
+ flags.into(),
+ by_ref(&encode_sockaddr_xdp(addr)),
+ size_of::<c::sockaddr_xdp, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn recv(
+ fd: BorrowedFd<'_>,
+ buf: *mut u8,
+ len: usize,
+ flags: RecvFlags,
+) -> io::Result<usize> {
+ #[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "riscv64",
+ target_arch = "x86",
+ target_arch = "x86_64",
+ )))]
+ {
+ ret_usize(syscall!(__NR_recv, fd, buf, pass_usize(len), flags))
+ }
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "riscv64",
+ target_arch = "x86_64",
+ ))]
+ {
+ ret_usize(syscall!(
+ __NR_recvfrom,
+ fd,
+ buf,
+ pass_usize(len),
+ flags,
+ zero(),
+ zero()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ {
+ ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_RECV),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf.into(),
+ pass_usize(len),
+ flags.into(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn recvfrom(
+ fd: BorrowedFd<'_>,
+ buf: *mut u8,
+ len: usize,
+ flags: RecvFlags,
+) -> io::Result<(usize, Option<SocketAddrAny>)> {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+
+ // `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());
+
+ #[cfg(not(target_arch = "x86"))]
+ let nread = ret_usize(syscall!(
+ __NR_recvfrom,
+ fd,
+ buf,
+ pass_usize(len),
+ flags,
+ &mut storage,
+ by_mut(&mut addrlen)
+ ))?;
+ #[cfg(target_arch = "x86")]
+ let nread = ret_usize(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_RECVFROM),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ buf.into(),
+ pass_usize(len),
+ flags.into(),
+ (&mut storage).into(),
+ by_mut(&mut addrlen),
+ ])
+ ))?;
+
+ Ok((
+ nread,
+ maybe_read_sockaddr_os(&storage.assume_init(), addrlen.try_into().unwrap()),
+ ))
+}
+
+#[inline]
+pub(crate) fn getpeername(fd: BorrowedFd<'_>) -> io::Result<Option<SocketAddrAny>> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ ret(syscall!(
+ __NR_getpeername,
+ fd,
+ &mut storage,
+ by_mut(&mut addrlen)
+ ))?;
+ Ok(maybe_read_sockaddr_os(
+ &storage.assume_init(),
+ addrlen.try_into().unwrap(),
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ ret(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_GETPEERNAME),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ (&mut storage).into(),
+ by_mut(&mut addrlen),
+ ])
+ ))?;
+ Ok(maybe_read_sockaddr_os(
+ &storage.assume_init(),
+ addrlen.try_into().unwrap(),
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn getsockname(fd: BorrowedFd<'_>) -> io::Result<SocketAddrAny> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ ret(syscall!(
+ __NR_getsockname,
+ fd,
+ &mut storage,
+ by_mut(&mut addrlen)
+ ))?;
+ Ok(read_sockaddr_os(
+ &storage.assume_init(),
+ addrlen.try_into().unwrap(),
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ let mut addrlen = core::mem::size_of::<sockaddr>() as socklen_t;
+ let mut storage = MaybeUninit::<sockaddr>::uninit();
+ ret(syscall!(
+ __NR_socketcall,
+ x86_sys(SYS_GETSOCKNAME),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ (&mut storage).into(),
+ by_mut(&mut addrlen),
+ ])
+ ))?;
+ Ok(read_sockaddr_os(
+ &storage.assume_init(),
+ addrlen.try_into().unwrap(),
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn bind_v4(fd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_bind,
+ fd,
+ by_ref(&encode_sockaddr_v4(addr)),
+ size_of::<sockaddr_in, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_BIND),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&encode_sockaddr_v4(addr)),
+ size_of::<sockaddr_in, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn bind_v6(fd: BorrowedFd<'_>, addr: &SocketAddrV6) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_bind,
+ fd,
+ by_ref(&encode_sockaddr_v6(addr)),
+ size_of::<sockaddr_in6, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_BIND),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&encode_sockaddr_v6(addr)),
+ size_of::<sockaddr_in6, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn bind_unix(fd: BorrowedFd<'_>, addr: &SocketAddrUnix) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_bind,
+ fd,
+ by_ref(&addr.unix),
+ socklen_t(addr.addr_len())
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_BIND),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&addr.unix),
+ socklen_t(addr.addr_len()),
+ ])
+ ))
+ }
+}
+
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn bind_xdp(fd: BorrowedFd<'_>, addr: &SocketAddrXdp) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_bind,
+ fd,
+ by_ref(&encode_sockaddr_xdp(addr)),
+ size_of::<c::sockaddr_xdp, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_BIND),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&encode_sockaddr_xdp(addr)),
+ size_of::<c::sockaddr_xdp, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn connect_v4(fd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_connect,
+ fd,
+ by_ref(&encode_sockaddr_v4(addr)),
+ size_of::<sockaddr_in, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_CONNECT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&encode_sockaddr_v4(addr)),
+ size_of::<sockaddr_in, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn connect_v6(fd: BorrowedFd<'_>, addr: &SocketAddrV6) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_connect,
+ fd,
+ by_ref(&encode_sockaddr_v6(addr)),
+ size_of::<sockaddr_in6, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_CONNECT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&encode_sockaddr_v6(addr)),
+ size_of::<sockaddr_in6, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn connect_unix(fd: BorrowedFd<'_>, addr: &SocketAddrUnix) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_connect,
+ fd,
+ by_ref(&addr.unix),
+ socklen_t(addr.addr_len())
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_CONNECT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&addr.unix),
+ socklen_t(addr.addr_len()),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn connect_unspec(fd: BorrowedFd<'_>) -> io::Result<()> {
+ debug_assert_eq!(c::AF_UNSPEC, 0);
+ let addr = MaybeUninit::<c::sockaddr_storage>::zeroed();
+
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_connect,
+ fd,
+ by_ref(&addr),
+ size_of::<c::sockaddr_storage, _>()
+ ))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_CONNECT),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+ fd.into(),
+ by_ref(&addr),
+ size_of::<c::sockaddr_storage, _>(),
+ ])
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn listen(fd: BorrowedFd<'_>, backlog: c::c_int) -> io::Result<()> {
+ #[cfg(not(target_arch = "x86"))]
+ unsafe {
+ ret(syscall_readonly!(__NR_listen, fd, c_int(backlog)))
+ }
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_socketcall,
+ x86_sys(SYS_LISTEN),
+ slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[fd.into(), c_int(backlog)])
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/net/write_sockaddr.rs b/src/backend/linux_raw/net/write_sockaddr.rs
new file mode 100644
index 0000000..fb6e51e
--- /dev/null
+++ b/src/backend/linux_raw/net/write_sockaddr.rs
@@ -0,0 +1,82 @@
+//! 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.
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+use crate::net::{SocketAddrAny, SocketAddrStorage, SocketAddrUnix, 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),
+ 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 {
+ sin_family: c::AF_INET as _,
+ sin_port: u16::to_be(v4.port()),
+ sin_addr: c::in_addr {
+ s_addr: u32::from_ne_bytes(v4.ip().octets()),
+ },
+ __pad: [0_u8; 8],
+ }
+}
+
+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 {
+ c::sockaddr_in6 {
+ sin6_family: c::AF_INET6 as _,
+ sin6_port: u16::to_be(v6.port()),
+ sin6_flowinfo: u32::to_be(v6.flowinfo()),
+ sin6_addr: c::in6_addr {
+ in6_u: linux_raw_sys::net::in6_addr__bindgen_ty_1 {
+ u6_addr8: v6.ip().octets(),
+ },
+ },
+ sin6_scope_id: 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>()
+}
+
+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/linux_raw/param/auxv.rs b/src/backend/linux_raw/param/auxv.rs
new file mode 100644
index 0000000..796062a
--- /dev/null
+++ b/src/backend/linux_raw/param/auxv.rs
@@ -0,0 +1,565 @@
+//! Linux auxv support.
+//!
+//! # Safety
+//!
+//! This uses raw pointers to locate and read the kernel-provided auxv array.
+#![allow(unsafe_code)]
+
+use super::super::conv::{c_int, pass_usize, ret_usize};
+use crate::backend::c;
+use crate::fd::OwnedFd;
+#[cfg(feature = "param")]
+use crate::ffi::CStr;
+use crate::fs::{Mode, OFlags};
+use crate::utils::{as_ptr, check_raw_pointer};
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+use core::mem::size_of;
+use core::ptr::{null_mut, read_unaligned, NonNull};
+#[cfg(feature = "runtime")]
+use core::sync::atomic::AtomicU8;
+use core::sync::atomic::Ordering::Relaxed;
+use core::sync::atomic::{AtomicPtr, AtomicUsize};
+use linux_raw_sys::elf::*;
+use linux_raw_sys::general::{
+ AT_BASE, AT_CLKTCK, AT_EXECFN, AT_HWCAP, AT_HWCAP2, AT_NULL, AT_PAGESZ, AT_SYSINFO_EHDR,
+};
+#[cfg(feature = "runtime")]
+use linux_raw_sys::general::{
+ AT_EGID, AT_ENTRY, AT_EUID, AT_GID, AT_PHDR, AT_PHENT, AT_PHNUM, AT_RANDOM, AT_SECURE, AT_UID,
+};
+#[cfg(feature = "alloc")]
+use {alloc::borrow::Cow, alloc::vec};
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn page_size() -> usize {
+ let mut page_size = PAGE_SIZE.load(Relaxed);
+
+ if page_size == 0 {
+ init_auxv();
+ page_size = PAGE_SIZE.load(Relaxed);
+ }
+
+ page_size
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn clock_ticks_per_second() -> u64 {
+ let mut ticks = CLOCK_TICKS_PER_SECOND.load(Relaxed);
+
+ if ticks == 0 {
+ init_auxv();
+ ticks = CLOCK_TICKS_PER_SECOND.load(Relaxed);
+ }
+
+ ticks as u64
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn linux_hwcap() -> (usize, usize) {
+ let mut hwcap = HWCAP.load(Relaxed);
+ let mut hwcap2 = HWCAP2.load(Relaxed);
+
+ if hwcap == 0 || hwcap2 == 0 {
+ init_auxv();
+ hwcap = HWCAP.load(Relaxed);
+ hwcap2 = HWCAP2.load(Relaxed);
+ }
+
+ (hwcap, hwcap2)
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn linux_execfn() -> &'static CStr {
+ let mut execfn = EXECFN.load(Relaxed);
+
+ if execfn.is_null() {
+ init_auxv();
+ execfn = EXECFN.load(Relaxed);
+ }
+
+ // SAFETY: We assume the `AT_EXECFN` value provided by the kernel is a
+ // valid pointer to a valid NUL-terminated array of bytes.
+ unsafe { CStr::from_ptr(execfn.cast()) }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn linux_secure() -> bool {
+ let mut secure = SECURE.load(Relaxed);
+
+ // 0 means not initialized yet.
+ if secure == 0 {
+ init_auxv();
+ secure = SECURE.load(Relaxed);
+ }
+
+ // 0 means not present. Libc `getauxval(AT_SECURE)` would return 0.
+ // 1 means not in secure mode.
+ // 2 means in secure mode.
+ secure > 1
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn exe_phdrs() -> (*const c::c_void, usize, usize) {
+ let mut phdr = PHDR.load(Relaxed);
+ let mut phent = PHENT.load(Relaxed);
+ let mut phnum = PHNUM.load(Relaxed);
+
+ if phdr.is_null() || phnum == 0 {
+ init_auxv();
+ phdr = PHDR.load(Relaxed);
+ phent = PHENT.load(Relaxed);
+ phnum = PHNUM.load(Relaxed);
+ }
+
+ (phdr.cast(), phent, phnum)
+}
+
+/// `AT_SYSINFO_EHDR` isn't present on all platforms in all configurations, so
+/// if we don't see it, this function returns a null pointer.
+///
+/// And, this function returns a null pointer, rather than panicking, if the
+/// auxv records can't be read.
+#[inline]
+pub(in super::super) fn sysinfo_ehdr() -> *const Elf_Ehdr {
+ let mut ehdr = SYSINFO_EHDR.load(Relaxed);
+
+ if ehdr.is_null() {
+ // Use `maybe_init_auxv` to to read the aux vectors if it can, but do
+ // nothing if it can't. If it can't, then we'll get a null pointer
+ // here, which our callers are prepared to deal with.
+ maybe_init_auxv();
+
+ ehdr = SYSINFO_EHDR.load(Relaxed);
+ }
+
+ ehdr
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn entry() -> usize {
+ let mut entry = ENTRY.load(Relaxed);
+
+ if entry == 0 {
+ init_auxv();
+ entry = ENTRY.load(Relaxed);
+ }
+
+ entry
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn random() -> *const [u8; 16] {
+ let mut random = RANDOM.load(Relaxed);
+
+ if random.is_null() {
+ init_auxv();
+ random = RANDOM.load(Relaxed);
+ }
+
+ random
+}
+
+static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
+static CLOCK_TICKS_PER_SECOND: AtomicUsize = AtomicUsize::new(0);
+static HWCAP: AtomicUsize = AtomicUsize::new(0);
+static HWCAP2: AtomicUsize = AtomicUsize::new(0);
+static EXECFN: AtomicPtr<c::c_char> = AtomicPtr::new(null_mut());
+static SYSINFO_EHDR: AtomicPtr<Elf_Ehdr> = AtomicPtr::new(null_mut());
+#[cfg(feature = "runtime")]
+static SECURE: AtomicU8 = AtomicU8::new(0);
+#[cfg(feature = "runtime")]
+static PHDR: AtomicPtr<Elf_Phdr> = AtomicPtr::new(null_mut());
+#[cfg(feature = "runtime")]
+static PHENT: AtomicUsize = AtomicUsize::new(0);
+#[cfg(feature = "runtime")]
+static PHNUM: AtomicUsize = AtomicUsize::new(0);
+#[cfg(feature = "runtime")]
+static ENTRY: AtomicUsize = AtomicUsize::new(0);
+#[cfg(feature = "runtime")]
+static RANDOM: AtomicPtr<[u8; 16]> = AtomicPtr::new(null_mut());
+
+const PR_GET_AUXV: c::c_int = 0x4155_5856;
+
+/// Use Linux >= 6.4's `PR_GET_AUXV` to read the aux records, into a provided
+/// statically-sized buffer. Return:
+/// - `Ok(...)` if the buffer is big enough.
+/// - `Err(Ok(len))` if we need a buffer of length `len`.
+/// - `Err(Err(err))` if we failed with `err`.
+#[cold]
+fn pr_get_auxv_static(buffer: &mut [u8; 512]) -> Result<&mut [u8], crate::io::Result<usize>> {
+ let len = unsafe {
+ ret_usize(syscall_always_asm!(
+ __NR_prctl,
+ c_int(PR_GET_AUXV),
+ buffer.as_mut_ptr(),
+ pass_usize(buffer.len()),
+ pass_usize(0),
+ pass_usize(0)
+ ))
+ .map_err(Err)?
+ };
+ if len <= buffer.len() {
+ return Ok(&mut buffer[..len]);
+ }
+ Err(Ok(len))
+}
+
+/// Use Linux >= 6.4's `PR_GET_AUXV` to read the aux records, using a provided
+/// statically-sized buffer if possible, or a dynamically allocated buffer
+/// otherwise. Return:
+/// - Ok(...) on success.
+/// - Err(err) on failure.
+#[cfg(feature = "alloc")]
+#[cold]
+fn pr_get_auxv_dynamic(buffer: &mut [u8; 512]) -> crate::io::Result<Cow<'_, [u8]>> {
+ // First try use the static buffer.
+ let len = match pr_get_auxv_static(buffer) {
+ Ok(buffer) => return Ok(Cow::Borrowed(buffer)),
+ Err(Ok(len)) => len,
+ Err(Err(err)) => return Err(err),
+ };
+
+ // If that indicates it needs a bigger buffer, allocate one.
+ let mut buffer = vec![0u8; len];
+ let len = unsafe {
+ ret_usize(syscall_always_asm!(
+ __NR_prctl,
+ c_int(PR_GET_AUXV),
+ buffer.as_mut_ptr(),
+ pass_usize(buffer.len()),
+ pass_usize(0),
+ pass_usize(0)
+ ))?
+ };
+ assert_eq!(len, buffer.len());
+ Ok(Cow::Owned(buffer))
+}
+
+/// Read the auxv records and initialize the various static variables. Panic
+/// if an error is encountered.
+#[cold]
+fn init_auxv() {
+ init_auxv_impl().unwrap();
+}
+
+/// Like `init_auxv`, but don't panic if an error is encountered. The caller
+/// must be prepared for initialization to be skipped.
+#[cold]
+fn maybe_init_auxv() {
+ if let Ok(()) = init_auxv_impl() {
+ return;
+ }
+}
+
+/// If we don't have "use-explicitly-provided-auxv" or "use-libc-auxv", we
+/// read the aux vector via the `prctl` `PR_GET_AUXV`, with a fallback to
+/// /proc/self/auxv for kernels that don't support `PR_GET_AUXV`.
+#[cold]
+fn init_auxv_impl() -> Result<(), ()> {
+ let mut buffer = [0u8; 512];
+
+ // If we don't have "alloc", just try to read into our statically-sized
+ // buffer. This might fail due to the buffer being insufficient; we're
+ // prepared to cope, though we may do suboptimal things.
+ #[cfg(not(feature = "alloc"))]
+ let result = pr_get_auxv_static(&mut buffer);
+
+ // If we do have "alloc" then read into our statically-sized buffer if
+ // it fits, or fall back to a dynamically-allocated buffer.
+ #[cfg(feature = "alloc")]
+ let result = pr_get_auxv_dynamic(&mut buffer);
+
+ if let Ok(buffer) = result {
+ // SAFETY: We assume the kernel returns a valid auxv.
+ unsafe {
+ init_from_aux_iter(AuxPointer(buffer.as_ptr().cast())).unwrap();
+ }
+ return Ok(());
+ }
+
+ // If `PR_GET_AUXV` is unavailable, or if we don't have "alloc" and
+ // the aux records don't fit in our static buffer, then fall back to trying
+ // to open "/proc/self/auxv". We don't use `proc_self_fd` because its extra
+ // checking breaks on QEMU.
+ if let Ok(file) = crate::fs::open("/proc/self/auxv", OFlags::RDONLY, Mode::empty()) {
+ #[cfg(feature = "alloc")]
+ init_from_auxv_file(file).unwrap();
+
+ #[cfg(not(feature = "alloc"))]
+ unsafe {
+ init_from_aux_iter(AuxFile(file)).unwrap();
+ }
+
+ return Ok(());
+ }
+
+ Err(())
+}
+
+/// Process auxv entries from the open file `auxv`.
+#[cfg(feature = "alloc")]
+#[cold]
+#[must_use]
+fn init_from_auxv_file(auxv: OwnedFd) -> Option<()> {
+ let mut buffer = Vec::<u8>::with_capacity(512);
+ loop {
+ let cur = buffer.len();
+
+ // Request one extra byte; `Vec` will often allocate more.
+ buffer.reserve(1);
+
+ // Use all the space it allocated.
+ buffer.resize(buffer.capacity(), 0);
+
+ // Read up to that many bytes.
+ let n = match crate::io::read(&auxv, &mut buffer[cur..]) {
+ Err(crate::io::Errno::INTR) => 0,
+ Err(_err) => panic!(),
+ Ok(0) => break,
+ Ok(n) => n,
+ };
+
+ // Account for the number of bytes actually read.
+ buffer.resize(cur + n, 0_u8);
+ }
+
+ // SAFETY: We loaded from an auxv file into the buffer.
+ unsafe { init_from_aux_iter(AuxPointer(buffer.as_ptr().cast())) }
+}
+
+/// Process auxv entries from the auxv array pointed to by `auxp`.
+///
+/// # Safety
+///
+/// This must be passed a pointer to an auxv array.
+///
+/// The buffer contains `Elf_aux_t` elements, though it need not be aligned;
+/// function uses `read_unaligned` to read from it.
+#[cold]
+#[must_use]
+unsafe fn init_from_aux_iter(aux_iter: impl Iterator<Item = Elf_auxv_t>) -> Option<()> {
+ let mut pagesz = 0;
+ let mut clktck = 0;
+ let mut hwcap = 0;
+ let mut hwcap2 = 0;
+ let mut execfn = null_mut();
+ let mut sysinfo_ehdr = null_mut();
+ #[cfg(feature = "runtime")]
+ let mut secure = 0;
+ #[cfg(feature = "runtime")]
+ let mut phdr = null_mut();
+ #[cfg(feature = "runtime")]
+ let mut phnum = 0;
+ #[cfg(feature = "runtime")]
+ let mut phent = 0;
+ #[cfg(feature = "runtime")]
+ let mut entry = 0;
+ #[cfg(feature = "runtime")]
+ let mut uid = None;
+ #[cfg(feature = "runtime")]
+ let mut euid = None;
+ #[cfg(feature = "runtime")]
+ let mut gid = None;
+ #[cfg(feature = "runtime")]
+ let mut egid = None;
+ #[cfg(feature = "runtime")]
+ let mut random = null_mut();
+
+ for Elf_auxv_t { a_type, a_val } in aux_iter {
+ match a_type as _ {
+ AT_PAGESZ => pagesz = a_val as usize,
+ AT_CLKTCK => clktck = a_val as usize,
+ AT_HWCAP => hwcap = a_val as usize,
+ AT_HWCAP2 => hwcap2 = a_val as usize,
+ AT_EXECFN => execfn = check_raw_pointer::<c::c_char>(a_val as *mut _)?.as_ptr(),
+ AT_SYSINFO_EHDR => sysinfo_ehdr = check_elf_base(a_val as *mut _)?.as_ptr(),
+
+ AT_BASE => {
+ // The `AT_BASE` value can be NULL in a static executable that
+ // doesn't use a dynamic linker. If so, ignore it.
+ if !a_val.is_null() {
+ let _ = check_elf_base(a_val.cast())?;
+ }
+ }
+
+ #[cfg(feature = "runtime")]
+ AT_SECURE => secure = (a_val as usize != 0) as u8 + 1,
+ #[cfg(feature = "runtime")]
+ AT_UID => uid = Some(a_val),
+ #[cfg(feature = "runtime")]
+ AT_EUID => euid = Some(a_val),
+ #[cfg(feature = "runtime")]
+ AT_GID => gid = Some(a_val),
+ #[cfg(feature = "runtime")]
+ AT_EGID => egid = Some(a_val),
+ #[cfg(feature = "runtime")]
+ AT_PHDR => phdr = check_raw_pointer::<Elf_Phdr>(a_val as *mut _)?.as_ptr(),
+ #[cfg(feature = "runtime")]
+ AT_PHNUM => phnum = a_val as usize,
+ #[cfg(feature = "runtime")]
+ AT_PHENT => phent = a_val as usize,
+ #[cfg(feature = "runtime")]
+ AT_ENTRY => entry = a_val as usize,
+ #[cfg(feature = "runtime")]
+ AT_RANDOM => random = check_raw_pointer::<[u8; 16]>(a_val as *mut _)?.as_ptr(),
+
+ AT_NULL => break,
+ _ => (),
+ }
+ }
+
+ #[cfg(feature = "runtime")]
+ assert_eq!(phent, size_of::<Elf_Phdr>());
+
+ // If we're running set-uid or set-gid, enable “secure execution” mode,
+ // which doesn't do much, but users may be depending on the things that
+ // it does do.
+ #[cfg(feature = "runtime")]
+ if uid != euid || gid != egid {
+ secure = 2;
+ }
+
+ // The base and sysinfo_ehdr (if present) matches our platform. Accept the
+ // aux values.
+ PAGE_SIZE.store(pagesz, Relaxed);
+ CLOCK_TICKS_PER_SECOND.store(clktck, Relaxed);
+ HWCAP.store(hwcap, Relaxed);
+ HWCAP2.store(hwcap2, Relaxed);
+ EXECFN.store(execfn, Relaxed);
+ SYSINFO_EHDR.store(sysinfo_ehdr, Relaxed);
+ #[cfg(feature = "runtime")]
+ SECURE.store(secure, Relaxed);
+ #[cfg(feature = "runtime")]
+ PHDR.store(phdr, Relaxed);
+ #[cfg(feature = "runtime")]
+ PHNUM.store(phnum, Relaxed);
+ #[cfg(feature = "runtime")]
+ ENTRY.store(entry, Relaxed);
+ #[cfg(feature = "runtime")]
+ RANDOM.store(random, Relaxed);
+
+ Some(())
+}
+
+/// Check that `base` is a valid pointer to the kernel-provided vDSO.
+///
+/// `base` is some value we got from a `AT_SYSINFO_EHDR` aux record somewhere,
+/// which hopefully holds the value of the kernel-provided vDSO in memory. Do a
+/// series of checks to be as sure as we can that it's safe to use.
+#[cold]
+#[must_use]
+unsafe fn check_elf_base(base: *const Elf_Ehdr) -> Option<NonNull<Elf_Ehdr>> {
+ // If we're reading a 64-bit auxv on a 32-bit platform, we'll see a zero
+ // `a_val` because `AT_*` values are never greater than `u32::MAX`. Zero is
+ // used by libc's `getauxval` to indicate errors, so it should never be a
+ // valid value.
+ if base.is_null() {
+ return None;
+ }
+
+ let hdr = match check_raw_pointer::<Elf_Ehdr>(base as *mut _) {
+ Some(hdr) => hdr,
+ None => return None,
+ };
+
+ let hdr = hdr.as_ref();
+ if hdr.e_ident[..SELFMAG] != ELFMAG {
+ return None; // Wrong ELF magic
+ }
+ if !matches!(hdr.e_ident[EI_OSABI], ELFOSABI_SYSV | ELFOSABI_LINUX) {
+ return None; // Unrecognized ELF OS ABI
+ }
+ if hdr.e_ident[EI_ABIVERSION] != ELFABIVERSION {
+ return None; // Unrecognized ELF ABI version
+ }
+ if hdr.e_type != ET_DYN {
+ return None; // Wrong ELF type
+ }
+
+ // If ELF is extended, we'll need to adjust.
+ if hdr.e_ident[EI_VERSION] != EV_CURRENT
+ || hdr.e_ehsize as usize != size_of::<Elf_Ehdr>()
+ || hdr.e_phentsize as usize != size_of::<Elf_Phdr>()
+ {
+ return None;
+ }
+ // We don't currently support extra-large numbers of segments.
+ if hdr.e_phnum == PN_XNUM {
+ return None;
+ }
+
+ // If `e_phoff` is zero, it's more likely that we're looking at memory that
+ // has been zeroed than that the kernel has somehow aliased the `Ehdr` and
+ // the `Phdr`.
+ if hdr.e_phoff < size_of::<Elf_Ehdr>() {
+ return None;
+ }
+
+ // Verify that the `EI_CLASS`/`EI_DATA`/`e_machine` fields match the
+ // architecture we're running as. This helps catch cases where we're
+ // running under QEMU.
+ if hdr.e_ident[EI_CLASS] != ELFCLASS {
+ return None; // Wrong ELF class
+ }
+ if hdr.e_ident[EI_DATA] != ELFDATA {
+ return None; // Wrong ELF data
+ }
+ if hdr.e_machine != EM_CURRENT {
+ return None; // Wrong machine type
+ }
+
+ Some(NonNull::new_unchecked(as_ptr(hdr) as *mut _))
+}
+
+// Aux reading utilities
+
+// Read auxv records from an array in memory.
+struct AuxPointer(*const Elf_auxv_t);
+
+impl Iterator for AuxPointer {
+ type Item = Elf_auxv_t;
+
+ #[cold]
+ fn next(&mut self) -> Option<Self::Item> {
+ unsafe {
+ let value = read_unaligned(self.0);
+ self.0 = self.0.add(1);
+ Some(value)
+ }
+ }
+}
+
+// Read auxv records from a file.
+#[cfg(not(feature = "alloc"))]
+struct AuxFile(OwnedFd);
+
+#[cfg(not(feature = "alloc"))]
+impl Iterator for AuxFile {
+ type Item = Elf_auxv_t;
+
+ // This implementation does lots of `read`s and it isn't amazing, but
+ // hopefully we won't use it often.
+ #[cold]
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut buf = [0_u8; size_of::<Self::Item>()];
+ let mut slice = &mut buf[..];
+ while !slice.is_empty() {
+ match crate::io::read(&self.0, slice) {
+ Ok(0) => panic!("unexpected end of auxv file"),
+ Ok(n) => slice = &mut slice[n..],
+ Err(crate::io::Errno::INTR) => continue,
+ Err(err) => panic!("{:?}", err),
+ }
+ }
+ Some(unsafe { read_unaligned(buf.as_ptr().cast()) })
+ }
+}
diff --git a/src/backend/linux_raw/param/init.rs b/src/backend/linux_raw/param/init.rs
new file mode 100644
index 0000000..fe29e9c
--- /dev/null
+++ b/src/backend/linux_raw/param/init.rs
@@ -0,0 +1,171 @@
+//! Linux auxv `init` function, for "use-explicitly-provided-auxv" mode.
+//!
+//! # Safety
+//!
+//! This uses raw pointers to locate and read the kernel-provided auxv array.
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+#[cfg(feature = "param")]
+use crate::ffi::CStr;
+use core::ffi::c_void;
+use core::ptr::{null_mut, read, NonNull};
+#[cfg(feature = "runtime")]
+use core::sync::atomic::AtomicBool;
+use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
+use linux_raw_sys::elf::*;
+use linux_raw_sys::general::{
+ AT_CLKTCK, AT_EXECFN, AT_HWCAP, AT_HWCAP2, AT_NULL, AT_PAGESZ, AT_SYSINFO_EHDR,
+};
+#[cfg(feature = "runtime")]
+use linux_raw_sys::general::{AT_ENTRY, AT_PHDR, AT_PHENT, AT_PHNUM, AT_RANDOM, AT_SECURE};
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn page_size() -> usize {
+ unsafe { PAGE_SIZE.load(Ordering::Relaxed) }
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn clock_ticks_per_second() -> u64 {
+ unsafe { CLOCK_TICKS_PER_SECOND.load(Ordering::Relaxed) as u64 }
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn linux_hwcap() -> (usize, usize) {
+ unsafe {
+ (
+ HWCAP.load(Ordering::Relaxed),
+ HWCAP2.load(Ordering::Relaxed),
+ )
+ }
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn linux_execfn() -> &'static CStr {
+ let execfn = unsafe { EXECFN.load(Ordering::Relaxed) };
+
+ // SAFETY: We initialize `EXECFN` to a valid `CStr` pointer, and we assume
+ // the `AT_EXECFN` value provided by the kernel points to a valid C string.
+ unsafe { CStr::from_ptr(execfn.cast()) }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn linux_secure() -> bool {
+ unsafe { SECURE.load(Ordering::Relaxed) }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn exe_phdrs() -> (*const c_void, usize, usize) {
+ unsafe {
+ (
+ PHDR.load(Ordering::Relaxed).cast(),
+ PHENT.load(Ordering::Relaxed),
+ PHNUM.load(Ordering::Relaxed),
+ )
+ }
+}
+
+/// `AT_SYSINFO_EHDR` isn't present on all platforms in all configurations, so
+/// if we don't see it, this function returns a null pointer.
+#[inline]
+pub(in super::super) fn sysinfo_ehdr() -> *const Elf_Ehdr {
+ unsafe { SYSINFO_EHDR.load(Ordering::Relaxed) }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn entry() -> usize {
+ unsafe { ENTRY.load(Ordering::Relaxed) }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn random() -> *const [u8; 16] {
+ unsafe { RANDOM.load(Ordering::Relaxed) }
+}
+
+static mut PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
+static mut CLOCK_TICKS_PER_SECOND: AtomicUsize = AtomicUsize::new(0);
+static mut HWCAP: AtomicUsize = AtomicUsize::new(0);
+static mut HWCAP2: AtomicUsize = AtomicUsize::new(0);
+static mut SYSINFO_EHDR: AtomicPtr<Elf_Ehdr> = AtomicPtr::new(null_mut());
+// Initialize `EXECFN` to a valid `CStr` pointer so that we don't need to check
+// for null on every `execfn` call.
+static mut EXECFN: AtomicPtr<c::c_char> = AtomicPtr::new(b"\0".as_ptr() as _);
+#[cfg(feature = "runtime")]
+static mut SECURE: AtomicBool = AtomicBool::new(false);
+// Use `dangling` so that we can always treat it like an empty slice.
+#[cfg(feature = "runtime")]
+static mut PHDR: AtomicPtr<Elf_Phdr> = AtomicPtr::new(NonNull::dangling().as_ptr());
+#[cfg(feature = "runtime")]
+static mut PHENT: AtomicUsize = AtomicUsize::new(0);
+#[cfg(feature = "runtime")]
+static mut PHNUM: AtomicUsize = AtomicUsize::new(0);
+#[cfg(feature = "runtime")]
+static mut ENTRY: AtomicUsize = AtomicUsize::new(0);
+#[cfg(feature = "runtime")]
+static mut RANDOM: AtomicPtr<[u8; 16]> = AtomicPtr::new(NonNull::dangling().as_ptr());
+
+/// When "use-explicitly-provided-auxv" is enabled, we export a function to be
+/// called during initialization, and passed a pointer to the original
+/// environment variable block set up by the OS.
+pub(crate) unsafe fn init(envp: *mut *mut u8) {
+ init_from_envp(envp);
+}
+
+/// # Safety
+///
+/// This must be passed a pointer to the environment variable buffer
+/// provided by the kernel, which is followed in memory by the auxv array.
+unsafe fn init_from_envp(mut envp: *mut *mut u8) {
+ while !(*envp).is_null() {
+ envp = envp.add(1);
+ }
+ init_from_auxp(envp.add(1).cast())
+}
+
+/// Process auxv entries from the auxv array pointed to by `auxp`.
+///
+/// # Safety
+///
+/// This must be passed a pointer to an auxv array.
+///
+/// The buffer contains `Elf_aux_t` elements, though it need not be aligned;
+/// function uses `read_unaligned` to read from it.
+unsafe fn init_from_auxp(mut auxp: *const Elf_auxv_t) {
+ loop {
+ let Elf_auxv_t { a_type, a_val } = read(auxp);
+
+ match a_type as _ {
+ AT_PAGESZ => PAGE_SIZE.store(a_val as usize, Ordering::Relaxed),
+ AT_CLKTCK => CLOCK_TICKS_PER_SECOND.store(a_val as usize, Ordering::Relaxed),
+ AT_HWCAP => HWCAP.store(a_val as usize, Ordering::Relaxed),
+ AT_HWCAP2 => HWCAP2.store(a_val as usize, Ordering::Relaxed),
+ AT_EXECFN => EXECFN.store(a_val.cast::<c::c_char>(), Ordering::Relaxed),
+ AT_SYSINFO_EHDR => SYSINFO_EHDR.store(a_val.cast::<Elf_Ehdr>(), Ordering::Relaxed),
+
+ #[cfg(feature = "runtime")]
+ AT_SECURE => SECURE.store(a_val as usize != 0, Ordering::Relaxed),
+ #[cfg(feature = "runtime")]
+ AT_PHDR => PHDR.store(a_val.cast::<Elf_Phdr>(), Ordering::Relaxed),
+ #[cfg(feature = "runtime")]
+ AT_PHNUM => PHNUM.store(a_val as usize, Ordering::Relaxed),
+ #[cfg(feature = "runtime")]
+ AT_PHENT => PHENT.store(a_val as usize, Ordering::Relaxed),
+ #[cfg(feature = "runtime")]
+ AT_ENTRY => ENTRY.store(a_val as usize, Ordering::Relaxed),
+ #[cfg(feature = "runtime")]
+ AT_RANDOM => RANDOM.store(a_val.cast::<[u8; 16]>(), Ordering::Relaxed),
+
+ AT_NULL => break,
+ _ => (),
+ }
+ auxp = auxp.add(1);
+ }
+}
diff --git a/src/backend/linux_raw/param/libc_auxv.rs b/src/backend/linux_raw/param/libc_auxv.rs
new file mode 100644
index 0000000..cfdd7a5
--- /dev/null
+++ b/src/backend/linux_raw/param/libc_auxv.rs
@@ -0,0 +1,175 @@
+//! Linux auxv support, using libc.
+//!
+//! # Safety
+//!
+//! This uses raw pointers to locate and read the kernel-provided auxv array.
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+#[cfg(feature = "param")]
+use crate::ffi::CStr;
+#[cfg(not(feature = "runtime"))]
+use core::ptr::null;
+use linux_raw_sys::elf::*;
+
+// `getauxval` wasn't supported in glibc until 2.16. Also this lets us use
+// `*mut` as the return type to preserve strict provenance.
+#[cfg(not(feature = "runtime"))]
+weak!(fn getauxval(c::c_ulong) -> *mut c::c_void);
+
+// With the "runtime" feature, go ahead and depend on `getauxval` existing so
+// that we never fail.
+#[cfg(feature = "runtime")]
+extern "C" {
+ fn getauxval(type_: c::c_ulong) -> *mut c::c_void;
+}
+
+#[cfg(feature = "runtime")]
+const AT_PHDR: c::c_ulong = 3;
+#[cfg(feature = "runtime")]
+const AT_PHENT: c::c_ulong = 4;
+#[cfg(feature = "runtime")]
+const AT_PHNUM: c::c_ulong = 5;
+#[cfg(feature = "runtime")]
+const AT_ENTRY: c::c_ulong = 9;
+const AT_HWCAP: c::c_ulong = 16;
+#[cfg(feature = "runtime")]
+const AT_RANDOM: c::c_ulong = 25;
+const AT_HWCAP2: c::c_ulong = 26;
+const AT_SECURE: c::c_ulong = 23;
+const AT_EXECFN: c::c_ulong = 31;
+const AT_SYSINFO_EHDR: c::c_ulong = 33;
+
+// Declare `sysconf` ourselves so that we don't depend on all of libc just for
+// this.
+extern "C" {
+ fn sysconf(name: c::c_int) -> c::c_long;
+}
+
+#[cfg(target_os = "android")]
+const _SC_PAGESIZE: c::c_int = 39;
+#[cfg(target_os = "linux")]
+const _SC_PAGESIZE: c::c_int = 30;
+#[cfg(target_os = "android")]
+const _SC_CLK_TCK: c::c_int = 6;
+#[cfg(target_os = "linux")]
+const _SC_CLK_TCK: c::c_int = 2;
+
+#[test]
+fn test_abi() {
+ const_assert_eq!(self::_SC_PAGESIZE, ::libc::_SC_PAGESIZE);
+ const_assert_eq!(self::_SC_CLK_TCK, ::libc::_SC_CLK_TCK);
+ const_assert_eq!(self::AT_HWCAP, ::libc::AT_HWCAP);
+ const_assert_eq!(self::AT_HWCAP2, ::libc::AT_HWCAP2);
+ const_assert_eq!(self::AT_EXECFN, ::libc::AT_EXECFN);
+ const_assert_eq!(self::AT_SECURE, ::libc::AT_SECURE);
+ const_assert_eq!(self::AT_SYSINFO_EHDR, ::libc::AT_SYSINFO_EHDR);
+ #[cfg(feature = "runtime")]
+ const_assert_eq!(self::AT_PHDR, ::libc::AT_PHDR);
+ #[cfg(feature = "runtime")]
+ const_assert_eq!(self::AT_PHNUM, ::libc::AT_PHNUM);
+ #[cfg(feature = "runtime")]
+ const_assert_eq!(self::AT_ENTRY, ::libc::AT_ENTRY);
+ #[cfg(feature = "runtime")]
+ const_assert_eq!(self::AT_RANDOM, ::libc::AT_RANDOM);
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn page_size() -> usize {
+ unsafe { sysconf(_SC_PAGESIZE) as usize }
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn clock_ticks_per_second() -> u64 {
+ unsafe { sysconf(_SC_CLK_TCK) as u64 }
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn linux_hwcap() -> (usize, usize) {
+ #[cfg(not(feature = "runtime"))]
+ unsafe {
+ if let Some(libc_getauxval) = getauxval.get() {
+ let hwcap = libc_getauxval(AT_HWCAP) as usize;
+ let hwcap2 = libc_getauxval(AT_HWCAP2) as usize;
+ (hwcap, hwcap2)
+ } else {
+ (0, 0)
+ }
+ }
+
+ #[cfg(feature = "runtime")]
+ unsafe {
+ let hwcap = getauxval(AT_HWCAP) as usize;
+ let hwcap2 = getauxval(AT_HWCAP2) as usize;
+ (hwcap, hwcap2)
+ }
+}
+
+#[cfg(feature = "param")]
+#[inline]
+pub(crate) fn linux_execfn() -> &'static CStr {
+ #[cfg(not(feature = "runtime"))]
+ unsafe {
+ if let Some(libc_getauxval) = getauxval.get() {
+ CStr::from_ptr(libc_getauxval(AT_EXECFN).cast())
+ } else {
+ cstr!("")
+ }
+ }
+
+ #[cfg(feature = "runtime")]
+ unsafe {
+ CStr::from_ptr(getauxval(AT_EXECFN).cast())
+ }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn linux_secure() -> bool {
+ unsafe { getauxval(AT_SECURE) as usize != 0 }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn exe_phdrs() -> (*const c::c_void, usize, usize) {
+ unsafe {
+ let phdr = getauxval(AT_PHDR) as *const c::c_void;
+ let phent = getauxval(AT_PHENT) as usize;
+ let phnum = getauxval(AT_PHNUM) as usize;
+ (phdr, phent, phnum)
+ }
+}
+
+/// `AT_SYSINFO_EHDR` isn't present on all platforms in all configurations, so
+/// if we don't see it, this function returns a null pointer.
+#[inline]
+pub(in super::super) fn sysinfo_ehdr() -> *const Elf_Ehdr {
+ #[cfg(not(feature = "runtime"))]
+ unsafe {
+ if let Some(libc_getauxval) = getauxval.get() {
+ libc_getauxval(AT_SYSINFO_EHDR) as *const Elf_Ehdr
+ } else {
+ null()
+ }
+ }
+
+ #[cfg(feature = "runtime")]
+ unsafe {
+ getauxval(AT_SYSINFO_EHDR) as *const Elf_Ehdr
+ }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn entry() -> usize {
+ unsafe { getauxval(AT_ENTRY) as usize }
+}
+
+#[cfg(feature = "runtime")]
+#[inline]
+pub(crate) fn random() -> *const [u8; 16] {
+ unsafe { getauxval(AT_RANDOM) as *const [u8; 16] }
+}
diff --git a/src/backend/linux_raw/param/mod.rs b/src/backend/linux_raw/param/mod.rs
new file mode 100644
index 0000000..365f016
--- /dev/null
+++ b/src/backend/linux_raw/param/mod.rs
@@ -0,0 +1,15 @@
+// With "use-explicitly-provided-auxv" enabled, we expect to be initialized
+// with an explicit `rustix::param::init` call.
+//
+// With "use-libc-auxv" enabled, use libc's `getauxval`.
+//
+// Otherwise, we read aux values from /proc/self/auxv.
+#[cfg_attr(feature = "use-explicitly-provided-auxv", path = "init.rs")]
+#[cfg_attr(
+ all(
+ not(feature = "use-explicitly-provided-auxv"),
+ feature = "use-libc-auxv"
+ ),
+ path = "libc_auxv.rs"
+)]
+pub(crate) mod auxv;
diff --git a/src/backend/linux_raw/pid/mod.rs b/src/backend/linux_raw/pid/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/linux_raw/pid/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/pid/syscalls.rs b/src/backend/linux_raw/pid/syscalls.rs
new file mode 100644
index 0000000..9665ab3
--- /dev/null
+++ b/src/backend/linux_raw/pid/syscalls.rs
@@ -0,0 +1,17 @@
+//! linux_raw syscalls for PIDs
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::ret_usize_infallible;
+use crate::pid::{Pid, RawPid};
+
+#[inline]
+pub(crate) fn getpid() -> Pid {
+ unsafe {
+ let pid = ret_usize_infallible(syscall_readonly!(__NR_getpid)) as RawPid;
+ Pid::from_raw_unchecked(pid)
+ }
+}
diff --git a/src/backend/linux_raw/pipe/mod.rs b/src/backend/linux_raw/pipe/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/linux_raw/pipe/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/pipe/syscalls.rs b/src/backend/linux_raw/pipe/syscalls.rs
new file mode 100644
index 0000000..ec3e459
--- /dev/null
+++ b/src/backend/linux_raw/pipe/syscalls.rs
@@ -0,0 +1,135 @@
+//! linux_raw syscalls supporting `rustix::pipe`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::{c_int, c_uint, opt_mut, pass_usize, ret, ret_usize, slice};
+use crate::backend::{c, MAX_IOV};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::io;
+use crate::pipe::{IoSliceRaw, PipeFlags, SpliceFlags};
+use core::cmp;
+use core::mem::MaybeUninit;
+use linux_raw_sys::general::{F_GETPIPE_SZ, F_SETPIPE_SZ};
+
+#[inline]
+pub(crate) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> {
+ // aarch64 and risc64 omit `__NR_pipe`. On mips, `__NR_pipe` uses a special
+ // calling convention, but using it is not worth complicating our syscall
+ // wrapping infrastructure at this time.
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "riscv64",
+ ))]
+ {
+ pipe_with(PipeFlags::empty())
+ }
+ #[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "riscv64",
+ )))]
+ unsafe {
+ let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(syscall!(__NR_pipe, &mut result))?;
+ let [p0, p1] = result.assume_init();
+ Ok((p0, p1))
+ }
+}
+
+#[inline]
+pub(crate) fn pipe_with(flags: PipeFlags) -> io::Result<(OwnedFd, OwnedFd)> {
+ unsafe {
+ let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
+ ret(syscall!(__NR_pipe2, &mut result, flags))?;
+ let [p0, p1] = result.assume_init();
+ Ok((p0, p1))
+ }
+}
+
+#[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> {
+ unsafe {
+ ret_usize(syscall!(
+ __NR_splice,
+ fd_in,
+ opt_mut(off_in),
+ fd_out,
+ opt_mut(off_out),
+ pass_usize(len),
+ flags
+ ))
+ }
+}
+
+#[inline]
+pub unsafe fn vmsplice(
+ fd: BorrowedFd<'_>,
+ bufs: &[IoSliceRaw<'_>],
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ let (bufs_addr, bufs_len) = slice(&bufs[..cmp::min(bufs.len(), MAX_IOV)]);
+ ret_usize(syscall!(__NR_vmsplice, fd, bufs_addr, bufs_len, flags))
+}
+
+#[inline]
+pub fn tee(
+ fd_in: BorrowedFd<'_>,
+ fd_out: BorrowedFd<'_>,
+ len: usize,
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ unsafe { ret_usize(syscall!(__NR_tee, fd_in, fd_out, pass_usize(len), flags)) }
+}
+
+#[inline]
+pub(crate) fn fcntl_getpipe_sz(fd: BorrowedFd<'_>) -> io::Result<usize> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret_usize(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETPIPE_SZ)))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret_usize(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETPIPE_SZ)))
+ }
+}
+
+#[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)?;
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fcntl64,
+ fd,
+ c_uint(F_SETPIPE_SZ),
+ c_int(size)
+ ))
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_fcntl,
+ fd,
+ c_uint(F_SETPIPE_SZ),
+ c_int(size)
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/pipe/types.rs b/src/backend/linux_raw/pipe/types.rs
new file mode 100644
index 0000000..2d1ed9a
--- /dev/null
+++ b/src/backend/linux_raw/pipe/types.rs
@@ -0,0 +1,80 @@
+use crate::backend::c;
+use bitflags::bitflags;
+use core::marker::PhantomData;
+
+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: c::c_uint {
+ /// `O_CLOEXEC`
+ const CLOEXEC = linux_raw_sys::general::O_CLOEXEC;
+ /// `O_DIRECT`
+ const DIRECT = linux_raw_sys::general::O_DIRECT;
+ /// `O_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::O_NONBLOCK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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 = linux_raw_sys::general::SPLICE_F_MOVE;
+ /// `SPLICE_F_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::SPLICE_F_NONBLOCK;
+ /// `SPLICE_F_MORE`
+ const MORE = linux_raw_sys::general::SPLICE_F_MORE;
+ /// `SPLICE_F_GIFT`
+ const GIFT = linux_raw_sys::general::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
+#[repr(transparent)]
+pub struct IoSliceRaw<'a> {
+ _buf: c::iovec,
+ _lifetime: PhantomData<&'a ()>,
+}
+
+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).cast::<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().cast::<c::c_void>(),
+ iov_len: buf.len() as _,
+ },
+ _lifetime: PhantomData,
+ }
+ }
+}
diff --git a/src/backend/linux_raw/prctl/mod.rs b/src/backend/linux_raw/prctl/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/linux_raw/prctl/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/prctl/syscalls.rs b/src/backend/linux_raw/prctl/syscalls.rs
new file mode 100644
index 0000000..1410d51
--- /dev/null
+++ b/src/backend/linux_raw/prctl/syscalls.rs
@@ -0,0 +1,21 @@
+//! linux_raw syscalls supporting modules that use `prctl`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+use crate::backend::conv::{c_int, 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(syscall!(__NR_prctl, c_int(option), arg2, arg3, arg4, arg5))
+}
diff --git a/src/backend/linux_raw/process/cpu_set.rs b/src/backend/linux_raw/process/cpu_set.rs
new file mode 100644
index 0000000..35146cf
--- /dev/null
+++ b/src/backend/linux_raw/process/cpu_set.rs
@@ -0,0 +1,46 @@
+//! Rust implementation of the `CPU_*` macro API.
+
+#![allow(non_snake_case)]
+
+use super::types::RawCpuSet;
+use core::mem::size_of_val;
+
+#[inline]
+pub(crate) fn CPU_SET(cpu: usize, cpuset: &mut RawCpuSet) {
+ let size_in_bits = 8 * size_of_val(&cpuset.bits[0]); // 32, 64 etc
+ let (idx, offset) = (cpu / size_in_bits, cpu % size_in_bits);
+ cpuset.bits[idx] |= 1 << offset
+}
+
+#[inline]
+pub(crate) fn CPU_ZERO(cpuset: &mut RawCpuSet) {
+ cpuset.bits.fill(0)
+}
+
+#[inline]
+pub(crate) fn CPU_CLR(cpu: usize, cpuset: &mut RawCpuSet) {
+ let size_in_bits = 8 * size_of_val(&cpuset.bits[0]); // 32, 64 etc
+ let (idx, offset) = (cpu / size_in_bits, cpu % size_in_bits);
+ cpuset.bits[idx] &= !(1 << offset)
+}
+
+#[inline]
+pub(crate) fn CPU_ISSET(cpu: usize, cpuset: &RawCpuSet) -> bool {
+ let size_in_bits = 8 * size_of_val(&cpuset.bits[0]);
+ let (idx, offset) = (cpu / size_in_bits, cpu % size_in_bits);
+ (cpuset.bits[idx] & (1 << offset)) != 0
+}
+
+#[inline]
+pub(crate) fn CPU_COUNT_S(size_in_bytes: usize, cpuset: &RawCpuSet) -> u32 {
+ let size_of_mask = size_of_val(&cpuset.bits[0]);
+ let idx = size_in_bytes / size_of_mask;
+ cpuset.bits[..idx]
+ .iter()
+ .fold(0, |acc, i| acc + i.count_ones())
+}
+
+#[inline]
+pub(crate) fn CPU_COUNT(cpuset: &RawCpuSet) -> u32 {
+ CPU_COUNT_S(core::mem::size_of::<RawCpuSet>(), cpuset)
+}
diff --git a/src/backend/linux_raw/process/mod.rs b/src/backend/linux_raw/process/mod.rs
new file mode 100644
index 0000000..9b2c25f
--- /dev/null
+++ b/src/backend/linux_raw/process/mod.rs
@@ -0,0 +1,4 @@
+pub(crate) mod cpu_set;
+pub(crate) mod syscalls;
+pub(crate) mod types;
+pub(crate) mod wait;
diff --git a/src/backend/linux_raw/process/syscalls.rs b/src/backend/linux_raw/process/syscalls.rs
new file mode 100644
index 0000000..bc00af9
--- /dev/null
+++ b/src/backend/linux_raw/process/syscalls.rs
@@ -0,0 +1,603 @@
+//! linux_raw syscalls supporting `rustix::process`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use super::types::RawCpuSet;
+use crate::backend::c;
+#[cfg(all(feature = "alloc", feature = "fs"))]
+use crate::backend::conv::slice_mut;
+use crate::backend::conv::{
+ by_mut, by_ref, c_int, c_uint, negative_pid, pass_usize, raw_fd, ret, ret_c_int,
+ ret_c_int_infallible, ret_c_uint, ret_infallible, ret_owned_fd, ret_usize, size_of,
+ slice_just_addr, zero,
+};
+use crate::fd::{AsRawFd, BorrowedFd, OwnedFd, RawFd};
+#[cfg(feature = "fs")]
+use crate::ffi::CStr;
+use crate::io;
+use crate::pid::RawPid;
+use crate::process::{
+ Cpuid, MembarrierCommand, MembarrierQuery, Pid, PidfdFlags, PidfdGetfdFlags, Resource, Rlimit,
+ Uid, WaitId, WaitOptions, WaitStatus, WaitidOptions, WaitidStatus,
+};
+use crate::signal::Signal;
+use crate::utils::as_mut_ptr;
+use core::mem::MaybeUninit;
+use core::ptr::{null, null_mut};
+use linux_raw_sys::general::{
+ membarrier_cmd, membarrier_cmd_flag, rlimit64, PRIO_PGRP, PRIO_PROCESS, PRIO_USER,
+ RLIM64_INFINITY,
+};
+#[cfg(feature = "fs")]
+use {crate::backend::conv::ret_c_uint_infallible, crate::fs::Mode};
+#[cfg(feature = "alloc")]
+use {crate::backend::conv::slice_just_addr_mut, crate::process::Gid};
+
+// `sched_getcpu` has special optimizations via the vDSO on some architectures.
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+pub(crate) use crate::backend::vdso_wrappers::sched_getcpu;
+
+// `sched_getcpu` on platforms without a vDSO entry for it.
+#[cfg(not(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+)))]
+#[inline]
+pub(crate) fn sched_getcpu() -> usize {
+ let mut cpu = MaybeUninit::<u32>::uninit();
+ unsafe {
+ let r = ret(syscall!(__NR_getcpu, &mut cpu, zero(), zero()));
+ debug_assert!(r.is_ok());
+ cpu.assume_init() as usize
+ }
+}
+
+#[cfg(feature = "fs")]
+#[inline]
+pub(crate) fn chdir(filename: &CStr) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_chdir, filename)) }
+}
+
+#[inline]
+pub(crate) fn fchdir(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_fchdir, fd)) }
+}
+
+#[cfg(feature = "fs")]
+#[inline]
+pub(crate) fn chroot(filename: &CStr) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_chroot, filename)) }
+}
+
+#[cfg(all(feature = "alloc", feature = "fs"))]
+#[inline]
+pub(crate) fn getcwd(buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
+ let (buf_addr_mut, buf_len) = slice_mut(buf);
+ unsafe { ret_usize(syscall!(__NR_getcwd, buf_addr_mut, buf_len)) }
+}
+
+#[inline]
+pub(crate) fn membarrier_query() -> MembarrierQuery {
+ unsafe {
+ match ret_c_uint(syscall!(
+ __NR_membarrier,
+ c_int(membarrier_cmd::MEMBARRIER_CMD_QUERY as _),
+ c_uint(0)
+ )) {
+ Ok(query) => MembarrierQuery::from_bits_retain(query),
+ Err(_) => MembarrierQuery::empty(),
+ }
+ }
+}
+
+#[inline]
+pub(crate) fn membarrier(cmd: MembarrierCommand) -> io::Result<()> {
+ unsafe { ret(syscall!(__NR_membarrier, cmd, c_uint(0))) }
+}
+
+#[inline]
+pub(crate) fn membarrier_cpu(cmd: MembarrierCommand, cpu: Cpuid) -> io::Result<()> {
+ unsafe {
+ ret(syscall!(
+ __NR_membarrier,
+ cmd,
+ c_uint(membarrier_cmd_flag::MEMBARRIER_CMD_FLAG_CPU as _),
+ cpu
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn getppid() -> Option<Pid> {
+ unsafe {
+ let ppid = ret_c_int_infallible(syscall_readonly!(__NR_getppid));
+ Pid::from_raw(ppid)
+ }
+}
+
+#[inline]
+pub(crate) fn getpgid(pid: Option<Pid>) -> io::Result<Pid> {
+ unsafe {
+ let pgid = ret_c_int(syscall_readonly!(__NR_getpgid, c_int(Pid::as_raw(pid))))?;
+ debug_assert!(pgid > 0);
+ Ok(Pid::from_raw_unchecked(pgid))
+ }
+}
+
+#[inline]
+pub(crate) fn setpgid(pid: Option<Pid>, pgid: Option<Pid>) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_setpgid,
+ c_int(Pid::as_raw(pid)),
+ c_int(Pid::as_raw(pgid))
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn getpgrp() -> Pid {
+ // Use the `getpgrp` syscall if available.
+ #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
+ unsafe {
+ let pgid = ret_c_int_infallible(syscall_readonly!(__NR_getpgrp));
+ debug_assert!(pgid > 0);
+ Pid::from_raw_unchecked(pgid)
+ }
+
+ // Otherwise use `getpgrp` and pass it zero.
+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
+ unsafe {
+ let pgid = ret_c_int_infallible(syscall_readonly!(__NR_getpgid, c_uint(0)));
+ debug_assert!(pgid > 0);
+ Pid::from_raw_unchecked(pgid)
+ }
+}
+
+#[inline]
+pub(crate) fn sched_getaffinity(pid: Option<Pid>, cpuset: &mut RawCpuSet) -> io::Result<()> {
+ unsafe {
+ // The raw Linux syscall returns the size (in bytes) of the `cpumask_t`
+ // data type that is used internally by the kernel to represent the CPU
+ // set bit mask.
+ let size = ret_usize(syscall!(
+ __NR_sched_getaffinity,
+ c_int(Pid::as_raw(pid)),
+ size_of::<RawCpuSet, _>(),
+ by_mut(&mut cpuset.bits)
+ ))?;
+ let bytes = as_mut_ptr(cpuset).cast::<u8>();
+ let rest = bytes.wrapping_add(size);
+ // Zero every byte in the cpuset not set by the kernel.
+ rest.write_bytes(0, core::mem::size_of::<RawCpuSet>() - size);
+ Ok(())
+ }
+}
+
+#[inline]
+pub(crate) fn sched_setaffinity(pid: Option<Pid>, cpuset: &RawCpuSet) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_sched_setaffinity,
+ c_int(Pid::as_raw(pid)),
+ size_of::<RawCpuSet, _>(),
+ slice_just_addr(&cpuset.bits)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn sched_yield() {
+ unsafe {
+ // See the documentation for [`crate::process::sched_yield`] for why
+ // errors are ignored.
+ syscall_readonly!(__NR_sched_yield).decode_void();
+ }
+}
+
+#[cfg(feature = "fs")]
+#[inline]
+pub(crate) fn umask(mode: Mode) -> Mode {
+ unsafe { Mode::from_bits_retain(ret_c_uint_infallible(syscall_readonly!(__NR_umask, mode))) }
+}
+
+#[inline]
+pub(crate) fn nice(inc: i32) -> io::Result<i32> {
+ let priority = (if inc > -40 && inc < 40 {
+ inc + getpriority_process(None)?
+ } else {
+ inc
+ })
+ .clamp(-20, 19);
+ setpriority_process(None, priority)?;
+ Ok(priority)
+}
+
+#[inline]
+pub(crate) fn getpriority_user(uid: Uid) -> io::Result<i32> {
+ unsafe {
+ Ok(20
+ - ret_c_int(syscall_readonly!(
+ __NR_getpriority,
+ c_uint(PRIO_USER),
+ c_uint(uid.as_raw())
+ ))?)
+ }
+}
+
+#[inline]
+pub(crate) fn getpriority_pgrp(pgid: Option<Pid>) -> io::Result<i32> {
+ unsafe {
+ Ok(20
+ - ret_c_int(syscall_readonly!(
+ __NR_getpriority,
+ c_uint(PRIO_PGRP),
+ c_int(Pid::as_raw(pgid))
+ ))?)
+ }
+}
+
+#[inline]
+pub(crate) fn getpriority_process(pid: Option<Pid>) -> io::Result<i32> {
+ unsafe {
+ Ok(20
+ - ret_c_int(syscall_readonly!(
+ __NR_getpriority,
+ c_uint(PRIO_PROCESS),
+ c_int(Pid::as_raw(pid))
+ ))?)
+ }
+}
+
+#[inline]
+pub(crate) fn setpriority_user(uid: Uid, priority: i32) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_setpriority,
+ c_uint(PRIO_USER),
+ c_uint(uid.as_raw()),
+ c_int(priority)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn setpriority_pgrp(pgid: Option<Pid>, priority: i32) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_setpriority,
+ c_uint(PRIO_PGRP),
+ c_int(Pid::as_raw(pgid)),
+ c_int(priority)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn setpriority_process(pid: Option<Pid>, priority: i32) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_setpriority,
+ c_uint(PRIO_PROCESS),
+ c_int(Pid::as_raw(pid)),
+ c_int(priority)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn getrlimit(limit: Resource) -> Rlimit {
+ let mut result = MaybeUninit::<rlimit64>::uninit();
+ unsafe {
+ ret_infallible(syscall!(
+ __NR_prlimit64,
+ c_uint(0),
+ limit,
+ null::<c::c_void>(),
+ &mut result
+ ));
+ rlimit_from_linux(result.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn setrlimit(limit: Resource, new: Rlimit) -> io::Result<()> {
+ unsafe {
+ let lim = rlimit_to_linux(new.clone());
+ match ret(syscall_readonly!(
+ __NR_prlimit64,
+ c_uint(0),
+ limit,
+ by_ref(&lim),
+ null_mut::<c::c_void>()
+ )) {
+ Ok(()) => Ok(()),
+ Err(err) => Err(err),
+ }
+ }
+}
+
+#[inline]
+pub(crate) fn prlimit(pid: Option<Pid>, limit: Resource, new: Rlimit) -> io::Result<Rlimit> {
+ let lim = rlimit_to_linux(new);
+ let mut result = MaybeUninit::<rlimit64>::uninit();
+ unsafe {
+ match ret(syscall!(
+ __NR_prlimit64,
+ c_int(Pid::as_raw(pid)),
+ limit,
+ by_ref(&lim),
+ &mut result
+ )) {
+ Ok(()) => Ok(rlimit_from_linux(result.assume_init())),
+ Err(err) => Err(err),
+ }
+ }
+}
+
+/// Convert a Rust [`Rlimit`] to a C `rlimit64`.
+#[inline]
+fn rlimit_from_linux(lim: rlimit64) -> Rlimit {
+ let current = if lim.rlim_cur == RLIM64_INFINITY as _ {
+ None
+ } else {
+ Some(lim.rlim_cur)
+ };
+ let maximum = if lim.rlim_max == RLIM64_INFINITY as _ {
+ None
+ } else {
+ Some(lim.rlim_max)
+ };
+ Rlimit { current, maximum }
+}
+
+/// Convert a C `rlimit64` to a Rust `Rlimit`.
+#[inline]
+fn rlimit_to_linux(lim: Rlimit) -> rlimit64 {
+ let rlim_cur = match lim.current {
+ Some(r) => r,
+ None => RLIM64_INFINITY as _,
+ };
+ let rlim_max = match lim.maximum {
+ Some(r) => r,
+ None => RLIM64_INFINITY as _,
+ };
+ rlimit64 { rlim_cur, rlim_max }
+}
+
+#[inline]
+pub(crate) fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
+ _waitpid(!0, waitopts)
+}
+
+#[inline]
+pub(crate) fn waitpid(
+ pid: Option<Pid>,
+ waitopts: WaitOptions,
+) -> io::Result<Option<(Pid, WaitStatus)>> {
+ _waitpid(Pid::as_raw(pid), waitopts)
+}
+
+#[inline]
+pub(crate) fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
+ _waitpid(-pgid.as_raw_nonzero().get(), waitopts)
+}
+
+#[inline]
+pub(crate) fn _waitpid(
+ pid: RawPid,
+ waitopts: WaitOptions,
+) -> io::Result<Option<(Pid, WaitStatus)>> {
+ unsafe {
+ let mut status = MaybeUninit::<u32>::uninit();
+ let pid = ret_c_int(syscall!(
+ __NR_wait4,
+ c_int(pid as _),
+ &mut status,
+ c_int(waitopts.bits() as _),
+ zero()
+ ))?;
+ Ok(Pid::from_raw(pid).map(|pid| (pid, WaitStatus::new(status.assume_init()))))
+ }
+}
+
+#[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(pid) => _waitid_pgid(pid, options),
+ WaitId::PidFd(fd) => _waitid_pidfd(fd, options),
+ }
+}
+
+#[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(syscall!(
+ __NR_waitid,
+ c_uint(c::P_ALL),
+ c_uint(0),
+ by_mut(&mut status),
+ c_int(options.bits() as _),
+ zero()
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+#[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(syscall!(
+ __NR_waitid,
+ c_uint(c::P_PID),
+ c_int(Pid::as_raw(Some(pid))),
+ by_mut(&mut status),
+ c_int(options.bits() as _),
+ zero()
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+#[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(syscall!(
+ __NR_waitid,
+ c_uint(c::P_PGID),
+ c_int(Pid::as_raw(pgid)),
+ by_mut(&mut status),
+ c_int(options.bits() as _),
+ zero()
+ ))?
+ };
+
+ Ok(unsafe { cvt_waitid_status(status) })
+}
+
+#[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(syscall!(
+ __NR_waitid,
+ c_uint(c::P_PIDFD),
+ c_uint(fd.as_raw_fd() as _),
+ by_mut(&mut status),
+ c_int(options.bits() as _),
+ zero()
+ ))?
+ };
+
+ 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.
+#[inline]
+#[rustfmt::skip]
+unsafe fn cvt_waitid_status(status: MaybeUninit<c::siginfo_t>) -> Option<WaitidStatus> {
+ let status = status.assume_init();
+ if status.__bindgen_anon_1.__bindgen_anon_1._sifields._sigchld._pid == 0 {
+ None
+ } else {
+ Some(WaitidStatus(status))
+ }
+}
+
+#[inline]
+pub(crate) fn getsid(pid: Option<Pid>) -> io::Result<Pid> {
+ unsafe {
+ let pid = ret_c_int(syscall_readonly!(__NR_getsid, c_int(Pid::as_raw(pid))))?;
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[inline]
+pub(crate) fn setsid() -> io::Result<Pid> {
+ unsafe {
+ let pid = ret_c_int(syscall_readonly!(__NR_setsid))?;
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[inline]
+pub(crate) fn kill_process(pid: Pid, sig: Signal) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_kill, pid, sig)) }
+}
+
+#[inline]
+pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_kill, negative_pid(pid), sig)) }
+}
+
+#[inline]
+pub(crate) fn kill_current_process_group(sig: Signal) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_kill, pass_usize(0), sig)) }
+}
+
+#[inline]
+pub(crate) fn test_kill_process(pid: Pid) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_kill, pid, pass_usize(0))) }
+}
+
+#[inline]
+pub(crate) fn test_kill_process_group(pid: Pid) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_kill,
+ negative_pid(pid),
+ pass_usize(0)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn test_kill_current_process_group() -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_kill, pass_usize(0), pass_usize(0))) }
+}
+
+#[inline]
+pub(crate) fn pidfd_getfd(
+ pidfd: BorrowedFd<'_>,
+ targetfd: RawFd,
+ flags: PidfdGetfdFlags,
+) -> io::Result<OwnedFd> {
+ unsafe {
+ ret_owned_fd(syscall_readonly!(
+ __NR_pidfd_getfd,
+ pidfd,
+ raw_fd(targetfd),
+ c_int(flags.bits() as _)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn pidfd_open(pid: Pid, flags: PidfdFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_pidfd_open, pid, flags)) }
+}
+
+#[cfg(feature = "alloc")]
+#[inline]
+pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result<usize> {
+ let len = buf.len().try_into().map_err(|_| io::Errno::NOMEM)?;
+
+ unsafe {
+ ret_usize(syscall!(
+ __NR_getgroups,
+ c_int(len),
+ slice_just_addr_mut(buf)
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/process/types.rs b/src/backend/linux_raw/process/types.rs
new file mode 100644
index 0000000..841668a
--- /dev/null
+++ b/src/backend/linux_raw/process/types.rs
@@ -0,0 +1,104 @@
+use linux_raw_sys::general::membarrier_cmd;
+
+/// 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
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[repr(u32)]
+pub enum MembarrierCommand {
+ /// `MEMBARRIER_CMD_GLOBAL`
+ #[doc(alias = "Shared")]
+ #[doc(alias = "MEMBARRIER_CMD_SHARED")]
+ Global = membarrier_cmd::MEMBARRIER_CMD_GLOBAL as _,
+ /// `MEMBARRIER_CMD_GLOBAL_EXPEDITED`
+ GlobalExpedited = membarrier_cmd::MEMBARRIER_CMD_GLOBAL_EXPEDITED as _,
+ /// `MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED`
+ RegisterGlobalExpedited = membarrier_cmd::MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED as _,
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED`
+ PrivateExpedited = membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED as _,
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED`
+ RegisterPrivateExpedited = membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED as _,
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE`
+ PrivateExpeditedSyncCore = membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE as _,
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE`
+ RegisterPrivateExpeditedSyncCore =
+ membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE as _,
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ` (since Linux 5.10)
+ PrivateExpeditedRseq = membarrier_cmd::MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ as _,
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ` (since Linux 5.10)
+ RegisterPrivateExpeditedRseq =
+ membarrier_cmd::MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ as _,
+}
+
+/// A resource value for use with [`getrlimit`], [`setrlimit`], and
+/// [`prlimit`].
+///
+/// [`getrlimit`]: crate::process::getrlimit
+/// [`setrlimit`]: crate::process::setrlimit
+/// [`prlimit`]: crate::process::prlimit
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum Resource {
+ /// `RLIMIT_CPU`
+ Cpu = linux_raw_sys::general::RLIMIT_CPU,
+ /// `RLIMIT_FSIZE`
+ Fsize = linux_raw_sys::general::RLIMIT_FSIZE,
+ /// `RLIMIT_DATA`
+ Data = linux_raw_sys::general::RLIMIT_DATA,
+ /// `RLIMIT_STACK`
+ Stack = linux_raw_sys::general::RLIMIT_STACK,
+ /// `RLIMIT_CORE`
+ Core = linux_raw_sys::general::RLIMIT_CORE,
+ /// `RLIMIT_RSS`
+ Rss = linux_raw_sys::general::RLIMIT_RSS,
+ /// `RLIMIT_NPROC`
+ Nproc = linux_raw_sys::general::RLIMIT_NPROC,
+ /// `RLIMIT_NOFILE`
+ Nofile = linux_raw_sys::general::RLIMIT_NOFILE,
+ /// `RLIMIT_MEMLOCK`
+ Memlock = linux_raw_sys::general::RLIMIT_MEMLOCK,
+ /// `RLIMIT_AS`
+ As = linux_raw_sys::general::RLIMIT_AS,
+ /// `RLIMIT_LOCKS`
+ Locks = linux_raw_sys::general::RLIMIT_LOCKS,
+ /// `RLIMIT_SIGPENDING`
+ Sigpending = linux_raw_sys::general::RLIMIT_SIGPENDING,
+ /// `RLIMIT_MSGQUEUE`
+ Msgqueue = linux_raw_sys::general::RLIMIT_MSGQUEUE,
+ /// `RLIMIT_NICE`
+ Nice = linux_raw_sys::general::RLIMIT_NICE,
+ /// `RLIMIT_RTPRIO`
+ Rtprio = linux_raw_sys::general::RLIMIT_RTPRIO,
+ /// `RLIMIT_RTTIME`
+ Rttime = linux_raw_sys::general::RLIMIT_RTTIME,
+}
+
+/// A CPU identifier as a raw integer.
+pub type RawCpuid = u32;
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub(crate) struct RawCpuSet {
+ #[cfg(all(target_pointer_width = "32", not(target_arch = "x86_64")))]
+ pub(crate) bits: [u32; 32],
+ #[cfg(not(all(target_pointer_width = "32", not(target_arch = "x86_64"))))]
+ pub(crate) bits: [u64; 16],
+}
+
+#[inline]
+pub(crate) fn raw_cpu_set_new() -> RawCpuSet {
+ #[cfg(all(target_pointer_width = "32", not(target_arch = "x86_64")))]
+ {
+ RawCpuSet { bits: [0; 32] }
+ }
+ #[cfg(not(all(target_pointer_width = "32", not(target_arch = "x86_64"))))]
+ {
+ RawCpuSet { bits: [0; 16] }
+ }
+}
+
+pub(crate) const CPU_SETSIZE: usize = 8 * core::mem::size_of::<RawCpuSet>();
diff --git a/src/backend/linux_raw/process/wait.rs b/src/backend/linux_raw/process/wait.rs
new file mode 100644
index 0000000..9af7f2b
--- /dev/null
+++ b/src/backend/linux_raw/process/wait.rs
@@ -0,0 +1,68 @@
+// The functions replacing the C macros use the same names as in libc.
+#![allow(non_snake_case, unsafe_code)]
+
+use linux_raw_sys::ctypes::c_int;
+pub(crate) use linux_raw_sys::general::{
+ siginfo_t, WCONTINUED, WEXITED, WNOHANG, WNOWAIT, WSTOPPED, WUNTRACED,
+};
+
+#[inline]
+pub(crate) fn WIFSTOPPED(status: u32) -> bool {
+ (status & 0xff) == 0x7f
+}
+
+#[inline]
+pub(crate) fn WSTOPSIG(status: u32) -> u32 {
+ (status >> 8) & 0xff
+}
+
+#[inline]
+pub(crate) fn WIFCONTINUED(status: u32) -> bool {
+ status == 0xffff
+}
+
+#[inline]
+pub(crate) fn WIFSIGNALED(status: u32) -> bool {
+ ((status & 0x7f) + 1) as i8 >= 2
+}
+
+#[inline]
+pub(crate) fn WTERMSIG(status: u32) -> u32 {
+ status & 0x7f
+}
+
+#[inline]
+pub(crate) fn WIFEXITED(status: u32) -> bool {
+ (status & 0x7f) == 0
+}
+
+#[inline]
+pub(crate) fn WEXITSTATUS(status: u32) -> u32 {
+ (status >> 8) & 0xff
+}
+
+pub(crate) trait SiginfoExt {
+ fn si_code(&self) -> c_int;
+ unsafe fn si_status(&self) -> c_int;
+}
+
+impl SiginfoExt for siginfo_t {
+ #[inline]
+ fn si_code(&self) -> c_int {
+ // SAFETY: This is technically a union access, but it's only a union
+ // with padding.
+ unsafe { self.__bindgen_anon_1.__bindgen_anon_1.si_code }
+ }
+
+ /// Return the exit status or signal number recorded in a `siginfo_t`.
+ ///
+ /// # Safety
+ ///
+ /// `si_signo` must equal `SIGCHLD` (as it is guaranteed to do after a
+ /// `waitid` call).
+ #[inline]
+ #[rustfmt::skip]
+ unsafe fn si_status(&self) -> c_int {
+ self.__bindgen_anon_1.__bindgen_anon_1._sifields._sigchld._status
+ }
+}
diff --git a/src/backend/linux_raw/pty/mod.rs b/src/backend/linux_raw/pty/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/linux_raw/pty/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/pty/syscalls.rs b/src/backend/linux_raw/pty/syscalls.rs
new file mode 100644
index 0000000..b64344f
--- /dev/null
+++ b/src/backend/linux_raw/pty/syscalls.rs
@@ -0,0 +1,43 @@
+//! linux_raw syscalls supporting `rustix::pty`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::{by_ref, c_uint, ret};
+use crate::fd::BorrowedFd;
+use crate::io;
+use linux_raw_sys::ioctl::TIOCSPTLCK;
+#[cfg(feature = "alloc")]
+use {
+ crate::backend::c, crate::ffi::CString, crate::path::DecInt, alloc::vec::Vec,
+ core::mem::MaybeUninit, linux_raw_sys::ioctl::TIOCGPTN,
+};
+
+#[cfg(feature = "alloc")]
+#[inline]
+pub(crate) fn ptsname(fd: BorrowedFd<'_>, mut buffer: Vec<u8>) -> io::Result<CString> {
+ unsafe {
+ let mut n = MaybeUninit::<c::c_int>::uninit();
+ ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGPTN), &mut n))?;
+
+ buffer.clear();
+ buffer.extend_from_slice(b"/dev/pts/");
+ buffer.extend_from_slice(DecInt::new(n.assume_init()).as_bytes());
+ buffer.push(b'\0');
+ Ok(CString::from_vec_with_nul_unchecked(buffer))
+ }
+}
+
+#[inline]
+pub(crate) fn unlockpt(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ioctl,
+ fd,
+ c_uint(TIOCSPTLCK),
+ by_ref(&0)
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/rand/mod.rs b/src/backend/linux_raw/rand/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/linux_raw/rand/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/rand/syscalls.rs b/src/backend/linux_raw/rand/syscalls.rs
new file mode 100644
index 0000000..c0f497b
--- /dev/null
+++ b/src/backend/linux_raw/rand/syscalls.rs
@@ -0,0 +1,19 @@
+//! linux_raw syscalls supporting `rustix::rand`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::{pass_usize, ret_usize};
+use crate::io;
+use crate::rand::GetRandomFlags;
+
+#[inline]
+pub(crate) unsafe fn getrandom(
+ buf: *mut u8,
+ cap: usize,
+ flags: GetRandomFlags,
+) -> io::Result<usize> {
+ ret_usize(syscall!(__NR_getrandom, buf, pass_usize(cap), flags))
+}
diff --git a/src/backend/linux_raw/rand/types.rs b/src/backend/linux_raw/rand/types.rs
new file mode 100644
index 0000000..9bc857f
--- /dev/null
+++ b/src/backend/linux_raw/rand/types.rs
@@ -0,0 +1,20 @@
+use bitflags::bitflags;
+
+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 = linux_raw_sys::general::GRND_RANDOM;
+ /// `GRND_NONBLOCK`
+ const NONBLOCK = linux_raw_sys::general::GRND_NONBLOCK;
+ /// `GRND_INSECURE`
+ const INSECURE = linux_raw_sys::general::GRND_INSECURE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/linux_raw/reg.rs b/src/backend/linux_raw/reg.rs
new file mode 100644
index 0000000..f05ac5f
--- /dev/null
+++ b/src/backend/linux_raw/reg.rs
@@ -0,0 +1,259 @@
+//! Encapsulation for system call arguments and return values.
+//!
+//! The inline-asm code paths do some amount of reordering of arguments; to
+//! ensure that we don't accidentally misroute an argument or return value, we
+//! use distinct types for each argument index and return value.
+//!
+//! # Safety
+//!
+//! The `ToAsm` and `FromAsm` traits are unsafe to use; they should only be
+//! used by the syscall code which executes actual syscall machine
+//! instructions.
+
+#![allow(unsafe_code)]
+
+use super::c;
+use super::fd::RawFd;
+use core::marker::PhantomData;
+use core::ops::Range;
+
+pub(super) trait ToAsm: private::Sealed {
+ /// Convert `self` to a `usize` ready to be passed to a syscall
+ /// machine instruction.
+ ///
+ /// # Safety
+ ///
+ /// This should be used immediately before the syscall instruction, and the
+ /// returned value shouldn't be used for any other purpose.
+ #[must_use]
+ unsafe fn to_asm(self) -> *mut Opaque;
+}
+
+pub(super) trait FromAsm: private::Sealed {
+ /// Convert `raw` from a value produced by a syscall machine instruction
+ /// into a `Self`.
+ ///
+ /// # Safety
+ ///
+ /// This should be used immediately after the syscall instruction, and the
+ /// operand value shouldn't be used for any other purpose.
+ #[must_use]
+ unsafe fn from_asm(raw: *mut Opaque) -> Self;
+}
+
+/// To preserve provenance, syscall arguments and return values are passed as
+/// pointer types. They need a type to point to, so we define a custom private
+/// type, to prevent it from being used for anything else.
+#[repr(transparent)]
+#[allow(dead_code)]
+pub(super) struct Opaque(c::c_void);
+
+// Argument numbers.
+pub(super) struct A0(());
+pub(super) struct A1(());
+pub(super) struct A2(());
+pub(super) struct A3(());
+pub(super) struct A4(());
+pub(super) struct A5(());
+#[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
+pub(super) struct A6(());
+#[cfg(target_arch = "x86")]
+pub(super) struct SocketArg;
+
+pub(super) trait ArgNumber: private::Sealed {}
+impl ArgNumber for A0 {}
+impl ArgNumber for A1 {}
+impl ArgNumber for A2 {}
+impl ArgNumber for A3 {}
+impl ArgNumber for A4 {}
+impl ArgNumber for A5 {}
+#[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
+impl ArgNumber for A6 {}
+#[cfg(target_arch = "x86")]
+impl ArgNumber for SocketArg {}
+
+// Return value numbers.
+pub(super) struct R0(());
+
+pub(super) trait RetNumber: private::Sealed {}
+impl RetNumber for R0 {}
+
+/// Syscall arguments use register-sized types. We use a newtype to
+/// discourage accidental misuse of the raw integer values.
+///
+/// This type doesn't implement `Clone` or `Copy`; it should be used exactly
+/// once. And it has a lifetime to ensure that it doesn't outlive any resources
+/// it might be pointing to.
+#[repr(transparent)]
+#[must_use]
+pub(super) struct ArgReg<'a, Num: ArgNumber> {
+ raw: *mut Opaque,
+ _phantom: PhantomData<(&'a (), Num)>,
+}
+
+impl<'a, Num: ArgNumber> ToAsm for ArgReg<'a, Num> {
+ #[inline]
+ unsafe fn to_asm(self) -> *mut Opaque {
+ self.raw
+ }
+}
+
+/// Syscall return values use register-sized types. We use a newtype to
+/// discourage accidental misuse of the raw integer values.
+///
+/// This type doesn't implement `Clone` or `Copy`; it should be used exactly
+/// once.
+#[repr(transparent)]
+#[must_use]
+pub(super) struct RetReg<Num: RetNumber> {
+ raw: *mut Opaque,
+ _phantom: PhantomData<Num>,
+}
+
+impl<Num: RetNumber> RetReg<Num> {
+ #[inline]
+ pub(super) fn decode_usize(self) -> usize {
+ debug_assert!(!(-4095..0).contains(&(self.raw as isize)));
+ self.raw as usize
+ }
+
+ #[inline]
+ pub(super) fn decode_raw_fd(self) -> RawFd {
+ let bits = self.decode_usize();
+ let raw_fd = bits as RawFd;
+
+ // Converting `raw` to `RawFd` should be lossless.
+ debug_assert_eq!(raw_fd as usize, bits);
+
+ raw_fd
+ }
+
+ #[inline]
+ pub(super) fn decode_c_int(self) -> c::c_int {
+ let bits = self.decode_usize();
+ let c_int_ = bits as c::c_int;
+
+ // Converting `raw` to `c_int` should be lossless.
+ debug_assert_eq!(c_int_ as usize, bits);
+
+ c_int_
+ }
+
+ #[inline]
+ pub(super) fn decode_c_uint(self) -> c::c_uint {
+ let bits = self.decode_usize();
+ let c_uint_ = bits as c::c_uint;
+
+ // Converting `raw` to `c_uint` should be lossless.
+ debug_assert_eq!(c_uint_ as usize, bits);
+
+ c_uint_
+ }
+
+ #[inline]
+ pub(super) fn decode_void_star(self) -> *mut c::c_void {
+ self.raw.cast()
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ #[inline]
+ pub(super) fn decode_u64(self) -> u64 {
+ self.decode_usize() as u64
+ }
+
+ #[inline]
+ pub(super) fn decode_void(self) {
+ let ignore = self.decode_usize();
+ debug_assert_eq!(ignore, 0);
+ }
+
+ #[inline]
+ pub(super) fn decode_error_code(self) -> u16 {
+ let bits = self.raw as usize;
+
+ // `raw` must be in `-4095..0`. Linux always returns errors in
+ // `-4095..0`, and we double-check it here.
+ debug_assert!((-4095..0).contains(&(bits as isize)));
+
+ bits as u16
+ }
+
+ #[inline]
+ pub(super) fn is_nonzero(&self) -> bool {
+ !self.raw.is_null()
+ }
+
+ #[inline]
+ pub(super) fn is_negative(&self) -> bool {
+ (self.raw as isize) < 0
+ }
+
+ #[inline]
+ pub(super) fn is_in_range(&self, range: Range<isize>) -> bool {
+ range.contains(&(self.raw as isize))
+ }
+}
+
+impl<Num: RetNumber> FromAsm for RetReg<Num> {
+ #[inline]
+ unsafe fn from_asm(raw: *mut Opaque) -> Self {
+ Self {
+ raw,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+#[repr(transparent)]
+pub(super) struct SyscallNumber<'a> {
+ nr: usize,
+ _phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> ToAsm for SyscallNumber<'a> {
+ #[inline]
+ unsafe fn to_asm(self) -> *mut Opaque {
+ self.nr as usize as *mut Opaque
+ }
+}
+
+/// Encode a system call argument as an `ArgReg`.
+#[inline]
+pub(super) fn raw_arg<'a, Num: ArgNumber>(raw: *mut Opaque) -> ArgReg<'a, Num> {
+ ArgReg {
+ raw,
+ _phantom: PhantomData,
+ }
+}
+
+/// Encode a system call number (a `__NR_*` constant) as a `SyscallNumber`.
+#[inline]
+pub(super) const fn nr<'a>(nr: u32) -> SyscallNumber<'a> {
+ SyscallNumber {
+ nr: nr as usize,
+ _phantom: PhantomData,
+ }
+}
+
+/// Seal our various traits using the technique documented [here].
+///
+/// [here]: https://rust-lang.github.io/api-guidelines/future-proofing.html
+mod private {
+ pub trait Sealed {}
+
+ // Implement for those same types, but no others.
+ impl<'a, Num: super::ArgNumber> Sealed for super::ArgReg<'a, Num> {}
+ impl<Num: super::RetNumber> Sealed for super::RetReg<Num> {}
+ impl<'a> Sealed for super::SyscallNumber<'a> {}
+ impl Sealed for super::A0 {}
+ impl Sealed for super::A1 {}
+ impl Sealed for super::A2 {}
+ impl Sealed for super::A3 {}
+ impl Sealed for super::A4 {}
+ impl Sealed for super::A5 {}
+ #[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
+ impl Sealed for super::A6 {}
+ #[cfg(target_arch = "x86")]
+ impl Sealed for super::SocketArg {}
+ impl Sealed for super::R0 {}
+}
diff --git a/src/backend/linux_raw/runtime/mod.rs b/src/backend/linux_raw/runtime/mod.rs
new file mode 100644
index 0000000..0b48649
--- /dev/null
+++ b/src/backend/linux_raw/runtime/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod tls;
diff --git a/src/backend/linux_raw/runtime/syscalls.rs b/src/backend/linux_raw/runtime/syscalls.rs
new file mode 100644
index 0000000..e00acc6
--- /dev/null
+++ b/src/backend/linux_raw/runtime/syscalls.rs
@@ -0,0 +1,318 @@
+//! linux_raw syscalls supporting `rustix::runtime`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+#[cfg(target_arch = "x86")]
+use crate::backend::conv::by_mut;
+#[cfg(target_arch = "x86_64")]
+use crate::backend::conv::c_uint;
+use crate::backend::conv::{
+ by_ref, c_int, ret, ret_c_int, ret_c_int_infallible, ret_error, ret_infallible, ret_void_star,
+ size_of, zero,
+};
+#[cfg(feature = "fs")]
+use crate::fd::BorrowedFd;
+use crate::ffi::CStr;
+#[cfg(feature = "fs")]
+use crate::fs::AtFlags;
+use crate::io;
+use crate::pid::{Pid, RawPid};
+use crate::runtime::{Fork, How, Sigaction, Siginfo, Sigset, Stack};
+use crate::signal::Signal;
+use crate::timespec::Timespec;
+use crate::utils::option_as_ptr;
+use core::ffi::c_void;
+use core::mem::MaybeUninit;
+#[cfg(target_pointer_width = "32")]
+use linux_raw_sys::general::__kernel_old_timespec;
+use linux_raw_sys::general::kernel_sigset_t;
+#[cfg(target_arch = "x86_64")]
+use linux_raw_sys::general::ARCH_SET_FS;
+
+#[inline]
+pub(crate) unsafe fn fork() -> io::Result<Fork> {
+ let mut child_pid = MaybeUninit::<RawPid>::uninit();
+
+ // Unix `fork` only returns the child PID in the parent; we'd like it in
+ // the child too, so set `CLONE_CHILD_SETTID` and pass in the address of
+ // a memory location to store it to in the child.
+ //
+ // Architectures differ on the order of the parameters.
+ #[cfg(target_arch = "x86_64")]
+ let pid = ret_c_int(syscall_readonly!(
+ __NR_clone,
+ c_int(c::SIGCHLD | c::CLONE_CHILD_SETTID),
+ zero(),
+ zero(),
+ &mut child_pid,
+ zero()
+ ))?;
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "powerpc64",
+ target_arch = "riscv64",
+ target_arch = "x86"
+ ))]
+ let pid = ret_c_int(syscall_readonly!(
+ __NR_clone,
+ c_int(c::SIGCHLD | c::CLONE_CHILD_SETTID),
+ zero(),
+ zero(),
+ zero(),
+ &mut child_pid
+ ))?;
+
+ Ok(if let Some(pid) = Pid::from_raw(pid) {
+ Fork::Parent(pid)
+ } else {
+ Fork::Child(Pid::from_raw_unchecked(child_pid.assume_init()))
+ })
+}
+
+#[cfg(feature = "fs")]
+pub(crate) unsafe fn execveat(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ args: *const *const u8,
+ env_vars: *const *const u8,
+ flags: AtFlags,
+) -> io::Errno {
+ ret_error(syscall_readonly!(
+ __NR_execveat,
+ dirfd,
+ path,
+ args,
+ env_vars,
+ flags
+ ))
+}
+
+pub(crate) unsafe fn execve(
+ path: &CStr,
+ args: *const *const u8,
+ env_vars: *const *const u8,
+) -> io::Errno {
+ ret_error(syscall_readonly!(__NR_execve, path, args, env_vars))
+}
+
+pub(crate) mod tls {
+ use super::*;
+ #[cfg(target_arch = "x86")]
+ use crate::backend::runtime::tls::UserDesc;
+
+ #[cfg(target_arch = "x86")]
+ #[inline]
+ pub(crate) unsafe fn set_thread_area(u_info: &mut UserDesc) -> io::Result<()> {
+ ret(syscall!(__NR_set_thread_area, by_mut(u_info)))
+ }
+
+ #[cfg(target_arch = "arm")]
+ #[inline]
+ pub(crate) unsafe fn arm_set_tls(data: *mut c::c_void) -> io::Result<()> {
+ ret(syscall_readonly!(__ARM_NR_set_tls, data))
+ }
+
+ #[cfg(target_arch = "x86_64")]
+ #[inline]
+ pub(crate) unsafe fn set_fs(data: *mut c::c_void) {
+ ret_infallible(syscall_readonly!(
+ __NR_arch_prctl,
+ c_uint(ARCH_SET_FS),
+ data,
+ zero(),
+ zero(),
+ zero()
+ ))
+ }
+
+ #[inline]
+ pub(crate) unsafe fn set_tid_address(data: *mut c::c_void) -> Pid {
+ let tid: i32 = ret_c_int_infallible(syscall_readonly!(__NR_set_tid_address, data));
+ Pid::from_raw_unchecked(tid)
+ }
+
+ #[inline]
+ pub(crate) fn exit_thread(code: c::c_int) -> ! {
+ unsafe { syscall_noreturn!(__NR_exit, c_int(code)) }
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn sigaction(signal: Signal, new: Option<Sigaction>) -> io::Result<Sigaction> {
+ let mut old = MaybeUninit::<Sigaction>::uninit();
+ let new = option_as_ptr(new.as_ref());
+ ret(syscall!(
+ __NR_rt_sigaction,
+ signal,
+ new,
+ &mut old,
+ size_of::<kernel_sigset_t, _>()
+ ))?;
+ Ok(old.assume_init())
+}
+
+#[inline]
+pub(crate) unsafe fn sigaltstack(new: Option<Stack>) -> io::Result<Stack> {
+ let mut old = MaybeUninit::<Stack>::uninit();
+ let new = option_as_ptr(new.as_ref());
+ ret(syscall!(__NR_sigaltstack, new, &mut old))?;
+ Ok(old.assume_init())
+}
+
+#[inline]
+pub(crate) unsafe fn tkill(tid: Pid, sig: Signal) -> io::Result<()> {
+ ret(syscall_readonly!(__NR_tkill, tid, sig))
+}
+
+#[inline]
+pub(crate) unsafe fn sigprocmask(how: How, new: Option<&Sigset>) -> io::Result<Sigset> {
+ let mut old = MaybeUninit::<Sigset>::uninit();
+ let new = option_as_ptr(new);
+ ret(syscall!(
+ __NR_rt_sigprocmask,
+ how,
+ new,
+ &mut old,
+ size_of::<kernel_sigset_t, _>()
+ ))?;
+ Ok(old.assume_init())
+}
+
+#[inline]
+pub(crate) fn sigpending() -> Sigset {
+ let mut pending = MaybeUninit::<Sigset>::uninit();
+ unsafe {
+ ret_infallible(syscall!(
+ __NR_rt_sigpending,
+ &mut pending,
+ size_of::<kernel_sigset_t, _>()
+ ));
+ pending.assume_init()
+ }
+}
+
+#[inline]
+pub(crate) fn sigsuspend(set: &Sigset) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_rt_sigsuspend,
+ by_ref(set),
+ size_of::<kernel_sigset_t, _>()
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn sigwait(set: &Sigset) -> io::Result<Signal> {
+ unsafe {
+ match Signal::from_raw(ret_c_int(syscall_readonly!(
+ __NR_rt_sigtimedwait,
+ by_ref(set),
+ zero(),
+ zero(),
+ size_of::<kernel_sigset_t, _>()
+ ))?) {
+ Some(signum) => Ok(signum),
+ None => Err(io::Errno::NOTSUP),
+ }
+ }
+}
+
+#[inline]
+pub(crate) fn sigwaitinfo(set: &Sigset) -> io::Result<Siginfo> {
+ let mut info = MaybeUninit::<Siginfo>::uninit();
+ unsafe {
+ let _signum = ret_c_int(syscall!(
+ __NR_rt_sigtimedwait,
+ by_ref(set),
+ &mut info,
+ zero(),
+ size_of::<kernel_sigset_t, _>()
+ ))?;
+ Ok(info.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn sigtimedwait(set: &Sigset, timeout: Option<Timespec>) -> io::Result<Siginfo> {
+ let mut info = MaybeUninit::<Siginfo>::uninit();
+ let timeout_ptr = option_as_ptr(timeout.as_ref());
+
+ // `rt_sigtimedwait_time64` was introduced in Linux 5.1. The old
+ // `rt_sigtimedwait` syscall is not y2038-compatible on 32-bit
+ // architectures.
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ match ret_c_int(syscall!(
+ __NR_rt_sigtimedwait_time64,
+ by_ref(set),
+ &mut info,
+ timeout_ptr,
+ size_of::<kernel_sigset_t, _>()
+ )) {
+ Ok(_signum) => (),
+ Err(io::Errno::NOSYS) => sigtimedwait_old(set, timeout, &mut info)?,
+ Err(err) => return Err(err),
+ }
+ Ok(info.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ let _signum = ret_c_int(syscall!(
+ __NR_rt_sigtimedwait,
+ by_ref(set),
+ &mut info,
+ timeout_ptr,
+ size_of::<kernel_sigset_t, _>()
+ ))?;
+ Ok(info.assume_init())
+ }
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn sigtimedwait_old(
+ set: &Sigset,
+ timeout: Option<Timespec>,
+ info: &mut MaybeUninit<Siginfo>,
+) -> io::Result<()> {
+ let old_timeout = match timeout {
+ Some(timeout) => Some(__kernel_old_timespec {
+ tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: timeout.tv_nsec as _,
+ }),
+ None => None,
+ };
+
+ let old_timeout_ptr = option_as_ptr(old_timeout.as_ref());
+
+ let _signum = ret_c_int(syscall!(
+ __NR_rt_sigtimedwait,
+ by_ref(set),
+ info,
+ old_timeout_ptr,
+ size_of::<kernel_sigset_t, _>()
+ ))?;
+
+ Ok(())
+}
+
+#[inline]
+pub(crate) fn exit_group(code: c::c_int) -> ! {
+ unsafe { syscall_noreturn!(__NR_exit_group, c_int(code)) }
+}
+
+#[inline]
+pub(crate) unsafe fn brk(addr: *mut c::c_void) -> io::Result<*mut c_void> {
+ // This is non-`readonly`, to prevent loads from being reordered past it.
+ ret_void_star(syscall!(__NR_brk, addr))
+}
diff --git a/src/backend/linux_raw/runtime/tls.rs b/src/backend/linux_raw/runtime/tls.rs
new file mode 100644
index 0000000..69bd5ce
--- /dev/null
+++ b/src/backend/linux_raw/runtime/tls.rs
@@ -0,0 +1,98 @@
+//! TLS utilities.
+//!
+//! # Safety
+//!
+//! This file contains code that reads the raw phdr array pointed to by the
+//! kernel-provided AUXV values.
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use crate::backend::param::auxv::exe_phdrs;
+use core::arch::global_asm;
+use core::ptr::{null, NonNull};
+use linux_raw_sys::elf::*;
+
+/// For use with [`set_thread_area`].
+///
+/// [`set_thread_area`]: crate::runtime::set_thread_area
+#[cfg(target_arch = "x86")]
+pub type UserDesc = linux_raw_sys::general::user_desc;
+
+pub(crate) fn startup_tls_info() -> StartupTlsInfo {
+ let mut base = null();
+ let mut tls_phdr = null();
+ let mut stack_size = 0;
+
+ let (first_phdr, phent, phnum) = exe_phdrs();
+ let mut current_phdr = first_phdr.cast::<Elf_Phdr>();
+
+ // The dynamic address of the dynamic section, which we can compare with
+ // the `PT_DYNAMIC` header's static address, if present.
+ let dynamic_addr: *const c::c_void = unsafe { &_DYNAMIC };
+
+ // SAFETY: We assume the phdr array pointer and length the kernel provided
+ // to the process describe a valid phdr array.
+ unsafe {
+ let phdrs_end = current_phdr.cast::<u8>().add(phnum * phent).cast();
+ while current_phdr != phdrs_end {
+ let phdr = &*current_phdr;
+ current_phdr = current_phdr.cast::<u8>().add(phent).cast();
+
+ match phdr.p_type {
+ // Compute the offset from the static virtual addresses
+ // in the `p_vaddr` fields to the dynamic addresses. We don't
+ // always get a `PT_PHDR` or `PT_DYNAMIC` header, so use
+ // whichever one we get.
+ PT_PHDR => base = first_phdr.cast::<u8>().wrapping_sub(phdr.p_vaddr),
+ PT_DYNAMIC => base = dynamic_addr.cast::<u8>().wrapping_sub(phdr.p_vaddr),
+
+ PT_TLS => tls_phdr = phdr,
+ PT_GNU_STACK => stack_size = phdr.p_memsz,
+ _ => {}
+ }
+ }
+
+ if tls_phdr.is_null() {
+ StartupTlsInfo {
+ addr: NonNull::dangling().as_ptr(),
+ mem_size: 0,
+ file_size: 0,
+ align: 1,
+ stack_size: 0,
+ }
+ } else {
+ StartupTlsInfo {
+ addr: base.cast::<u8>().wrapping_add((*tls_phdr).p_vaddr).cast(),
+ mem_size: (*tls_phdr).p_memsz,
+ file_size: (*tls_phdr).p_filesz,
+ align: (*tls_phdr).p_align,
+ stack_size,
+ }
+ }
+ }
+}
+
+extern "C" {
+ /// Declare the `_DYNAMIC` symbol so that we can compare its address with
+ /// the static address in the `PT_DYNAMIC` header to learn our offset. Use
+ /// a weak symbol because `_DYNAMIC` is not always present.
+ static _DYNAMIC: c::c_void;
+}
+// Rust has `extern_weak` but it isn't stable, so use a `global_asm`.
+global_asm!(".weak _DYNAMIC");
+
+/// The values returned from [`startup_tls_info`].
+///
+/// [`startup_tls_info`]: crate::runtime::startup_tls_info
+pub struct StartupTlsInfo {
+ /// The base address of the TLS segment.
+ pub addr: *const c::c_void,
+ /// The size of the memory region.
+ pub mem_size: usize,
+ /// The size beyond which all memory is zero-initialized.
+ pub file_size: usize,
+ /// The required alignment for the TLS segment.
+ pub align: usize,
+ /// The requested minimum size for stacks.
+ pub stack_size: usize,
+}
diff --git a/src/backend/linux_raw/shm/mod.rs b/src/backend/linux_raw/shm/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/linux_raw/shm/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/shm/syscalls.rs b/src/backend/linux_raw/shm/syscalls.rs
new file mode 100644
index 0000000..3b083f4
--- /dev/null
+++ b/src/backend/linux_raw/shm/syscalls.rs
@@ -0,0 +1,47 @@
+use crate::ffi::CStr;
+
+use crate::backend::fs::syscalls::{open, unlink};
+use crate::backend::fs::types::{Mode, OFlags};
+use crate::fd::OwnedFd;
+use crate::io;
+use crate::shm::ShmOFlags;
+
+const NAME_MAX: usize = 255;
+const SHM_DIR: &[u8] = b"/dev/shm/";
+
+fn get_shm_name(name: &CStr) -> io::Result<([u8; NAME_MAX + SHM_DIR.len() + 1], usize)> {
+ let name = name.to_bytes();
+
+ if name.len() > NAME_MAX {
+ return Err(io::Errno::NAMETOOLONG);
+ }
+
+ let num_slashes = name.iter().take_while(|x| **x == b'/').count();
+ let after_slashes = &name[num_slashes..];
+ if after_slashes.is_empty()
+ || after_slashes == b"."
+ || after_slashes == b".."
+ || after_slashes.contains(&b'/')
+ {
+ return Err(io::Errno::INVAL);
+ }
+
+ let mut path = [0; NAME_MAX + SHM_DIR.len() + 1];
+ path[..SHM_DIR.len()].copy_from_slice(SHM_DIR);
+ path[SHM_DIR.len()..SHM_DIR.len() + name.len()].copy_from_slice(name);
+ Ok((path, SHM_DIR.len() + name.len() + 1))
+}
+
+pub(crate) fn shm_open(name: &CStr, oflags: ShmOFlags, mode: Mode) -> io::Result<OwnedFd> {
+ let (path, len) = get_shm_name(name)?;
+ open(
+ CStr::from_bytes_with_nul(&path[..len]).unwrap(),
+ OFlags::from_bits(oflags.bits()).unwrap() | OFlags::CLOEXEC,
+ mode,
+ )
+}
+
+pub(crate) fn shm_unlink(name: &CStr) -> io::Result<()> {
+ let (path, len) = get_shm_name(name)?;
+ unlink(CStr::from_bytes_with_nul(&path[..len]).unwrap())
+}
diff --git a/src/backend/linux_raw/shm/types.rs b/src/backend/linux_raw/shm/types.rs
new file mode 100644
index 0000000..3343d44
--- /dev/null
+++ b/src/backend/linux_raw/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: c::c_uint {
+ /// `O_CREAT`
+ #[doc(alias = "CREAT")]
+ const CREATE = linux_raw_sys::general::O_CREAT;
+
+ /// `O_EXCL`
+ const EXCL = linux_raw_sys::general::O_EXCL;
+
+ /// `O_RDONLY`
+ const RDONLY = linux_raw_sys::general::O_RDONLY;
+
+ /// `O_RDWR`
+ const RDWR = linux_raw_sys::general::O_RDWR;
+
+ /// `O_TRUNC`
+ const TRUNC = linux_raw_sys::general::O_TRUNC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
diff --git a/src/backend/linux_raw/system/mod.rs b/src/backend/linux_raw/system/mod.rs
new file mode 100644
index 0000000..1e0181a
--- /dev/null
+++ b/src/backend/linux_raw/system/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/system/syscalls.rs b/src/backend/linux_raw/system/syscalls.rs
new file mode 100644
index 0000000..6b41cdb
--- /dev/null
+++ b/src/backend/linux_raw/system/syscalls.rs
@@ -0,0 +1,49 @@
+//! linux_raw syscalls supporting `rustix::system`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use super::types::RawUname;
+use crate::backend::c;
+use crate::backend::conv::{c_int, ret, ret_infallible, slice};
+use crate::io;
+use crate::system::{RebootCommand, Sysinfo};
+use core::mem::MaybeUninit;
+
+#[inline]
+pub(crate) fn uname() -> RawUname {
+ let mut uname = MaybeUninit::<RawUname>::uninit();
+ unsafe {
+ ret_infallible(syscall!(__NR_uname, &mut uname));
+ uname.assume_init()
+ }
+}
+
+#[inline]
+pub(crate) fn sysinfo() -> Sysinfo {
+ let mut info = MaybeUninit::<Sysinfo>::uninit();
+ unsafe {
+ ret_infallible(syscall!(__NR_sysinfo, &mut info));
+ info.assume_init()
+ }
+}
+
+#[inline]
+pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> {
+ let (ptr, len) = slice(name);
+ unsafe { ret(syscall_readonly!(__NR_sethostname, ptr, len)) }
+}
+
+#[inline]
+pub(crate) fn reboot(cmd: RebootCommand) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_reboot,
+ c_int(c::LINUX_REBOOT_MAGIC1),
+ c_int(c::LINUX_REBOOT_MAGIC2),
+ c_int(cmd as i32)
+ ))
+ }
+}
diff --git a/src/backend/linux_raw/system/types.rs b/src/backend/linux_raw/system/types.rs
new file mode 100644
index 0000000..4729273
--- /dev/null
+++ b/src/backend/linux_raw/system/types.rs
@@ -0,0 +1,4 @@
+/// `sysinfo`
+pub type Sysinfo = linux_raw_sys::system::sysinfo;
+
+pub(crate) type RawUname = linux_raw_sys::system::new_utsname;
diff --git a/src/backend/linux_raw/termios/mod.rs b/src/backend/linux_raw/termios/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/linux_raw/termios/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/termios/syscalls.rs b/src/backend/linux_raw/termios/syscalls.rs
new file mode 100644
index 0000000..5a24c0a
--- /dev/null
+++ b/src/backend/linux_raw/termios/syscalls.rs
@@ -0,0 +1,330 @@
+//! linux_raw syscalls supporting `rustix::termios`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+use crate::backend::conv::{by_ref, c_uint, ret};
+use crate::fd::BorrowedFd;
+use crate::io;
+use crate::pid::Pid;
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+use crate::procfs;
+use crate::termios::{
+ Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes, QueueSelector,
+ SpecialCodeIndex, Termios, Winsize,
+};
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+use crate::{ffi::CStr, fs::FileType, path::DecInt};
+use core::mem::MaybeUninit;
+use linux_raw_sys::general::IBSHIFT;
+use linux_raw_sys::ioctl::{
+ TCFLSH, TCSBRK, TCXONC, TIOCGPGRP, TIOCGSID, TIOCGWINSZ, TIOCSPGRP, TIOCSWINSZ,
+};
+
+#[inline]
+pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
+ unsafe {
+ let mut result = MaybeUninit::<Winsize>::uninit();
+ ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGWINSZ), &mut result))?;
+ Ok(result.assume_init())
+ }
+}
+
+#[inline]
+pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
+ unsafe {
+ let mut result = MaybeUninit::<Termios>::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"))]
+ {
+ result.write(core::mem::zeroed());
+ }
+
+ ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), &mut result))?;
+
+ let result = result.assume_init();
+
+ // 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"))]
+ let result = {
+ use crate::termios::speed;
+ let mut result = result;
+ if result.output_speed == 0 && (result.control_modes.bits() & c::CBAUD) != c::BOTHER {
+ if let Some(output_speed) = speed::decode(result.control_modes.bits() & c::CBAUD) {
+ result.output_speed = output_speed;
+ }
+ }
+ if result.input_speed == 0
+ && ((result.control_modes.bits() & 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 ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) == c::B0 {
+ result.input_speed = result.output_speed;
+ } else if let Some(input_speed) =
+ speed::decode((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT)
+ {
+ result.input_speed = input_speed;
+ }
+ }
+ result
+ };
+
+ Ok(result)
+ }
+}
+
+#[inline]
+pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
+ unsafe {
+ let mut result = MaybeUninit::<c::pid_t>::uninit();
+ ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGPGRP), &mut result))?;
+ let pid = result.assume_init();
+
+ // This doesn't appear to be documented, but it appears `tcsetpgrp` can
+ // succceed and set the pid to 0 if we pass it a pseudo-terminal device
+ // fd. For now, fail with `OPNOTSUPP`.
+ if pid == 0 {
+ return Err(io::Errno::OPNOTSUPP);
+ }
+
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[inline]
+pub(crate) fn tcsetattr(
+ fd: BorrowedFd<'_>,
+ optional_actions: OptionalActions,
+ termios: &Termios,
+) -> io::Result<()> {
+ // Translate from `optional_actions` into an ioctl request code. On MIPS,
+ // `optional_actions` already has `TCGETS` added to it.
+ let request = linux_raw_sys::ioctl::TCSETS2
+ + if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )) {
+ optional_actions as u32 - linux_raw_sys::ioctl::TCSETS
+ } else {
+ optional_actions as u32
+ };
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ioctl,
+ fd,
+ c_uint(request),
+ by_ref(termios)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(0))) }
+}
+
+#[inline]
+pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(1))) }
+}
+
+#[inline]
+pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ioctl,
+ fd,
+ c_uint(TCFLSH),
+ c_uint(queue_selector as u32)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ioctl,
+ fd,
+ c_uint(TCXONC),
+ c_uint(action as u32)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
+ unsafe {
+ let mut result = MaybeUninit::<c::pid_t>::uninit();
+ ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGSID), &mut result))?;
+ let pid = result.assume_init();
+ Ok(Pid::from_raw_unchecked(pid))
+ }
+}
+
+#[inline]
+pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ioctl,
+ fd,
+ c_uint(TIOCSWINSZ),
+ by_ref(&winsize)
+ ))
+ }
+}
+
+#[inline]
+pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
+ let raw_pid: c::c_int = pid.as_raw_nonzero().get();
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_ioctl,
+ fd,
+ c_uint(TIOCSPGRP),
+ by_ref(&raw_pid)
+ ))
+ }
+}
+
+/// A wrapper around a conceptual `cfsetspeed` which handles an arbitrary
+/// integer speed value.
+#[inline]
+pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
+ let encoded_speed = crate::termios::speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
+
+ 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 << IBSHIFT));
+
+ termios.input_speed = arbitrary_speed;
+ termios.output_speed = arbitrary_speed;
+
+ Ok(())
+}
+
+/// A wrapper around a conceptual `cfsetospeed` which handles an arbitrary
+/// integer speed value.
+#[inline]
+pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
+ let encoded_speed = crate::termios::speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
+
+ 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(())
+}
+
+/// A wrapper around a conceptual `cfsetispeed` which handles an arbitrary
+/// integer speed value.
+#[inline]
+pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
+ let encoded_speed = crate::termios::speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
+
+ 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 << IBSHIFT);
+
+ termios.input_speed = arbitrary_speed;
+
+ Ok(())
+}
+
+#[inline]
+pub(crate) fn cfmakeraw(termios: &mut Termios) {
+ // From the Linux [`cfmakeraw` manual page]:
+ //
+ // [`cfmakeraw` manual page]: https://man7.org/linux/man-pages/man3/cfmakeraw.3.html
+ termios.input_modes -= InputModes::IGNBRK
+ | InputModes::BRKINT
+ | InputModes::PARMRK
+ | InputModes::ISTRIP
+ | InputModes::INLCR
+ | InputModes::IGNCR
+ | InputModes::ICRNL
+ | InputModes::IXON;
+ termios.output_modes -= OutputModes::OPOST;
+ termios.local_modes -= LocalModes::ECHO
+ | LocalModes::ECHONL
+ | LocalModes::ICANON
+ | LocalModes::ISIG
+ | LocalModes::IEXTEN;
+ termios.control_modes -= ControlModes::CSIZE | ControlModes::PARENB;
+ termios.control_modes |= ControlModes::CS8;
+
+ // Musl and glibc also do these:
+ termios.special_codes[SpecialCodeIndex::VMIN] = 1;
+ termios.special_codes[SpecialCodeIndex::VTIME] = 0;
+}
+
+#[inline]
+pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
+ // On error, Linux will return either `EINVAL` (2.6.32) or `ENOTTY`
+ // (otherwise), because we assume we're never passing an invalid
+ // file descriptor (which would get `EBADF`). Either way, an error
+ // means we don't have a tty.
+ tcgetwinsize(fd).is_ok()
+}
+
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+pub(crate) fn ttyname(fd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
+ let fd_stat = crate::backend::fs::syscalls::fstat(fd)?;
+
+ // Quick check: if `fd` isn't a character device, it's not a tty.
+ if FileType::from_raw_mode(fd_stat.st_mode) != FileType::CharacterDevice {
+ return Err(io::Errno::NOTTY);
+ }
+
+ // Check that `fd` is really a tty.
+ tcgetwinsize(fd)?;
+
+ // Get a fd to "/proc/self/fd".
+ let proc_self_fd = procfs::proc_self_fd()?;
+
+ // Gather the ttyname by reading the "fd" file inside `proc_self_fd`.
+ let r = crate::backend::fs::syscalls::readlinkat(
+ proc_self_fd,
+ DecInt::from_fd(fd).as_c_str(),
+ buf,
+ )?;
+
+ // If the number of bytes is equal to the buffer length, truncation may
+ // have occurred. This check also ensures that we have enough space for
+ // adding a NUL terminator.
+ if r == buf.len() {
+ return Err(io::Errno::RANGE);
+ }
+
+ // `readlinkat` returns the number of bytes placed in the buffer.
+ // NUL-terminate the string at that offset.
+ buf[r].write(b'\0');
+
+ // Check that the path we read refers to the same file as `fd`.
+ {
+ // SAFETY: We just wrote the NUL byte above
+ let path = unsafe { CStr::from_ptr(buf.as_ptr().cast()) };
+
+ let path_stat = crate::backend::fs::syscalls::stat(path)?;
+ if path_stat.st_dev != fd_stat.st_dev || path_stat.st_ino != fd_stat.st_ino {
+ return Err(io::Errno::NODEV);
+ }
+ }
+
+ Ok(r)
+}
diff --git a/src/backend/linux_raw/thread/futex.rs b/src/backend/linux_raw/thread/futex.rs
new file mode 100644
index 0000000..263e980
--- /dev/null
+++ b/src/backend/linux_raw/thread/futex.rs
@@ -0,0 +1,45 @@
+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 = linux_raw_sys::general::FUTEX_PRIVATE_FLAG;
+ /// `FUTEX_CLOCK_REALTIME`
+ const CLOCK_REALTIME = linux_raw_sys::general::FUTEX_CLOCK_REALTIME;
+
+ // This deliberately lacks a `const _ = !0`, so that users can use
+ // `from_bits_truncate` to extract the `SocketFlags` from a flags
+ // value that also includes a `SocketType`.
+ }
+}
+
+/// `FUTEX_*` operations for use with [`futex`].
+///
+/// [`futex`]: crate::thread::futex
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+pub enum FutexOperation {
+ /// `FUTEX_WAIT`
+ Wait = linux_raw_sys::general::FUTEX_WAIT,
+ /// `FUTEX_WAKE`
+ Wake = linux_raw_sys::general::FUTEX_WAKE,
+ /// `FUTEX_FD`
+ Fd = linux_raw_sys::general::FUTEX_FD,
+ /// `FUTEX_REQUEUE`
+ Requeue = linux_raw_sys::general::FUTEX_REQUEUE,
+ /// `FUTEX_CMP_REQUEUE`
+ CmpRequeue = linux_raw_sys::general::FUTEX_CMP_REQUEUE,
+ /// `FUTEX_WAKE_OP`
+ WakeOp = linux_raw_sys::general::FUTEX_WAKE_OP,
+ /// `FUTEX_LOCK_PI`
+ LockPi = linux_raw_sys::general::FUTEX_LOCK_PI,
+ /// `FUTEX_UNLOCK_PI`
+ UnlockPi = linux_raw_sys::general::FUTEX_UNLOCK_PI,
+ /// `FUTEX_TRYLOCK_PI`
+ TrylockPi = linux_raw_sys::general::FUTEX_TRYLOCK_PI,
+ /// `FUTEX_WAIT_BITSET`
+ WaitBitset = linux_raw_sys::general::FUTEX_WAIT_BITSET,
+}
diff --git a/src/backend/linux_raw/thread/mod.rs b/src/backend/linux_raw/thread/mod.rs
new file mode 100644
index 0000000..6a002c6
--- /dev/null
+++ b/src/backend/linux_raw/thread/mod.rs
@@ -0,0 +1,2 @@
+pub(crate) mod futex;
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/thread/syscalls.rs b/src/backend/linux_raw/thread/syscalls.rs
new file mode 100644
index 0000000..0095eed
--- /dev/null
+++ b/src/backend/linux_raw/thread/syscalls.rs
@@ -0,0 +1,347 @@
+//! linux_raw syscalls supporting `rustix::thread`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+use crate::backend::conv::{
+ by_mut, by_ref, c_int, c_uint, ret, ret_c_int, ret_c_int_infallible, ret_usize,
+ slice_just_addr, slice_just_addr_mut, zero,
+};
+use crate::fd::BorrowedFd;
+use crate::io;
+use crate::pid::Pid;
+use crate::thread::{ClockId, FutexFlags, FutexOperation, NanosleepRelativeResult, Timespec};
+use core::mem::MaybeUninit;
+#[cfg(target_pointer_width = "32")]
+use linux_raw_sys::general::timespec as __kernel_old_timespec;
+use linux_raw_sys::general::{__kernel_timespec, TIMER_ABSTIME};
+
+#[inline]
+pub(crate) fn clock_nanosleep_relative(
+ id: ClockId,
+ req: &__kernel_timespec,
+) -> NanosleepRelativeResult {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ let mut rem = MaybeUninit::<__kernel_timespec>::uninit();
+ match ret(syscall!(
+ __NR_clock_nanosleep_time64,
+ id,
+ c_int(0),
+ by_ref(req),
+ &mut rem
+ ))
+ .or_else(|err| {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ if err == io::Errno::NOSYS {
+ clock_nanosleep_relative_old(id, req, &mut rem)
+ } else {
+ Err(err)
+ }
+ }) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => NanosleepRelativeResult::Interrupted(rem.assume_init()),
+ Err(err) => NanosleepRelativeResult::Err(err),
+ }
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ let mut rem = MaybeUninit::<__kernel_timespec>::uninit();
+ match ret(syscall!(
+ __NR_clock_nanosleep,
+ id,
+ c_int(0),
+ by_ref(req),
+ &mut rem
+ )) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => NanosleepRelativeResult::Interrupted(rem.assume_init()),
+ Err(err) => NanosleepRelativeResult::Err(err),
+ }
+ }
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn clock_nanosleep_relative_old(
+ id: ClockId,
+ req: &__kernel_timespec,
+ rem: &mut MaybeUninit<__kernel_timespec>,
+) -> io::Result<()> {
+ let old_req = __kernel_old_timespec {
+ tv_sec: req.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
+ tv_nsec: req.tv_nsec.try_into().map_err(|_| io::Errno::INVAL)?,
+ };
+ let mut old_rem = MaybeUninit::<__kernel_old_timespec>::uninit();
+ ret(syscall!(
+ __NR_clock_nanosleep,
+ id,
+ c_int(0),
+ by_ref(&old_req),
+ &mut old_rem
+ ))?;
+ let old_rem = old_rem.assume_init();
+ rem.write(__kernel_timespec {
+ tv_sec: old_rem.tv_sec.into(),
+ tv_nsec: old_rem.tv_nsec.into(),
+ });
+ Ok(())
+}
+
+#[inline]
+pub(crate) fn clock_nanosleep_absolute(id: ClockId, req: &__kernel_timespec) -> io::Result<()> {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_clock_nanosleep_time64,
+ id,
+ c_uint(TIMER_ABSTIME),
+ by_ref(req),
+ zero()
+ ))
+ .or_else(|err| {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ if err == io::Errno::NOSYS {
+ clock_nanosleep_absolute_old(id, req)
+ } else {
+ Err(err)
+ }
+ })
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_clock_nanosleep,
+ id,
+ c_uint(TIMER_ABSTIME),
+ by_ref(req),
+ zero()
+ ))
+ }
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn clock_nanosleep_absolute_old(id: ClockId, req: &__kernel_timespec) -> io::Result<()> {
+ let old_req = __kernel_old_timespec {
+ tv_sec: req.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
+ tv_nsec: req.tv_nsec.try_into().map_err(|_| io::Errno::INVAL)?,
+ };
+ ret(syscall_readonly!(
+ __NR_clock_nanosleep,
+ id,
+ c_int(0),
+ by_ref(&old_req),
+ zero()
+ ))
+}
+
+#[inline]
+pub(crate) fn nanosleep(req: &__kernel_timespec) -> NanosleepRelativeResult {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ let mut rem = MaybeUninit::<__kernel_timespec>::uninit();
+ match ret(syscall!(
+ __NR_clock_nanosleep_time64,
+ ClockId::Realtime,
+ c_int(0),
+ by_ref(req),
+ &mut rem
+ ))
+ .or_else(|err| {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ if err == io::Errno::NOSYS {
+ nanosleep_old(req, &mut rem)
+ } else {
+ Err(err)
+ }
+ }) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => NanosleepRelativeResult::Interrupted(rem.assume_init()),
+ Err(err) => NanosleepRelativeResult::Err(err),
+ }
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ let mut rem = MaybeUninit::<__kernel_timespec>::uninit();
+ match ret(syscall!(__NR_nanosleep, by_ref(req), &mut rem)) {
+ Ok(()) => NanosleepRelativeResult::Ok,
+ Err(io::Errno::INTR) => NanosleepRelativeResult::Interrupted(rem.assume_init()),
+ Err(err) => NanosleepRelativeResult::Err(err),
+ }
+ }
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn nanosleep_old(
+ req: &__kernel_timespec,
+ rem: &mut MaybeUninit<__kernel_timespec>,
+) -> io::Result<()> {
+ let old_req = __kernel_old_timespec {
+ tv_sec: req.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
+ tv_nsec: req.tv_nsec.try_into().map_err(|_| io::Errno::INVAL)?,
+ };
+ let mut old_rem = MaybeUninit::<__kernel_old_timespec>::uninit();
+ ret(syscall!(__NR_nanosleep, by_ref(&old_req), &mut old_rem))?;
+ let old_rem = old_rem.assume_init();
+ rem.write(__kernel_timespec {
+ tv_sec: old_rem.tv_sec.into(),
+ tv_nsec: old_rem.tv_nsec.into(),
+ });
+ Ok(())
+}
+
+#[inline]
+pub(crate) fn gettid() -> Pid {
+ unsafe {
+ let tid = ret_c_int_infallible(syscall_readonly!(__NR_gettid));
+ Pid::from_raw_unchecked(tid)
+ }
+}
+
+// TODO: This could be de-multiplexed.
+#[inline]
+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(target_pointer_width = "32")]
+ {
+ ret_usize(syscall!(
+ __NR_futex_time64,
+ uaddr,
+ (op, flags),
+ c_uint(val),
+ utime,
+ uaddr2,
+ c_uint(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(target_pointer_width = "64")]
+ ret_usize(syscall!(
+ __NR_futex,
+ uaddr,
+ (op, flags),
+ c_uint(val),
+ utime,
+ uaddr2,
+ c_uint(val3)
+ ))
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn futex_old(
+ uaddr: *mut u32,
+ op: FutexOperation,
+ flags: FutexFlags,
+ val: u32,
+ utime: *const Timespec,
+ uaddr2: *mut u32,
+ val3: u32,
+) -> io::Result<usize> {
+ let old_utime = __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(syscall!(
+ __NR_futex,
+ uaddr,
+ (op, flags),
+ c_uint(val),
+ by_ref(&old_utime),
+ uaddr2,
+ c_uint(val3)
+ ))
+}
+
+#[inline]
+pub(crate) fn setns(fd: BorrowedFd<'_>, nstype: c::c_int) -> io::Result<c::c_int> {
+ unsafe { ret_c_int(syscall_readonly!(__NR_setns, fd, c_int(nstype))) }
+}
+
+#[inline]
+pub(crate) fn unshare(flags: crate::thread::UnshareFlags) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_unshare, flags)) }
+}
+
+#[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<()> {
+ unsafe {
+ ret(syscall!(
+ __NR_capget,
+ by_mut(header),
+ slice_just_addr_mut(data)
+ ))
+ }
+}
+
+#[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<()> {
+ unsafe { ret(syscall!(__NR_capset, by_mut(header), slice_just_addr(data))) }
+}
+
+#[inline]
+pub(crate) fn setuid_thread(uid: crate::ugid::Uid) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_setuid, uid)) }
+}
+
+#[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"))]
+ unsafe {
+ ret(syscall_readonly!(__NR_setresuid32, ruid, euid, suid))
+ }
+ #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))]
+ unsafe {
+ ret(syscall_readonly!(__NR_setresuid, ruid, euid, suid))
+ }
+}
+
+#[inline]
+pub(crate) fn setgid_thread(gid: crate::ugid::Gid) -> io::Result<()> {
+ unsafe { ret(syscall_readonly!(__NR_setgid, gid)) }
+}
+
+#[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"))]
+ unsafe {
+ ret(syscall_readonly!(__NR_setresgid32, rgid, egid, sgid))
+ }
+ #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))]
+ unsafe {
+ ret(syscall_readonly!(__NR_setresgid, rgid, egid, sgid))
+ }
+}
diff --git a/src/backend/linux_raw/time/mod.rs b/src/backend/linux_raw/time/mod.rs
new file mode 100644
index 0000000..c42592c
--- /dev/null
+++ b/src/backend/linux_raw/time/mod.rs
@@ -0,0 +1,3 @@
+#[cfg(any(feature = "time", target_arch = "x86"))]
+pub(crate) mod syscalls;
+pub(crate) mod types;
diff --git a/src/backend/linux_raw/time/syscalls.rs b/src/backend/linux_raw/time/syscalls.rs
new file mode 100644
index 0000000..d20bcfa
--- /dev/null
+++ b/src/backend/linux_raw/time/syscalls.rs
@@ -0,0 +1,257 @@
+//! linux_raw syscalls supporting `rustix::time`.
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::conv::{ret, ret_infallible};
+use crate::clockid::ClockId;
+use crate::io;
+use crate::timespec::Timespec;
+use core::mem::MaybeUninit;
+#[cfg(all(feature = "time", target_pointer_width = "32"))]
+use linux_raw_sys::general::itimerspec as __kernel_old_itimerspec;
+#[cfg(target_pointer_width = "32")]
+use linux_raw_sys::general::timespec as __kernel_old_timespec;
+#[cfg(feature = "time")]
+use {
+ crate::backend::conv::{by_ref, ret_owned_fd},
+ crate::fd::BorrowedFd,
+ crate::fd::OwnedFd,
+ crate::time::{Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags},
+};
+
+// `clock_gettime` has special optimizations via the vDSO.
+#[cfg(feature = "time")]
+pub(crate) use crate::backend::vdso_wrappers::{clock_gettime, clock_gettime_dynamic};
+
+#[inline]
+pub(crate) fn clock_getres(which_clock: ClockId) -> Timespec {
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ let mut result = MaybeUninit::<Timespec>::uninit();
+ if let Err(err) = ret(syscall!(__NR_clock_getres_time64, which_clock, &mut result)) {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ debug_assert_eq!(err, io::Errno::NOSYS);
+ clock_getres_old(which_clock, &mut result);
+ }
+ result.assume_init()
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ let mut result = MaybeUninit::<Timespec>::uninit();
+ ret_infallible(syscall!(__NR_clock_getres, which_clock, &mut result));
+ result.assume_init()
+ }
+}
+
+#[cfg(target_pointer_width = "32")]
+unsafe fn clock_getres_old(which_clock: ClockId, result: &mut MaybeUninit<Timespec>) {
+ let mut old_result = MaybeUninit::<__kernel_old_timespec>::uninit();
+ ret_infallible(syscall!(__NR_clock_getres, which_clock, &mut old_result));
+ let old_result = old_result.assume_init();
+ result.write(Timespec {
+ tv_sec: old_result.tv_sec.into(),
+ tv_nsec: old_result.tv_nsec.into(),
+ });
+}
+
+#[cfg(feature = "time")]
+#[inline]
+pub(crate) fn clock_settime(which_clock: ClockId, timespec: Timespec) -> io::Result<()> {
+ // `clock_settime64` was introduced in Linux 5.1. The old `clock_settime`
+ // syscall is not y2038-compatible on 32-bit architectures.
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ match ret(syscall_readonly!(
+ __NR_clock_settime64,
+ which_clock,
+ by_ref(&timespec)
+ )) {
+ Err(io::Errno::NOSYS) => clock_settime_old(which_clock, timespec),
+ otherwise => otherwise,
+ }
+ }
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall_readonly!(
+ __NR_clock_settime,
+ which_clock,
+ by_ref(&timespec)
+ ))
+ }
+}
+
+#[cfg(feature = "time")]
+#[cfg(target_pointer_width = "32")]
+unsafe fn clock_settime_old(which_clock: ClockId, timespec: Timespec) -> io::Result<()> {
+ let old_timespec = __kernel_old_timespec {
+ tv_sec: timespec
+ .tv_sec
+ .try_into()
+ .map_err(|_| io::Errno::OVERFLOW)?,
+ tv_nsec: timespec.tv_nsec as _,
+ };
+ ret(syscall_readonly!(
+ __NR_clock_settime,
+ which_clock,
+ by_ref(&old_timespec)
+ ))
+}
+
+#[cfg(feature = "time")]
+#[inline]
+pub(crate) fn timerfd_create(clockid: TimerfdClockId, flags: TimerfdFlags) -> io::Result<OwnedFd> {
+ unsafe { ret_owned_fd(syscall_readonly!(__NR_timerfd_create, clockid, flags)) }
+}
+
+#[cfg(feature = "time")]
+#[inline]
+pub(crate) fn timerfd_settime(
+ fd: BorrowedFd<'_>,
+ flags: TimerfdTimerFlags,
+ new_value: &Itimerspec,
+) -> io::Result<Itimerspec> {
+ let mut result = MaybeUninit::<Itimerspec>::uninit();
+
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall!(
+ __NR_timerfd_settime,
+ fd,
+ flags,
+ by_ref(new_value),
+ &mut result
+ ))?;
+ Ok(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall!(
+ __NR_timerfd_settime64,
+ fd,
+ flags,
+ by_ref(new_value),
+ &mut result
+ ))
+ .or_else(|err| {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ if err == io::Errno::NOSYS {
+ timerfd_settime_old(fd, flags, new_value, &mut result)
+ } else {
+ Err(err)
+ }
+ })?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(feature = "time")]
+#[cfg(target_pointer_width = "32")]
+unsafe fn timerfd_settime_old(
+ fd: BorrowedFd<'_>,
+ flags: TimerfdTimerFlags,
+ new_value: &Itimerspec,
+ result: &mut MaybeUninit<Itimerspec>,
+) -> io::Result<()> {
+ let mut old_result = MaybeUninit::<__kernel_old_itimerspec>::uninit();
+
+ // Convert `new_value` to the old `__kernel_old_itimerspec` format.
+ let old_new_value = __kernel_old_itimerspec {
+ it_interval: __kernel_old_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: __kernel_old_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)?,
+ },
+ };
+ ret(syscall!(
+ __NR_timerfd_settime,
+ fd,
+ flags,
+ by_ref(&old_new_value),
+ &mut old_result
+ ))?;
+ let old_result = old_result.assume_init();
+ result.write(Itimerspec {
+ it_interval: Timespec {
+ tv_sec: old_result.it_interval.tv_sec.into(),
+ tv_nsec: old_result.it_interval.tv_nsec.into(),
+ },
+ it_value: Timespec {
+ tv_sec: old_result.it_value.tv_sec.into(),
+ tv_nsec: old_result.it_value.tv_nsec.into(),
+ },
+ });
+ Ok(())
+}
+
+#[cfg(feature = "time")]
+#[inline]
+pub(crate) fn timerfd_gettime(fd: BorrowedFd<'_>) -> io::Result<Itimerspec> {
+ let mut result = MaybeUninit::<Itimerspec>::uninit();
+
+ #[cfg(target_pointer_width = "64")]
+ unsafe {
+ ret(syscall!(__NR_timerfd_gettime, fd, &mut result))?;
+ Ok(result.assume_init())
+ }
+
+ #[cfg(target_pointer_width = "32")]
+ unsafe {
+ ret(syscall!(__NR_timerfd_gettime64, fd, &mut result)).or_else(|err| {
+ // See the comments in `rustix_clock_gettime_via_syscall` about
+ // emulation.
+ if err == io::Errno::NOSYS {
+ timerfd_gettime_old(fd, &mut result)
+ } else {
+ Err(err)
+ }
+ })?;
+ Ok(result.assume_init())
+ }
+}
+
+#[cfg(feature = "time")]
+#[cfg(target_pointer_width = "32")]
+unsafe fn timerfd_gettime_old(
+ fd: BorrowedFd<'_>,
+ result: &mut MaybeUninit<Itimerspec>,
+) -> io::Result<()> {
+ let mut old_result = MaybeUninit::<__kernel_old_itimerspec>::uninit();
+ ret(syscall!(__NR_timerfd_gettime, fd, &mut old_result))?;
+ let old_result = old_result.assume_init();
+ result.write(Itimerspec {
+ it_interval: Timespec {
+ tv_sec: old_result.it_interval.tv_sec.into(),
+ tv_nsec: old_result.it_interval.tv_nsec.into(),
+ },
+ it_value: Timespec {
+ tv_sec: old_result.it_value.tv_sec.into(),
+ tv_nsec: old_result.it_value.tv_nsec.into(),
+ },
+ });
+ Ok(())
+}
diff --git a/src/backend/linux_raw/time/types.rs b/src/backend/linux_raw/time/types.rs
new file mode 100644
index 0000000..c26812c
--- /dev/null
+++ b/src/backend/linux_raw/time/types.rs
@@ -0,0 +1,100 @@
+use crate::backend::c;
+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
+pub type Itimerspec = linux_raw_sys::general::__kernel_itimerspec;
+
+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: c::c_uint {
+ /// `TFD_NONBLOCK`
+ #[doc(alias = "TFD_NONBLOCK")]
+ const NONBLOCK = linux_raw_sys::general::TFD_NONBLOCK;
+
+ /// `TFD_CLOEXEC`
+ #[doc(alias = "TFD_CLOEXEC")]
+ const CLOEXEC = linux_raw_sys::general::TFD_CLOEXEC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+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: c::c_uint {
+ /// `TFD_TIMER_ABSTIME`
+ #[doc(alias = "TFD_TIMER_ABSTIME")]
+ const ABSTIME = linux_raw_sys::general::TFD_TIMER_ABSTIME;
+
+ /// `TFD_TIMER_CANCEL_ON_SET`
+ #[doc(alias = "TFD_TIMER_CANCEL_ON_SET")]
+ const CANCEL_ON_SET = linux_raw_sys::general::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
+#[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 = linux_raw_sys::general::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 = linux_raw_sys::general::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 = linux_raw_sys::general::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 = linux_raw_sys::general::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 = linux_raw_sys::general::CLOCK_BOOTTIME_ALARM,
+}
diff --git a/src/backend/linux_raw/ugid/mod.rs b/src/backend/linux_raw/ugid/mod.rs
new file mode 100644
index 0000000..ef944f0
--- /dev/null
+++ b/src/backend/linux_raw/ugid/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod syscalls;
diff --git a/src/backend/linux_raw/ugid/syscalls.rs b/src/backend/linux_raw/ugid/syscalls.rs
new file mode 100644
index 0000000..5f1551e
--- /dev/null
+++ b/src/backend/linux_raw/ugid/syscalls.rs
@@ -0,0 +1,66 @@
+//! linux_raw syscalls for UIDs and GIDs
+//!
+//! # Safety
+//!
+//! See the `rustix::backend` module documentation for details.
+#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
+
+use crate::backend::c;
+use crate::backend::conv::ret_usize_infallible;
+use crate::ugid::{Gid, Uid};
+
+#[inline]
+pub(crate) fn getuid() -> Uid {
+ #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))]
+ unsafe {
+ let uid = ret_usize_infallible(syscall_readonly!(__NR_getuid32)) as c::uid_t;
+ Uid::from_raw(uid)
+ }
+ #[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))]
+ unsafe {
+ let uid = ret_usize_infallible(syscall_readonly!(__NR_getuid)) as c::uid_t;
+ Uid::from_raw(uid)
+ }
+}
+
+#[inline]
+pub(crate) fn geteuid() -> Uid {
+ #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))]
+ unsafe {
+ let uid = ret_usize_infallible(syscall_readonly!(__NR_geteuid32)) as c::uid_t;
+ Uid::from_raw(uid)
+ }
+ #[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))]
+ unsafe {
+ let uid = ret_usize_infallible(syscall_readonly!(__NR_geteuid)) as c::uid_t;
+ Uid::from_raw(uid)
+ }
+}
+
+#[inline]
+pub(crate) fn getgid() -> Gid {
+ #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))]
+ unsafe {
+ let gid = ret_usize_infallible(syscall_readonly!(__NR_getgid32)) as c::gid_t;
+ Gid::from_raw(gid)
+ }
+ #[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))]
+ unsafe {
+ let gid = ret_usize_infallible(syscall_readonly!(__NR_getgid)) as c::gid_t;
+ Gid::from_raw(gid)
+ }
+}
+
+#[inline]
+pub(crate) fn getegid() -> Gid {
+ #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))]
+ unsafe {
+ let gid = ret_usize_infallible(syscall_readonly!(__NR_getegid32)) as c::gid_t;
+ Gid::from_raw(gid)
+ }
+ #[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))]
+ unsafe {
+ let gid = ret_usize_infallible(syscall_readonly!(__NR_getegid)) as c::gid_t;
+ Gid::from_raw(gid)
+ }
+}
diff --git a/src/backend/linux_raw/vdso.rs b/src/backend/linux_raw/vdso.rs
new file mode 100644
index 0000000..102fa6b
--- /dev/null
+++ b/src/backend/linux_raw/vdso.rs
@@ -0,0 +1,313 @@
+//! Parse the Linux vDSO.
+//!
+//! The following code is transliterated from
+//! tools/testing/selftests/vDSO/parse_vdso.c in Linux 5.11, which is licensed
+//! with Creative Commons Zero License, version 1.0,
+//! available at <https://creativecommons.org/publicdomain/zero/1.0/legalcode>
+//!
+//! # Safety
+//!
+//! Parsing the vDSO involves a lot of raw pointer manipulation. This
+//! implementation follows Linux's reference implementation, and adds several
+//! additional safety checks.
+#![allow(unsafe_code)]
+
+use super::c;
+use crate::ffi::CStr;
+use crate::utils::check_raw_pointer;
+use core::ffi::c_void;
+use core::mem::size_of;
+use core::ptr::{null, null_mut};
+use linux_raw_sys::elf::*;
+
+pub(super) struct Vdso {
+ // Load information
+ load_addr: *const Elf_Ehdr,
+ load_end: *const c_void, // the end of the `PT_LOAD` segment
+ pv_offset: usize, // recorded paddr - recorded vaddr
+
+ // Symbol table
+ symtab: *const Elf_Sym,
+ symstrings: *const u8,
+ bucket: *const u32,
+ chain: *const u32,
+ nbucket: u32,
+ //nchain: u32,
+
+ // Version table
+ versym: *const u16,
+ verdef: *const Elf_Verdef,
+}
+
+// Straight from the ELF specification.
+fn elf_hash(name: &CStr) -> u32 {
+ let mut h: u32 = 0;
+ for b in name.to_bytes() {
+ h = (h << 4).wrapping_add(u32::from(*b));
+ let g = h & 0xf000_0000;
+ if g != 0 {
+ h ^= g >> 24;
+ }
+ h &= !g;
+ }
+ h
+}
+
+/// Create a `Vdso` value by parsing the vDSO at the `sysinfo_ehdr` address.
+fn init_from_sysinfo_ehdr() -> Option<Vdso> {
+ // SAFETY: The auxv initialization code does extensive checks to ensure
+ // that the value we get really is an `AT_SYSINFO_EHDR` value from the
+ // kernel.
+ unsafe {
+ let hdr = super::param::auxv::sysinfo_ehdr();
+
+ // If the platform doesn't provide a `AT_SYSINFO_EHDR`, we can't locate
+ // the vDSO.
+ if hdr.is_null() {
+ return None;
+ }
+
+ let mut vdso = Vdso {
+ load_addr: hdr,
+ load_end: hdr.cast(),
+ pv_offset: 0,
+ symtab: null(),
+ symstrings: null(),
+ bucket: null(),
+ chain: null(),
+ nbucket: 0,
+ //nchain: 0,
+ versym: null(),
+ verdef: null(),
+ };
+
+ let hdr = &*hdr;
+ let pt = check_raw_pointer::<Elf_Phdr>(vdso.base_plus(hdr.e_phoff)? as *mut _)?.as_ptr();
+ let mut dyn_: *const Elf_Dyn = null();
+ let mut num_dyn = 0;
+
+ // We need two things from the segment table: the load offset
+ // and the dynamic table.
+ let mut found_vaddr = false;
+ for i in 0..hdr.e_phnum {
+ let phdr = &*pt.add(i as usize);
+ if phdr.p_flags & PF_W != 0 {
+ // Don't trust any vDSO that claims to be loading writable
+ // segments into memory.
+ return None;
+ }
+ if phdr.p_type == PT_LOAD && !found_vaddr {
+ // The segment should be readable and executable, because it
+ // contains the symbol table and the function bodies.
+ if phdr.p_flags & (PF_R | PF_X) != (PF_R | PF_X) {
+ return None;
+ }
+ found_vaddr = true;
+ vdso.load_end = vdso.base_plus(phdr.p_offset.checked_add(phdr.p_memsz)?)?;
+ vdso.pv_offset = phdr.p_offset.wrapping_sub(phdr.p_vaddr);
+ } else if phdr.p_type == PT_DYNAMIC {
+ // If `p_offset` is zero, it's more likely that we're looking
+ // at memory that has been zeroed than that the kernel has
+ // somehow aliased the `Ehdr` and the `Elf_Dyn` array.
+ if phdr.p_offset < size_of::<Elf_Ehdr>() {
+ return None;
+ }
+
+ dyn_ = check_raw_pointer::<Elf_Dyn>(vdso.base_plus(phdr.p_offset)? as *mut _)?
+ .as_ptr();
+ num_dyn = phdr.p_memsz / size_of::<Elf_Dyn>();
+ } else if phdr.p_type == PT_INTERP || phdr.p_type == PT_GNU_RELRO {
+ // Don't trust any ELF image that has an “interpreter” or
+ // that uses RELRO, which is likely to be a user ELF image
+ // rather and not the kernel vDSO.
+ return None;
+ }
+ }
+
+ if !found_vaddr || dyn_.is_null() {
+ return None; // Failed
+ }
+
+ // Fish out the useful bits of the dynamic table.
+ let mut hash: *const u32 = null();
+ vdso.symstrings = null();
+ vdso.symtab = null();
+ vdso.versym = null();
+ vdso.verdef = null();
+ let mut i = 0;
+ loop {
+ if i == num_dyn {
+ return None;
+ }
+ let d = &*dyn_.add(i);
+ match d.d_tag {
+ DT_STRTAB => {
+ vdso.symstrings =
+ check_raw_pointer::<u8>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)?
+ .as_ptr();
+ }
+ DT_SYMTAB => {
+ vdso.symtab =
+ check_raw_pointer::<Elf_Sym>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)?
+ .as_ptr();
+ }
+ DT_HASH => {
+ hash = check_raw_pointer::<u32>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)?
+ .as_ptr();
+ }
+ DT_VERSYM => {
+ vdso.versym =
+ check_raw_pointer::<u16>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)?
+ .as_ptr();
+ }
+ DT_VERDEF => {
+ vdso.verdef = check_raw_pointer::<Elf_Verdef>(
+ vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _,
+ )?
+ .as_ptr();
+ }
+ DT_SYMENT => {
+ if d.d_un.d_ptr != size_of::<Elf_Sym>() {
+ return None; // Failed
+ }
+ }
+ DT_NULL => break,
+ _ => {}
+ }
+ i = i.checked_add(1)?;
+ }
+ // The upstream code checks `symstrings`, `symtab`, and `hash` for
+ // null; here, `check_raw_pointer` has already done that.
+
+ if vdso.verdef.is_null() {
+ vdso.versym = null();
+ }
+
+ // Parse the hash table header.
+ vdso.nbucket = *hash.add(0);
+ //vdso.nchain = *hash.add(1);
+ vdso.bucket = hash.add(2);
+ vdso.chain = hash.add(vdso.nbucket as usize + 2);
+
+ // That's all we need.
+ Some(vdso)
+ }
+}
+
+impl Vdso {
+ /// Parse the vDSO.
+ ///
+ /// Returns `None` if the vDSO can't be located or if it doesn't conform to
+ /// our expectations.
+ #[inline]
+ pub(super) fn new() -> Option<Self> {
+ init_from_sysinfo_ehdr()
+ }
+
+ /// Check the version for a symbol.
+ ///
+ /// # Safety
+ ///
+ /// The raw pointers inside `self` must be valid.
+ unsafe fn match_version(&self, mut ver: u16, name: &CStr, hash: u32) -> bool {
+ // This is a helper function to check if the version indexed by
+ // ver matches name (which hashes to hash).
+ //
+ // The version definition table is a mess, and I don't know how
+ // to do this in better than linear time without allocating memory
+ // to build an index. I also don't know why the table has
+ // variable size entries in the first place.
+ //
+ // For added fun, I can't find a comprehensible specification of how
+ // to parse all the weird flags in the table.
+ //
+ // So I just parse the whole table every time.
+
+ // First step: find the version definition
+ ver &= 0x7fff; // Apparently bit 15 means "hidden"
+ let mut def = self.verdef;
+ loop {
+ if (*def).vd_version != VER_DEF_CURRENT {
+ return false; // Failed
+ }
+
+ if ((*def).vd_flags & VER_FLG_BASE) == 0 && ((*def).vd_ndx & 0x7fff) == ver {
+ break;
+ }
+
+ if (*def).vd_next == 0 {
+ return false; // No definition.
+ }
+
+ def = def
+ .cast::<u8>()
+ .add((*def).vd_next as usize)
+ .cast::<Elf_Verdef>();
+ }
+
+ // Now figure out whether it matches.
+ let aux = &*(def.cast::<u8>())
+ .add((*def).vd_aux as usize)
+ .cast::<Elf_Verdaux>();
+ (*def).vd_hash == hash
+ && (name == CStr::from_ptr(self.symstrings.add(aux.vda_name as usize).cast()))
+ }
+
+ /// Look up a symbol in the vDSO.
+ pub(super) fn sym(&self, version: &CStr, name: &CStr) -> *mut c::c_void {
+ let ver_hash = elf_hash(version);
+ let name_hash = elf_hash(name);
+
+ // SAFETY: The pointers in `self` must be valid.
+ unsafe {
+ let mut chain = *self.bucket.add((name_hash % self.nbucket) as usize);
+
+ while chain != STN_UNDEF {
+ let sym = &*self.symtab.add(chain as usize);
+
+ // Check for a defined global or weak function w/ right name.
+ //
+ // The reference parser in Linux's parse_vdso.c requires
+ // symbols to have type `STT_FUNC`, but on powerpc64, the vDSO
+ // uses `STT_NOTYPE`, so allow that too.
+ if (ELF_ST_TYPE(sym.st_info) != STT_FUNC &&
+ ELF_ST_TYPE(sym.st_info) != STT_NOTYPE)
+ || (ELF_ST_BIND(sym.st_info) != STB_GLOBAL
+ && ELF_ST_BIND(sym.st_info) != STB_WEAK)
+ || sym.st_shndx == SHN_UNDEF
+ || sym.st_shndx == SHN_ABS
+ || ELF_ST_VISIBILITY(sym.st_other) != STV_DEFAULT
+ || (name != CStr::from_ptr(self.symstrings.add(sym.st_name as usize).cast()))
+ // Check symbol version.
+ || (!self.versym.is_null()
+ && !self.match_version(*self.versym.add(chain as usize), version, ver_hash))
+ {
+ chain = *self.chain.add(chain as usize);
+ continue;
+ }
+
+ let sum = self.addr_from_elf(sym.st_value).unwrap();
+ assert!(
+ sum as usize >= self.load_addr as usize
+ && sum as usize <= self.load_end as usize
+ );
+ return sum as *mut c::c_void;
+ }
+ }
+
+ null_mut()
+ }
+
+ /// Add the given address to the vDSO base address.
+ unsafe fn base_plus(&self, offset: usize) -> Option<*const c_void> {
+ // Check for overflow.
+ let _ = (self.load_addr as usize).checked_add(offset)?;
+ // Add the offset to the base.
+ Some(self.load_addr.cast::<u8>().add(offset).cast())
+ }
+
+ /// Translate an ELF-address-space address into a usable virtual address.
+ unsafe fn addr_from_elf(&self, elf_addr: usize) -> Option<*const c_void> {
+ self.base_plus(elf_addr.wrapping_add(self.pv_offset))
+ }
+}
diff --git a/src/backend/linux_raw/vdso_wrappers.rs b/src/backend/linux_raw/vdso_wrappers.rs
new file mode 100644
index 0000000..441738f
--- /dev/null
+++ b/src/backend/linux_raw/vdso_wrappers.rs
@@ -0,0 +1,608 @@
+//! Implement syscalls using the vDSO.
+//!
+//! <https://man7.org/linux/man-pages/man7/vdso.7.html>
+//!
+//! # Safety
+//!
+//! Similar to syscalls.rs, this file performs raw system calls, and sometimes
+//! passes them uninitialized memory buffers. This file also calls vDSO
+//! functions.
+#![allow(unsafe_code)]
+
+#[cfg(target_arch = "x86")]
+use super::reg::{ArgReg, RetReg, SyscallNumber, A0, A1, A2, A3, A4, A5, R0};
+use super::vdso;
+#[cfg(target_arch = "x86")]
+use core::arch::global_asm;
+#[cfg(feature = "process")]
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+use core::ffi::c_void;
+use core::mem::transmute;
+use core::ptr::null_mut;
+use core::sync::atomic::AtomicPtr;
+use core::sync::atomic::Ordering::Relaxed;
+#[cfg(target_pointer_width = "32")]
+#[cfg(feature = "time")]
+use linux_raw_sys::general::timespec as __kernel_old_timespec;
+#[cfg(any(
+ all(
+ feature = "process",
+ any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+ )
+ ),
+ feature = "time"
+))]
+use {super::c, super::conv::ret, core::mem::MaybeUninit};
+#[cfg(feature = "time")]
+use {
+ super::conv::c_int,
+ crate::clockid::{ClockId, DynamicClockId},
+ crate::io,
+ crate::timespec::Timespec,
+ linux_raw_sys::general::{__kernel_clockid_t, __kernel_timespec},
+};
+
+#[cfg(feature = "time")]
+#[inline]
+pub(crate) fn clock_gettime(which_clock: ClockId) -> __kernel_timespec {
+ // SAFETY: `CLOCK_GETTIME` contains either null or the address of a
+ // function with an ABI like libc `clock_gettime`, and calling it has the
+ // side effect of writing to the result buffer, and no others.
+ unsafe {
+ let mut result = MaybeUninit::<__kernel_timespec>::uninit();
+ let callee = match transmute(CLOCK_GETTIME.load(Relaxed)) {
+ Some(callee) => callee,
+ None => init_clock_gettime(),
+ };
+ let r0 = callee(which_clock as c::c_int, result.as_mut_ptr());
+ // The `ClockId` enum only contains clocks which never fail. It may be
+ // tempting to change this to `debug_assert_eq`, however they can still
+ // fail on uncommon kernel configs, so we leave this in place to ensure
+ // that we don't execute undefined behavior if they ever do fail.
+ assert_eq!(r0, 0);
+ result.assume_init()
+ }
+}
+
+#[cfg(feature = "time")]
+#[inline]
+pub(crate) fn clock_gettime_dynamic(which_clock: DynamicClockId<'_>) -> io::Result<Timespec> {
+ let id = match which_clock {
+ DynamicClockId::Known(id) => id as __kernel_clockid_t,
+
+ DynamicClockId::Dynamic(fd) => {
+ // See `FD_TO_CLOCKID` in Linux's `clock_gettime` documentation.
+ use crate::backend::fd::AsRawFd;
+ const CLOCKFD: i32 = 3;
+ ((!fd.as_raw_fd() << 3) | CLOCKFD) as __kernel_clockid_t
+ }
+
+ DynamicClockId::RealtimeAlarm => c::CLOCK_REALTIME_ALARM as __kernel_clockid_t,
+ DynamicClockId::Tai => c::CLOCK_TAI as __kernel_clockid_t,
+ DynamicClockId::Boottime => c::CLOCK_BOOTTIME as __kernel_clockid_t,
+ DynamicClockId::BoottimeAlarm => c::CLOCK_BOOTTIME_ALARM as __kernel_clockid_t,
+ };
+
+ // SAFETY: `CLOCK_GETTIME` contains either null or the address of a
+ // function with an ABI like libc `clock_gettime`, and calling it has the
+ // side effect of writing to the result buffer, and no others.
+ unsafe {
+ const EINVAL: c::c_int = -(c::EINVAL as c::c_int);
+ let mut timespec = MaybeUninit::<Timespec>::uninit();
+ let callee = match transmute(CLOCK_GETTIME.load(Relaxed)) {
+ Some(callee) => callee,
+ None => init_clock_gettime(),
+ };
+ match callee(id, timespec.as_mut_ptr()) {
+ 0 => (),
+ EINVAL => return Err(io::Errno::INVAL),
+ _ => _rustix_clock_gettime_via_syscall(id, timespec.as_mut_ptr())?,
+ }
+ Ok(timespec.assume_init())
+ }
+}
+
+#[cfg(feature = "process")]
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+#[inline]
+pub(crate) fn sched_getcpu() -> usize {
+ // SAFETY: `GETCPU` contains either null or the address of a function with
+ // an ABI like libc `getcpu`, and calling it has the side effect of writing
+ // to the result buffers, and no others.
+ unsafe {
+ let mut cpu = MaybeUninit::<u32>::uninit();
+ let callee = match transmute(GETCPU.load(Relaxed)) {
+ Some(callee) => callee,
+ None => init_getcpu(),
+ };
+ let r0 = callee(cpu.as_mut_ptr(), null_mut(), null_mut());
+ debug_assert_eq!(r0, 0);
+ cpu.assume_init() as usize
+ }
+}
+
+#[cfg(target_arch = "x86")]
+pub(super) mod x86_via_vdso {
+ use super::{transmute, ArgReg, Relaxed, RetReg, SyscallNumber, A0, A1, A2, A3, A4, A5, R0};
+ use crate::backend::arch::asm;
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall0(nr: SyscallNumber<'_>) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall0(callee, nr)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall1<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ ) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall1(callee, nr, a0)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall1_noreturn<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ ) -> ! {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall1_noreturn(callee, nr, a0)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall2<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ a1: ArgReg<'a, A1>,
+ ) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall2(callee, nr, a0, a1)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall3<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ a1: ArgReg<'a, A1>,
+ a2: ArgReg<'a, A2>,
+ ) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall3(callee, nr, a0, a1, a2)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall4<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ a1: ArgReg<'a, A1>,
+ a2: ArgReg<'a, A2>,
+ a3: ArgReg<'a, A3>,
+ ) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall4(callee, nr, a0, a1, a2, a3)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall5<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ a1: ArgReg<'a, A1>,
+ a2: ArgReg<'a, A2>,
+ a3: ArgReg<'a, A3>,
+ a4: ArgReg<'a, A4>,
+ ) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall5(callee, nr, a0, a1, a2, a3, a4)
+ }
+
+ #[inline]
+ pub(in crate::backend) unsafe fn syscall6<'a>(
+ nr: SyscallNumber<'a>,
+ a0: ArgReg<'a, A0>,
+ a1: ArgReg<'a, A1>,
+ a2: ArgReg<'a, A2>,
+ a3: ArgReg<'a, A3>,
+ a4: ArgReg<'a, A4>,
+ a5: ArgReg<'a, A5>,
+ ) -> RetReg<R0> {
+ let callee = match transmute(super::SYSCALL.load(Relaxed)) {
+ Some(callee) => callee,
+ None => super::init_syscall(),
+ };
+ asm::indirect_syscall6(callee, nr, a0, a1, a2, a3, a4, a5)
+ }
+
+ // With the indirect call, it isn't meaningful to do a separate
+ // `_readonly` optimization.
+ #[allow(unused_imports)]
+ pub(in crate::backend) use {
+ syscall0 as syscall0_readonly, syscall1 as syscall1_readonly,
+ syscall2 as syscall2_readonly, syscall3 as syscall3_readonly,
+ syscall4 as syscall4_readonly, syscall5 as syscall5_readonly,
+ syscall6 as syscall6_readonly,
+ };
+}
+
+#[cfg(feature = "time")]
+type ClockGettimeType = unsafe extern "C" fn(c::c_int, *mut Timespec) -> c::c_int;
+
+#[cfg(feature = "process")]
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+type GetcpuType = unsafe extern "C" fn(*mut u32, *mut u32, *mut c_void) -> c::c_int;
+
+/// The underlying syscall functions are only called from asm, using the
+/// special syscall calling convention to pass arguments and return values,
+/// which the signature here doesn't reflect.
+#[cfg(target_arch = "x86")]
+pub(super) type SyscallType = unsafe extern "C" fn();
+
+/// Initialize `CLOCK_GETTIME` and return its value.
+#[cfg(feature = "time")]
+#[cold]
+fn init_clock_gettime() -> ClockGettimeType {
+ init();
+ // SAFETY: Load the function address from static storage that we just
+ // initialized.
+ unsafe { transmute(CLOCK_GETTIME.load(Relaxed)) }
+}
+
+/// Initialize `GETCPU` and return its value.
+#[cfg(feature = "process")]
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+#[cold]
+fn init_getcpu() -> GetcpuType {
+ init();
+ // SAFETY: Load the function address from static storage that we just
+ // initialized.
+ unsafe { transmute(GETCPU.load(Relaxed)) }
+}
+
+/// Initialize `SYSCALL` and return its value.
+#[cfg(target_arch = "x86")]
+#[cold]
+fn init_syscall() -> SyscallType {
+ init();
+ // SAFETY: Load the function address from static storage that we just
+ // initialized.
+ unsafe { transmute(SYSCALL.load(Relaxed)) }
+}
+
+/// `AtomicPtr` can't hold a `fn` pointer, so we use a `*` pointer to this
+/// placeholder type, and cast it as needed.
+struct Function;
+#[cfg(feature = "time")]
+static mut CLOCK_GETTIME: AtomicPtr<Function> = AtomicPtr::new(null_mut());
+#[cfg(feature = "process")]
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+static mut GETCPU: AtomicPtr<Function> = AtomicPtr::new(null_mut());
+#[cfg(target_arch = "x86")]
+static mut SYSCALL: AtomicPtr<Function> = AtomicPtr::new(null_mut());
+
+#[cfg(feature = "time")]
+unsafe extern "C" fn rustix_clock_gettime_via_syscall(
+ clockid: c::c_int,
+ res: *mut Timespec,
+) -> c::c_int {
+ match _rustix_clock_gettime_via_syscall(clockid, res) {
+ Ok(()) => 0,
+ Err(err) => err.raw_os_error().wrapping_neg(),
+ }
+}
+
+#[cfg(feature = "time")]
+#[cfg(target_pointer_width = "32")]
+unsafe fn _rustix_clock_gettime_via_syscall(
+ clockid: c::c_int,
+ res: *mut Timespec,
+) -> io::Result<()> {
+ let r0 = syscall!(__NR_clock_gettime64, c_int(clockid), res);
+ match ret(r0) {
+ Err(io::Errno::NOSYS) => _rustix_clock_gettime_via_syscall_old(clockid, res),
+ otherwise => otherwise,
+ }
+}
+
+#[cfg(feature = "time")]
+#[cfg(target_pointer_width = "32")]
+unsafe fn _rustix_clock_gettime_via_syscall_old(
+ clockid: c::c_int,
+ res: *mut Timespec,
+) -> io::Result<()> {
+ // Ordinarily `rustix` doesn't like to emulate system calls, but in the
+ // case of time APIs, it's specific to Linux, specific to 32-bit
+ // architectures *and* specific to old kernel versions, and it's not that
+ // hard to fix up here, so that no other code needs to worry about this.
+ let mut old_result = MaybeUninit::<__kernel_old_timespec>::uninit();
+ let r0 = syscall!(__NR_clock_gettime, c_int(clockid), &mut old_result);
+ match ret(r0) {
+ Ok(()) => {
+ let old_result = old_result.assume_init();
+ *res = Timespec {
+ tv_sec: old_result.tv_sec.into(),
+ tv_nsec: old_result.tv_nsec.into(),
+ };
+ Ok(())
+ }
+ otherwise => otherwise,
+ }
+}
+
+#[cfg(feature = "time")]
+#[cfg(target_pointer_width = "64")]
+unsafe fn _rustix_clock_gettime_via_syscall(
+ clockid: c::c_int,
+ res: *mut Timespec,
+) -> io::Result<()> {
+ ret(syscall!(__NR_clock_gettime, c_int(clockid), res))
+}
+
+#[cfg(feature = "process")]
+#[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+))]
+unsafe extern "C" fn rustix_getcpu_via_syscall(
+ cpu: *mut u32,
+ node: *mut u32,
+ unused: *mut c_void,
+) -> c::c_int {
+ match ret(syscall!(__NR_getcpu, cpu, node, unused)) {
+ Ok(()) => 0,
+ Err(err) => err.raw_os_error().wrapping_neg(),
+ }
+}
+
+#[cfg(target_arch = "x86")]
+extern "C" {
+ /// A symbol pointing to an `int 0x80` instruction. This “function” is only
+ /// called from assembly, and only with the x86 syscall calling convention,
+ /// so its signature here is not its true signature.
+ ///
+ /// This extern block and the `global_asm!` below can be replaced with
+ /// `#[naked]` if it's stabilized.
+ fn rustix_int_0x80();
+}
+
+#[cfg(target_arch = "x86")]
+global_asm!(
+ r#"
+ .section .text.rustix_int_0x80,"ax",@progbits
+ .p2align 4
+ .weak rustix_int_0x80
+ .hidden rustix_int_0x80
+ .type rustix_int_0x80, @function
+rustix_int_0x80:
+ .cfi_startproc
+ int 0x80
+ ret
+ .cfi_endproc
+ .size rustix_int_0x80, .-rustix_int_0x80
+"#
+);
+
+fn minimal_init() {
+ // SAFETY: Store default function addresses in static storage so that if we
+ // end up making any system calls while we read the vDSO, they'll work. If
+ // the memory happens to already be initialized, this is redundant, but not
+ // harmful.
+ unsafe {
+ #[cfg(feature = "time")]
+ {
+ CLOCK_GETTIME
+ .compare_exchange(
+ null_mut(),
+ rustix_clock_gettime_via_syscall as *mut Function,
+ Relaxed,
+ Relaxed,
+ )
+ .ok();
+ }
+
+ #[cfg(feature = "process")]
+ #[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+ ))]
+ {
+ GETCPU
+ .compare_exchange(
+ null_mut(),
+ rustix_getcpu_via_syscall as *mut Function,
+ Relaxed,
+ Relaxed,
+ )
+ .ok();
+ }
+
+ #[cfg(target_arch = "x86")]
+ {
+ SYSCALL
+ .compare_exchange(
+ null_mut(),
+ rustix_int_0x80 as *mut Function,
+ Relaxed,
+ Relaxed,
+ )
+ .ok();
+ }
+ }
+}
+
+fn init() {
+ minimal_init();
+
+ if let Some(vdso) = vdso::Vdso::new() {
+ #[cfg(feature = "time")]
+ {
+ // Look up the platform-specific `clock_gettime` symbol as
+ // documented [here], except on 32-bit platforms where we look up
+ // the `64`-suffixed variant and fail if we don't find it.
+ //
+ // [here]: https://man7.org/linux/man-pages/man7/vdso.7.html
+ #[cfg(target_arch = "x86_64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime"));
+ #[cfg(target_arch = "arm")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime64"));
+ #[cfg(target_arch = "aarch64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6.39"), cstr!("__kernel_clock_gettime"));
+ #[cfg(target_arch = "x86")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime64"));
+ #[cfg(target_arch = "riscv64")]
+ let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__vdso_clock_gettime"));
+ #[cfg(target_arch = "powerpc64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6.15"), cstr!("__kernel_clock_gettime"));
+ #[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime64"));
+ #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_clock_gettime"));
+
+ // On all 64-bit platforms, the 64-bit `clock_gettime` symbols are
+ // always available.
+ #[cfg(target_pointer_width = "64")]
+ let ok = true;
+
+ // On some 32-bit platforms, the 64-bit `clock_gettime` symbols are
+ // not available on older kernel versions.
+ #[cfg(any(
+ target_arch = "arm",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "x86"
+ ))]
+ let ok = !ptr.is_null();
+
+ if ok {
+ assert!(!ptr.is_null());
+
+ // SAFETY: Store the computed function addresses in static
+ // storage so that we don't need to compute it again (but if
+ // we do, it doesn't hurt anything).
+ unsafe {
+ CLOCK_GETTIME.store(ptr.cast(), Relaxed);
+ }
+ }
+ }
+
+ #[cfg(feature = "process")]
+ #[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+ ))]
+ {
+ // Look up the platform-specific `getcpu` symbol as documented
+ // [here].
+ //
+ // [here]: https://man7.org/linux/man-pages/man7/vdso.7.html
+ #[cfg(target_arch = "x86_64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_getcpu"));
+ #[cfg(target_arch = "x86")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6"), cstr!("__vdso_getcpu"));
+ #[cfg(target_arch = "riscv64")]
+ let ptr = vdso.sym(cstr!("LINUX_4.15"), cstr!("__kernel_getcpu"));
+ #[cfg(target_arch = "powerpc64")]
+ let ptr = vdso.sym(cstr!("LINUX_2.6.15"), cstr!("__kernel_getcpu"));
+
+ #[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "riscv64",
+ target_arch = "powerpc64"
+ ))]
+ let ok = true;
+
+ // On 32-bit x86, the symbol doesn't appear present sometimes.
+ #[cfg(target_arch = "x86")]
+ let ok = !ptr.is_null();
+
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ ))]
+ let ok = false;
+
+ if ok {
+ assert!(!ptr.is_null());
+
+ // SAFETY: Store the computed function addresses in static
+ // storage so that we don't need to compute it again (but if
+ // we do, it doesn't hurt anything).
+ unsafe {
+ GETCPU.store(ptr.cast(), Relaxed);
+ }
+ }
+ }
+
+ // On x86, also look up the vsyscall entry point.
+ #[cfg(target_arch = "x86")]
+ {
+ let ptr = vdso.sym(cstr!("LINUX_2.5"), cstr!("__kernel_vsyscall"));
+ assert!(!ptr.is_null());
+
+ // SAFETY: As above, store the computed function addresses in
+ // static storage.
+ unsafe {
+ SYSCALL.store(ptr.cast(), Relaxed);
+ }
+ }
+ }
+}
diff --git a/src/bitcast.rs b/src/bitcast.rs
new file mode 100644
index 0000000..77e0e63
--- /dev/null
+++ b/src/bitcast.rs
@@ -0,0 +1,34 @@
+//! The `bitcast` and `bitflags_bits` macros.
+
+#![allow(unused_macros)]
+
+// Ensure that the source and destination types are both primitive integer
+// types and the same size, and then bitcast.
+macro_rules! bitcast {
+ ($x:expr) => {{
+ if false {
+ // Ensure the source and destinations are primitive integer types.
+ let _ = !$x;
+ let _ = $x as u8;
+ 0
+ } else if false {
+ // Ensure that the source and destinations are the same size.
+ // SAFETY: This code is under an `if false`.
+ #[allow(unsafe_code, unused_unsafe, clippy::useless_transmute)]
+ unsafe {
+ ::core::mem::transmute($x)
+ }
+ } else {
+ // Do the conversion.
+ $x as _
+ }
+ }};
+}
+
+/// Return a [`bitcast`] of the value of `$x.bits()`, where `$x` is a
+/// `bitflags` type.
+macro_rules! bitflags_bits {
+ ($x:expr) => {{
+ bitcast!($x.bits())
+ }};
+}
diff --git a/src/buffer.rs b/src/buffer.rs
new file mode 100644
index 0000000..e4b40c7
--- /dev/null
+++ b/src/buffer.rs
@@ -0,0 +1,21 @@
+//! Utilities to help with buffering.
+
+#![allow(unsafe_code)]
+
+use core::mem::MaybeUninit;
+use core::slice;
+
+/// Split an uninitialized byte slice into initialized and uninitialized parts.
+///
+/// # Safety
+///
+/// At least `init` bytes must be initialized.
+#[inline]
+pub(super) unsafe fn split_init(
+ buf: &mut [MaybeUninit<u8>],
+ init: usize,
+) -> (&mut [u8], &mut [MaybeUninit<u8>]) {
+ let (init, uninit) = buf.split_at_mut(init);
+ let init = slice::from_raw_parts_mut(init.as_mut_ptr() as *mut u8, init.len());
+ (init, uninit)
+}
diff --git a/src/check_types.rs b/src/check_types.rs
new file mode 100644
index 0000000..0140b6d
--- /dev/null
+++ b/src/check_types.rs
@@ -0,0 +1,91 @@
+//! Macros for checking that types have the same layout as other types.
+
+#![allow(unused_macros)]
+
+/// Check that the size and alignment of a type match the `sys` bindings.
+macro_rules! check_type {
+ ($struct:ident) => {
+ assert_eq_size!($struct, c::$struct);
+ assert_eq_align!($struct, c::$struct);
+ };
+}
+
+/// The same as `check_type`, but for unions and anonymous structs we've
+/// renamed to avoid having types like `bindgen_ty_1` in the API.
+macro_rules! check_renamed_type {
+ ($to:ident, $from:ident) => {
+ assert_eq_size!($to, c::$from);
+ assert_eq_align!($to, c::$from);
+ };
+}
+
+/// Check that the field of a struct has the same offset as the corresponding
+/// field in the `sys` bindings.
+macro_rules! check_struct_field {
+ ($struct:ident, $field:ident) => {
+ const_assert_eq!(
+ memoffset::offset_of!($struct, $field),
+ memoffset::offset_of!(c::$struct, $field)
+ );
+
+ // This can't use `const_assert_eq` because `span_of` returns a
+ // `Range`, which can't be compared in const contexts.
+ assert_eq!(
+ memoffset::span_of!($struct, $field),
+ memoffset::span_of!(c::$struct, $field)
+ );
+ };
+}
+
+/// The same as `check_struct_field`, but for unions and anonymous structs
+/// we've renamed to avoid having types like `bindgen_ty_1` in the API.
+macro_rules! check_struct_renamed_field {
+ ($struct:ident, $to:ident, $from:ident) => {
+ const_assert_eq!(
+ memoffset::offset_of!($struct, $to),
+ memoffset::offset_of!(c::$struct, $from)
+ );
+
+ // As above, this can't use `const_assert_eq`.
+ assert_eq!(
+ memoffset::span_of!($struct, $to),
+ memoffset::span_of!(c::$struct, $from)
+ );
+ };
+}
+
+/// The same as `check_struct_renamed_field`, but for when both the struct and
+/// a field are renamed.
+macro_rules! check_renamed_struct_renamed_field {
+ ($to_struct:ident, $from_struct:ident, $to:ident, $from:ident) => {
+ const_assert_eq!(
+ memoffset::offset_of!($to_struct, $to),
+ memoffset::offset_of!(c::$from_struct, $from)
+ );
+
+ // As above, this can't use `const_assert_eq`.
+ assert_eq!(
+ memoffset::span_of!($to_struct, $to),
+ memoffset::span_of!(c::$from_struct, $from)
+ );
+ };
+}
+
+/// For the common case of no renaming, check all fields of a struct.
+macro_rules! check_struct {
+ ($name:ident, $($field:ident),*) => {
+ // Check the size and alignment.
+ check_type!($name);
+
+ // Check that we have all the fields.
+ if false {
+ #[allow(unreachable_code)]
+ let _test = $name {
+ $($field: panic!()),*
+ };
+ }
+
+ // Check that the fields have the right sizes and offsets.
+ $(check_struct_field!($name, $field));*
+ };
+}
diff --git a/src/clockid.rs b/src/clockid.rs
new file mode 100644
index 0000000..1c7deeb
--- /dev/null
+++ b/src/clockid.rs
@@ -0,0 +1,164 @@
+use crate::backend::c;
+use crate::fd::BorrowedFd;
+
+/// `CLOCK_*` constants for use with [`clock_gettime`].
+///
+/// These constants are always supported at runtime, so `clock_gettime` never
+/// has to fail with `INVAL` due to an unsupported clock. See
+/// [`DynamicClockId`] for a greater set of clocks, with the caveat that not
+/// all of them are always supported.
+///
+/// [`clock_gettime`]: crate::time::clock_gettime
+#[cfg(not(any(apple, target_os = "wasi")))]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(not(any(target_os = "aix", target_os = "dragonfly")), repr(i32))]
+#[cfg_attr(target_os = "dragonfly", repr(u64))]
+#[cfg_attr(target_os = "aix", repr(i64))]
+#[non_exhaustive]
+pub enum ClockId {
+ /// `CLOCK_REALTIME`
+ #[doc(alias = "CLOCK_REALTIME")]
+ Realtime = bitcast!(c::CLOCK_REALTIME),
+
+ /// `CLOCK_MONOTONIC`
+ #[doc(alias = "CLOCK_MONOTONIC")]
+ Monotonic = bitcast!(c::CLOCK_MONOTONIC),
+
+ /// `CLOCK_UPTIME`
+ #[cfg(any(freebsdlike, target_os = "openbsd"))]
+ #[doc(alias = "CLOCK_UPTIME")]
+ Uptime = c::CLOCK_UPTIME,
+
+ /// `CLOCK_PROCESS_CPUTIME_ID`
+ #[cfg(not(any(
+ solarish,
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "vita"
+ )))]
+ #[doc(alias = "CLOCK_PROCESS_CPUTIME_ID")]
+ ProcessCPUTime = c::CLOCK_PROCESS_CPUTIME_ID,
+
+ /// `CLOCK_THREAD_CPUTIME_ID`
+ #[cfg(not(any(
+ solarish,
+ target_os = "netbsd",
+ target_os = "redox",
+ target_os = "vita"
+ )))]
+ #[doc(alias = "CLOCK_THREAD_CPUTIME_ID")]
+ ThreadCPUTime = c::CLOCK_THREAD_CPUTIME_ID,
+
+ /// `CLOCK_REALTIME_COARSE`
+ #[cfg(any(linux_kernel, target_os = "freebsd"))]
+ #[doc(alias = "CLOCK_REALTIME_COARSE")]
+ RealtimeCoarse = c::CLOCK_REALTIME_COARSE,
+
+ /// `CLOCK_MONOTONIC_COARSE`
+ #[cfg(any(linux_kernel, target_os = "freebsd"))]
+ #[doc(alias = "CLOCK_MONOTONIC_COARSE")]
+ MonotonicCoarse = c::CLOCK_MONOTONIC_COARSE,
+
+ /// `CLOCK_MONOTONIC_RAW`
+ #[cfg(linux_kernel)]
+ #[doc(alias = "CLOCK_MONOTONIC_RAW")]
+ MonotonicRaw = c::CLOCK_MONOTONIC_RAW,
+
+ /// `CLOCK_REALTIME_ALARM`
+ #[cfg(linux_kernel)]
+ #[doc(alias = "CLOCK_REALTIME_ALARM")]
+ RealtimeAlarm = bitcast!(c::CLOCK_REALTIME_ALARM),
+
+ /// `CLOCK_TAI`, available on Linux >= 3.10
+ #[cfg(all(linux_kernel, feature = "linux_4_11"))]
+ #[doc(alias = "CLOCK_TAI")]
+ Tai = bitcast!(c::CLOCK_TAI),
+
+ /// `CLOCK_BOOTTIME`
+ ///
+ /// On FreeBSD, use [`Self::Uptime`], as `CLOCK_BOOTTIME` is an alias for
+ /// `CLOCK_UPTIME`.
+ ///
+ /// [`Self::Uptime`]: https://docs.rs/rustix/*/x86_64-unknown-freebsd/rustix/time/enum.ClockId.html#variant.Uptime
+ #[cfg(any(linux_kernel, target_os = "fuchsia", target_os = "openbsd"))]
+ #[doc(alias = "CLOCK_BOOTTIME")]
+ Boottime = bitcast!(c::CLOCK_BOOTTIME),
+
+ /// `CLOCK_BOOTTIME_ALARM`
+ #[cfg(any(linux_kernel, target_os = "fuchsia"))]
+ #[doc(alias = "CLOCK_BOOTTIME_ALARM")]
+ BoottimeAlarm = bitcast!(c::CLOCK_BOOTTIME_ALARM),
+}
+
+/// `CLOCK_*` constants for use with [`clock_gettime`].
+///
+/// These constants are always supported at runtime, so `clock_gettime` never
+/// has to fail with `INVAL` due to an unsupported clock. See
+/// [`DynamicClockId`] for a greater set of clocks, with the caveat that not
+/// all of them are always supported.
+///
+/// [`clock_gettime`]: crate::time::clock_gettime
+#[cfg(apple)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(u32)]
+#[non_exhaustive]
+pub enum ClockId {
+ /// `CLOCK_REALTIME`
+ #[doc(alias = "CLOCK_REALTIME")]
+ Realtime = c::CLOCK_REALTIME,
+
+ /// `CLOCK_MONOTONIC`
+ #[doc(alias = "CLOCK_MONOTONIC")]
+ Monotonic = c::CLOCK_MONOTONIC,
+
+ /// `CLOCK_PROCESS_CPUTIME_ID`
+ #[doc(alias = "CLOCK_PROCESS_CPUTIME_ID")]
+ ProcessCPUTime = c::CLOCK_PROCESS_CPUTIME_ID,
+
+ /// `CLOCK_THREAD_CPUTIME_ID`
+ #[doc(alias = "CLOCK_THREAD_CPUTIME_ID")]
+ ThreadCPUTime = c::CLOCK_THREAD_CPUTIME_ID,
+}
+
+/// `CLOCK_*` constants for use with [`clock_gettime_dynamic`].
+///
+/// These constants may be unsupported at runtime, depending on the OS version,
+/// and `clock_gettime_dynamic` may fail with `INVAL`. See [`ClockId`] for
+/// clocks which are always supported at runtime.
+///
+/// [`clock_gettime_dynamic`]: crate::time::clock_gettime_dynamic
+#[cfg(not(target_os = "wasi"))]
+#[derive(Debug, Copy, Clone)]
+#[non_exhaustive]
+pub enum DynamicClockId<'a> {
+ /// `ClockId` values that are always supported at runtime.
+ Known(ClockId),
+
+ /// Linux dynamic clocks.
+ Dynamic(BorrowedFd<'a>),
+
+ /// `CLOCK_REALTIME_ALARM`
+ #[cfg(linux_kernel)]
+ #[doc(alias = "CLOCK_REALTIME_ALARM")]
+ RealtimeAlarm,
+
+ /// `CLOCK_TAI`, available on Linux >= 3.10
+ #[cfg(linux_kernel)]
+ #[doc(alias = "CLOCK_TAI")]
+ Tai,
+
+ /// `CLOCK_BOOTTIME`
+ #[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "openbsd"
+ ))]
+ #[doc(alias = "CLOCK_BOOTTIME")]
+ Boottime,
+
+ /// `CLOCK_BOOTTIME_ALARM`
+ #[cfg(any(linux_kernel, target_os = "fuchsia"))]
+ #[doc(alias = "CLOCK_BOOTTIME_ALARM")]
+ BoottimeAlarm,
+}
diff --git a/src/cstr.rs b/src/cstr.rs
new file mode 100644
index 0000000..17a8c8b
--- /dev/null
+++ b/src/cstr.rs
@@ -0,0 +1,77 @@
+/// A macro for [`CStr`] literals.
+///
+/// This can make passing string literals to rustix APIs more efficient, since
+/// most underlying system calls with string arguments expect NUL-terminated
+/// strings, and passing strings to rustix as `CStr`s means that rustix doesn't
+/// need to copy them into a separate buffer to NUL-terminate them.
+///
+/// [`CStr`]: crate::ffi::CStr
+///
+/// # Examples
+///
+/// ```
+/// # #[cfg(feature = "fs")]
+/// # fn main() -> rustix::io::Result<()> {
+/// use rustix::cstr;
+/// use rustix::fs::{statat, AtFlags, CWD};
+///
+/// let metadata = statat(CWD, cstr!("Cargo.toml"), AtFlags::empty())?;
+/// # Ok(())
+/// # }
+/// # #[cfg(not(feature = "fs"))]
+/// # fn main() {}
+/// ```
+#[allow(unused_macros)]
+#[macro_export]
+macro_rules! cstr {
+ ($str:literal) => {{
+ // Check for NUL manually, to ensure safety.
+ //
+ // In release builds, with strings that don't contain NULs, this
+ // constant-folds away.
+ //
+ // We don't use std's `CStr::from_bytes_with_nul`; as of this writing,
+ // that function isn't defined as `#[inline]` in std and doesn't
+ // constant-fold away.
+ assert!(
+ !$str.bytes().any(|b| b == b'\0'),
+ "cstr argument contains embedded NUL bytes",
+ );
+
+ #[allow(unsafe_code, unused_unsafe)]
+ {
+ // Now that we know the string doesn't have embedded NULs, we can
+ // call `from_bytes_with_nul_unchecked`, which as of this writing
+ // is defined as `#[inline]` and completely optimizes away.
+ //
+ // SAFETY: We have manually checked that the string does not
+ // contain embedded NULs above, and we append or own NUL terminator
+ // here.
+ unsafe {
+ $crate::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes())
+ }
+ }
+ }};
+}
+
+#[test]
+fn test_cstr() {
+ use crate::ffi::CString;
+ use alloc::borrow::ToOwned;
+ assert_eq!(cstr!(""), &*CString::new("").unwrap());
+ assert_eq!(cstr!("").to_owned(), CString::new("").unwrap());
+ assert_eq!(cstr!("hello"), &*CString::new("hello").unwrap());
+ assert_eq!(cstr!("hello").to_owned(), CString::new("hello").unwrap());
+}
+
+#[test]
+#[should_panic]
+fn test_invalid_cstr() {
+ let _ = cstr!("hello\0world");
+}
+
+#[test]
+#[should_panic]
+fn test_invalid_empty_cstr() {
+ let _ = cstr!("\0");
+}
diff --git a/src/event/eventfd.rs b/src/event/eventfd.rs
new file mode 100644
index 0000000..a76f2cf
--- /dev/null
+++ b/src/event/eventfd.rs
@@ -0,0 +1,20 @@
+use crate::fd::OwnedFd;
+use crate::{backend, io};
+
+pub use backend::event::types::EventfdFlags;
+
+/// `eventfd(initval, flags)`—Creates a file descriptor for event
+/// notification.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/eventfd.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?eventfd
+/// [illumos]: https://illumos.org/man/3C/eventfd
+#[inline]
+pub fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result<OwnedFd> {
+ backend::event::syscalls::eventfd(initval, flags)
+}
diff --git a/src/event/kqueue.rs b/src/event/kqueue.rs
new file mode 100644
index 0000000..d6b7cde
--- /dev/null
+++ b/src/event/kqueue.rs
@@ -0,0 +1,449 @@
+//! An API for interfacing with `kqueue`.
+
+use crate::fd::{AsFd, OwnedFd, RawFd};
+use crate::pid::Pid;
+use crate::signal::Signal;
+use crate::{backend, io};
+
+use backend::c::{self, intptr_t, kevent as kevent_t, uintptr_t};
+use backend::event::syscalls;
+
+use alloc::vec::Vec;
+use core::mem::zeroed;
+use core::ptr::slice_from_raw_parts_mut;
+use core::time::Duration;
+
+/// A `kqueue` event for use with [`kevent`].
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct Event {
+ // The layout varies between BSDs and macOS.
+ inner: kevent_t,
+}
+
+impl Event {
+ /// Create a new `Event`.
+ #[allow(clippy::needless_update)]
+ pub fn new(filter: EventFilter, flags: EventFlags, udata: isize) -> Event {
+ let (ident, data, filter, fflags) = match filter {
+ EventFilter::Read(fd) => (fd as uintptr_t, 0, c::EVFILT_READ, 0),
+ EventFilter::Write(fd) => (fd as _, 0, c::EVFILT_WRITE, 0),
+ #[cfg(target_os = "freebsd")]
+ EventFilter::Empty(fd) => (fd as _, 0, c::EVFILT_EMPTY, 0),
+ EventFilter::Vnode { vnode, flags } => (vnode as _, 0, c::EVFILT_VNODE, flags.bits()),
+ EventFilter::Proc { pid, flags } => {
+ (Pid::as_raw(Some(pid)) as _, 0, c::EVFILT_PROC, flags.bits())
+ }
+ EventFilter::Signal { signal, times: _ } => (signal as _, 0, c::EVFILT_SIGNAL, 0),
+ EventFilter::Timer { ident, timer } => {
+ #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))]
+ let (data, fflags) = match timer {
+ Some(timer) => {
+ if timer.subsec_millis() == 0 {
+ (timer.as_secs() as _, c::NOTE_SECONDS)
+ } else if timer.subsec_nanos() == 0 {
+ (timer.as_micros() as _, c::NOTE_USECONDS)
+ } else {
+ (timer.as_nanos() as _, c::NOTE_NSECONDS)
+ }
+ }
+ None => (intptr_t::MAX, c::NOTE_SECONDS),
+ };
+ #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
+ let (data, fflags) = match timer {
+ Some(timer) => (timer.as_millis() as _, 0),
+ None => (intptr_t::MAX, 0),
+ };
+
+ (ident as _, data, c::EVFILT_TIMER, fflags)
+ }
+ #[cfg(any(apple, freebsdlike))]
+ EventFilter::User {
+ ident,
+ flags,
+ user_flags,
+ } => (ident as _, 0, c::EVFILT_USER, flags.bits() | user_flags.0),
+ EventFilter::Unknown => panic!("unknown filter"),
+ };
+
+ Event {
+ inner: kevent_t {
+ ident,
+ filter: filter as _,
+ flags: flags.bits() as _,
+ fflags,
+ data: {
+ // On OpenBSD, data is an `i64` and not an `isize`.
+ data as _
+ },
+ udata: {
+ // On NetBSD, udata is an `isize` and not a pointer.
+ // TODO: Strict provenance, prevent int-to-ptr cast.
+ udata as _
+ },
+ ..unsafe { zeroed() }
+ },
+ }
+ }
+
+ /// Get the event flags for this event.
+ pub fn flags(&self) -> EventFlags {
+ EventFlags::from_bits_retain(self.inner.flags as _)
+ }
+
+ /// Get the user data for this event.
+ pub fn udata(&self) -> isize {
+ // On NetBSD, udata is an isize and not a pointer.
+ // TODO: Strict provenance, prevent ptr-to-int cast.
+
+ self.inner.udata as _
+ }
+
+ /// Get the raw data for this event.
+ pub fn data(&self) -> i64 {
+ // On some BSDs, data is an `isize` and not an `i64`.
+ self.inner.data as _
+ }
+
+ /// Get the filter of this event.
+ pub fn filter(&self) -> EventFilter {
+ match self.inner.filter as _ {
+ c::EVFILT_READ => EventFilter::Read(self.inner.ident as _),
+ c::EVFILT_WRITE => EventFilter::Write(self.inner.ident as _),
+ #[cfg(target_os = "freebsd")]
+ c::EVFILT_EMPTY => EventFilter::Empty(self.inner.ident as _),
+ c::EVFILT_VNODE => EventFilter::Vnode {
+ vnode: self.inner.ident as _,
+ flags: VnodeEvents::from_bits_retain(self.inner.fflags),
+ },
+ c::EVFILT_PROC => EventFilter::Proc {
+ pid: Pid::from_raw(self.inner.ident as _).unwrap(),
+ flags: ProcessEvents::from_bits_retain(self.inner.fflags),
+ },
+ c::EVFILT_SIGNAL => EventFilter::Signal {
+ signal: Signal::from_raw(self.inner.ident as _).unwrap(),
+ times: self.inner.data as _,
+ },
+ c::EVFILT_TIMER => EventFilter::Timer {
+ ident: self.inner.ident as _,
+ timer: {
+ let (data, fflags) = (self.inner.data, self.inner.fflags);
+ #[cfg(not(any(apple, target_os = "freebsd", target_os = "netbsd")))]
+ let _ = fflags;
+ #[cfg(any(apple, target_os = "freebsd", target_os = "netbsd"))]
+ match fflags as _ {
+ c::NOTE_SECONDS => Some(Duration::from_secs(data as _)),
+ c::NOTE_USECONDS => Some(Duration::from_micros(data as _)),
+ c::NOTE_NSECONDS => Some(Duration::from_nanos(data as _)),
+ _ => {
+ // Unknown timer flags.
+ None
+ }
+ }
+ #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))]
+ Some(Duration::from_millis(data as _))
+ },
+ },
+ #[cfg(any(apple, freebsdlike))]
+ c::EVFILT_USER => EventFilter::User {
+ ident: self.inner.ident as _,
+ flags: UserFlags::from_bits_retain(self.inner.fflags),
+ user_flags: UserDefinedFlags(self.inner.fflags & EVFILT_USER_FLAGS),
+ },
+ _ => EventFilter::Unknown,
+ }
+ }
+}
+
+/// Bottom 24 bits of a u32.
+#[cfg(any(apple, freebsdlike))]
+const EVFILT_USER_FLAGS: u32 = 0x00ff_ffff;
+
+/// The possible filters for a `kqueue`.
+#[repr(i16)]
+#[non_exhaustive]
+pub enum EventFilter {
+ /// A read filter.
+ Read(RawFd),
+
+ /// A write filter.
+ Write(RawFd),
+
+ /// An empty filter.
+ #[cfg(target_os = "freebsd")]
+ Empty(RawFd),
+
+ /// A VNode filter.
+ Vnode {
+ /// The file descriptor we looked for events in.
+ vnode: RawFd,
+
+ /// The flags for this event.
+ flags: VnodeEvents,
+ },
+
+ /// A process filter.
+ Proc {
+ /// The process ID we waited on.
+ pid: Pid,
+
+ /// The flags for this event.
+ flags: ProcessEvents,
+ },
+
+ /// A signal filter.
+ Signal {
+ /// The signal number we waited on.
+ signal: Signal,
+
+ /// The number of times the signal has been
+ /// received since the last call to kevent.
+ times: usize,
+ },
+
+ /// A timer filter.
+ Timer {
+ /// The identifier for this event.
+ ident: intptr_t,
+
+ /// The duration for this event.
+ timer: Option<Duration>,
+ },
+
+ /// A user filter.
+ #[cfg(any(apple, freebsdlike))]
+ User {
+ /// The identifier for this event.
+ ident: intptr_t,
+
+ /// The flags for this event.
+ flags: UserFlags,
+
+ /// The user-defined flags for this event.
+ user_flags: UserDefinedFlags,
+ },
+
+ /// This filter is unknown.
+ ///
+ /// # Panics
+ ///
+ /// Passing this into `Event::new()` will result in a panic.
+ Unknown,
+}
+
+bitflags::bitflags! {
+ /// The flags for a `kqueue` event specifying actions to perform.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct EventFlags: u16 {
+ /// Add the event to the `kqueue`.
+ const ADD = c::EV_ADD as _;
+
+ /// Enable the event.
+ const ENABLE = c::EV_ENABLE as _;
+
+ /// Disable the event.
+ const DISABLE = c::EV_DISABLE as _;
+
+ /// Delete the event from the `kqueue`.
+ const DELETE = c::EV_DELETE as _;
+
+ /// TODO
+ const RECEIPT = c::EV_RECEIPT as _;
+
+ /// Clear the event after it is triggered.
+ const ONESHOT = c::EV_ONESHOT as _;
+
+ /// TODO
+ const CLEAR = c::EV_CLEAR as _;
+
+ /// TODO
+ const EOF = c::EV_EOF as _;
+
+ /// TODO
+ const ERROR = c::EV_ERROR as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// The flags for a virtual node event.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct VnodeEvents: u32 {
+ /// The file was deleted.
+ const DELETE = c::NOTE_DELETE;
+
+ /// The file was written to.
+ const WRITE = c::NOTE_WRITE;
+
+ /// The file was extended.
+ const EXTEND = c::NOTE_EXTEND;
+
+ /// The file had its attributes changed.
+ const ATTRIBUTES = c::NOTE_ATTRIB;
+
+ /// The file was renamed.
+ const RENAME = c::NOTE_RENAME;
+
+ /// Access to the file was revoked.
+ const REVOKE = c::NOTE_REVOKE;
+
+ /// The link count of the file has changed.
+ const LINK = c::NOTE_LINK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// The flags for a process event.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ProcessEvents: u32 {
+ /// The process exited.
+ const EXIT = c::NOTE_EXIT;
+
+ /// The process forked itself.
+ const FORK = c::NOTE_FORK;
+
+ /// The process executed a new process.
+ const EXEC = c::NOTE_EXEC;
+
+ /// Follow the process through `fork` calls (write only).
+ const TRACK = c::NOTE_TRACK;
+
+ /// An error has occurred with following the process.
+ const TRACKERR = c::NOTE_TRACKERR;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(any(apple, freebsdlike))]
+bitflags::bitflags! {
+ /// The flags for a user event.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UserFlags: u32 {
+ /// Ignore the user input flags.
+ const NOINPUT = c::NOTE_FFNOP;
+
+ /// Bitwise AND `fflags`.
+ const AND = c::NOTE_FFAND;
+
+ /// Bitwise OR `fflags`.
+ const OR = c::NOTE_FFOR;
+
+ /// Copy `fflags`.
+ const COPY = c::NOTE_FFCOPY;
+
+ /// Control mask for operations.
+ const CTRLMASK = c::NOTE_FFCTRLMASK;
+
+ /// User defined flags for masks.
+ const UDFMASK = c::NOTE_FFLAGSMASK;
+
+ /// Trigger the event.
+ const TRIGGER = c::NOTE_TRIGGER;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// User-defined flags.
+///
+/// Only the lower 24 bits are used in this struct.
+#[repr(transparent)]
+#[cfg(any(apple, freebsdlike))]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct UserDefinedFlags(u32);
+
+#[cfg(any(apple, freebsdlike))]
+impl UserDefinedFlags {
+ /// Create a new `UserDefinedFlags` from a `u32`.
+ pub fn new(flags: u32) -> Self {
+ Self(flags & EVFILT_USER_FLAGS)
+ }
+
+ /// Get the underlying `u32`.
+ pub fn get(self) -> u32 {
+ self.0
+ }
+}
+
+/// `kqueue()`—Create a new `kqueue` file descriptor.
+///
+/// # References
+/// - [Apple]
+/// - [FreeBSD]
+/// - [OpenBSD]
+/// - [NetBSD]
+/// - [DragonFly BSD]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
+/// [OpenBSD]: https://man.openbsd.org/kqueue.2
+/// [NetBSD]: https://man.netbsd.org/kqueue.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=kqueue&section=2
+pub fn kqueue() -> io::Result<OwnedFd> {
+ syscalls::kqueue()
+}
+
+/// `kevent(kqueue, changelist, eventlist, timeout)`—Wait for events on a
+/// `kqueue`.
+///
+/// Note: in order to receive events, make sure to allocate capacity in the
+/// eventlist! Otherwise, the function will return immediately.
+///
+/// # Safety
+///
+/// The file descriptors referred to by the `Event` structs must be valid for
+/// the lifetime of the `kqueue` file descriptor.
+///
+/// # References
+/// - [Apple]
+/// - [FreeBSD]
+/// - [OpenBSD]
+/// - [NetBSD]
+/// - [DragonFly BSD]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kevent.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=kevent&sektion=2
+/// [OpenBSD]: https://man.openbsd.org/kevent.2
+/// [NetBSD]: https://man.netbsd.org/kevent.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=kevent&section=2
+pub unsafe fn kevent(
+ kqueue: impl AsFd,
+ changelist: &[Event],
+ eventlist: &mut Vec<Event>,
+ timeout: Option<Duration>,
+) -> io::Result<usize> {
+ let timeout = timeout.map(|timeout| backend::c::timespec {
+ tv_sec: timeout.as_secs() as _,
+ tv_nsec: timeout.subsec_nanos() as _,
+ });
+
+ // Populate the event list with events.
+ eventlist.set_len(0);
+ let out_slice = slice_from_raw_parts_mut(eventlist.as_mut_ptr().cast(), eventlist.capacity());
+ let res = syscalls::kevent(
+ kqueue.as_fd(),
+ changelist,
+ &mut *out_slice,
+ timeout.as_ref(),
+ )
+ .map(|res| res as _);
+
+ // Update the event list.
+ if let Ok(len) = res {
+ eventlist.set_len(len);
+ }
+
+ res
+}
diff --git a/src/event/mod.rs b/src/event/mod.rs
new file mode 100644
index 0000000..c497feb
--- /dev/null
+++ b/src/event/mod.rs
@@ -0,0 +1,29 @@
+//! Event operations.
+
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "espidf"
+))]
+mod eventfd;
+#[cfg(all(feature = "alloc", bsd))]
+pub mod kqueue;
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+mod pause;
+mod poll;
+#[cfg(solarish)]
+pub mod port;
+
+#[cfg(any(linux_kernel, target_os = "redox"))]
+pub use crate::backend::event::epoll;
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "illumos",
+ target_os = "espidf"
+))]
+pub use eventfd::{eventfd, EventfdFlags};
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+pub use pause::*;
+pub use poll::{poll, PollFd, PollFlags};
diff --git a/src/event/pause.rs b/src/event/pause.rs
new file mode 100644
index 0000000..f191084
--- /dev/null
+++ b/src/event/pause.rs
@@ -0,0 +1,31 @@
+use crate::backend;
+
+/// `pause()`
+///
+/// The POSIX `pause` interface returns an error code, but the only thing
+/// `pause` does is sleep until interrupted by a signal, so it always
+/// returns the same thing with the same error code, so in rustix, the
+/// return value is omitted.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/pause.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/pause.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=pause&sektion=3
+/// [NetBSD]: https://man.netbsd.org/pause.3
+/// [OpenBSD]: https://man.openbsd.org/pause.3
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=pause&section=3
+/// [illumos]: https://illumos.org/man/2/pause
+#[inline]
+pub fn pause() {
+ backend::event::syscalls::pause()
+}
diff --git a/src/event/poll.rs b/src/event/poll.rs
new file mode 100644
index 0000000..30e6a4b
--- /dev/null
+++ b/src/event/poll.rs
@@ -0,0 +1,32 @@
+use crate::{backend, io};
+
+pub use backend::event::poll_fd::{PollFd, PollFlags};
+
+/// `poll(self.fds, timeout)`
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/slightly-advanced-techniques.html#poll
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/poll.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/poll.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=poll&sektion=2
+/// [NetBSD]: https://man.netbsd.org/poll.2
+/// [OpenBSD]: https://man.openbsd.org/poll.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=poll&section=2
+/// [illumos]: https://illumos.org/man/2/poll
+#[inline]
+pub fn poll(fds: &mut [PollFd<'_>], timeout: i32) -> io::Result<usize> {
+ backend::event::syscalls::poll(fds, timeout)
+}
diff --git a/src/event/port.rs b/src/event/port.rs
new file mode 100644
index 0000000..39fe5ac
--- /dev/null
+++ b/src/event/port.rs
@@ -0,0 +1,151 @@
+//! Solaris/illumos event ports.
+
+use crate::backend::c;
+use crate::backend::event::syscalls;
+use crate::fd::{AsFd, AsRawFd, OwnedFd};
+use crate::io;
+
+use super::PollFlags;
+
+use core::time::Duration;
+
+/// The structure representing a port event.
+#[repr(transparent)]
+pub struct Event(pub(crate) c::port_event);
+
+impl Event {
+ /// Get the events associated with this event.
+ pub fn events(&self) -> i32 {
+ self.0.portev_events
+ }
+
+ /// Get the event source associated with this event.
+ pub fn object(&self) -> usize {
+ self.0.portev_object
+ }
+
+ /// Get the userdata associated with this event.
+ pub fn userdata(&self) -> *mut c::c_void {
+ self.0.portev_user
+ }
+}
+
+/// `port_create()`—Creates a new port.
+///
+/// # References
+/// - [OpenSolaris]
+/// - [illumos]
+///
+/// [OpenSolaris]: https://www.unix.com/man-page/opensolaris/3C/port_create/
+/// [illumos]: https://illumos.org/man/3C/port_create
+pub fn port_create() -> io::Result<OwnedFd> {
+ syscalls::port_create()
+}
+
+/// `port_associate(_, PORT_SOURCE_FD, _, _, _)`—Associates a file descriptor
+/// with a port.
+///
+/// # Safety
+///
+/// Any `object`s passed into the `port` must be valid for the lifetime of the
+/// `port`. Logically, `port` keeps a borrowed reference to the `object` until
+/// it is removed via `port_dissociate_fd`.
+///
+/// # References
+/// - [OpenSolaris]
+/// - [illumos]
+///
+/// [OpenSolaris]: https://www.unix.com/man-page/opensolaris/3C/port_associate/
+/// [illumos]: https://illumos.org/man/3C/port_associate
+pub unsafe fn port_associate_fd(
+ port: impl AsFd,
+ object: impl AsRawFd,
+ events: PollFlags,
+ userdata: *mut c::c_void,
+) -> io::Result<()> {
+ syscalls::port_associate(
+ port.as_fd(),
+ c::PORT_SOURCE_FD,
+ object.as_raw_fd() as _,
+ events.bits() as _,
+ userdata.cast(),
+ )
+}
+
+/// `port_dissociate(_, PORT_SOURCE_FD, _)`—Dissociates a file descriptor
+/// from a port.
+///
+/// # Safety
+///
+/// The file descriptor passed into this function must have been previously
+/// associated with the port via [`port_associate_fd`].
+///
+/// # References
+/// - [OpenSolaris]
+/// - [illumos]
+///
+/// [OpenSolaris]: https://www.unix.com/man-page/opensolaris/3C/port_dissociate
+/// [illumos]: https://illumos.org/man/3C/port_dissociate
+pub unsafe fn port_dissociate_fd(port: impl AsFd, object: impl AsRawFd) -> io::Result<()> {
+ syscalls::port_dissociate(port.as_fd(), c::PORT_SOURCE_FD, object.as_raw_fd() as _)
+}
+
+/// `port_get(port, timeout)`—Gets an event from a port.
+///
+/// # References
+/// - [OpenSolaris]
+/// - [illumos]
+///
+/// [OpenSolaris]: https://www.unix.com/man-page/opensolaris/3C/port_get/
+/// [illumos]: https://illumos.org/man/3C/port_get
+pub fn port_get(port: impl AsFd, timeout: Option<Duration>) -> io::Result<Event> {
+ let mut timeout = timeout.map(|timeout| c::timespec {
+ tv_sec: timeout.as_secs().try_into().unwrap(),
+ tv_nsec: timeout.subsec_nanos() as _,
+ });
+
+ syscalls::port_get(port.as_fd(), timeout.as_mut())
+}
+
+/// `port_getn(port, events, min_events, timeout)`—Gets multiple events from a
+/// port.
+///
+/// # References
+/// - [OpenSolaris]
+/// - [illumos]
+///
+/// [OpenSolaris]: https://www.unix.com/man-page/opensolaris/3C/port_getn/
+/// [illumos]: https://illumos.org/man/3C/port_getn
+#[cfg(feature = "alloc")]
+pub fn port_getn(
+ port: impl AsFd,
+ events: &mut Vec<Event>,
+ min_events: usize,
+ timeout: Option<Duration>,
+) -> io::Result<()> {
+ events.clear();
+
+ let mut timeout = timeout.map(|timeout| c::timespec {
+ tv_sec: timeout.as_secs().try_into().unwrap(),
+ tv_nsec: timeout.subsec_nanos() as _,
+ });
+
+ syscalls::port_getn(
+ port.as_fd(),
+ timeout.as_mut(),
+ events,
+ min_events.try_into().unwrap(),
+ )
+}
+
+/// `port_send(port, events, userdata)`—Sends an event to a port.
+///
+/// # References
+/// - [OpenSolaris]
+/// - [illumos]
+///
+/// [OpenSolaris]: https://www.unix.com/man-page/opensolaris/3C/port_send/
+/// [illumos]: https://illumos.org/man/3C/port_send
+pub fn port_send(port: impl AsFd, events: i32, userdata: *mut c::c_void) -> io::Result<()> {
+ syscalls::port_send(port.as_fd(), events, userdata.cast())
+}
diff --git a/src/ffi.rs b/src/ffi.rs
new file mode 100644
index 0000000..ef9f87d
--- /dev/null
+++ b/src/ffi.rs
@@ -0,0 +1,15 @@
+//! Utilities related to FFI bindings.
+
+// If we have std, use it.
+#[cfg(feature = "std")]
+pub use {
+ std::ffi::{CStr, CString, FromBytesWithNulError, NulError},
+ std::os::raw::c_char,
+};
+
+// If we don't have std, we can depend on core and alloc having these features
+// in Rust 1.64+.
+#[cfg(all(feature = "alloc", not(feature = "std")))]
+pub use alloc::ffi::{CString, NulError};
+#[cfg(not(feature = "std"))]
+pub use core::ffi::{c_char, CStr, FromBytesWithNulError};
diff --git a/src/fs/abs.rs b/src/fs/abs.rs
new file mode 100644
index 0000000..6e7fdf7
--- /dev/null
+++ b/src/fs/abs.rs
@@ -0,0 +1,301 @@
+//! POSIX-style filesystem functions which operate on bare paths.
+
+use crate::fd::OwnedFd;
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+use crate::fs::Access;
+#[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 = "haiku", target_os = "redox", target_os = "wasi")))]
+use crate::fs::StatVfs;
+use crate::fs::{Mode, OFlags, Stat};
+#[cfg(not(target_os = "wasi"))]
+use crate::ugid::{Gid, Uid};
+use crate::{backend, io, path};
+#[cfg(feature = "alloc")]
+use {
+ crate::ffi::{CStr, CString},
+ crate::path::SMALL_PATH_BUFFER_SIZE,
+ alloc::vec::Vec,
+};
+
+/// `open(path, oflags, mode)`—Opens a file.
+///
+/// POSIX guarantees that `open` will use the lowest unused file descriptor,
+/// however it is not safe in general to rely on this, as file descriptors may
+/// be unexpectedly allocated on other threads or in libraries.
+///
+/// The `Mode` argument is only significant when creating a file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/open.2.html
+#[inline]
+pub fn open<P: path::Arg>(path: P, flags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
+ path.into_with_c_str(|path| backend::fs::syscalls::open(path, flags, mode))
+}
+
+/// `chmod(path, mode)`—Sets file or directory permissions.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/chmod.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn chmod<P: path::Arg>(path: P, mode: Mode) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::chmod(path, mode))
+}
+
+/// `stat(path)`—Queries metadata for a file or directory.
+///
+/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
+/// interpret the `st_mode` field.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/stat.2.html
+/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
+/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
+#[inline]
+pub fn stat<P: path::Arg>(path: P) -> io::Result<Stat> {
+ path.into_with_c_str(backend::fs::syscalls::stat)
+}
+
+/// `lstat(path)`—Queries metadata for a file or directory, without following
+/// symlinks.
+///
+/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
+/// interpret the `st_mode` field.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/lstat.2.html
+/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
+/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
+#[inline]
+pub fn lstat<P: path::Arg>(path: P) -> io::Result<Stat> {
+ path.into_with_c_str(backend::fs::syscalls::lstat)
+}
+
+/// `readlink(path)`—Reads the contents of a symlink.
+///
+/// If `reuse` is non-empty, reuse its buffer to store the result if possible.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/readlink.2.html
+#[cfg(feature = "alloc")]
+#[inline]
+pub fn readlink<P: path::Arg, B: Into<Vec<u8>>>(path: P, reuse: B) -> io::Result<CString> {
+ path.into_with_c_str(|path| _readlink(path, reuse.into()))
+}
+
+#[cfg(feature = "alloc")]
+fn _readlink(path: &CStr, 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 {
+ let nread = backend::fs::syscalls::readlink(path, &mut buffer)?;
+
+ let nread = nread as usize;
+ assert!(nread <= buffer.len());
+ if nread < buffer.len() {
+ buffer.resize(nread, 0_u8);
+ return Ok(CString::new(buffer).unwrap());
+ }
+ // Use `Vec` reallocation strategy to grow capacity exponentially.
+ buffer.reserve(1);
+ buffer.resize(buffer.capacity(), 0_u8);
+ }
+}
+
+/// `rename(old_path, new_path)`—Renames a file or directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/rename.2.html
+#[inline]
+pub fn rename<P: path::Arg, Q: path::Arg>(old_path: P, new_path: Q) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| backend::fs::syscalls::rename(old_path, new_path))
+ })
+}
+
+/// `unlink(path)`—Unlinks a file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/unlink.2.html
+#[inline]
+pub fn unlink<P: path::Arg>(path: P) -> io::Result<()> {
+ path.into_with_c_str(backend::fs::syscalls::unlink)
+}
+
+/// `rmdir(path)`—Removes a directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/rmdir.2.html
+#[inline]
+pub fn rmdir<P: path::Arg>(path: P) -> io::Result<()> {
+ path.into_with_c_str(backend::fs::syscalls::rmdir)
+}
+
+/// `link(old_path, new_path)`—Creates a hard link.
+///
+/// POSIX leaves it implementation-defined whether `link` follows a symlink in
+/// `old_path`, or creates a new link to the symbolic link itself. On platforms
+/// which have it, [`linkat`] avoids this problem since it has an [`AtFlags`]
+/// parameter and the [`AtFlags::SYMLINK_FOLLOW`] flag determines whether
+/// symlinks should be followed.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/link.2.html
+/// [`linkat`]: crate::fs::linkat
+/// [`AtFlags`]: crate::fs::AtFlags
+/// [`AtFlags::SYMLINK_FOLLOW`]: crate::fs::AtFlags::SYMLINK_FOLLOW
+#[inline]
+pub fn link<P: path::Arg, Q: path::Arg>(old_path: P, new_path: Q) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| backend::fs::syscalls::link(old_path, new_path))
+ })
+}
+
+/// `symlink(old_path, new_path)`—Creates a symlink.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/symlink.2.html
+#[inline]
+pub fn symlink<P: path::Arg, Q: path::Arg>(old_path: P, new_path: Q) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| backend::fs::syscalls::symlink(old_path, new_path))
+ })
+}
+
+/// `mkdir(path, mode)`—Creates a directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mkdir.2.html
+#[inline]
+pub fn mkdir<P: path::Arg>(path: P, mode: Mode) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::mkdir(path, mode))
+}
+
+/// `access(path, access)`—Tests permissions for a file or directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/access.2.html
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+#[inline]
+pub fn access<P: path::Arg>(path: P, access: Access) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::access(path, access))
+}
+
+/// `statfs`—Queries filesystem metadata.
+///
+/// Compared to [`statvfs`], this function often provides more information,
+/// though it's less portable.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/statfs.2.html
+#[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 fn statfs<P: path::Arg>(path: P) -> io::Result<StatFs> {
+ path.into_with_c_str(backend::fs::syscalls::statfs)
+}
+
+/// `statvfs`—Queries filesystem metadata, POSIX version.
+///
+/// Compared to [`statfs`], this function often provides less information, but
+/// it is more portable. But even so, filesystems are very diverse and not all
+/// the fields are meaningful for every filesystem. And `f_fsid` doesn't seem
+/// to have a clear meaning anywhere.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/statvfs.2.html
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub fn statvfs<P: path::Arg>(path: P) -> io::Result<StatVfs> {
+ path.into_with_c_str(backend::fs::syscalls::statvfs)
+}
+
+/// `chown(path, owner, group)`—Sets open file or directory ownership.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/chown.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn chown<P: path::Arg>(path: P, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::chown(path, owner, group))
+}
diff --git a/src/fs/at.rs b/src/fs/at.rs
new file mode 100644
index 0000000..f1c49a9
--- /dev/null
+++ b/src/fs/at.rs
@@ -0,0 +1,472 @@
+//! POSIX-style `*at` functions.
+//!
+//! The `dirfd` argument to these functions may be a file descriptor for a
+//! directory, or the special value [`CWD`].
+//!
+//! [`cwd`]: crate::fs::CWD
+
+use crate::fd::OwnedFd;
+use crate::ffi::CStr;
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+use crate::fs::Access;
+#[cfg(not(target_os = "espidf"))]
+use crate::fs::AtFlags;
+#[cfg(apple)]
+use crate::fs::CloneFlags;
+#[cfg(linux_kernel)]
+use crate::fs::RenameFlags;
+#[cfg(not(target_os = "espidf"))]
+use crate::fs::Stat;
+#[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+use crate::fs::{Dev, FileType};
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+use crate::fs::{Gid, Uid};
+use crate::fs::{Mode, OFlags};
+use crate::{backend, io, path};
+use backend::fd::{AsFd, BorrowedFd};
+use core::mem::MaybeUninit;
+use core::slice;
+#[cfg(feature = "alloc")]
+use {crate::ffi::CString, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec};
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+use {crate::fs::Timestamps, crate::timespec::Nsecs};
+
+/// `UTIME_NOW` for use with [`utimensat`].
+///
+/// [`utimensat`]: crate::fs::utimensat
+#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
+pub const UTIME_NOW: Nsecs = backend::c::UTIME_NOW as Nsecs;
+
+/// `UTIME_OMIT` for use with [`utimensat`].
+///
+/// [`utimensat`]: crate::fs::utimensat
+#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
+pub const UTIME_OMIT: Nsecs = backend::c::UTIME_OMIT as Nsecs;
+
+/// `openat(dirfd, path, oflags, mode)`—Opens a file.
+///
+/// POSIX guarantees that `openat` will use the lowest unused file descriptor,
+/// however it is not safe in general to rely on this, as file descriptors may
+/// be unexpectedly allocated on other threads or in libraries.
+///
+/// The `Mode` argument is only significant when creating a file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/openat.2.html
+#[inline]
+pub fn openat<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ oflags: OFlags,
+ create_mode: Mode,
+) -> io::Result<OwnedFd> {
+ path.into_with_c_str(|path| {
+ backend::fs::syscalls::openat(dirfd.as_fd(), path, oflags, create_mode)
+ })
+}
+
+/// `readlinkat(fd, path)`—Reads the contents of a symlink.
+///
+/// If `reuse` already has available capacity, reuse it if possible.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
+#[cfg(feature = "alloc")]
+#[inline]
+pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>(
+ dirfd: Fd,
+ path: P,
+ reuse: B,
+) -> io::Result<CString> {
+ path.into_with_c_str(|path| _readlinkat(dirfd.as_fd(), path, reuse.into()))
+}
+
+#[cfg(feature = "alloc")]
+#[allow(unsafe_code)]
+fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> {
+ buffer.clear();
+ buffer.reserve(SMALL_PATH_BUFFER_SIZE);
+
+ loop {
+ let nread =
+ backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buffer.spare_capacity_mut())?;
+
+ debug_assert!(nread <= buffer.capacity());
+ if nread < buffer.capacity() {
+ // SAFETY: From the [documentation]: “On success, these calls
+ // return the number of bytes placed in buf.”
+ //
+ // [documentation]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
+ unsafe {
+ buffer.set_len(nread);
+ }
+
+ // SAFETY:
+ // - “readlink places the contents of the symbolic link pathname in
+ // the buffer buf”
+ // - [POSIX definition 3.271: Pathname]: “A string that is used to
+ // identify a file.”
+ // - [POSIX definition 3.375: String]: “A contiguous sequence of
+ // bytes terminated by and including the first null byte.”
+ // - “readlink does not append a terminating null byte to buf.”
+ //
+ // Thus, there will be no NUL bytes in the string.
+ //
+ // [POSIX definition 3.271: Pathname]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271
+ // [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_375
+ unsafe {
+ return Ok(CString::from_vec_unchecked(buffer));
+ }
+ }
+
+ // Use `Vec` reallocation strategy to grow capacity exponentially.
+ buffer.reserve(buffer.capacity() + 1);
+ }
+}
+
+/// `readlinkat(fd, path)`—Reads the contents of a symlink, without
+/// allocating.
+///
+/// This is the "raw" version which avoids allocating, but which is
+/// significantly trickier to use; most users should use plain [`readlinkat`].
+///
+/// This version writes bytes into the buffer and returns two slices, one
+/// containing the written bytes, and one containing the remaining
+/// uninitialized space. If the number of written bytes is equal to the length
+/// of the buffer, it means the buffer wasn't big enough to hold the full
+/// string, and callers should try again with a bigger buffer.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
+#[inline]
+pub fn readlinkat_raw<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ buf: &mut [MaybeUninit<u8>],
+) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
+ path.into_with_c_str(|path| _readlinkat_raw(dirfd.as_fd(), path, buf))
+}
+
+#[allow(unsafe_code)]
+fn _readlinkat_raw<'a>(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ buf: &'a mut [MaybeUninit<u8>],
+) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
+ let n = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buf)?;
+ unsafe {
+ Ok((
+ slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), n),
+ &mut buf[n..],
+ ))
+ }
+}
+
+/// `mkdirat(fd, path, mode)`—Creates a directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdirat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html
+#[inline]
+pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::mkdirat(dirfd.as_fd(), path, mode))
+}
+
+/// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard
+/// link.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
+ old_dirfd: PFd,
+ old_path: P,
+ new_dirfd: QFd,
+ new_path: Q,
+ flags: AtFlags,
+) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| {
+ backend::fs::syscalls::linkat(
+ old_dirfd.as_fd(),
+ old_path,
+ new_dirfd.as_fd(),
+ new_path,
+ flags,
+ )
+ })
+ })
+}
+
+/// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory.
+///
+/// With the [`REMOVEDIR`] flag, this removes a directory. This is in place of
+/// a `rmdirat` function.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [`REMOVEDIR`]: AtFlags::REMOVEDIR
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::unlinkat(dirfd.as_fd(), path, flags))
+}
+
+/// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or
+/// directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html
+#[inline]
+pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
+ old_dirfd: PFd,
+ old_path: P,
+ new_dirfd: QFd,
+ new_path: Q,
+) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| {
+ backend::fs::syscalls::renameat(
+ old_dirfd.as_fd(),
+ old_path,
+ new_dirfd.as_fd(),
+ new_path,
+ )
+ })
+ })
+}
+
+/// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a
+/// file or directory.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "renameat2")]
+pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
+ old_dirfd: PFd,
+ old_path: P,
+ new_dirfd: QFd,
+ new_path: Q,
+ flags: RenameFlags,
+) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| {
+ backend::fs::syscalls::renameat2(
+ old_dirfd.as_fd(),
+ old_path,
+ new_dirfd.as_fd(),
+ new_path,
+ flags,
+ )
+ })
+ })
+}
+
+/// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html
+#[inline]
+pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>(
+ old_path: P,
+ new_dirfd: Fd,
+ new_path: Q,
+) -> io::Result<()> {
+ old_path.into_with_c_str(|old_path| {
+ new_path.into_with_c_str(|new_path| {
+ backend::fs::syscalls::symlinkat(old_path, new_dirfd.as_fd(), new_path)
+ })
+ })
+}
+
+/// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory.
+///
+/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
+/// interpret the `st_mode` field.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html
+/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
+/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "fstatat")]
+pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> {
+ path.into_with_c_str(|path| backend::fs::syscalls::statat(dirfd.as_fd(), path, flags))
+}
+
+/// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or
+/// directory.
+///
+/// On Linux before 5.8, this function uses the `faccessat` system call which
+/// doesn't support any flags. This function emulates support for the
+/// [`AtFlags::EACCESS`] flag by checking whether the uid and gid of the
+/// process match the effective uid and gid, in which case the `EACCESS` flag
+/// can be ignored. In Linux 5.8 and beyond `faccessat2` is used, which
+/// supports flags.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+#[inline]
+#[doc(alias = "faccessat")]
+pub fn accessat<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ access: Access,
+ flags: AtFlags,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::accessat(dirfd.as_fd(), path, access, flags))
+}
+
+/// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+#[inline]
+pub fn utimensat<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ times: &Timestamps,
+ flags: AtFlags,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::utimensat(dirfd.as_fd(), path, times, flags))
+}
+
+/// `fchmodat(dirfd, path, mode, flags)`—Sets file or directory permissions.
+///
+/// Platform support for flags varies widely, for example on Linux
+/// [`AtFlags::SYMLINK_NOFOLLOW`] is not implemented and therefore
+/// [`io::Errno::OPNOTSUPP`] will be returned.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "fchmodat")]
+pub fn chmodat<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ mode: Mode,
+ flags: AtFlags,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| backend::fs::syscalls::chmodat(dirfd.as_fd(), path, mode, flags))
+}
+
+/// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files.
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html
+#[cfg(apple)]
+#[inline]
+pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>(
+ src: Fd,
+ dst_dir: DstFd,
+ dst: P,
+ flags: CloneFlags,
+) -> io::Result<()> {
+ dst.into_with_c_str(|dst| {
+ backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags)
+ })
+}
+
+/// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html
+#[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[inline]
+pub fn mknodat<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ file_type: FileType,
+ mode: Mode,
+ dev: Dev,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| {
+ backend::fs::syscalls::mknodat(dirfd.as_fd(), path, file_type, mode, dev)
+ })
+}
+
+/// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory
+/// ownership.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "fchownat")]
+pub fn chownat<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ owner: Option<Uid>,
+ group: Option<Gid>,
+ flags: AtFlags,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| {
+ backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags)
+ })
+}
diff --git a/src/fs/constants.rs b/src/fs/constants.rs
new file mode 100644
index 0000000..a9237af
--- /dev/null
+++ b/src/fs/constants.rs
@@ -0,0 +1,7 @@
+//! Filesystem API constants, translated into `bitflags` constants.
+
+use crate::backend;
+
+pub use crate::io::FdFlags;
+pub use crate::timespec::{Nsecs, Secs, Timespec};
+pub use backend::fs::types::*;
diff --git a/src/fs/copy_file_range.rs b/src/fs/copy_file_range.rs
new file mode 100644
index 0000000..b927d57
--- /dev/null
+++ b/src/fs/copy_file_range.rs
@@ -0,0 +1,20 @@
+use crate::{backend, io};
+use backend::fd::AsFd;
+
+/// `copy_file_range(fd_in, off_in, fd_out, off_out, len, 0)`—Copies data
+/// from one file to another.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/copy_file_range.2.html
+#[inline]
+pub fn copy_file_range<InFd: AsFd, OutFd: AsFd>(
+ fd_in: InFd,
+ off_in: Option<&mut u64>,
+ fd_out: OutFd,
+ off_out: Option<&mut u64>,
+ len: usize,
+) -> io::Result<usize> {
+ backend::fs::syscalls::copy_file_range(fd_in.as_fd(), off_in, fd_out.as_fd(), off_out, len)
+}
diff --git a/src/fs/cwd.rs b/src/fs/cwd.rs
new file mode 100644
index 0000000..e66360e
--- /dev/null
+++ b/src/fs/cwd.rs
@@ -0,0 +1,39 @@
+//! The `cwd` function, representing the current working directory.
+//!
+//! # Safety
+//!
+//! This file uses `AT_FDCWD`, which is a raw file descriptor, but which is
+//! always valid.
+
+#![allow(unsafe_code)]
+
+use crate::backend;
+use backend::c;
+use backend::fd::{BorrowedFd, RawFd};
+
+/// `AT_FDCWD`—A handle representing the current working directory.
+///
+/// This is a file descriptor which refers to the process current directory
+/// which can be used as the directory argument in `*at` functions such as
+/// [`openat`].
+///
+/// # References
+/// - [POSIX]
+///
+/// [`openat`]: crate::fs::openat
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/fcntl.h.html
+// SAFETY: `AT_FDCWD` is a reserved value that is never dynamically
+// allocated, so it'll remain valid for the duration of `'static`.
+#[doc(alias = "AT_FDCWD")]
+pub const CWD: BorrowedFd<'static> =
+ unsafe { BorrowedFd::<'static>::borrow_raw(c::AT_FDCWD as RawFd) };
+
+/// Return the value of [`CWD`].
+#[deprecated(note = "Use `CWD` in place of `cwd()`.")]
+pub const fn cwd() -> BorrowedFd<'static> {
+ let at_fdcwd = c::AT_FDCWD as RawFd;
+
+ // SAFETY: `AT_FDCWD` is a reserved value that is never dynamically
+ // allocated, so it'll remain valid for the duration of `'static`.
+ unsafe { BorrowedFd::<'static>::borrow_raw(at_fdcwd) }
+}
diff --git a/src/fs/dir.rs b/src/fs/dir.rs
new file mode 100644
index 0000000..b3e1e3b
--- /dev/null
+++ b/src/fs/dir.rs
@@ -0,0 +1,5 @@
+//! `Dir` and `DirEntry`.
+
+use crate::backend;
+
+pub use backend::fs::dir::{Dir, DirEntry};
diff --git a/src/fs/fadvise.rs b/src/fs/fadvise.rs
new file mode 100644
index 0000000..a760a18
--- /dev/null
+++ b/src/fs/fadvise.rs
@@ -0,0 +1,18 @@
+use crate::{backend, io};
+use backend::fd::AsFd;
+use backend::fs::types::Advice;
+
+/// `posix_fadvise(fd, offset, len, advice)`—Declares an expected access
+/// pattern for a file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fadvise.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/posix_fadvise.2.html
+#[inline]
+#[doc(alias = "posix_fadvise")]
+pub fn fadvise<Fd: AsFd>(fd: Fd, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
+ backend::fs::syscalls::fadvise(fd.as_fd(), offset, len, advice)
+}
diff --git a/src/fs/fcntl.rs b/src/fs/fcntl.rs
new file mode 100644
index 0000000..f80db84
--- /dev/null
+++ b/src/fs/fcntl.rs
@@ -0,0 +1,112 @@
+//! The Unix `fcntl` function is effectively lots of different functions hidden
+//! behind a single dynamic dispatch interface. In order to provide a type-safe
+//! API, rustix makes them all separate functions so that they can have
+//! dedicated static type signatures.
+
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::fs::FlockOperation;
+use crate::{backend, io};
+use backend::fd::AsFd;
+use backend::fs::types::OFlags;
+
+// These `fcntl` functions live in the `io` module because they're not specific
+// to files, directories, or memfd objects. We re-export them here in the `fs`
+// module because the other the `fcntl` functions are here.
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub use crate::io::fcntl_dupfd_cloexec;
+pub use crate::io::{fcntl_getfd, fcntl_setfd};
+
+/// `fcntl(fd, F_GETFL)`—Returns a file descriptor's access mode and status.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[inline]
+#[doc(alias = "F_GETFL")]
+pub fn fcntl_getfl<Fd: AsFd>(fd: Fd) -> io::Result<OFlags> {
+ backend::fs::syscalls::fcntl_getfl(fd.as_fd())
+}
+
+/// `fcntl(fd, F_SETFL, flags)`—Sets a file descriptor's status.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[inline]
+#[doc(alias = "F_SETFL")]
+pub fn fcntl_setfl<Fd: AsFd>(fd: Fd, flags: OFlags) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_setfl(fd.as_fd(), flags)
+}
+
+/// `fcntl(fd, F_GET_SEALS)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "F_GET_SEALS")]
+pub fn fcntl_get_seals<Fd: AsFd>(fd: Fd) -> io::Result<SealFlags> {
+ backend::fs::syscalls::fcntl_get_seals(fd.as_fd())
+}
+
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+use backend::fs::types::SealFlags;
+
+/// `fcntl(fd, F_ADD_SEALS)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[cfg(any(linux_kernel, target_os = "freebsd", target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "F_ADD_SEALS")]
+pub fn fcntl_add_seals<Fd: AsFd>(fd: Fd, seals: SealFlags) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_add_seals(fd.as_fd(), seals)
+}
+
+/// `fcntl(fd, F_SETLK)`—Acquire or release an `fcntl`-style lock.
+///
+/// This function doesn't currently have an offset or len; it currently always
+/// sets the `l_len` field to 0, which is a special case that means the entire
+/// file should be locked.
+///
+/// Unlike `flock`-style locks, `fcntl`-style locks are process-associated,
+/// meaning that they don't guard against being acquired by two threads in the
+/// same process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+#[doc(alias = "F_SETLK")]
+#[doc(alias = "F_SETLKW")]
+pub fn fcntl_lock<Fd: AsFd>(fd: Fd, operation: FlockOperation) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_lock(fd.as_fd(), operation)
+}
diff --git a/src/fs/fcntl_apple.rs b/src/fs/fcntl_apple.rs
new file mode 100644
index 0000000..a32e46d
--- /dev/null
+++ b/src/fs/fcntl_apple.rs
@@ -0,0 +1,66 @@
+use crate::{backend, io};
+use backend::fd::AsFd;
+
+/// `fcntl(fd, F_RDADVISE, radvisory { offset, len })`
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+#[doc(alias = "F_RDADVISE")]
+#[inline]
+pub fn fcntl_rdadvise<Fd: AsFd>(fd: Fd, offset: u64, len: u64) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_rdadvise(fd.as_fd(), offset, len)
+}
+
+/// `fcntl(fd, F_FULLFSYNC)`
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+#[doc(alias = "F_FULLSYNC")]
+#[inline]
+pub fn fcntl_fullfsync<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_fullfsync(fd.as_fd())
+}
+
+/// `fcntl(fd, F_NOCACHE, value)`—Turn data caching off or on for a file
+/// descriptor.
+///
+/// See [this mailing list post] for additional information about the meanings
+/// of `F_NOCACHE` and `F_GLOBAL_NOCACHE`.
+///
+/// [this mailing list post]: https://lists.apple.com/archives/filesystem-dev/2007/Sep/msg00010.html
+///
+/// See also [`fcntl_global_nocache`].
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+#[doc(alias = "F_NOCACHE")]
+#[inline]
+pub fn fcntl_nocache<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_nocache(fd.as_fd(), value)
+}
+
+/// `fcntl(fd, F_GLOBAL_NOCACHE, value)`—Turn data caching off or on for all
+/// file descriptors.
+///
+/// See [this mailing list post] for additional information about the meanings
+/// of `F_NOCACHE` and `F_GLOBAL_NOCACHE`.
+///
+/// [this mailing list post]: https://lists.apple.com/archives/filesystem-dev/2007/Sep/msg00010.html
+///
+/// See also [`fcntl_nocache`].
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+#[doc(alias = "F_GLOBAL_NOCACHE")]
+#[inline]
+pub fn fcntl_global_nocache<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::fs::syscalls::fcntl_global_nocache(fd.as_fd(), value)
+}
diff --git a/src/fs/fcopyfile.rs b/src/fs/fcopyfile.rs
new file mode 100644
index 0000000..e8f26ff
--- /dev/null
+++ b/src/fs/fcopyfile.rs
@@ -0,0 +1,88 @@
+use crate::fs::CopyfileFlags;
+use crate::{backend, io};
+use backend::fd::AsFd;
+use backend::fs::types::copyfile_state_t;
+
+/// `fcopyfile(from, to, state, flags)`
+///
+/// # Safety
+///
+/// The `state` operand must be allocated with `copyfile_state_alloc` and not
+/// yet freed with `copyfile_state_free`.
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/fcopyfile.3.html
+#[inline]
+pub unsafe fn fcopyfile<FromFd: AsFd, ToFd: AsFd>(
+ from: FromFd,
+ to: ToFd,
+ state: copyfile_state_t,
+ flags: CopyfileFlags,
+) -> io::Result<()> {
+ backend::fs::syscalls::fcopyfile(from.as_fd(), to.as_fd(), state, flags)
+}
+
+/// `copyfile_state_alloc()`
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/fcopyfile.3.html
+#[inline]
+pub fn copyfile_state_alloc() -> io::Result<copyfile_state_t> {
+ backend::fs::syscalls::copyfile_state_alloc()
+}
+
+/// `copyfile_state_free(state)`
+///
+/// # Safety
+///
+/// The `state` operand must be allocated with `copyfile_state_alloc` and not
+/// yet freed with `copyfile_state_free`.
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/fcopyfile.3.html
+#[inline]
+pub unsafe fn copyfile_state_free(state: copyfile_state_t) -> io::Result<()> {
+ backend::fs::syscalls::copyfile_state_free(state)
+}
+
+/// `copyfile_state_get(state, COPYFILE_STATE_COPIED)`
+///
+/// # Safety
+///
+/// The `state` operand must be allocated with `copyfile_state_alloc` and not
+/// yet freed with `copyfile_state_free`.
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/fcopyfile.3.html
+#[inline]
+pub unsafe fn copyfile_state_get_copied(state: copyfile_state_t) -> io::Result<u64> {
+ backend::fs::syscalls::copyfile_state_get_copied(state)
+}
+
+/// `copyfile_state_get(state, flags, dst)`
+///
+/// # Safety
+///
+/// The `state` operand must be allocated with `copyfile_state_alloc` and not
+/// yet freed with `copyfile_state_free`.
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/fcopyfile.3.html
+#[inline]
+pub unsafe fn copyfile_state_get(
+ state: copyfile_state_t,
+ flag: u32,
+ dst: *mut core::ffi::c_void,
+) -> io::Result<()> {
+ backend::fs::syscalls::copyfile_state_get(state, flag, dst)
+}
diff --git a/src/fs/fd.rs b/src/fs/fd.rs
new file mode 100644
index 0000000..3ef4b38
--- /dev/null
+++ b/src/fs/fd.rs
@@ -0,0 +1,357 @@
+//! Functions which operate on file descriptors.
+
+#[cfg(not(target_os = "wasi"))]
+use crate::fs::Mode;
+#[cfg(not(target_os = "wasi"))]
+use crate::fs::{Gid, Uid};
+use crate::fs::{OFlags, SeekFrom, Timespec};
+use crate::{backend, io};
+use backend::fd::{AsFd, BorrowedFd};
+#[cfg(not(any(
+ netbsdlike,
+ solarish,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+use backend::fs::types::FallocateFlags;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "solaris",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use backend::fs::types::FlockOperation;
+#[cfg(linux_kernel)]
+use backend::fs::types::FsWord;
+use backend::fs::types::Stat;
+#[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 backend::fs::types::StatFs;
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+use backend::fs::types::StatVfs;
+
+/// Timestamps used by [`utimensat`] and [`futimens`].
+///
+/// [`utimensat`]: crate::fs::utimensat
+/// [`futimens`]: crate::fs::futimens
+// This is `repr(C)` and specifically laid out to match the representation used
+// by `utimensat` and `futimens`, which expect 2-element arrays of timestamps.
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct Timestamps {
+ /// The timestamp of the last access to a filesystem object.
+ pub last_access: Timespec,
+
+ /// The timestamp of the last modification of a filesystem object.
+ pub last_modification: Timespec,
+}
+
+/// The filesystem magic number for procfs.
+///
+/// See [the `fstatfs` manual page] for more information.
+///
+/// [the `fstatfs` manual page]: https://man7.org/linux/man-pages/man2/fstatfs.2.html#DESCRIPTION
+#[cfg(linux_kernel)]
+pub const PROC_SUPER_MAGIC: FsWord = backend::c::PROC_SUPER_MAGIC as FsWord;
+
+/// The filesystem magic number for NFS.
+///
+/// See [the `fstatfs` manual page] for more information.
+///
+/// [the `fstatfs` manual page]: https://man7.org/linux/man-pages/man2/fstatfs.2.html#DESCRIPTION
+#[cfg(linux_kernel)]
+pub const NFS_SUPER_MAGIC: FsWord = backend::c::NFS_SUPER_MAGIC as FsWord;
+
+/// `lseek(fd, offset, whence)`—Repositions a file descriptor within a file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/lseek.2.html
+#[inline]
+#[doc(alias = "lseek")]
+pub fn seek<Fd: AsFd>(fd: Fd, pos: SeekFrom) -> io::Result<u64> {
+ backend::fs::syscalls::seek(fd.as_fd(), pos)
+}
+
+/// `lseek(fd, 0, SEEK_CUR)`—Returns the current position within a file.
+///
+/// Return the current position of the file descriptor. This is a subset of
+/// the functionality of `seek`, but this interface makes it easier for users
+/// to declare their intent not to mutate any state.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/lseek.2.html
+#[inline]
+#[doc(alias = "lseek")]
+pub fn tell<Fd: AsFd>(fd: Fd) -> io::Result<u64> {
+ backend::fs::syscalls::tell(fd.as_fd())
+}
+
+/// `fchmod(fd, mode)`—Sets open file or directory permissions.
+///
+/// This implementation does not support [`OFlags::PATH`] file descriptors,
+/// even on platforms where the host libc emulates it.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fchmod.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn fchmod<Fd: AsFd>(fd: Fd, mode: Mode) -> io::Result<()> {
+ backend::fs::syscalls::fchmod(fd.as_fd(), mode)
+}
+
+/// `fchown(fd, owner, group)`—Sets open file or directory ownership.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fchown.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn fchown<Fd: AsFd>(fd: Fd, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
+ backend::fs::syscalls::fchown(fd.as_fd(), owner, group)
+}
+
+/// `fstat(fd)`—Queries metadata for an open file or directory.
+///
+/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
+/// interpret the `st_mode` field.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fstat.2.html
+/// [`Mode::from_raw_mode`]: Mode::from_raw_mode
+/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
+#[inline]
+pub fn fstat<Fd: AsFd>(fd: Fd) -> io::Result<Stat> {
+ backend::fs::syscalls::fstat(fd.as_fd())
+}
+
+/// `fstatfs(fd)`—Queries filesystem statistics for an open file or directory.
+///
+/// Compared to [`fstatvfs`], this function often provides more information,
+/// though it's less portable.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fstatfs.2.html
+#[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 fn fstatfs<Fd: AsFd>(fd: Fd) -> io::Result<StatFs> {
+ backend::fs::syscalls::fstatfs(fd.as_fd())
+}
+
+/// `fstatvfs(fd)`—Queries filesystem statistics for an open file or
+/// directory, POSIX version.
+///
+/// Compared to [`fstatfs`], this function often provides less information,
+/// but it is more portable. But even so, filesystems are very diverse and not
+/// all the fields are meaningful for every filesystem. And `f_fsid` doesn't
+/// seem to have a clear meaning anywhere.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fstatvfs.2.html
+#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub fn fstatvfs<Fd: AsFd>(fd: Fd) -> io::Result<StatVfs> {
+ backend::fs::syscalls::fstatvfs(fd.as_fd())
+}
+
+/// `futimens(fd, times)`—Sets timestamps for an open file or directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+#[inline]
+pub fn futimens<Fd: AsFd>(fd: Fd, times: &Timestamps) -> io::Result<()> {
+ backend::fs::syscalls::futimens(fd.as_fd(), times)
+}
+
+/// `fallocate(fd, mode, offset, len)`—Adjusts file allocation.
+///
+/// This is a more general form of `posix_fallocate`, adding a `mode` argument
+/// which modifies the behavior. On platforms which only support
+/// `posix_fallocate` and not the more general form, no `FallocateFlags` values
+/// are defined so it will always be empty.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux `fallocate`]
+/// - [Linux `posix_fallocate`]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fallocate.html
+/// [Linux `fallocate`]: https://man7.org/linux/man-pages/man2/fallocate.2.html
+/// [Linux `posix_fallocate`]: https://man7.org/linux/man-pages/man3/posix_fallocate.3.html
+#[cfg(not(any(
+ netbsdlike,
+ solarish,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+)))] // not implemented in libc for netbsd yet
+#[inline]
+#[doc(alias = "posix_fallocate")]
+pub fn fallocate<Fd: AsFd>(fd: Fd, mode: FallocateFlags, offset: u64, len: u64) -> io::Result<()> {
+ backend::fs::syscalls::fallocate(fd.as_fd(), mode, offset, len)
+}
+
+/// `fcntl(fd, F_GETFL) & O_ACCMODE`
+///
+/// Returns a pair of booleans indicating whether the file descriptor is
+/// readable and/or writable, respectively. This is only reliable on files; for
+/// example, it doesn't reflect whether sockets have been shut down; for
+/// general I/O handle support, use [`io::is_read_write`].
+#[inline]
+pub fn is_file_read_write<Fd: AsFd>(fd: Fd) -> io::Result<(bool, bool)> {
+ _is_file_read_write(fd.as_fd())
+}
+
+pub(crate) fn _is_file_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> {
+ let mode = backend::fs::syscalls::fcntl_getfl(fd)?;
+
+ // Check for `O_PATH`.
+ #[cfg(any(linux_kernel, target_os = "emscripten", target_os = "fuchsia"))]
+ if mode.contains(OFlags::PATH) {
+ return Ok((false, false));
+ }
+
+ // Use `RWMODE` rather than `ACCMODE` as `ACCMODE` may include `O_PATH`.
+ // We handled `O_PATH` above.
+ match mode & OFlags::RWMODE {
+ OFlags::RDONLY => Ok((true, false)),
+ OFlags::RDWR => Ok((true, true)),
+ OFlags::WRONLY => Ok((false, true)),
+ _ => unreachable!(),
+ }
+}
+
+/// `fsync(fd)`—Ensures that file data and metadata is written to the
+/// underlying storage device.
+///
+/// On iOS and macOS this isn't sufficient to ensure that data has reached
+/// persistent storage; use [`fcntl_fullfsync`] to ensure that.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fsync.2.html
+/// [`fcntl_fullfsync`]: https://docs.rs/rustix/*/x86_64-apple-darwin/rustix/fs/fn.fcntl_fullfsync.html
+#[inline]
+pub fn fsync<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::fs::syscalls::fsync(fd.as_fd())
+}
+
+/// `fdatasync(fd)`—Ensures that file data is written to the underlying
+/// storage device.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fdatasync.2.html
+#[cfg(not(any(
+ apple,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+#[inline]
+pub fn fdatasync<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::fs::syscalls::fdatasync(fd.as_fd())
+}
+
+/// `ftruncate(fd, length)`—Sets the length of a file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/ftruncate.2.html
+#[inline]
+pub fn ftruncate<Fd: AsFd>(fd: Fd, length: u64) -> io::Result<()> {
+ backend::fs::syscalls::ftruncate(fd.as_fd(), length)
+}
+
+/// `flock(fd, operation)`—Acquire or release an advisory lock on an open file.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/flock.2.html
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "solaris",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub fn flock<Fd: AsFd>(fd: Fd, operation: FlockOperation) -> io::Result<()> {
+ backend::fs::syscalls::flock(fd.as_fd(), operation)
+}
+
+/// `syncfs(fd)`—Flush cached filesystem data.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/syncfs.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn syncfs<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::fs::syscalls::syncfs(fd.as_fd())
+}
diff --git a/src/fs/getpath.rs b/src/fs/getpath.rs
new file mode 100644
index 0000000..8e14ff2
--- /dev/null
+++ b/src/fs/getpath.rs
@@ -0,0 +1,14 @@
+use crate::ffi::CString;
+use crate::{backend, io};
+use backend::fd::AsFd;
+
+/// `fcntl(fd, F_GETPATH)`
+///
+/// # References
+/// - [Apple]
+///
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+#[inline]
+pub fn getpath<Fd: AsFd>(fd: Fd) -> io::Result<CString> {
+ backend::fs::syscalls::getpath(fd.as_fd())
+}
diff --git a/src/fs/id.rs b/src/fs/id.rs
new file mode 100644
index 0000000..1fc2ef8
--- /dev/null
+++ b/src/fs/id.rs
@@ -0,0 +1 @@
+pub use crate::ugid::{Gid, Uid};
diff --git a/src/fs/ioctl.rs b/src/fs/ioctl.rs
new file mode 100644
index 0000000..ad2fe4f
--- /dev/null
+++ b/src/fs/ioctl.rs
@@ -0,0 +1,165 @@
+//! Filesystem-oriented `ioctl` functions.
+
+#![allow(unsafe_code)]
+
+#[cfg(linux_kernel)]
+use {
+ crate::fd::AsFd,
+ crate::{backend, io, ioctl},
+ backend::c,
+};
+
+use bitflags::bitflags;
+
+#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
+use crate::fd::{AsRawFd, BorrowedFd};
+
+/// `ioctl(fd, BLKSSZGET)`—Returns the logical block size of a block device.
+///
+/// This is mentioned in the [Linux `openat` manual page].
+///
+/// [Linux `openat` manual page]: https://man7.org/linux/man-pages/man2/openat.2.html
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "BLKSSZGET")]
+pub fn ioctl_blksszget<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ // SAFETY: BLZSSZGET is a getter opcode that gets a u32.
+ unsafe {
+ let ctl = ioctl::Getter::<ioctl::BadOpcode<{ c::BLKSSZGET }>, c::c_uint>::new();
+ ioctl::ioctl(fd, ctl)
+ }
+}
+
+/// `ioctl(fd, BLKPBSZGET)`—Returns the physical block size of a block device.
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "BLKPBSZGET")]
+pub fn ioctl_blkpbszget<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ // SAFETY: BLKPBSZGET is a getter opcode that gets a u32.
+ unsafe {
+ let ctl = ioctl::Getter::<ioctl::BadOpcode<{ c::BLKPBSZGET }>, c::c_uint>::new();
+ ioctl::ioctl(fd, ctl)
+ }
+}
+
+/// `ioctl(fd, FICLONE, src_fd)`—Share data between open files.
+///
+/// This ioctl is not available on Sparc platforms
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
+#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
+#[inline]
+#[doc(alias = "FICLONE")]
+pub fn ioctl_ficlone<Fd: AsFd, SrcFd: AsFd>(fd: Fd, src_fd: SrcFd) -> io::Result<()> {
+ unsafe { ioctl::ioctl(fd, Ficlone(src_fd.as_fd())) }
+}
+
+/// `ioctl(fd, EXT4_IOC_RESIZE_FS, blocks)`—Resize ext4 filesystem on fd.
+#[cfg(feature = "linux-raw-sys")]
+#[inline]
+#[doc(alias = "EXT4_IOC_RESIZE_FS")]
+pub fn ext4_ioc_resize_fs<Fd: AsFd>(fd: Fd, blocks: u64) -> io::Result<()> {
+ // SAFETY: EXT4_IOC_RESIZE_FS is a pointer setter opcode.
+ unsafe {
+ let ctl = ioctl::Setter::<ioctl::BadOpcode<{ backend::fs::EXT4_IOC_RESIZE_FS }>, u64>::new(
+ blocks,
+ );
+ ioctl::ioctl(fd, ctl)
+ }
+}
+
+#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
+struct Ficlone<'a>(BorrowedFd<'a>);
+
+#[cfg(all(linux_kernel, not(any(target_arch = "sparc", target_arch = "sparc64"))))]
+unsafe impl ioctl::Ioctl for Ficlone<'_> {
+ type Output = ();
+
+ const IS_MUTATING: bool = false;
+ const OPCODE: ioctl::Opcode = ioctl::Opcode::old(c::FICLONE as ioctl::RawOpcode);
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ self.0.as_raw_fd() as *mut c::c_void
+ }
+
+ unsafe fn output_from_ptr(
+ _: ioctl::IoctlOutput,
+ _: *mut c::c_void,
+ ) -> io::Result<Self::Output> {
+ Ok(())
+ }
+}
+
+#[cfg(feature = "linux-raw-sys")]
+bitflags! {
+ /// `FS_*` constants for use with [`ioctl_getflags`][crate::io::ioctl::ioctl_getflags].
+ pub struct IFlags: c::c_uint {
+ /// `FS_APPEND_FL`
+ const APPEND = linux_raw_sys::general::FS_APPEND_FL;
+ /// `FS_COMPR_FL`
+ const COMPRESSED = linux_raw_sys::general::FS_COMPR_FL;
+ /// `FS_DIRSYNC_FL`
+ const DIRSYNC = linux_raw_sys::general::FS_DIRSYNC_FL;
+ /// `FS_IMMUTABLE_FL`
+ const IMMUTABLE = linux_raw_sys::general::FS_IMMUTABLE_FL;
+ /// `FS_JOURNAL_DATA_FL`
+ const JOURNALING = linux_raw_sys::general::FS_JOURNAL_DATA_FL;
+ /// `FS_NOATIME_FL`
+ const NOATIME = linux_raw_sys::general::FS_NOATIME_FL;
+ /// `FS_NOCOW_FL`
+ const NOCOW = linux_raw_sys::general::FS_NOCOW_FL;
+ /// `FS_NODUMP_FL`
+ const NODUMP = linux_raw_sys::general::FS_NODUMP_FL;
+ /// `FS_NOTAIL_FL`
+ const NOTAIL = linux_raw_sys::general::FS_NOTAIL_FL;
+ /// `FS_PROJINHERIT_FL`
+ const PROJECT_INHERIT = linux_raw_sys::general::FS_PROJINHERIT_FL;
+ /// `FS_SECRM_FL`
+ const SECURE_REMOVAL = linux_raw_sys::general::FS_SECRM_FL;
+ /// `FS_SYNC_FL`
+ const SYNC = linux_raw_sys::general::FS_SYNC_FL;
+ /// `FS_TOPDIR_FL`
+ const TOPDIR = linux_raw_sys::general::FS_TOPDIR_FL;
+ /// `FS_UNRM_FL`
+ const UNRM = linux_raw_sys::general::FS_UNRM_FL;
+ }
+}
+
+/// `ioctl(fd, FS_IOC_GETFLAGS)`—Returns the [inode flags] attributes
+///
+/// [inode flags]: https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html
+#[cfg(feature = "linux-raw-sys")]
+#[inline]
+#[doc(alias = "FS_IOC_GETFLAGS")]
+pub fn ioctl_getflags<Fd: AsFd>(fd: Fd) -> io::Result<IFlags> {
+ unsafe {
+ #[cfg(target_pointer_width = "32")]
+ let ctl = ioctl::Getter::<ioctl::BadOpcode<{ c::FS_IOC32_GETFLAGS }>, u32>::new();
+ #[cfg(target_pointer_width = "64")]
+ let ctl = ioctl::Getter::<ioctl::BadOpcode<{ c::FS_IOC_GETFLAGS }>, u32>::new();
+
+ ioctl::ioctl(fd, ctl).map(IFlags::from_bits_retain)
+ }
+}
+
+/// `ioctl(fd, FS_IOC_SETFLAGS)`—Modify the [inode flags] attributes
+///
+/// [inode flags]: https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html
+#[cfg(feature = "linux-raw-sys")]
+#[inline]
+#[doc(alias = "FS_IOC_SETFLAGS")]
+pub fn ioctl_setflags<Fd: AsFd>(fd: Fd, flags: IFlags) -> io::Result<()> {
+ unsafe {
+ #[cfg(target_pointer_width = "32")]
+ let ctl =
+ ioctl::Setter::<ioctl::BadOpcode<{ c::FS_IOC32_SETFLAGS }>, u32>::new(flags.bits());
+
+ #[cfg(target_pointer_width = "64")]
+ let ctl = ioctl::Setter::<ioctl::BadOpcode<{ c::FS_IOC_SETFLAGS }>, u32>::new(flags.bits());
+
+ ioctl::ioctl(fd, ctl)
+ }
+}
diff --git a/src/fs/makedev.rs b/src/fs/makedev.rs
new file mode 100644
index 0000000..5793058
--- /dev/null
+++ b/src/fs/makedev.rs
@@ -0,0 +1,35 @@
+use crate::backend;
+use crate::fs::Dev;
+
+/// `makedev(maj, min)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/makedev.3.html
+#[inline]
+pub fn makedev(maj: u32, min: u32) -> Dev {
+ backend::fs::makedev::makedev(maj, min)
+}
+
+/// `minor(dev)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/minor.3.html
+#[inline]
+pub fn minor(dev: Dev) -> u32 {
+ backend::fs::makedev::minor(dev)
+}
+
+/// `major(dev)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/major.3.html
+#[inline]
+pub fn major(dev: Dev) -> u32 {
+ backend::fs::makedev::major(dev)
+}
diff --git a/src/fs/memfd_create.rs b/src/fs/memfd_create.rs
new file mode 100644
index 0000000..97154d2
--- /dev/null
+++ b/src/fs/memfd_create.rs
@@ -0,0 +1,18 @@
+use crate::fd::OwnedFd;
+use crate::{backend, io, path};
+use backend::fs::types::MemfdFlags;
+
+/// `memfd_create(name, flags)`
+///
+/// # References
+/// - [Linux]
+/// - [glibc]
+/// - [FreeBSD]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/memfd_create.2.html
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Memory_002dmapped-I_002fO.html#index-memfd_005fcreate
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?memfd_create
+#[inline]
+pub fn memfd_create<P: path::Arg>(name: P, flags: MemfdFlags) -> io::Result<OwnedFd> {
+ name.into_with_c_str(|name| backend::fs::syscalls::memfd_create(name, flags))
+}
diff --git a/src/fs/mod.rs b/src/fs/mod.rs
new file mode 100644
index 0000000..b14ac80
--- /dev/null
+++ b/src/fs/mod.rs
@@ -0,0 +1,178 @@
+//! Filesystem operations.
+
+mod abs;
+#[cfg(not(target_os = "redox"))]
+mod at;
+mod constants;
+#[cfg(linux_kernel)]
+mod copy_file_range;
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+#[cfg(not(target_os = "haiku"))] // Haiku needs <https://github.com/rust-lang/rust/pull/112371>
+mod cwd;
+#[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))]
+mod dir;
+#[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+mod fadvise;
+pub(crate) mod fcntl;
+#[cfg(apple)]
+mod fcntl_apple;
+#[cfg(apple)]
+mod fcopyfile;
+pub(crate) mod fd;
+#[cfg(all(apple, feature = "alloc"))]
+mod getpath;
+#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
+mod id;
+#[cfg(linux_kernel)]
+mod ioctl;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+mod makedev;
+#[cfg(any(linux_kernel, target_os = "freebsd"))]
+mod memfd_create;
+#[cfg(linux_kernel)]
+#[cfg(feature = "fs")]
+mod mount;
+#[cfg(feature = "linux-raw-sys")]
+mod openat2;
+#[cfg(feature = "linux-raw-sys")]
+mod raw_dir;
+mod seek_from;
+#[cfg(target_os = "linux")]
+mod sendfile;
+#[cfg(linux_kernel)]
+mod statx;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+mod sync;
+#[cfg(any(apple, linux_kernel))]
+mod xattr;
+
+#[cfg(linux_kernel)]
+pub use crate::backend::fs::inotify;
+pub use abs::*;
+#[cfg(not(target_os = "redox"))]
+pub use at::*;
+pub use constants::*;
+#[cfg(linux_kernel)]
+pub use copy_file_range::copy_file_range;
+#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
+#[cfg(not(target_os = "haiku"))] // Haiku needs <https://github.com/rust-lang/rust/pull/112371>
+pub use cwd::*;
+#[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))]
+pub use dir::{Dir, DirEntry};
+#[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+)))]
+pub use fadvise::fadvise;
+pub use fcntl::*;
+#[cfg(apple)]
+pub use fcntl_apple::*;
+#[cfg(apple)]
+pub use fcopyfile::*;
+pub use fd::*;
+#[cfg(all(apple, feature = "alloc"))]
+pub use getpath::getpath;
+#[cfg(not(target_os = "wasi"))]
+pub use id::*;
+#[cfg(linux_kernel)]
+pub use ioctl::*;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub use makedev::*;
+#[cfg(any(linux_kernel, target_os = "freebsd"))]
+pub use memfd_create::memfd_create;
+#[cfg(linux_kernel)]
+#[cfg(feature = "fs")]
+pub use mount::*;
+#[cfg(feature = "linux-raw-sys")]
+pub use openat2::openat2;
+#[cfg(feature = "linux-raw-sys")]
+pub use raw_dir::{RawDir, RawDirEntry};
+pub use seek_from::SeekFrom;
+#[cfg(target_os = "linux")]
+pub use sendfile::sendfile;
+#[cfg(linux_kernel)]
+pub use statx::statx;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub use sync::sync;
+#[cfg(any(apple, linux_kernel))]
+pub use xattr::*;
+
+/// Re-export types common to POSIX-ish platforms.
+#[cfg(feature = "std")]
+#[cfg(unix)]
+pub use std::os::unix::fs::{DirEntryExt, FileExt, FileTypeExt, MetadataExt, OpenOptionsExt};
+#[cfg(feature = "std")]
+#[cfg(all(wasi_ext, target_os = "wasi"))]
+pub use std::os::wasi::fs::{DirEntryExt, FileExt, FileTypeExt, MetadataExt, OpenOptionsExt};
+
+/// Extension trait for accessing timestamp fields of `Stat`.
+///
+/// Rustix's `Stat` type on some platforms has unsigned `st_mtime`,
+/// `st_atime`, and `st_ctime` fields. This is incorrect, as Unix defines
+/// these fields to be signed, with negative values representing dates before
+/// the Unix epoch. Until the next semver bump, these unsigned fields are
+/// deprecated, and this trait provides accessors which return their values
+/// as signed integers.
+#[cfg(all(unix, not(any(target_os = "aix", target_os = "nto"))))]
+pub trait StatExt {
+ /// Return the value of the `st_atime` field, casted to the correct type.
+ fn atime(&self) -> i64;
+ /// Return the value of the `st_mtime` field, casted to the correct type.
+ fn mtime(&self) -> i64;
+ /// Return the value of the `st_ctime` field, casted to the correct type.
+ fn ctime(&self) -> i64;
+}
+
+#[cfg(all(unix, not(any(target_os = "aix", target_os = "nto"))))]
+#[allow(deprecated)]
+impl StatExt for Stat {
+ #[inline]
+ fn atime(&self) -> i64 {
+ self.st_atime as i64
+ }
+ #[inline]
+ fn mtime(&self) -> i64 {
+ self.st_mtime as i64
+ }
+ #[inline]
+ fn ctime(&self) -> i64 {
+ self.st_ctime as i64
+ }
+}
diff --git a/src/fs/mount.rs b/src/fs/mount.rs
new file mode 100644
index 0000000..bd44aaa
--- /dev/null
+++ b/src/fs/mount.rs
@@ -0,0 +1,55 @@
+//! Linux `mount`.
+//!
+//! These have been moved to a new `rustix::mount` module.
+
+#[deprecated(note = "`rustix::fs::UnmountFlags` moved to `rustix::mount::UnmountFlags`.")]
+#[doc(hidden)]
+pub use crate::mount::UnmountFlags;
+
+#[deprecated(note = "`rustix::fs::MountFlags` moved to `rustix::mount::MountFlags`.")]
+#[doc(hidden)]
+pub use crate::mount::MountFlags;
+
+#[deprecated(
+ note = "`rustix::fs::MountPropagationFlags` moved to `rustix::mount::MountPropagationFlags`."
+)]
+#[doc(hidden)]
+pub use crate::mount::MountPropagationFlags;
+
+#[deprecated(note = "`rustix::fs::mount` moved to `rustix::mount::mount`.")]
+#[doc(hidden)]
+pub use crate::mount::mount;
+
+#[deprecated(note = "`rustix::fs::unmount` moved to `rustix::mount::unmount`.")]
+#[doc(hidden)]
+pub use crate::mount::unmount;
+
+#[deprecated(
+ note = "`rustix::fs::remount` is renamed and moved to `rustix::mount::mount_remount`."
+)]
+#[doc(hidden)]
+pub use crate::mount::mount_remount as remount;
+
+#[deprecated(
+ note = "`rustix::fs::bind_mount` is renamed and moved to `rustix::mount::mount_bind`."
+)]
+#[doc(hidden)]
+pub use crate::mount::mount_bind as bind_mount;
+
+#[deprecated(
+ note = "`rustix::fs::recursive_bind_mount` is renamed and moved to `rustix::mount::mount_recursive_bind`."
+)]
+#[doc(hidden)]
+pub use crate::mount::mount_recursive_bind as recursive_bind_mount;
+
+#[deprecated(
+ note = "`rustix::fs::change_mount` is renamed and moved to `rustix::mount::mount_change`."
+)]
+#[doc(hidden)]
+pub use crate::mount::mount_change as change_mount;
+
+#[deprecated(
+ note = "`rustix::fs::move_mount` is renamed and moved to `rustix::mount::mount_move`."
+)]
+#[doc(hidden)]
+pub use crate::mount::mount_move as move_mount;
diff --git a/src/fs/openat2.rs b/src/fs/openat2.rs
new file mode 100644
index 0000000..4918e00
--- /dev/null
+++ b/src/fs/openat2.rs
@@ -0,0 +1,23 @@
+use crate::fd::OwnedFd;
+use crate::{backend, io, path};
+use backend::fd::AsFd;
+use backend::fs::types::{Mode, OFlags, ResolveFlags};
+
+/// `openat2(dirfd, path, OpenHow { oflags, mode, resolve }, sizeof(OpenHow))`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/openat2.2.html
+#[inline]
+pub fn openat2<Fd: AsFd, P: path::Arg>(
+ dirfd: Fd,
+ path: P,
+ oflags: OFlags,
+ mode: Mode,
+ resolve: ResolveFlags,
+) -> io::Result<OwnedFd> {
+ path.into_with_c_str(|path| {
+ backend::fs::syscalls::openat2(dirfd.as_fd(), path, oflags, mode, resolve)
+ })
+}
diff --git a/src/fs/raw_dir.rs b/src/fs/raw_dir.rs
new file mode 100644
index 0000000..93686b1
--- /dev/null
+++ b/src/fs/raw_dir.rs
@@ -0,0 +1,237 @@
+//! `RawDir` and `RawDirEntry`.
+
+use core::fmt;
+use core::mem::{align_of, MaybeUninit};
+use linux_raw_sys::general::linux_dirent64;
+
+use crate::backend::fs::syscalls::getdents_uninit;
+use crate::fd::AsFd;
+use crate::ffi::CStr;
+use crate::fs::FileType;
+use crate::io;
+
+/// A directory iterator implemented with getdents.
+///
+/// Note: This implementation does not handle growing the buffer. If this
+/// functionality is necessary, you'll need to drop the current iterator,
+/// resize the buffer, and then re-create the iterator. The iterator is
+/// guaranteed to continue where it left off provided the file descriptor isn't
+/// changed. See the example in [`RawDir::new`].
+pub struct RawDir<'buf, Fd: AsFd> {
+ fd: Fd,
+ buf: &'buf mut [MaybeUninit<u8>],
+ initialized: usize,
+ offset: usize,
+}
+
+impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
+ /// Create a new iterator from the given file descriptor and buffer.
+ ///
+ /// Note: the buffer size may be trimmed to accommodate alignment
+ /// requirements.
+ ///
+ /// # Examples
+ ///
+ /// ## Simple but non-portable
+ ///
+ /// These examples are non-portable, because file systems may not have a
+ /// maximum file name length. If you can make assumptions that bound
+ /// this length, then these examples may suffice.
+ ///
+ /// Using the heap:
+ ///
+ /// ```
+ /// # use std::mem::MaybeUninit;
+ /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
+ /// # use rustix::cstr;
+ ///
+ /// let fd = openat(
+ /// CWD,
+ /// cstr!("."),
+ /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
+ /// Mode::empty(),
+ /// )
+ /// .unwrap();
+ ///
+ /// let mut buf = Vec::with_capacity(8192);
+ /// let mut iter = RawDir::new(fd, buf.spare_capacity_mut());
+ /// while let Some(entry) = iter.next() {
+ /// let entry = entry.unwrap();
+ /// dbg!(&entry);
+ /// }
+ /// ```
+ ///
+ /// Using the stack:
+ ///
+ /// ```
+ /// # use std::mem::MaybeUninit;
+ /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
+ /// # use rustix::cstr;
+ ///
+ /// let fd = openat(
+ /// CWD,
+ /// cstr!("."),
+ /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
+ /// Mode::empty(),
+ /// )
+ /// .unwrap();
+ ///
+ /// let mut buf = [MaybeUninit::uninit(); 2048];
+ /// let mut iter = RawDir::new(fd, &mut buf);
+ /// while let Some(entry) = iter.next() {
+ /// let entry = entry.unwrap();
+ /// dbg!(&entry);
+ /// }
+ /// ```
+ ///
+ /// ## Portable
+ ///
+ /// Heap allocated growing buffer for supporting directory entries with
+ /// arbitrarily large file names:
+ ///
+ /// ```notrust
+ /// # // The `notrust` above can be removed when we can depend on Rust 1.65.
+ /// # use std::mem::MaybeUninit;
+ /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
+ /// # use rustix::io::Errno;
+ /// # use rustix::cstr;
+ ///
+ /// let fd = openat(
+ /// CWD,
+ /// cstr!("."),
+ /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
+ /// Mode::empty(),
+ /// )
+ /// .unwrap();
+ ///
+ /// let mut buf = Vec::with_capacity(8192);
+ /// 'read: loop {
+ /// 'resize: {
+ /// let mut iter = RawDir::new(&fd, buf.spare_capacity_mut());
+ /// while let Some(entry) = iter.next() {
+ /// let entry = match entry {
+ /// Err(Errno::INVAL) => break 'resize,
+ /// r => r.unwrap(),
+ /// };
+ /// dbg!(&entry);
+ /// }
+ /// break 'read;
+ /// }
+ ///
+ /// let new_capacity = buf.capacity() * 2;
+ /// buf.reserve(new_capacity);
+ /// }
+ /// ```
+ pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self {
+ Self {
+ fd,
+ buf: {
+ let offset = buf.as_ptr().align_offset(align_of::<linux_dirent64>());
+ if offset < buf.len() {
+ &mut buf[offset..]
+ } else {
+ &mut []
+ }
+ },
+ initialized: 0,
+ offset: 0,
+ }
+ }
+}
+
+/// A raw directory entry, similar to [`std::fs::DirEntry`].
+///
+/// Unlike the std version, this may represent the `.` or `..` entries.
+pub struct RawDirEntry<'a> {
+ file_name: &'a CStr,
+ file_type: u8,
+ inode_number: u64,
+ next_entry_cookie: i64,
+}
+
+impl<'a> fmt::Debug for RawDirEntry<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut f = f.debug_struct("RawDirEntry");
+ f.field("file_name", &self.file_name());
+ f.field("file_type", &self.file_type());
+ f.field("ino", &self.ino());
+ f.field("next_entry_cookie", &self.next_entry_cookie());
+ f.finish()
+ }
+}
+
+impl<'a> RawDirEntry<'a> {
+ /// Returns the file name of this directory entry.
+ #[inline]
+ pub fn file_name(&self) -> &CStr {
+ self.file_name
+ }
+
+ /// Returns the type of this directory entry.
+ #[inline]
+ pub fn file_type(&self) -> FileType {
+ FileType::from_dirent_d_type(self.file_type)
+ }
+
+ /// Returns the inode number of this directory entry.
+ #[inline]
+ #[doc(alias = "inode_number")]
+ pub fn ino(&self) -> u64 {
+ self.inode_number
+ }
+
+ /// Returns the seek cookie to the next directory entry.
+ #[inline]
+ #[doc(alias = "off")]
+ pub fn next_entry_cookie(&self) -> u64 {
+ self.next_entry_cookie as u64
+ }
+}
+
+impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
+ /// Identical to [`Iterator::next`] except that [`Iterator::Item`] borrows
+ /// from self.
+ ///
+ /// Note: this interface will be broken to implement a stdlib iterator API
+ /// with GAT support once one becomes available.
+ #[allow(unsafe_code)]
+ #[allow(clippy::should_implement_trait)]
+ pub fn next(&mut self) -> Option<io::Result<RawDirEntry<'_>>> {
+ if self.is_buffer_empty() {
+ match getdents_uninit(self.fd.as_fd(), self.buf) {
+ Ok(0) => return None,
+ Ok(bytes_read) => {
+ self.initialized = bytes_read;
+ self.offset = 0;
+ }
+ Err(e) => return Some(Err(e)),
+ }
+ }
+
+ let dirent_ptr = self.buf[self.offset..].as_ptr();
+ // SAFETY:
+ // - This data is initialized by the check above.
+ // - Assumption: the kernel will not give us partial structs.
+ // - Assumption: the kernel uses proper alignment between structs.
+ // - The starting pointer is aligned (performed in RawDir::new)
+ let dirent = unsafe { &*dirent_ptr.cast::<linux_dirent64>() };
+
+ self.offset += usize::from(dirent.d_reclen);
+
+ Some(Ok(RawDirEntry {
+ file_type: dirent.d_type,
+ inode_number: dirent.d_ino.into(),
+ next_entry_cookie: dirent.d_off.into(),
+ // SAFETY: The kernel guarantees a NUL-terminated string.
+ file_name: unsafe { CStr::from_ptr(dirent.d_name.as_ptr().cast()) },
+ }))
+ }
+
+ /// Returns true if the internal buffer is empty and will be refilled when
+ /// calling [`next`].
+ ///
+ /// [`next`]: Self::next
+ pub fn is_buffer_empty(&self) -> bool {
+ self.offset >= self.initialized
+ }
+}
diff --git a/src/fs/seek_from.rs b/src/fs/seek_from.rs
new file mode 100644
index 0000000..c08abd2
--- /dev/null
+++ b/src/fs/seek_from.rs
@@ -0,0 +1,53 @@
+//! The following is derived from Rust's
+//! library/std/src/io/mod.rs at revision
+//! dca3f1b786efd27be3b325ed1e01e247aa589c3b.
+
+/// Enumeration of possible methods to seek within an I/O object.
+///
+/// It is used by the [`seek`] function.
+///
+/// This is similar to [`std::io::SeekFrom`], however it adds platform-specific
+/// seek options.
+///
+/// [`seek`]: crate::fs::seek
+#[derive(Copy, PartialEq, Eq, Clone, Debug)]
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub enum SeekFrom {
+ /// Sets the offset to the provided number of bytes.
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ Start(#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))] u64),
+
+ /// Sets the offset to the size of this object plus the specified number of
+ /// bytes.
+ ///
+ /// It is possible to seek beyond the end of an object, but it's an error
+ /// to seek before byte 0.
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ End(#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))] i64),
+
+ /// Sets the offset to the current position plus the specified number of
+ /// bytes.
+ ///
+ /// It is possible to seek beyond the end of an object, but it's an error
+ /// to seek before byte 0.
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ Current(#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))] i64),
+
+ /// Sets the offset to the current position plus the specified number of
+ /// bytes, plus the distance to the next byte which is not in a hole.
+ ///
+ /// If the offset is in a hole at the end of the file, the seek will fail
+ /// with [`Errno::NXIO`].
+ ///
+ /// [`Errno::NXIO`]: crate::io::Errno::NXIO
+ #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
+ Data(i64),
+
+ /// Sets the offset to the current position plus the specified number of
+ /// bytes, plus the distance to the next byte which is in a hole.
+ ///
+ /// If there is no hole past the offset, it will be set to the end of the
+ /// file i.e. there is an implicit hole at the end of any file.
+ #[cfg(any(apple, freebsdlike, linux_kernel, solarish))]
+ Hole(i64),
+}
diff --git a/src/fs/sendfile.rs b/src/fs/sendfile.rs
new file mode 100644
index 0000000..7f5c848
--- /dev/null
+++ b/src/fs/sendfile.rs
@@ -0,0 +1,19 @@
+use crate::{backend, io};
+use backend::fd::AsFd;
+
+/// `sendfile(out_fd, in_fd, offset, count)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendfile.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn sendfile<OutFd: AsFd, InFd: AsFd>(
+ out_fd: OutFd,
+ in_fd: InFd,
+ offset: Option<&mut u64>,
+ count: usize,
+) -> io::Result<usize> {
+ backend::fs::syscalls::sendfile(out_fd.as_fd(), in_fd.as_fd(), offset, count)
+}
diff --git a/src/fs/statx.rs b/src/fs/statx.rs
new file mode 100644
index 0000000..b7d6787
--- /dev/null
+++ b/src/fs/statx.rs
@@ -0,0 +1,135 @@
+//! Linux `statx`.
+
+use crate::fd::AsFd;
+use crate::fs::AtFlags;
+use crate::{backend, io, path};
+use backend::fs::types::{Statx, StatxFlags};
+
+#[cfg(feature = "linux_4_11")]
+use backend::fs::syscalls::statx as _statx;
+#[cfg(not(feature = "linux_4_11"))]
+use compat::statx as _statx;
+
+/// `statx(dirfd, path, flags, mask, statxbuf)`
+///
+/// This function returns [`io::Errno::NOSYS`] if `statx` is not available on
+/// the platform, such as Linux before 4.11. This also includes older Docker
+/// versions where the actual syscall fails with different error codes; rustix
+/// handles this and translates them into `NOSYS`.
+///
+/// # References
+/// - [Linux]
+///
+/// # Examples
+///
+/// ```
+/// # use std::path::Path;
+/// # use std::io;
+/// # use rustix::fs::{AtFlags, StatxFlags};
+/// # use rustix::fd::BorrowedFd;
+/// /// Try to determine if the provided path is a mount root. Will return
+/// /// `Ok(None)` if the kernel is not new enough to support `statx` or
+/// /// [`libc::STATX_ATTR_MOUNT_ROOT`].
+/// fn is_mountpoint(root: BorrowedFd<'_>, path: &Path) -> io::Result<Option<bool>> {
+/// use rustix::fs::{AtFlags, StatxFlags};
+///
+/// let mountroot_flag = libc::STATX_ATTR_MOUNT_ROOT as u64;
+/// match rustix::fs::statx(
+/// root,
+/// path,
+/// AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW,
+/// StatxFlags::empty(),
+/// ) {
+/// Ok(r) => {
+/// let present = (r.stx_attributes_mask & mountroot_flag) > 0;
+/// Ok(present.then(|| r.stx_attributes & mountroot_flag > 0))
+/// }
+/// Err(e) if e == rustix::io::Errno::NOSYS => Ok(None),
+/// Err(e) => Err(e.into()),
+/// }
+/// }
+/// ```
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/statx.2.html
+#[inline]
+pub fn statx<P: path::Arg, Fd: AsFd>(
+ dirfd: Fd,
+ path: P,
+ flags: AtFlags,
+ mask: StatxFlags,
+) -> io::Result<Statx> {
+ path.into_with_c_str(|path| _statx(dirfd.as_fd(), path, flags, mask))
+}
+
+#[cfg(not(feature = "linux_4_11"))]
+mod compat {
+ use crate::fd::BorrowedFd;
+ use crate::ffi::CStr;
+ use crate::fs::AtFlags;
+ use crate::{backend, io};
+ use core::sync::atomic::{AtomicU8, Ordering};
+
+ use backend::fs::types::{Statx, StatxFlags};
+
+ // Linux kernel prior to 4.11 and old versions of Docker don't support
+ // `statx`. We store the availability in a global to avoid unnecessary
+ // syscalls.
+ //
+ // 0: Unknown
+ // 1: Not available
+ // 2: Available
+ static STATX_STATE: AtomicU8 = AtomicU8::new(0);
+
+ #[inline]
+ pub fn statx(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: AtFlags,
+ mask: StatxFlags,
+ ) -> io::Result<Statx> {
+ match STATX_STATE.load(Ordering::Relaxed) {
+ 0 => statx_init(dirfd, path, flags, mask),
+ 1 => Err(io::Errno::NOSYS),
+ _ => backend::fs::syscalls::statx(dirfd, path, flags, mask),
+ }
+ }
+
+ /// The first `statx` call. We don't know if `statx` is available yet.
+ fn statx_init(
+ dirfd: BorrowedFd<'_>,
+ path: &CStr,
+ flags: AtFlags,
+ mask: StatxFlags,
+ ) -> io::Result<Statx> {
+ match backend::fs::syscalls::statx(dirfd, path, flags, mask) {
+ Err(io::Errno::NOSYS) => statx_error_nosys(),
+ Err(io::Errno::PERM) => statx_error_perm(),
+ result => {
+ STATX_STATE.store(2, Ordering::Relaxed);
+ result
+ }
+ }
+ }
+
+ /// The first `statx` call failed with `NOSYS` (or something we're treating
+ /// like `NOSYS`).
+ #[cold]
+ fn statx_error_nosys() -> io::Result<Statx> {
+ STATX_STATE.store(1, Ordering::Relaxed);
+ Err(io::Errno::NOSYS)
+ }
+
+ /// The first `statx` call failed with `PERM`.
+ #[cold]
+ fn statx_error_perm() -> io::Result<Statx> {
+ // Some old versions of Docker have `statx` fail with `PERM` when it
+ // isn't recognized. Check whether `statx` really is available, and if
+ // so, fail with `PERM`, and if not, treat it like `NOSYS`.
+ if backend::fs::syscalls::is_statx_available() {
+ STATX_STATE.store(2, Ordering::Relaxed);
+ Err(io::Errno::PERM)
+ } else {
+ statx_error_nosys()
+ }
+ }
+}
diff --git a/src/fs/sync.rs b/src/fs/sync.rs
new file mode 100644
index 0000000..3d2d089
--- /dev/null
+++ b/src/fs/sync.rs
@@ -0,0 +1,14 @@
+use crate::backend;
+
+/// `sync`—Flush cached filesystem data for all filesystems.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sync.2.html
+#[inline]
+pub fn sync() {
+ backend::fs::syscalls::sync();
+}
diff --git a/src/fs/xattr.rs b/src/fs/xattr.rs
new file mode 100644
index 0000000..d5be7a3
--- /dev/null
+++ b/src/fs/xattr.rs
@@ -0,0 +1,202 @@
+use crate::{backend, io, path};
+use backend::c;
+use backend::fd::AsFd;
+use bitflags::bitflags;
+
+bitflags! {
+ /// `XATTR_*` constants for use with [`setxattr`], and other `*setxattr`
+ /// functions.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XattrFlags: c::c_uint {
+ /// `XATTR_CREATE`
+ const CREATE = c::XATTR_CREATE as c::c_uint;
+
+ /// `XATTR_REPLACE`
+ const REPLACE = c::XATTR_REPLACE as c::c_uint;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `getxattr(path, name, value.as_ptr(), value.len())`—Get extended
+/// filesystem attributes.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/getxattr.2.html
+#[inline]
+pub fn getxattr<P: path::Arg, Name: path::Arg>(
+ path: P,
+ name: Name,
+ value: &mut [u8],
+) -> io::Result<usize> {
+ path.into_with_c_str(|path| {
+ name.into_with_c_str(|name| backend::fs::syscalls::getxattr(path, name, value))
+ })
+}
+
+/// `lgetxattr(path, name, value.as_ptr(), value.len())`—Get extended
+/// filesystem attributes, without following symlinks in the last path
+/// component.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/lgetxattr.2.html
+#[inline]
+pub fn lgetxattr<P: path::Arg, Name: path::Arg>(
+ path: P,
+ name: Name,
+ value: &mut [u8],
+) -> io::Result<usize> {
+ path.into_with_c_str(|path| {
+ name.into_with_c_str(|name| backend::fs::syscalls::lgetxattr(path, name, value))
+ })
+}
+
+/// `fgetxattr(fd, name, value.as_ptr(), value.len())`—Get extended
+/// filesystem attributes on an open file descriptor.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fgetxattr.2.html
+#[inline]
+pub fn fgetxattr<Fd: AsFd, Name: path::Arg>(
+ fd: Fd,
+ name: Name,
+ value: &mut [u8],
+) -> io::Result<usize> {
+ name.into_with_c_str(|name| backend::fs::syscalls::fgetxattr(fd.as_fd(), name, value))
+}
+
+/// `setxattr(path, name, value.as_ptr(), value.len(), flags)`—Set extended
+/// filesystem attributes.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/setxattr.2.html
+#[inline]
+pub fn setxattr<P: path::Arg, Name: path::Arg>(
+ path: P,
+ name: Name,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| {
+ name.into_with_c_str(|name| backend::fs::syscalls::setxattr(path, name, value, flags))
+ })
+}
+
+/// `setxattr(path, name, value.as_ptr(), value.len(), flags)`—Set extended
+/// filesystem attributes, without following symlinks in the last path
+/// component.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/lsetxattr.2.html
+#[inline]
+pub fn lsetxattr<P: path::Arg, Name: path::Arg>(
+ path: P,
+ name: Name,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ path.into_with_c_str(|path| {
+ name.into_with_c_str(|name| backend::fs::syscalls::lsetxattr(path, name, value, flags))
+ })
+}
+
+/// `fsetxattr(fd, name, value.as_ptr(), value.len(), flags)`—Set extended
+/// filesystem attributes on an open file descriptor.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fsetxattr.2.html
+#[inline]
+pub fn fsetxattr<Fd: AsFd, Name: path::Arg>(
+ fd: Fd,
+ name: Name,
+ value: &[u8],
+ flags: XattrFlags,
+) -> io::Result<()> {
+ name.into_with_c_str(|name| backend::fs::syscalls::fsetxattr(fd.as_fd(), name, value, flags))
+}
+
+/// `listxattr(path, list.as_ptr(), list.len())`—List extended filesystem
+/// attributes.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/listxattr.2.html
+#[inline]
+pub fn listxattr<P: path::Arg>(path: P, list: &mut [c::c_char]) -> io::Result<usize> {
+ path.into_with_c_str(|path| backend::fs::syscalls::listxattr(path, list))
+}
+
+/// `llistxattr(path, list.as_ptr(), list.len())`—List extended filesystem
+/// attributes, without following symlinks in the last path component.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/llistxattr.2.html
+#[inline]
+pub fn llistxattr<P: path::Arg>(path: P, list: &mut [c::c_char]) -> io::Result<usize> {
+ path.into_with_c_str(|path| backend::fs::syscalls::llistxattr(path, list))
+}
+
+/// `flistxattr(fd, list.as_ptr(), list.len())`—List extended filesystem
+/// attributes on an open file descriptor.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/flistxattr.2.html
+#[inline]
+pub fn flistxattr<Fd: AsFd>(fd: Fd, list: &mut [c::c_char]) -> io::Result<usize> {
+ backend::fs::syscalls::flistxattr(fd.as_fd(), list)
+}
+
+/// `removexattr(path, name)`—Remove an extended filesystem attribute.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/removexattr.2.html
+pub fn removexattr<P: path::Arg, Name: path::Arg>(path: P, name: Name) -> io::Result<()> {
+ path.into_with_c_str(|path| {
+ name.into_with_c_str(|name| backend::fs::syscalls::removexattr(path, name))
+ })
+}
+
+/// `lremovexattr(path, name)`—Remove an extended filesystem attribute,
+/// without following symlinks in the last path component.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/lremovexattr.2.html
+pub fn lremovexattr<P: path::Arg, Name: path::Arg>(path: P, name: Name) -> io::Result<()> {
+ path.into_with_c_str(|path| {
+ name.into_with_c_str(|name| backend::fs::syscalls::lremovexattr(path, name))
+ })
+}
+
+/// `fremovexattr(fd, name)`—Remove an extended filesystem attribute on an
+/// open file descriptor.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fremovexattr.2.html
+pub fn fremovexattr<Fd: AsFd, Name: path::Arg>(fd: Fd, name: Name) -> io::Result<()> {
+ name.into_with_c_str(|name| backend::fs::syscalls::fremovexattr(fd.as_fd(), name))
+}
diff --git a/src/io/close.rs b/src/io/close.rs
new file mode 100644
index 0000000..474d252
--- /dev/null
+++ b/src/io/close.rs
@@ -0,0 +1,55 @@
+//! The unsafe `close` for raw file descriptors.
+//!
+//! # Safety
+//!
+//! Operating on raw file descriptors is unsafe.
+#![allow(unsafe_code)]
+
+use crate::backend;
+use backend::fd::RawFd;
+
+/// `close(raw_fd)`—Closes a `RawFd` directly.
+///
+/// Most users won't need to use this, as `OwnedFd` automatically closes its
+/// file descriptor on `Drop`.
+///
+/// This function does not return a `Result`, as it is the [responsibility] of
+/// filesystem designers to not return errors from `close`. Users who chose to
+/// use NFS or similar filesystems should take care to monitor for problems
+/// externally.
+///
+/// [responsibility]: https://lwn.net/Articles/576518/
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#close-and-shutdownget-outta-my-face
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/close.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/close.2.html#//apple_ref/doc/man/2/close
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-closesocket
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=close&sektion=2
+/// [NetBSD]: https://man.netbsd.org/close.2
+/// [OpenBSD]: https://man.openbsd.org/close.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=close&section=2
+/// [illumos]: https://illumos.org/man/2/close
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Opening-and-Closing-Files.html#index-close
+///
+/// # Safety
+///
+/// This function takes a `RawFd`, which must be valid before the call, and is
+/// not valid after the call.
+#[inline]
+pub unsafe fn close(raw_fd: RawFd) {
+ backend::io::syscalls::close(raw_fd)
+}
diff --git a/src/io/dup.rs b/src/io/dup.rs
new file mode 100644
index 0000000..46fb38a
--- /dev/null
+++ b/src/io/dup.rs
@@ -0,0 +1,124 @@
+//! Functions which duplicate file descriptors.
+
+use crate::fd::OwnedFd;
+use crate::{backend, io};
+use backend::fd::AsFd;
+
+#[cfg(not(target_os = "wasi"))]
+pub use backend::io::types::DupFlags;
+
+/// `dup(fd)`—Creates a new `OwnedFd` instance that shares the same
+/// underlying [file description] as `fd`.
+///
+/// This function does not set the `O_CLOEXEC` flag. To do a `dup` that does
+/// set `O_CLOEXEC`, use [`fcntl_dupfd_cloexec`].
+///
+/// POSIX guarantees that `dup` will use the lowest unused file descriptor,
+/// however it is not safe in general to rely on this, as file descriptors may
+/// be unexpectedly allocated on other threads or in libraries.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [file description]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_258
+/// [`fcntl_dupfd_cloexec`]: crate::io::fcntl_dupfd_cloexec
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/dup.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/dup.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=dup&sektion=2
+/// [NetBSD]: https://man.netbsd.org/dup.2
+/// [OpenBSD]: https://man.openbsd.org/dup.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=dup&section=2
+/// [illumos]: https://illumos.org/man/2/dup
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Duplicating-Descriptors.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn dup<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
+ backend::io::syscalls::dup(fd.as_fd())
+}
+
+/// `dup2(fd, new)`—Changes the [file description] of a file descriptor.
+///
+/// `dup2` conceptually closes `new` and then sets the file description for
+/// `new` to be the same as the one for `fd`. This is a very unusual operation,
+/// and should only be used on file descriptors where you know how `new` will
+/// be subsequently used.
+///
+/// This function does not set the `O_CLOEXEC` flag. To do a `dup2` that does
+/// set `O_CLOEXEC`, use [`dup3`] with [`DupFlags::CLOEXEC`] on platforms which
+/// support it, or [`fcntl_dupfd_cloexec`]
+///
+/// For `dup2` to stdin, stdout, and stderr, see [`stdio::dup2_stdin`],
+/// [`stdio::dup2_stdout`], and [`stdio::dup2_stderr`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [file description]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_258
+/// [`fcntl_dupfd_cloexec`]: crate::io::fcntl_dupfd_cloexec
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup2.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/dup2.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/dup2.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=dup2&sektion=2
+/// [NetBSD]: https://man.netbsd.org/dup2.2
+/// [OpenBSD]: https://man.openbsd.org/dup2.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=dup2&section=2
+/// [illumos]: https://illumos.org/man/2/dup
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Duplicating-Descriptors.html
+/// [`stdio::dup2_stdin`]: https://docs.rs/rustix/*/rustix/stdio/fn.dup2_stdin.html
+/// [`stdio::dup2_stdout`]: https://docs.rs/rustix/*/rustix/stdio/fn.dup2_stdout.html
+/// [`stdio::dup2_stderr`]: https://docs.rs/rustix/*/rustix/stdio/fn.dup2_stderr.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn dup2<Fd: AsFd>(fd: Fd, new: &mut OwnedFd) -> io::Result<()> {
+ backend::io::syscalls::dup2(fd.as_fd(), new)
+}
+
+/// `dup3(fd, new, flags)`—Changes the [file description] of a file
+/// descriptor, with flags.
+///
+/// `dup3` is the same as [`dup2`] but adds an additional flags operand, and it
+/// fails in the case that `fd` and `new` have the same file descriptor value.
+/// This additional difference is the reason this function isn't named
+/// `dup2_with`.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+///
+/// [file description]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_258
+/// [Linux]: https://man7.org/linux/man-pages/man2/dup3.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=dup3&sektion=3
+/// [NetBSD]: https://man.netbsd.org/dup3.2
+/// [OpenBSD]: https://man.openbsd.org/dup3.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=dup3&section=3
+#[cfg(not(any(
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "nto",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub fn dup3<Fd: AsFd>(fd: Fd, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
+ backend::io::syscalls::dup3(fd.as_fd(), new, flags)
+}
diff --git a/src/io/errno.rs b/src/io/errno.rs
new file mode 100644
index 0000000..2b72de0
--- /dev/null
+++ b/src/io/errno.rs
@@ -0,0 +1,72 @@
+//! The `Errno` type, which is a minimal wrapper around an error code.
+//!
+//! We define the error constants as individual `const`s instead of an enum
+//! because we may not know about all of the host's error values and we don't
+//! want unrecognized values to create undefined behavior.
+
+use crate::backend;
+use core::{fmt, result};
+#[cfg(feature = "std")]
+use std::error;
+
+/// A specialized [`Result`] type for `rustix` APIs.
+pub type Result<T> = result::Result<T, Errno>;
+
+pub use backend::io::errno::Errno;
+
+impl Errno {
+ /// Shorthand for `std::io::Error::from(self).kind()`.
+ #[cfg(feature = "std")]
+ #[inline]
+ pub fn kind(self) -> std::io::ErrorKind {
+ std::io::Error::from(self).kind()
+ }
+}
+
+impl fmt::Display for Errno {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ #[cfg(feature = "std")]
+ {
+ std::io::Error::from(*self).fmt(fmt)
+ }
+ #[cfg(not(feature = "std"))]
+ {
+ write!(fmt, "os error {}", self.raw_os_error())
+ }
+ }
+}
+
+impl fmt::Debug for Errno {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ #[cfg(feature = "std")]
+ {
+ std::io::Error::from(*self).fmt(fmt)
+ }
+ #[cfg(not(feature = "std"))]
+ {
+ write!(fmt, "os error {}", self.raw_os_error())
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl error::Error for Errno {}
+
+#[cfg(feature = "std")]
+impl From<Errno> for std::io::Error {
+ #[inline]
+ fn from(err: Errno) -> Self {
+ Self::from_raw_os_error(err.raw_os_error() as _)
+ }
+}
+
+/// Call `f` until it either succeeds or fails other than [`Errno::INTR`].
+#[inline]
+pub fn retry_on_intr<T, F: FnMut() -> Result<T>>(mut f: F) -> Result<T> {
+ loop {
+ match f() {
+ Err(Errno::INTR) => (),
+ result => return result,
+ }
+ }
+}
diff --git a/src/io/fcntl.rs b/src/io/fcntl.rs
new file mode 100644
index 0000000..31eb84c
--- /dev/null
+++ b/src/io/fcntl.rs
@@ -0,0 +1,141 @@
+//! The Unix `fcntl` function is effectively lots of different functions hidden
+//! behind a single dynamic dispatch interface. In order to provide a type-safe
+//! API, rustix makes them all separate functions so that they can have
+//! dedicated static type signatures.
+//!
+//! `fcntl` functions which are not specific to files or directories live in
+//! the [`io`] module instead.
+//!
+//! [`io`]: crate::io
+
+use crate::{backend, io};
+use backend::fd::{AsFd, OwnedFd, RawFd};
+
+pub use backend::io::types::FdFlags;
+
+/// `fcntl(fd, F_GETFD)`—Returns a file descriptor's flags.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=fcntl&sektion=2
+/// [NetBSD]: https://man.netbsd.org/fcntl.2
+/// [OpenBSD]: https://man.openbsd.org/fcntl.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=fcntl&section=2
+/// [illumos]: https://illumos.org/man/2/fcntl
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Control-Operations.html#index-fcntl-function
+#[inline]
+#[doc(alias = "F_GETFD")]
+pub fn fcntl_getfd<Fd: AsFd>(fd: Fd) -> io::Result<FdFlags> {
+ backend::io::syscalls::fcntl_getfd(fd.as_fd())
+}
+
+/// `fcntl(fd, F_SETFD, flags)`—Sets a file descriptor's flags.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=fcntl&sektion=2
+/// [NetBSD]: https://man.netbsd.org/fcntl.2
+/// [OpenBSD]: https://man.openbsd.org/fcntl.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=fcntl&section=2
+/// [illumos]: https://illumos.org/man/2/fcntl
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Control-Operations.html#index-fcntl-function
+#[inline]
+#[doc(alias = "F_SETFD")]
+pub fn fcntl_setfd<Fd: AsFd>(fd: Fd, flags: FdFlags) -> io::Result<()> {
+ backend::io::syscalls::fcntl_setfd(fd.as_fd(), flags)
+}
+
+/// `fcntl(fd, F_DUPFD_CLOEXEC)`—Creates a new `OwnedFd` instance, with value
+/// at least `min`, that has `O_CLOEXEC` set and that shares the same
+/// underlying [file description] as `fd`.
+///
+/// POSIX guarantees that `F_DUPFD_CLOEXEC` will use the lowest unused file
+/// descriptor which is at least `min`, however it is not safe in general to
+/// rely on this, as file descriptors may be unexpectedly allocated on other
+/// threads or in libraries.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=fcntl&sektion=2
+/// [NetBSD]: https://man.netbsd.org/fcntl.2
+/// [OpenBSD]: https://man.openbsd.org/fcntl.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=fcntl&section=2
+/// [illumos]: https://illumos.org/man/2/fcntl
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Control-Operations.html#index-fcntl-function
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "F_DUPFD_CLOEXEC")]
+pub fn fcntl_dupfd_cloexec<Fd: AsFd>(fd: Fd, min: RawFd) -> io::Result<OwnedFd> {
+ backend::io::syscalls::fcntl_dupfd_cloexec(fd.as_fd(), min)
+}
+
+/// `fcntl(fd, F_DUPFD)`—Creates a new `OwnedFd` instance, with value at
+/// least `min`, that shares the same underlying [file description] as `fd`.
+///
+/// POSIX guarantees that `F_DUPFD` will use the lowest unused file descriptor
+/// which is at least `min`, however it is not safe in general to rely on this,
+/// as file descriptors may be unexpectedly allocated on other threads or in
+/// libraries.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=fcntl&sektion=2
+/// [NetBSD]: https://man.netbsd.org/fcntl.2
+/// [OpenBSD]: https://man.openbsd.org/fcntl.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=fcntl&section=2
+/// [illumos]: https://illumos.org/man/2/fcntl
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Control-Operations.html#index-fcntl-function
+#[cfg(target_os = "espidf")]
+#[inline]
+#[doc(alias = "F_DUPFD")]
+pub fn fcntl_dupfd<Fd: AsFd>(fd: Fd, min: RawFd) -> io::Result<OwnedFd> {
+ backend::io::syscalls::fcntl_dupfd(fd.as_fd(), min)
+}
diff --git a/src/io/ioctl.rs b/src/io/ioctl.rs
new file mode 100644
index 0000000..99dec06
--- /dev/null
+++ b/src/io/ioctl.rs
@@ -0,0 +1,77 @@
+//! The Unix `ioctl` function is effectively lots of different functions hidden
+//! behind a single dynamic dispatch interface. In order to provide a type-safe
+//! API, rustix makes them all separate functions so that they can have
+//! dedicated static type signatures.
+//!
+//! Some ioctls, such as those related to filesystems, terminals, and
+//! processes, live in other top-level API modules.
+
+#![allow(unsafe_code)]
+
+use crate::{backend, io, ioctl};
+use backend::c;
+use backend::fd::AsFd;
+
+/// `ioctl(fd, FIOCLEX, NULL)`—Set the close-on-exec flag.
+///
+/// This is similar to `fcntl(fd, F_SETFD, FD_CLOEXEC)`, except that it avoids
+/// clearing any other flags that might be set.
+#[cfg(apple)]
+#[inline]
+#[doc(alias = "FIOCLEX")]
+#[doc(alias = "FD_CLOEXEC")]
+pub fn ioctl_fioclex<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ // SAFETY: FIOCLEX is a no-argument setter opcode.
+ unsafe {
+ let ctl = ioctl::NoArg::<ioctl::BadOpcode<{ c::FIOCLEX }>>::new();
+ ioctl::ioctl(fd, ctl)
+ }
+}
+
+/// `ioctl(fd, FIONBIO, &value)`—Enables or disables non-blocking mode.
+///
+/// # References
+/// - [Winsock]
+/// - [NetBSD]
+/// - [OpenBSD]
+///
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls#unix-ioctl-codes
+/// [NetBSD]: https://man.netbsd.org/ioctl.2#GENERIC%20IOCTLS
+/// [OpenBSD]: https://man.openbsd.org/ioctl.2#GENERIC_IOCTLS
+#[inline]
+#[doc(alias = "FIONBIO")]
+pub fn ioctl_fionbio<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ // SAFETY: FIONBIO is a pointer setter opcode.
+ unsafe {
+ let ctl = ioctl::Setter::<ioctl::BadOpcode<{ c::FIONBIO }>, c::c_int>::new(value.into());
+ ioctl::ioctl(fd, ctl)
+ }
+}
+
+/// `ioctl(fd, FIONREAD)`—Returns the number of bytes ready to be read.
+///
+/// The result of this function gets silently coerced into a C `int` by the OS,
+/// so it may contain a wrapped value.
+///
+/// # References
+/// - [Linux]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/ioctl_tty.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls#unix-ioctl-codes
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=ioctl&sektion=2#GENERIC%09IOCTLS
+/// [NetBSD]: https://man.netbsd.org/ioctl.2#GENERIC%20IOCTLS
+/// [OpenBSD]: https://man.openbsd.org/ioctl.2#GENERIC_IOCTLS
+#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+#[inline]
+#[doc(alias = "FIONREAD")]
+pub fn ioctl_fionread<Fd: AsFd>(fd: Fd) -> io::Result<u64> {
+ // SAFETY: FIONREAD is a getter opcode that gets a c_int.
+ unsafe {
+ let ctl = ioctl::Getter::<ioctl::BadOpcode<{ c::FIONREAD }>, c::c_int>::new();
+ ioctl::ioctl(fd, ctl).map(|n| n as u64)
+ }
+}
diff --git a/src/io/is_read_write.rs b/src/io/is_read_write.rs
new file mode 100644
index 0000000..af33806
--- /dev/null
+++ b/src/io/is_read_write.rs
@@ -0,0 +1,19 @@
+//! The [`is_read_write`] function.
+//!
+//! [`is_read_write`]: https://docs.rs/rustix/*/rustix/io/fn.is_read_write.html
+
+use crate::{backend, io};
+use backend::fd::AsFd;
+
+/// Returns a pair of booleans indicating whether the file descriptor is
+/// readable and/or writable, respectively.
+///
+/// Unlike [`is_file_read_write`], this correctly detects whether sockets
+/// have been shutdown, partially or completely.
+///
+/// [`is_file_read_write`]: crate::fs::is_file_read_write
+#[inline]
+#[cfg_attr(doc_cfg, doc(cfg(all(feature = "fs", feature = "net"))))]
+pub fn is_read_write<Fd: AsFd>(fd: Fd) -> io::Result<(bool, bool)> {
+ backend::io::syscalls::is_read_write(fd.as_fd())
+}
diff --git a/src/io/mod.rs b/src/io/mod.rs
new file mode 100644
index 0000000..bddd12a
--- /dev/null
+++ b/src/io/mod.rs
@@ -0,0 +1,32 @@
+//! I/O operations.
+//!
+//! If you're looking for [`SeekFrom`], that's in the [`fs`] module.
+//!
+//! [`SeekFrom`]: https://docs.rs/rustix/*/rustix/fs/enum.SeekFrom.html
+//! [`fs`]: https://docs.rs/rustix/*/rustix/fs/index.html
+
+mod close;
+#[cfg(not(windows))]
+mod dup;
+mod errno;
+#[cfg(not(windows))]
+mod fcntl;
+mod ioctl;
+#[cfg(not(any(windows, target_os = "redox")))]
+#[cfg(all(feature = "fs", feature = "net"))]
+mod is_read_write;
+#[cfg(not(windows))]
+mod read_write;
+
+pub use close::close;
+#[cfg(not(windows))]
+pub use dup::*;
+pub use errno::{retry_on_intr, Errno, Result};
+#[cfg(not(windows))]
+pub use fcntl::*;
+pub use ioctl::*;
+#[cfg(not(any(windows, target_os = "redox")))]
+#[cfg(all(feature = "fs", feature = "net"))]
+pub use is_read_write::*;
+#[cfg(not(windows))]
+pub use read_write::*;
diff --git a/src/io/read_write.rs b/src/io/read_write.rs
new file mode 100644
index 0000000..d5296fa
--- /dev/null
+++ b/src/io/read_write.rs
@@ -0,0 +1,312 @@
+//! `read` and `write`, optionally positioned, optionally vectored
+
+#![allow(unsafe_code)]
+
+use crate::buffer::split_init;
+use crate::{backend, io};
+use backend::fd::AsFd;
+use core::mem::MaybeUninit;
+
+// Declare `IoSlice` and `IoSliceMut`.
+#[cfg(not(windows))]
+pub use crate::maybe_polyfill::io::{IoSlice, IoSliceMut};
+
+#[cfg(feature = "linux-raw-sys")]
+pub use backend::io::types::ReadWriteFlags;
+
+/// `read(fd, buf)`—Reads from a stream.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/read.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/read.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=read&sektion=2
+/// [NetBSD]: https://man.netbsd.org/read.2
+/// [OpenBSD]: https://man.openbsd.org/read.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=read&section=2
+/// [illumos]: https://illumos.org/man/2/read
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/I_002fO-Primitives.html#index-reading-from-a-file-descriptor
+#[inline]
+pub fn read<Fd: AsFd>(fd: Fd, buf: &mut [u8]) -> io::Result<usize> {
+ unsafe { backend::io::syscalls::read(fd.as_fd(), buf.as_mut_ptr(), buf.len()) }
+}
+
+/// `read(fd, buf)`—Reads from a stream.
+///
+/// This is equivalent to [`read`], except that it can read into uninitialized
+/// memory. It returns the slice that was initialized by this function and the
+/// slice that remains uninitialized.
+#[inline]
+pub fn read_uninit<Fd: AsFd>(
+ fd: Fd,
+ buf: &mut [MaybeUninit<u8>],
+) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
+ // Get number of initialized bytes.
+ let length =
+ unsafe { backend::io::syscalls::read(fd.as_fd(), buf.as_mut_ptr() as *mut u8, buf.len()) };
+
+ // Split into the initialized and uninitialized portions.
+ Ok(unsafe { split_init(buf, length?) })
+}
+
+/// `write(fd, buf)`—Writes to a stream.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/write.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/write.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=write&sektion=2
+/// [NetBSD]: https://man.netbsd.org/write.2
+/// [OpenBSD]: https://man.openbsd.org/write.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=write&section=2
+/// [illumos]: https://illumos.org/man/2/write
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/I_002fO-Primitives.html#index-writing-to-a-file-descriptor
+#[inline]
+pub fn write<Fd: AsFd>(fd: Fd, buf: &[u8]) -> io::Result<usize> {
+ backend::io::syscalls::write(fd.as_fd(), buf)
+}
+
+/// `pread(fd, buf, offset)`—Reads from a file at a given position.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/pread.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/pread.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=pread&sektion=2
+/// [NetBSD]: https://man.netbsd.org/pread.2
+/// [OpenBSD]: https://man.openbsd.org/pread.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=pread&section=2
+/// [illumos]: https://illumos.org/man/2/pread
+#[inline]
+pub fn pread<Fd: AsFd>(fd: Fd, buf: &mut [u8], offset: u64) -> io::Result<usize> {
+ unsafe { backend::io::syscalls::pread(fd.as_fd(), buf.as_mut_ptr(), buf.len(), offset) }
+}
+
+/// `pread(fd, buf, offset)`—Reads from a file at a given position.
+///
+/// This is equivalent to [`pread`], except that it can read into uninitialized
+/// memory. It returns the slice that was initialized by this function and the
+/// slice that remains uninitialized.
+#[inline]
+pub fn pread_uninit<Fd: AsFd>(
+ fd: Fd,
+ buf: &mut [MaybeUninit<u8>],
+ offset: u64,
+) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
+ let length = unsafe {
+ backend::io::syscalls::pread(fd.as_fd(), buf.as_mut_ptr() as *mut u8, buf.len(), offset)
+ };
+ Ok(unsafe { split_init(buf, length?) })
+}
+
+/// `pwrite(fd, bufs)`—Writes to a file at a given position.
+///
+/// Contrary to POSIX, on many popular platforms including Linux and FreeBSD,
+/// if the file is opened in append mode, this ignores the offset appends the
+/// data to the end of the file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/pwrite.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/pwrite.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=pwrite&sektion=2
+/// [NetBSD]: https://man.netbsd.org/pwrite.2
+/// [OpenBSD]: https://man.openbsd.org/pwrite.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=pwrite&section=2
+/// [illumos]: https://illumos.org/man/2/pwrite
+#[inline]
+pub fn pwrite<Fd: AsFd>(fd: Fd, buf: &[u8], offset: u64) -> io::Result<usize> {
+ backend::io::syscalls::pwrite(fd.as_fd(), buf, offset)
+}
+
+/// `readv(fd, bufs)`—Reads from a stream into multiple buffers.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/readv.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/readv.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=readv&sektion=2
+/// [NetBSD]: https://man.netbsd.org/readv.2
+/// [OpenBSD]: https://man.openbsd.org/readv.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=readv&section=2
+/// [illumos]: https://illumos.org/man/2/readv
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+pub fn readv<Fd: AsFd>(fd: Fd, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
+ backend::io::syscalls::readv(fd.as_fd(), bufs)
+}
+
+/// `writev(fd, bufs)`—Writes to a stream from multiple buffers.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/writev.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/writev.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/writev.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=writev&sektion=2
+/// [NetBSD]: https://man.netbsd.org/writev.2
+/// [OpenBSD]: https://man.openbsd.org/writev.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=writev&section=2
+/// [illumos]: https://illumos.org/man/2/writev
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+pub fn writev<Fd: AsFd>(fd: Fd, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+ backend::io::syscalls::writev(fd.as_fd(), bufs)
+}
+
+/// `preadv(fd, bufs, offset)`—Reads from a file at a given position into
+/// multiple buffers.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/preadv.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=preadv&sektion=2
+/// [NetBSD]: https://man.netbsd.org/preadv.2
+/// [OpenBSD]: https://man.openbsd.org/preadv.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=preadv&section=2
+/// [illumos]: https://illumos.org/man/2/preadv
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "solaris",
+ target_os = "vita"
+)))]
+#[inline]
+pub fn preadv<Fd: AsFd>(fd: Fd, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
+ backend::io::syscalls::preadv(fd.as_fd(), bufs, offset)
+}
+
+/// `pwritev(fd, bufs, offset)`—Writes to a file at a given position from
+/// multiple buffers.
+///
+/// Contrary to POSIX, on many popular platforms including Linux and FreeBSD,
+/// if the file is opened in append mode, this ignores the offset appends the
+/// data to the end of the file.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/pwritev.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=pwritev&sektion=2
+/// [NetBSD]: https://man.netbsd.org/pwritev.2
+/// [OpenBSD]: https://man.openbsd.org/pwritev.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=pwritev&section=2
+/// [illumos]: https://illumos.org/man/2/pwritev
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "solaris",
+ target_os = "vita"
+)))]
+#[inline]
+pub fn pwritev<Fd: AsFd>(fd: Fd, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
+ backend::io::syscalls::pwritev(fd.as_fd(), bufs, offset)
+}
+
+/// `preadv2(fd, bufs, offset, flags)`—Reads data, with several options.
+///
+/// An `offset` of `u64::MAX` means to use and update the current file offset.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/preadv2.2.html
+#[cfg(feature = "linux-raw-sys")]
+#[inline]
+pub fn preadv2<Fd: AsFd>(
+ fd: Fd,
+ bufs: &mut [IoSliceMut<'_>],
+ offset: u64,
+ flags: ReadWriteFlags,
+) -> io::Result<usize> {
+ backend::io::syscalls::preadv2(fd.as_fd(), bufs, offset, flags)
+}
+
+/// `pwritev2(fd, bufs, offset, flags)`—Writes data, with several options.
+///
+/// An `offset` of `u64::MAX` means to use and update the current file offset.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/pwritev2.2.html
+#[cfg(feature = "linux-raw-sys")]
+#[inline]
+pub fn pwritev2<Fd: AsFd>(
+ fd: Fd,
+ bufs: &[IoSlice<'_>],
+ offset: u64,
+ flags: ReadWriteFlags,
+) -> io::Result<usize> {
+ backend::io::syscalls::pwritev2(fd.as_fd(), bufs, offset, flags)
+}
diff --git a/src/io_uring.rs b/src/io_uring.rs
new file mode 100644
index 0000000..8394192
--- /dev/null
+++ b/src/io_uring.rs
@@ -0,0 +1,1527 @@
+//! Linux [io_uring].
+//!
+//! This API is very low-level. The main adaptations it makes from the raw
+//! Linux io_uring API are the use of appropriately-sized `bitflags`, `enum`,
+//! `Result`, `OwnedFd`, `AsFd`, `RawFd`, and `*mut c_void` in place of plain
+//! integers.
+//!
+//! For a higher-level API built on top of this, see the [rustix-uring] crate.
+//!
+//! # Safety
+//!
+//! io_uring operates on raw pointers and raw file descriptors. Rustix does not
+//! attempt to provide a safe API for these, because the abstraction level is
+//! too low for this to be practical. Safety should be introduced in
+//! higher-level abstraction layers.
+//!
+//! # References
+//! - [Linux]
+//! - [io_uring header]
+//!
+//! [Linux]: https://man.archlinux.org/man/io_uring.7.en
+//! [io_uring]: https://en.wikipedia.org/wiki/Io_uring
+//! [io_uring header]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/io_uring.h
+//! [rustix-uring]: https://crates.io/crates/rustix-uring
+#![allow(unsafe_code)]
+
+use crate::fd::{AsFd, BorrowedFd, OwnedFd, RawFd};
+use crate::{backend, io};
+use core::ffi::c_void;
+use core::mem::MaybeUninit;
+use core::ptr::{null_mut, write_bytes};
+use linux_raw_sys::net;
+
+// Export types used in io_uring APIs.
+pub use crate::event::epoll::{
+ Event as EpollEvent, EventData as EpollEventData, EventFlags as EpollEventFlags,
+};
+pub use crate::fs::{Advice, AtFlags, Mode, OFlags, RenameFlags, ResolveFlags, Statx, StatxFlags};
+pub use crate::io::ReadWriteFlags;
+pub use crate::net::{RecvFlags, SendFlags, SocketFlags};
+pub use crate::timespec::Timespec;
+pub use linux_raw_sys::general::sigset_t;
+
+pub use net::{__kernel_sockaddr_storage as sockaddr_storage, msghdr, sockaddr, socklen_t};
+
+// Declare the `c_char` type for use with entries that take pointers
+// to C strings. Define it as unsigned or signed according to the platform
+// so that we match what Rust's `CStr` uses.
+//
+// When we can update to linux-raw-sys 0.5, we can remove this, as its
+// `c_char` type will declare this.
+/// The C `char` type.
+#[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "msp430",
+ target_arch = "powerpc",
+ target_arch = "powerpc64",
+ target_arch = "riscv32",
+ target_arch = "riscv64",
+ target_arch = "s390x",
+))]
+#[allow(non_camel_case_types)]
+pub type c_char = u8;
+/// The C `char` type.
+#[cfg(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "sparc64",
+ target_arch = "x86",
+ target_arch = "x86_64",
+ target_arch = "xtensa",
+))]
+#[allow(non_camel_case_types)]
+pub type c_char = i8;
+
+mod sys {
+ pub(super) use linux_raw_sys::io_uring::*;
+ #[cfg(test)]
+ pub(super) use {crate::backend::c::iovec, linux_raw_sys::general::open_how};
+}
+
+/// `io_uring_setup(entries, params)`—Setup a context for performing
+/// asynchronous I/O.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man.archlinux.org/man/io_uring_setup.2.en
+#[inline]
+pub fn io_uring_setup(entries: u32, params: &mut io_uring_params) -> io::Result<OwnedFd> {
+ backend::io_uring::syscalls::io_uring_setup(entries, params)
+}
+
+/// `io_uring_register(fd, opcode, arg, nr_args)`—Register files or user
+/// buffers for asynchronous I/O.
+///
+/// # Safety
+///
+/// io_uring operates on raw pointers and raw file descriptors. Users are
+/// responsible for ensuring that memory and resources are only accessed in
+/// valid ways.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man.archlinux.org/man/io_uring_register.2.en
+#[inline]
+pub unsafe fn io_uring_register<Fd: AsFd>(
+ fd: Fd,
+ opcode: IoringRegisterOp,
+ arg: *const c_void,
+ nr_args: u32,
+) -> io::Result<u32> {
+ backend::io_uring::syscalls::io_uring_register(fd.as_fd(), opcode, arg, nr_args)
+}
+
+/// `io_uring_enter(fd, to_submit, min_complete, flags, arg, size)`—Initiate
+/// and/or complete asynchronous I/O.
+///
+/// # Safety
+///
+/// io_uring operates on raw pointers and raw file descriptors. Users are
+/// responsible for ensuring that memory and resources are only accessed in
+/// valid ways.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man.archlinux.org/man/io_uring_enter.2.en
+#[inline]
+pub unsafe fn io_uring_enter<Fd: AsFd>(
+ fd: Fd,
+ to_submit: u32,
+ min_complete: u32,
+ flags: IoringEnterFlags,
+ arg: *const c_void,
+ size: usize,
+) -> io::Result<u32> {
+ backend::io_uring::syscalls::io_uring_enter(
+ fd.as_fd(),
+ to_submit,
+ min_complete,
+ flags,
+ arg,
+ size,
+ )
+}
+
+bitflags::bitflags! {
+ /// `IORING_ENTER_*` flags for use with [`io_uring_enter`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringEnterFlags: u32 {
+ /// `IORING_ENTER_GETEVENTS`
+ const GETEVENTS = sys::IORING_ENTER_GETEVENTS;
+
+ /// `IORING_ENTER_SQ_WAKEUP`
+ const SQ_WAKEUP = sys::IORING_ENTER_SQ_WAKEUP;
+
+ /// `IORING_ENTER_SQ_WAIT`
+ const SQ_WAIT = sys::IORING_ENTER_SQ_WAIT;
+
+ /// `IORING_ENTER_EXT_ARG`
+ const EXT_ARG = sys::IORING_ENTER_EXT_ARG;
+
+ /// `IORING_ENTER_REGISTERED_RING`
+ const REGISTERED_RING = sys::IORING_ENTER_REGISTERED_RING;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `IORING_REGISTER_*` and `IORING_UNREGISTER_*` constants for use with
+/// [`io_uring_register`].
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u8)]
+#[non_exhaustive]
+pub enum IoringRegisterOp {
+ /// `IORING_REGISTER_BUFFERS`
+ RegisterBuffers = sys::IORING_REGISTER_BUFFERS as _,
+
+ /// `IORING_UNREGISTER_BUFFERS`
+ UnregisterBuffers = sys::IORING_UNREGISTER_BUFFERS as _,
+
+ /// `IORING_REGISTER_FILES`
+ RegisterFiles = sys::IORING_REGISTER_FILES as _,
+
+ /// `IORING_UNREGISTER_FILES`
+ UnregisterFiles = sys::IORING_UNREGISTER_FILES as _,
+
+ /// `IORING_REGISTER_EVENTFD`
+ RegisterEventfd = sys::IORING_REGISTER_EVENTFD as _,
+
+ /// `IORING_UNREGISTER_EVENTFD`
+ UnregisterEventfd = sys::IORING_UNREGISTER_EVENTFD as _,
+
+ /// `IORING_REGISTER_FILES_UPDATE`
+ RegisterFilesUpdate = sys::IORING_REGISTER_FILES_UPDATE as _,
+
+ /// `IORING_REGISTER_EVENTFD_ASYNC`
+ RegisterEventfdAsync = sys::IORING_REGISTER_EVENTFD_ASYNC as _,
+
+ /// `IORING_REGISTER_PROBE`
+ RegisterProbe = sys::IORING_REGISTER_PROBE as _,
+
+ /// `IORING_REGISTER_PERSONALITY`
+ RegisterPersonality = sys::IORING_REGISTER_PERSONALITY as _,
+
+ /// `IORING_UNREGISTER_PERSONALITY`
+ UnregisterPersonality = sys::IORING_UNREGISTER_PERSONALITY as _,
+
+ /// `IORING_REGISTER_RESTRICTIONS`
+ RegisterRestrictions = sys::IORING_REGISTER_RESTRICTIONS as _,
+
+ /// `IORING_REGISTER_ENABLE_RINGS`
+ RegisterEnableRings = sys::IORING_REGISTER_ENABLE_RINGS as _,
+
+ /// `IORING_REGISTER_BUFFERS2`
+ RegisterBuffers2 = sys::IORING_REGISTER_BUFFERS2 as _,
+
+ /// `IORING_REGISTER_BUFFERS_UPDATE`
+ RegisterBuffersUpdate = sys::IORING_REGISTER_BUFFERS_UPDATE as _,
+
+ /// `IORING_REGISTER_FILES2`
+ RegisterFiles2 = sys::IORING_REGISTER_FILES2 as _,
+
+ /// `IORING_REGISTER_FILES_SKIP`
+ RegisterFilesSkip = sys::IORING_REGISTER_FILES_SKIP as _,
+
+ /// `IORING_REGISTER_FILES_UPDATE2`
+ RegisterFilesUpdate2 = sys::IORING_REGISTER_FILES_UPDATE2 as _,
+
+ /// `IORING_REGISTER_IOWQ_AFF`
+ RegisterIowqAff = sys::IORING_REGISTER_IOWQ_AFF as _,
+
+ /// `IORING_UNREGISTER_IOWQ_AFF`
+ UnregisterIowqAff = sys::IORING_UNREGISTER_IOWQ_AFF as _,
+
+ /// `IORING_REGISTER_IOWQ_MAX_WORKERS`
+ RegisterIowqMaxWorkers = sys::IORING_REGISTER_IOWQ_MAX_WORKERS as _,
+
+ /// `IORING_REGISTER_RING_FDS`
+ RegisterRingFds = sys::IORING_REGISTER_RING_FDS as _,
+
+ /// `IORING_UNREGISTER_RING_FDS`
+ UnregisterRingFds = sys::IORING_UNREGISTER_RING_FDS as _,
+
+ /// `IORING_REGISTER_PBUF_RING`
+ RegisterPbufRing = sys::IORING_REGISTER_PBUF_RING as _,
+
+ /// `IORING_UNREGISTER_PBUF_RING`
+ UnregisterPbufRing = sys::IORING_UNREGISTER_PBUF_RING as _,
+
+ /// `IORING_REGISTER_SYNC_CANCEL`
+ RegisterSyncCancel = sys::IORING_REGISTER_SYNC_CANCEL as _,
+
+ /// `IORING_REGISTER_FILE_ALLOC_RANGE`
+ RegisterFileAllocRange = sys::IORING_REGISTER_FILE_ALLOC_RANGE as _,
+}
+
+/// `IORING_OP_*` constants for use with [`io_uring_sqe`].
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u8)]
+#[non_exhaustive]
+pub enum IoringOp {
+ /// `IORING_OP_NOP`
+ Nop = sys::io_uring_op::IORING_OP_NOP as _,
+
+ /// `IORING_OP_ACCEPT`
+ Accept = sys::io_uring_op::IORING_OP_ACCEPT as _,
+
+ /// `IORING_OP_ASYNC_CANCEL`
+ AsyncCancel = sys::io_uring_op::IORING_OP_ASYNC_CANCEL as _,
+
+ /// `IORING_OP_CLOSE`
+ Close = sys::io_uring_op::IORING_OP_CLOSE as _,
+
+ /// `IORING_OP_CONNECT`
+ Connect = sys::io_uring_op::IORING_OP_CONNECT as _,
+
+ /// `IORING_OP_EPOLL_CTL`
+ EpollCtl = sys::io_uring_op::IORING_OP_EPOLL_CTL as _,
+
+ /// `IORING_OP_FADVISE`
+ Fadvise = sys::io_uring_op::IORING_OP_FADVISE as _,
+
+ /// `IORING_OP_FALLOCATE`
+ Fallocate = sys::io_uring_op::IORING_OP_FALLOCATE as _,
+
+ /// `IORING_OP_FILES_UPDATE`
+ FilesUpdate = sys::io_uring_op::IORING_OP_FILES_UPDATE as _,
+
+ /// `IORING_OP_FSYNC`
+ Fsync = sys::io_uring_op::IORING_OP_FSYNC as _,
+
+ /// `IORING_OP_LINKAT`
+ Linkat = sys::io_uring_op::IORING_OP_LINKAT as _,
+
+ /// `IORING_OP_LINK_TIMEOUT`
+ LinkTimeout = sys::io_uring_op::IORING_OP_LINK_TIMEOUT as _,
+
+ /// `IORING_OP_MADVISE`
+ Madvise = sys::io_uring_op::IORING_OP_MADVISE as _,
+
+ /// `IORING_OP_MKDIRAT`
+ Mkdirat = sys::io_uring_op::IORING_OP_MKDIRAT as _,
+
+ /// `IORING_OP_OPENAT`
+ Openat = sys::io_uring_op::IORING_OP_OPENAT as _,
+
+ /// `IORING_OP_OPENAT2`
+ Openat2 = sys::io_uring_op::IORING_OP_OPENAT2 as _,
+
+ /// `IORING_OP_POLL_ADD`
+ PollAdd = sys::io_uring_op::IORING_OP_POLL_ADD as _,
+
+ /// `IORING_OP_POLL_REMOVE`
+ PollRemove = sys::io_uring_op::IORING_OP_POLL_REMOVE as _,
+
+ /// `IORING_OP_PROVIDE_BUFFERS`
+ ProvideBuffers = sys::io_uring_op::IORING_OP_PROVIDE_BUFFERS as _,
+
+ /// `IORING_OP_READ`
+ Read = sys::io_uring_op::IORING_OP_READ as _,
+
+ /// `IORING_OP_READV`
+ Readv = sys::io_uring_op::IORING_OP_READV as _,
+
+ /// `IORING_OP_READ_FIXED`
+ ReadFixed = sys::io_uring_op::IORING_OP_READ_FIXED as _,
+
+ /// `IORING_OP_RECV`
+ Recv = sys::io_uring_op::IORING_OP_RECV as _,
+
+ /// `IORING_OP_RECVMSG`
+ Recvmsg = sys::io_uring_op::IORING_OP_RECVMSG as _,
+
+ /// `IORING_OP_REMOVE_BUFFERS`
+ RemoveBuffers = sys::io_uring_op::IORING_OP_REMOVE_BUFFERS as _,
+
+ /// `IORING_OP_RENAMEAT`
+ Renameat = sys::io_uring_op::IORING_OP_RENAMEAT as _,
+
+ /// `IORING_OP_SEND`
+ Send = sys::io_uring_op::IORING_OP_SEND as _,
+
+ /// `IORING_OP_SENDMSG`
+ Sendmsg = sys::io_uring_op::IORING_OP_SENDMSG as _,
+
+ /// `IORING_OP_SHUTDOWN`
+ Shutdown = sys::io_uring_op::IORING_OP_SHUTDOWN as _,
+
+ /// `IORING_OP_SPLICE`
+ Splice = sys::io_uring_op::IORING_OP_SPLICE as _,
+
+ /// `IORING_OP_STATX`
+ Statx = sys::io_uring_op::IORING_OP_STATX as _,
+
+ /// `IORING_OP_SYMLINKAT`
+ Symlinkat = sys::io_uring_op::IORING_OP_SYMLINKAT as _,
+
+ /// `IORING_OP_SYNC_FILE_RANGE`
+ SyncFileRange = sys::io_uring_op::IORING_OP_SYNC_FILE_RANGE as _,
+
+ /// `IORING_OP_TEE`
+ Tee = sys::io_uring_op::IORING_OP_TEE as _,
+
+ /// `IORING_OP_TIMEOUT`
+ Timeout = sys::io_uring_op::IORING_OP_TIMEOUT as _,
+
+ /// `IORING_OP_TIMEOUT_REMOVE`
+ TimeoutRemove = sys::io_uring_op::IORING_OP_TIMEOUT_REMOVE as _,
+
+ /// `IORING_OP_UNLINKAT`
+ Unlinkat = sys::io_uring_op::IORING_OP_UNLINKAT as _,
+
+ /// `IORING_OP_WRITE`
+ Write = sys::io_uring_op::IORING_OP_WRITE as _,
+
+ /// `IORING_OP_WRITEV`
+ Writev = sys::io_uring_op::IORING_OP_WRITEV as _,
+
+ /// `IORING_OP_WRITE_FIXED`
+ WriteFixed = sys::io_uring_op::IORING_OP_WRITE_FIXED as _,
+
+ /// `IORING_OP_MSG_RING`
+ MsgRing = sys::io_uring_op::IORING_OP_MSG_RING as _,
+
+ /// `IORING_OP_FSETXATTR`
+ Fsetxattr = sys::io_uring_op::IORING_OP_FSETXATTR as _,
+
+ /// `IORING_OP_SETXATTR`
+ Setxattr = sys::io_uring_op::IORING_OP_SETXATTR as _,
+
+ /// `IORING_OP_FGETXATTR`
+ Fgetxattr = sys::io_uring_op::IORING_OP_FGETXATTR as _,
+
+ /// `IORING_OP_GETXATTR`
+ Getxattr = sys::io_uring_op::IORING_OP_GETXATTR as _,
+
+ /// `IORING_OP_SOCKET`
+ Socket = sys::io_uring_op::IORING_OP_SOCKET as _,
+
+ /// `IORING_OP_URING_CMD`
+ UringCmd = sys::io_uring_op::IORING_OP_URING_CMD as _,
+
+ /// `IORING_OP_SEND_ZC`
+ SendZc = sys::io_uring_op::IORING_OP_SEND_ZC as _,
+
+ /// `IORING_OP_SENDMSG_ZC`
+ SendmsgZc = sys::io_uring_op::IORING_OP_SENDMSG_ZC as _,
+}
+
+impl Default for IoringOp {
+ #[inline]
+ fn default() -> Self {
+ Self::Nop
+ }
+}
+
+/// `IORING_RESTRICTION_*` constants for use with [`io_uring_restriction`].
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u16)]
+#[non_exhaustive]
+pub enum IoringRestrictionOp {
+ /// `IORING_RESTRICTION_REGISTER_OP`
+ RegisterOp = sys::IORING_RESTRICTION_REGISTER_OP as _,
+
+ /// `IORING_RESTRICTION_SQE_FLAGS_ALLOWED`
+ SqeFlagsAllowed = sys::IORING_RESTRICTION_SQE_FLAGS_ALLOWED as _,
+
+ /// `IORING_RESTRICTION_SQE_FLAGS_REQUIRED`
+ SqeFlagsRequired = sys::IORING_RESTRICTION_SQE_FLAGS_REQUIRED as _,
+
+ /// `IORING_RESTRICTION_SQE_OP`
+ SqeOp = sys::IORING_RESTRICTION_SQE_OP as _,
+}
+
+impl Default for IoringRestrictionOp {
+ #[inline]
+ fn default() -> Self {
+ Self::RegisterOp
+ }
+}
+
+/// `IORING_MSG_*` constants which represent commands for use with
+/// [`IoringOp::MsgRing`], (`seq.addr`)
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u64)]
+#[non_exhaustive]
+pub enum IoringMsgringCmds {
+ /// `IORING_MSG_DATA`
+ Data = sys::IORING_MSG_DATA as _,
+
+ /// `IORING_MSG_SEND_FD`
+ SendFd = sys::IORING_MSG_SEND_FD as _,
+}
+
+bitflags::bitflags! {
+ /// `IORING_SETUP_*` flags for use with [`io_uring_params`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringSetupFlags: u32 {
+ /// `IORING_SETUP_ATTACH_WQ`
+ const ATTACH_WQ = sys::IORING_SETUP_ATTACH_WQ;
+
+ /// `IORING_SETUP_CLAMP`
+ const CLAMP = sys::IORING_SETUP_CLAMP;
+
+ /// `IORING_SETUP_CQSIZE`
+ const CQSIZE = sys::IORING_SETUP_CQSIZE;
+
+ /// `IORING_SETUP_IOPOLL`
+ const IOPOLL = sys::IORING_SETUP_IOPOLL;
+
+ /// `IORING_SETUP_R_DISABLED`
+ const R_DISABLED = sys::IORING_SETUP_R_DISABLED;
+
+ /// `IORING_SETUP_SQPOLL`
+ const SQPOLL = sys::IORING_SETUP_SQPOLL;
+
+ /// `IORING_SETUP_SQ_AFF`
+ const SQ_AFF = sys::IORING_SETUP_SQ_AFF;
+
+ /// `IORING_SETUP_SQE128`
+ const SQE128 = sys::IORING_SETUP_SQE128;
+
+ /// `IORING_SETUP_CQE32`
+ const CQE32 = sys::IORING_SETUP_CQE32;
+
+ /// `IORING_SETUP_SUBMIT_ALL`
+ const SUBMIT_ALL = sys::IORING_SETUP_SUBMIT_ALL;
+
+ /// `IORING_SETUP_COOP_TRASKRUN`
+ const COOP_TASKRUN = sys::IORING_SETUP_COOP_TASKRUN;
+
+ /// `IORING_SETUP_TASKRUN_FLAG`
+ const TASKRUN_FLAG = sys::IORING_SETUP_TASKRUN_FLAG;
+
+ /// `IORING_SETUP_SINGLE_ISSUER`
+ const SINGLE_ISSUER = sys::IORING_SETUP_SINGLE_ISSUER;
+
+ /// `IORING_SETUP_DEFER_TASKRUN`
+ const DEFER_TASKRUN = sys::IORING_SETUP_DEFER_TASKRUN;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IOSQE_*` flags for use with [`io_uring_sqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringSqeFlags: u8 {
+ /// `1 << IOSQE_ASYNC_BIT`
+ const ASYNC = 1 << sys::IOSQE_ASYNC_BIT as u8;
+
+ /// `1 << IOSQE_BUFFER_SELECT_BIT`
+ const BUFFER_SELECT = 1 << sys::IOSQE_BUFFER_SELECT_BIT as u8;
+
+ /// `1 << IOSQE_FIXED_FILE_BIT`
+ const FIXED_FILE = 1 << sys::IOSQE_FIXED_FILE_BIT as u8;
+
+ /// 1 << `IOSQE_IO_DRAIN_BIT`
+ const IO_DRAIN = 1 << sys::IOSQE_IO_DRAIN_BIT as u8;
+
+ /// `1 << IOSQE_IO_HARDLINK_BIT`
+ const IO_HARDLINK = 1 << sys::IOSQE_IO_HARDLINK_BIT as u8;
+
+ /// `1 << IOSQE_IO_LINK_BIT`
+ const IO_LINK = 1 << sys::IOSQE_IO_LINK_BIT as u8;
+
+ /// `1 << IOSQE_CQE_SKIP_SUCCESS_BIT`
+ const CQE_SKIP_SUCCESS = 1 << sys::IOSQE_CQE_SKIP_SUCCESS_BIT as u8;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_CQE_F_*` flags for use with [`io_uring_cqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringCqeFlags: u32 {
+ /// `IORING_CQE_F_BUFFER`
+ const BUFFER = bitcast!(sys::IORING_CQE_F_BUFFER);
+
+ /// `IORING_CQE_F_MORE`
+ const MORE = bitcast!(sys::IORING_CQE_F_MORE);
+
+ /// `IORING_CQE_F_SOCK_NONEMPTY`
+ const SOCK_NONEMPTY = bitcast!(sys::IORING_CQE_F_SOCK_NONEMPTY);
+
+ /// `IORING_CQE_F_NOTIF`
+ const NOTIF = bitcast!(sys::IORING_CQE_F_NOTIF);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_FSYNC_*` flags for use with [`io_uring_sqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringFsyncFlags: u32 {
+ /// `IORING_FSYNC_DATASYNC`
+ const DATASYNC = sys::IORING_FSYNC_DATASYNC;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_TIMEOUT_*` and `IORING_LINK_TIMEOUT_UPDATE` flags for use with
+ /// [`io_uring_sqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringTimeoutFlags: u32 {
+ /// `IORING_TIMEOUT_ABS`
+ const ABS = sys::IORING_TIMEOUT_ABS;
+
+ /// `IORING_TIMEOUT_UPDATE`
+ const UPDATE = sys::IORING_TIMEOUT_UPDATE;
+
+ /// `IORING_TIMEOUT_BOOTTIME`
+ const BOOTTIME = sys::IORING_TIMEOUT_BOOTTIME;
+
+ /// `IORING_TIMEOUT_ETIME_SUCCESS`
+ const ETIME_SUCCESS = sys::IORING_TIMEOUT_ETIME_SUCCESS;
+
+ /// `IORING_TIMEOUT_REALTIME`
+ const REALTIME = sys::IORING_TIMEOUT_REALTIME;
+
+ /// `IORING_TIMEOUT_CLOCK_MASK`
+ const CLOCK_MASK = sys::IORING_TIMEOUT_CLOCK_MASK;
+
+ /// `IORING_TIMEOUT_UPDATE_MASK`
+ const UPDATE_MASK = sys::IORING_TIMEOUT_UPDATE_MASK;
+
+ /// `IORING_LINK_TIMEOUT_UPDATE`
+ const LINK_TIMEOUT_UPDATE = sys::IORING_LINK_TIMEOUT_UPDATE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `SPLICE_F_*` flags for use with [`io_uring_sqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SpliceFlags: u32 {
+ /// `SPLICE_F_FD_IN_FIXED`
+ const FD_IN_FIXED = sys::SPLICE_F_FD_IN_FIXED;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_MSG_RING_*` flags for use with [`io_uring_sqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringMsgringFlags: u32 {
+ /// `IORING_MSG_RING_CQE_SKIP`
+ const CQE_SKIP = sys::IORING_MSG_RING_CQE_SKIP;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_ASYNC_CANCEL_*` flags for use with [`io_uring_sqe`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringAsyncCancelFlags: u32 {
+ /// `IORING_ASYNC_CANCEL_ALL`
+ const ALL = sys::IORING_ASYNC_CANCEL_ALL;
+
+ /// `IORING_ASYNC_CANCEL_FD`
+ const FD = sys::IORING_ASYNC_CANCEL_FD;
+
+ /// `IORING_ASYNC_CANCEL_FD`
+ const ANY = sys::IORING_ASYNC_CANCEL_ANY;
+
+ /// `IORING_ASYNC_CANCEL_FD`
+ const FD_FIXED = sys::IORING_ASYNC_CANCEL_FD_FIXED;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_FEAT_*` flags for use with [`io_uring_params`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringFeatureFlags: u32 {
+ /// `IORING_FEAT_CQE_SKIP`
+ const CQE_SKIP = sys::IORING_FEAT_CQE_SKIP;
+
+ /// `IORING_FEAT_CUR_PERSONALITY`
+ const CUR_PERSONALITY = sys::IORING_FEAT_CUR_PERSONALITY;
+
+ /// `IORING_FEAT_EXT_ARG`
+ const EXT_ARG = sys::IORING_FEAT_EXT_ARG;
+
+ /// `IORING_FEAT_FAST_POLL`
+ const FAST_POLL = sys::IORING_FEAT_FAST_POLL;
+
+ /// `IORING_FEAT_NATIVE_WORKERS`
+ const NATIVE_WORKERS = sys::IORING_FEAT_NATIVE_WORKERS;
+
+ /// `IORING_FEAT_NODROP`
+ const NODROP = sys::IORING_FEAT_NODROP;
+
+ /// `IORING_FEAT_POLL_32BITS`
+ const POLL_32BITS = sys::IORING_FEAT_POLL_32BITS;
+
+ /// `IORING_FEAT_RSRC_TAGS`
+ const RSRC_TAGS = sys::IORING_FEAT_RSRC_TAGS;
+
+ /// `IORING_FEAT_RW_CUR_POS`
+ const RW_CUR_POS = sys::IORING_FEAT_RW_CUR_POS;
+
+ /// `IORING_FEAT_SINGLE_MMAP`
+ const SINGLE_MMAP = sys::IORING_FEAT_SINGLE_MMAP;
+
+ /// `IORING_FEAT_SQPOLL_NONFIXED`
+ const SQPOLL_NONFIXED = sys::IORING_FEAT_SQPOLL_NONFIXED;
+
+ /// `IORING_FEAT_SUBMIT_STABLE`
+ const SUBMIT_STABLE = sys::IORING_FEAT_SUBMIT_STABLE;
+
+ /// `IORING_FEAT_LINKED_FILE`
+ const LINKED_FILE = sys::IORING_FEAT_LINKED_FILE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IO_URING_OP_*` flags for use with [`io_uring_probe_op`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringOpFlags: u16 {
+ /// `IO_URING_OP_SUPPORTED`
+ const SUPPORTED = sys::IO_URING_OP_SUPPORTED as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_RSRC_*` flags for use with [`io_uring_rsrc_register`].
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringRsrcFlags: u32 {
+ /// `IORING_RSRC_REGISTER_SPARSE`
+ const REGISTER_SPARSE = sys::IORING_RSRC_REGISTER_SPARSE as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_SQ_*` flags.
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringSqFlags: u32 {
+ /// `IORING_SQ_NEED_WAKEUP`
+ const NEED_WAKEUP = sys::IORING_SQ_NEED_WAKEUP;
+
+ /// `IORING_SQ_CQ_OVERFLOW`
+ const CQ_OVERFLOW = sys::IORING_SQ_CQ_OVERFLOW;
+
+ /// `IORING_SQ_TASKRUN`
+ const TASKRUN = sys::IORING_SQ_TASKRUN;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_CQ_*` flags.
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringCqFlags: u32 {
+ /// `IORING_CQ_EVENTFD_DISABLED`
+ const EVENTFD_DISABLED = sys::IORING_CQ_EVENTFD_DISABLED;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// `IORING_POLL_*` flags.
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringPollFlags: u32 {
+ /// `IORING_POLL_ADD_MULTI`
+ const ADD_MULTI = sys::IORING_POLL_ADD_MULTI;
+
+ /// `IORING_POLL_UPDATE_EVENTS`
+ const UPDATE_EVENTS = sys::IORING_POLL_UPDATE_EVENTS;
+
+ /// `IORING_POLL_UPDATE_USER_DATA`
+ const UPDATE_USER_DATA = sys::IORING_POLL_UPDATE_USER_DATA;
+
+ /// `IORING_POLL_ADD_LEVEL`
+ const ADD_LEVEL = sys::IORING_POLL_ADD_LEVEL;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// send/sendmsg flags (`sqe.ioprio`)
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringSendFlags: u16 {
+ /// `IORING_RECVSEND_POLL_FIRST`.
+ ///
+ /// See also [`IoringRecvFlags::POLL_FIRST`].
+ const POLL_FIRST = sys::IORING_RECVSEND_POLL_FIRST as _;
+
+ /// `IORING_RECVSEND_FIXED_BUF`
+ ///
+ /// See also [`IoringRecvFlags::FIXED_BUF`].
+ const FIXED_BUF = sys::IORING_RECVSEND_FIXED_BUF as _;
+
+ /// `IORING_SEND_ZC_REPORT_USAGE` (since Linux 6.2)
+ const ZC_REPORT_USAGE = sys::IORING_SEND_ZC_REPORT_USAGE as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// recv/recvmsg flags (`sqe.ioprio`)
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringRecvFlags: u16 {
+ /// `IORING_RECVSEND_POLL_FIRST`
+ ///
+ /// See also [`IoringSendFlags::POLL_FIRST`].
+ const POLL_FIRST = sys::IORING_RECVSEND_POLL_FIRST as _;
+
+ /// `IORING_RECV_MULTISHOT`
+ const MULTISHOT = sys::IORING_RECV_MULTISHOT as _;
+
+ /// `IORING_RECVSEND_FIXED_BUF`
+ ///
+ /// See also [`IoringSendFlags::FIXED_BUF`].
+ const FIXED_BUF = sys::IORING_RECVSEND_FIXED_BUF as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// accept flags (`sqe.ioprio`)
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct IoringAcceptFlags: u16 {
+ /// `IORING_ACCEPT_MULTISHOT`
+ const MULTISHOT = sys::IORING_ACCEPT_MULTISHOT as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags::bitflags! {
+ /// recvmsg out flags
+ #[repr(transparent)]
+ #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct RecvmsgOutFlags: u32 {
+ /// `MSG_EOR`
+ const EOR = net::MSG_EOR;
+
+ /// `MSG_TRUNC`
+ const TRUNC = net::MSG_TRUNC;
+
+ /// `MSG_CTRUNC`
+ const CTRUNC = net::MSG_CTRUNC;
+
+ /// `MSG_OOB`
+ const OOB = net::MSG_OOB;
+
+ /// `MSG_ERRQUEUE`
+ const ERRQUEUE = net::MSG_ERRQUEUE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[allow(missing_docs)]
+pub const IORING_CQE_BUFFER_SHIFT: u32 = sys::IORING_CQE_BUFFER_SHIFT as _;
+#[allow(missing_docs)]
+pub const IORING_FILE_INDEX_ALLOC: i32 = sys::IORING_FILE_INDEX_ALLOC as _;
+
+// Re-export these as `u64`, which is the `offset` type in `rustix::io::mmap`.
+#[allow(missing_docs)]
+pub const IORING_OFF_SQ_RING: u64 = sys::IORING_OFF_SQ_RING as _;
+#[allow(missing_docs)]
+pub const IORING_OFF_CQ_RING: u64 = sys::IORING_OFF_CQ_RING as _;
+#[allow(missing_docs)]
+pub const IORING_OFF_SQES: u64 = sys::IORING_OFF_SQES as _;
+
+/// `IORING_REGISTER_FILES_SKIP`
+#[inline]
+#[doc(alias = "IORING_REGISTER_FILES_SKIP")]
+pub const fn io_uring_register_files_skip() -> BorrowedFd<'static> {
+ let files_skip = sys::IORING_REGISTER_FILES_SKIP as RawFd;
+
+ // SAFETY: `IORING_REGISTER_FILES_SKIP` is a reserved value that is never
+ // dynamically allocated, so it'll remain valid for the duration of
+ // `'static`.
+ unsafe { BorrowedFd::<'static>::borrow_raw(files_skip) }
+}
+
+/// `IORING_NOTIF_USAGE_ZC_COPIED` (since Linux 6.2)
+pub const IORING_NOTIF_USAGE_ZC_COPIED: i32 = sys::IORING_NOTIF_USAGE_ZC_COPIED as _;
+
+/// A pointer in the io_uring API.
+///
+/// `io_uring`'s native API represents pointers as `u64` values. In order to
+/// preserve strict-provenance, use a `*mut c_void`. On platforms where
+/// pointers are narrower than 64 bits, this requires additional padding.
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct io_uring_ptr {
+ #[cfg(all(target_pointer_width = "32", target_endian = "big"))]
+ #[doc(hidden)]
+ pub __pad32: u32,
+ #[cfg(all(target_pointer_width = "16", target_endian = "big"))]
+ #[doc(hidden)]
+ pub __pad16: u16,
+
+ /// The pointer value.
+ pub ptr: *mut c_void,
+
+ #[cfg(all(target_pointer_width = "16", target_endian = "little"))]
+ #[doc(hidden)]
+ pub __pad16: u16,
+ #[cfg(all(target_pointer_width = "32", target_endian = "little"))]
+ #[doc(hidden)]
+ pub __pad32: u32,
+}
+
+impl From<*mut c_void> for io_uring_ptr {
+ #[inline]
+ fn from(ptr: *mut c_void) -> Self {
+ Self {
+ ptr,
+
+ #[cfg(target_pointer_width = "16")]
+ __pad16: Default::default(),
+ #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
+ __pad32: Default::default(),
+ }
+ }
+}
+
+impl Default for io_uring_ptr {
+ #[inline]
+ fn default() -> Self {
+ Self::from(null_mut())
+ }
+}
+
+/// User data in the io_uring API.
+///
+/// `io_uring`'s native API represents `user_data` fields as `u64` values. In
+/// order to preserve strict-provenance, use a union which allows users to
+/// optionally store pointers.
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_user_data {
+ /// An arbitrary `u64`.
+ pub u64_: u64,
+
+ /// A pointer.
+ pub ptr: io_uring_ptr,
+}
+
+impl io_uring_user_data {
+ /// Return the `u64` value.
+ #[inline]
+ pub fn u64_(self) -> u64 {
+ // SAFETY: All the fields have the same underlying representation.
+ unsafe { self.u64_ }
+ }
+
+ /// Create a `Self` from a `u64` value.
+ #[inline]
+ pub fn from_u64(u64_: u64) -> Self {
+ Self { u64_ }
+ }
+
+ /// Return the `ptr` pointer value.
+ #[inline]
+ pub fn ptr(self) -> *mut c_void {
+ // SAFETY: All the fields have the same underlying representation.
+ unsafe { self.ptr }.ptr
+ }
+
+ /// Create a `Self` from a pointer value.
+ #[inline]
+ pub fn from_ptr(ptr: *mut c_void) -> Self {
+ Self {
+ ptr: io_uring_ptr::from(ptr),
+ }
+ }
+}
+
+impl Default for io_uring_user_data {
+ #[inline]
+ fn default() -> Self {
+ let mut s = MaybeUninit::<Self>::uninit();
+ // SAFETY: All of Linux's io_uring structs may be zero-initialized.
+ unsafe {
+ write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+
+impl core::fmt::Debug for io_uring_user_data {
+ fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ // SAFETY: Just format as a `u64`, since formatting doesn't preserve
+ // provenance, and we don't have a discriminant.
+ unsafe { self.u64_.fmt(fmt) }
+ }
+}
+
+/// An io_uring Submission Queue Entry.
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct io_uring_sqe {
+ pub opcode: IoringOp,
+ pub flags: IoringSqeFlags,
+ pub ioprio: ioprio_union,
+ pub fd: RawFd,
+ pub off_or_addr2: off_or_addr2_union,
+ pub addr_or_splice_off_in: addr_or_splice_off_in_union,
+ pub len: len_union,
+ pub op_flags: op_flags_union,
+ pub user_data: io_uring_user_data,
+ pub buf: buf_union,
+ pub personality: u16,
+ pub splice_fd_in_or_file_index: splice_fd_in_or_file_index_union,
+ pub addr3_or_cmd: addr3_or_cmd_union,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union ioprio_union {
+ pub recv_flags: IoringRecvFlags,
+ pub send_flags: IoringSendFlags,
+ pub accept_flags: IoringAcceptFlags,
+ pub ioprio: u16,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union len_union {
+ pub poll_flags: IoringPollFlags,
+ pub len: u32,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union addr3_or_cmd_union {
+ pub addr3: addr3_struct,
+ pub cmd: [u8; 0],
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct addr3_struct {
+ pub addr3: u64,
+ pub __pad2: [u64; 1],
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union off_or_addr2_union {
+ pub off: u64,
+ pub addr2: io_uring_ptr,
+ pub cmd_op: cmd_op_struct,
+ pub user_data: io_uring_user_data,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct cmd_op_struct {
+ pub cmd_op: u32,
+ pub __pad1: u32,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union addr_or_splice_off_in_union {
+ pub addr: io_uring_ptr,
+ pub splice_off_in: u64,
+ pub msgring_cmd: IoringMsgringCmds,
+ pub user_data: io_uring_user_data,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union op_flags_union {
+ pub rw_flags: crate::io::ReadWriteFlags,
+ pub fsync_flags: IoringFsyncFlags,
+ pub poll_events: u16,
+ pub poll32_events: u32,
+ pub sync_range_flags: u32,
+ /// `msg_flags` is split into `send_flags` and `recv_flags`.
+ #[doc(alias = "msg_flags")]
+ pub send_flags: SendFlags,
+ /// `msg_flags` is split into `send_flags` and `recv_flags`.
+ #[doc(alias = "msg_flags")]
+ pub recv_flags: RecvFlags,
+ pub timeout_flags: IoringTimeoutFlags,
+ pub accept_flags: SocketFlags,
+ pub cancel_flags: IoringAsyncCancelFlags,
+ pub open_flags: OFlags,
+ pub statx_flags: AtFlags,
+ pub fadvise_advice: Advice,
+ pub splice_flags: SpliceFlags,
+ pub rename_flags: RenameFlags,
+ pub unlink_flags: AtFlags,
+ pub hardlink_flags: AtFlags,
+ pub msg_ring_flags: IoringMsgringFlags,
+}
+
+#[allow(missing_docs)]
+#[repr(C, packed)]
+#[derive(Copy, Clone)]
+pub union buf_union {
+ pub buf_index: u16,
+ pub buf_group: u16,
+}
+
+// TODO: Rename this to include `addr_len` when we have a semver bump?
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union splice_fd_in_or_file_index_union {
+ pub splice_fd_in: i32,
+ pub file_index: u32,
+ pub addr_len: addr_len_struct,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct addr_len_struct {
+ pub addr_len: u16,
+ pub __pad3: [u16; 1],
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct io_uring_sync_cancel_reg {
+ pub addr: u64,
+ pub fd: i32,
+ pub flags: IoringAsyncCancelFlags,
+ pub timeout: Timespec,
+ pub pad: [u64; 4],
+}
+
+/// An io_uring Completion Queue Entry.
+///
+/// This does not derive `Copy` or `Clone` because the `big_cqe` field is not
+/// automatically copyable.
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct io_uring_cqe {
+ pub user_data: io_uring_user_data,
+ pub res: i32,
+ pub flags: IoringCqeFlags,
+ pub big_cqe: sys::__IncompleteArrayField<u64>,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct io_uring_restriction {
+ pub opcode: IoringRestrictionOp,
+ pub register_or_sqe_op_or_sqe_flags: register_or_sqe_op_or_sqe_flags_union,
+ pub resv: u8,
+ pub resv2: [u32; 3],
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union register_or_sqe_op_or_sqe_flags_union {
+ pub register_op: IoringRegisterOp,
+ pub sqe_op: IoringOp,
+ pub sqe_flags: IoringSqeFlags,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_params {
+ pub sq_entries: u32,
+ pub cq_entries: u32,
+ pub flags: IoringSetupFlags,
+ pub sq_thread_cpu: u32,
+ pub sq_thread_idle: u32,
+ pub features: IoringFeatureFlags,
+ pub wq_fd: u32,
+ pub resv: [u32; 3],
+ pub sq_off: io_sqring_offsets,
+ pub cq_off: io_cqring_offsets,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_sqring_offsets {
+ pub head: u32,
+ pub tail: u32,
+ pub ring_mask: u32,
+ pub ring_entries: u32,
+ pub flags: u32,
+ pub dropped: u32,
+ pub array: u32,
+ pub resv1: u32,
+ pub resv2: u64,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_cqring_offsets {
+ pub head: u32,
+ pub tail: u32,
+ pub ring_mask: u32,
+ pub ring_entries: u32,
+ pub overflow: u32,
+ pub cqes: u32,
+ pub flags: u32,
+ pub resv1: u32,
+ pub resv2: u64,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct io_uring_probe {
+ pub last_op: IoringOp,
+ pub ops_len: u8,
+ pub resv: u16,
+ pub resv2: [u32; 3],
+ pub ops: sys::__IncompleteArrayField<io_uring_probe_op>,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_probe_op {
+ pub op: IoringOp,
+ pub resv: u8,
+ pub flags: IoringOpFlags,
+ pub resv2: u32,
+}
+
+#[allow(missing_docs)]
+#[repr(C, align(8))]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_files_update {
+ pub offset: u32,
+ pub resv: u32,
+ pub fds: u64,
+}
+
+#[allow(missing_docs)]
+#[repr(C, align(8))]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_rsrc_register {
+ pub nr: u32,
+ pub flags: IoringRsrcFlags,
+ pub resv2: u64,
+ pub data: u64,
+ pub tags: u64,
+}
+
+#[allow(missing_docs)]
+#[repr(C, align(8))]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_rsrc_update {
+ pub offset: u32,
+ pub resv: u32,
+ pub data: u64,
+}
+
+#[allow(missing_docs)]
+#[repr(C, align(8))]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_rsrc_update2 {
+ pub offset: u32,
+ pub resv: u32,
+ pub data: u64,
+ pub tags: u64,
+ pub nr: u32,
+ pub resv2: u32,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_getevents_arg {
+ pub sigmask: u64,
+ pub sigmask_sz: u32,
+ pub pad: u32,
+ pub ts: u64,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_uring_recvmsg_out {
+ pub namelen: u32,
+ pub controllen: u32,
+ pub payloadlen: u32,
+ pub flags: RecvmsgOutFlags,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct iovec {
+ pub iov_base: *mut c_void,
+ pub iov_len: usize,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct open_how {
+ /// An [`OFlags`] value represented as a `u64`.
+ pub flags: u64,
+
+ /// A [`Mode`] value represented as a `u64`.
+ pub mode: u64,
+
+ pub resolve: ResolveFlags,
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_buf_reg {
+ pub ring_addr: u64,
+ pub ring_entries: u32,
+ pub bgid: u16,
+ pub pad: u16,
+ pub resv: [u64; 3_usize],
+}
+
+#[allow(missing_docs)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Default)]
+pub struct io_uring_buf {
+ pub addr: u64,
+ pub len: u32,
+ pub bid: u16,
+ pub resv: u16,
+}
+
+impl Default for ioprio_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(ioprio_union, ioprio)
+ }
+}
+
+impl Default for len_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(len_union, len)
+ }
+}
+
+impl Default for off_or_addr2_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(off_or_addr2_union, off)
+ }
+}
+
+impl Default for addr_or_splice_off_in_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(addr_or_splice_off_in_union, splice_off_in)
+ }
+}
+
+impl Default for addr3_or_cmd_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(addr3_or_cmd_union, addr3)
+ }
+}
+
+impl Default for op_flags_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(op_flags_union, sync_range_flags)
+ }
+}
+
+impl Default for buf_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(buf_union, buf_index)
+ }
+}
+
+impl Default for splice_fd_in_or_file_index_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(splice_fd_in_or_file_index_union, splice_fd_in)
+ }
+}
+
+impl Default for register_or_sqe_op_or_sqe_flags_union {
+ #[inline]
+ fn default() -> Self {
+ default_union!(register_or_sqe_op_or_sqe_flags_union, sqe_flags)
+ }
+}
+
+/// Check that our custom structs and unions have the same layout as the
+/// kernel's versions.
+#[test]
+fn io_uring_layouts() {
+ use sys as c;
+
+ assert_eq_size!(io_uring_ptr, u64);
+
+ check_renamed_type!(off_or_addr2_union, io_uring_sqe__bindgen_ty_1);
+ check_renamed_type!(addr_or_splice_off_in_union, io_uring_sqe__bindgen_ty_2);
+ check_renamed_type!(addr3_or_cmd_union, io_uring_sqe__bindgen_ty_6);
+ check_renamed_type!(op_flags_union, io_uring_sqe__bindgen_ty_3);
+ check_renamed_type!(buf_union, io_uring_sqe__bindgen_ty_4);
+ check_renamed_type!(splice_fd_in_or_file_index_union, io_uring_sqe__bindgen_ty_5);
+ check_renamed_type!(addr_len_struct, io_uring_sqe__bindgen_ty_5__bindgen_ty_1);
+ check_renamed_type!(
+ register_or_sqe_op_or_sqe_flags_union,
+ io_uring_restriction__bindgen_ty_1
+ );
+
+ check_renamed_type!(addr3_struct, io_uring_sqe__bindgen_ty_6__bindgen_ty_1);
+ check_renamed_type!(cmd_op_struct, io_uring_sqe__bindgen_ty_1__bindgen_ty_1);
+
+ check_type!(io_uring_sqe);
+ check_struct_field!(io_uring_sqe, opcode);
+ check_struct_field!(io_uring_sqe, flags);
+ check_struct_field!(io_uring_sqe, ioprio);
+ check_struct_field!(io_uring_sqe, fd);
+ check_struct_renamed_field!(io_uring_sqe, off_or_addr2, __bindgen_anon_1);
+ check_struct_renamed_field!(io_uring_sqe, addr_or_splice_off_in, __bindgen_anon_2);
+ check_struct_field!(io_uring_sqe, len);
+ check_struct_renamed_field!(io_uring_sqe, op_flags, __bindgen_anon_3);
+ check_struct_field!(io_uring_sqe, user_data);
+ check_struct_renamed_field!(io_uring_sqe, buf, __bindgen_anon_4);
+ check_struct_field!(io_uring_sqe, personality);
+ check_struct_renamed_field!(io_uring_sqe, splice_fd_in_or_file_index, __bindgen_anon_5);
+ check_struct_renamed_field!(io_uring_sqe, addr3_or_cmd, __bindgen_anon_6);
+
+ check_type!(io_uring_restriction);
+ check_struct_field!(io_uring_restriction, opcode);
+ check_struct_renamed_field!(
+ io_uring_restriction,
+ register_or_sqe_op_or_sqe_flags,
+ __bindgen_anon_1
+ );
+ check_struct_field!(io_uring_restriction, resv);
+ check_struct_field!(io_uring_restriction, resv2);
+
+ check_struct!(io_uring_cqe, user_data, res, flags, big_cqe);
+ check_struct!(
+ io_uring_params,
+ sq_entries,
+ cq_entries,
+ flags,
+ sq_thread_cpu,
+ sq_thread_idle,
+ features,
+ wq_fd,
+ resv,
+ sq_off,
+ cq_off
+ );
+ check_struct!(
+ io_sqring_offsets,
+ head,
+ tail,
+ ring_mask,
+ ring_entries,
+ flags,
+ dropped,
+ array,
+ resv1,
+ resv2
+ );
+ check_struct!(
+ io_cqring_offsets,
+ head,
+ tail,
+ ring_mask,
+ ring_entries,
+ overflow,
+ cqes,
+ flags,
+ resv1,
+ resv2
+ );
+ check_struct!(io_uring_recvmsg_out, namelen, controllen, payloadlen, flags);
+ check_struct!(io_uring_probe, last_op, ops_len, resv, resv2, ops);
+ check_struct!(io_uring_probe_op, op, resv, flags, resv2);
+ check_struct!(io_uring_files_update, offset, resv, fds);
+ check_struct!(io_uring_rsrc_register, nr, flags, resv2, data, tags);
+ check_struct!(io_uring_rsrc_update, offset, resv, data);
+ check_struct!(io_uring_rsrc_update2, offset, resv, data, tags, nr, resv2);
+ check_struct!(io_uring_getevents_arg, sigmask, sigmask_sz, pad, ts);
+ check_struct!(iovec, iov_base, iov_len);
+ check_struct!(open_how, flags, mode, resolve);
+ check_struct!(io_uring_buf_reg, ring_addr, ring_entries, bgid, pad, resv);
+ check_struct!(io_uring_buf, addr, len, bid, resv);
+ check_struct!(io_uring_sync_cancel_reg, addr, fd, flags, timeout, pad);
+}
diff --git a/src/ioctl/bsd.rs b/src/ioctl/bsd.rs
new file mode 100644
index 0000000..2639d81
--- /dev/null
+++ b/src/ioctl/bsd.rs
@@ -0,0 +1,27 @@
+//! `ioctl` opcode behavior for BSD platforms.
+
+use super::{Direction, RawOpcode};
+
+pub(super) const fn compose_opcode(
+ dir: Direction,
+ group: RawOpcode,
+ num: RawOpcode,
+ size: RawOpcode,
+) -> RawOpcode {
+ let dir = match dir {
+ Direction::None => NONE,
+ Direction::Read => READ,
+ Direction::Write => WRITE,
+ Direction::ReadWrite => READ | WRITE,
+ };
+
+ dir | num | (group << 8) | ((size & IOCPARAM_MASK) << 16)
+}
+
+// `IOC_VOID`
+pub const NONE: RawOpcode = 0x2000_0000;
+// `IOC_OUT` ("out" is from the perspective of the kernel)
+pub const READ: RawOpcode = 0x4000_0000;
+// `IOC_IN`
+pub const WRITE: RawOpcode = 0x8000_0000;
+pub const IOCPARAM_MASK: RawOpcode = 0x1FFF;
diff --git a/src/ioctl/linux.rs b/src/ioctl/linux.rs
new file mode 100644
index 0000000..2f3599f
--- /dev/null
+++ b/src/ioctl/linux.rs
@@ -0,0 +1,118 @@
+//! `ioctl` opcode behavior for Linux platforms.
+
+use super::{Direction, RawOpcode};
+use consts::*;
+
+/// Compose an opcode from its component parts.
+pub(super) const fn compose_opcode(
+ dir: Direction,
+ group: RawOpcode,
+ num: RawOpcode,
+ size: RawOpcode,
+) -> RawOpcode {
+ macro_rules! mask_and_shift {
+ ($val:expr, $shift:expr, $mask:expr) => {{
+ ($val & $mask) << $shift
+ }};
+ }
+
+ let dir = match dir {
+ Direction::None => NONE,
+ Direction::Read => READ,
+ Direction::Write => WRITE,
+ Direction::ReadWrite => READ | WRITE,
+ };
+
+ mask_and_shift!(group, GROUP_SHIFT, GROUP_MASK)
+ | mask_and_shift!(num, NUM_SHIFT, NUM_MASK)
+ | mask_and_shift!(size, SIZE_SHIFT, SIZE_MASK)
+ | mask_and_shift!(dir, DIR_SHIFT, DIR_MASK)
+}
+
+const NUM_BITS: RawOpcode = 8;
+const GROUP_BITS: RawOpcode = 8;
+
+const NUM_SHIFT: RawOpcode = 0;
+const GROUP_SHIFT: RawOpcode = NUM_SHIFT + NUM_BITS;
+const SIZE_SHIFT: RawOpcode = GROUP_SHIFT + GROUP_BITS;
+const DIR_SHIFT: RawOpcode = SIZE_SHIFT + SIZE_BITS;
+
+const NUM_MASK: RawOpcode = (1 << NUM_BITS) - 1;
+const GROUP_MASK: RawOpcode = (1 << GROUP_BITS) - 1;
+const SIZE_MASK: RawOpcode = (1 << SIZE_BITS) - 1;
+const DIR_MASK: RawOpcode = (1 << DIR_BITS) - 1;
+
+#[cfg(any(
+ target_arch = "x86",
+ target_arch = "arm",
+ target_arch = "s390x",
+ target_arch = "x86_64",
+ target_arch = "aarch64",
+ target_arch = "riscv32",
+ target_arch = "riscv64",
+ target_arch = "loongarch64",
+ target_arch = "csky"
+))]
+mod consts {
+ use super::RawOpcode;
+
+ pub(super) const NONE: RawOpcode = 0;
+ pub(super) const READ: RawOpcode = 2;
+ pub(super) const WRITE: RawOpcode = 1;
+ pub(super) const SIZE_BITS: RawOpcode = 14;
+ pub(super) const DIR_BITS: RawOpcode = 2;
+}
+
+#[cfg(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "powerpc",
+ target_arch = "powerpc64",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+))]
+mod consts {
+ use super::RawOpcode;
+
+ pub(super) const NONE: RawOpcode = 1;
+ pub(super) const READ: RawOpcode = 2;
+ pub(super) const WRITE: RawOpcode = 4;
+ pub(super) const SIZE_BITS: RawOpcode = 13;
+ pub(super) const DIR_BITS: RawOpcode = 3;
+}
+
+#[cfg(not(any(
+ // These have no ioctl opcodes defined in linux_raw_sys
+ // so can't use that as a known-good value for this test.
+ target_arch = "sparc",
+ target_arch = "sparc64"
+)))]
+#[test]
+fn check_known_opcodes() {
+ use crate::backend::c::{c_long, c_uint};
+ use core::mem::size_of;
+
+ // _IOR('U', 15, unsigned int)
+ assert_eq!(
+ compose_opcode(
+ Direction::Read,
+ b'U' as RawOpcode,
+ 15,
+ size_of::<c_uint>() as RawOpcode
+ ),
+ linux_raw_sys::ioctl::USBDEVFS_CLAIMINTERFACE as RawOpcode
+ );
+
+ // _IOW('v', 2, long)
+ assert_eq!(
+ compose_opcode(
+ Direction::Write,
+ b'v' as RawOpcode,
+ 2,
+ size_of::<c_long>() as RawOpcode
+ ),
+ linux_raw_sys::ioctl::FS_IOC_SETVERSION as RawOpcode
+ );
+}
diff --git a/src/ioctl/mod.rs b/src/ioctl/mod.rs
new file mode 100644
index 0000000..494cdc8
--- /dev/null
+++ b/src/ioctl/mod.rs
@@ -0,0 +1,357 @@
+//! Unsafe `ioctl` API.
+//!
+//! Unix systems expose a number of `ioctl`'s. `ioctl`s have been adopted as a
+//! general purpose system call for making calls into the kernel. In addition
+//! to the wide variety of system calls that are included by default in the
+//! kernel, many drivers expose their own `ioctl`'s for controlling their
+//! behavior, some of which are proprietary. Therefore it is impossible to make
+//! a safe interface for every `ioctl` call, as they all have wildly varying
+//! semantics.
+//!
+//! This module provides an unsafe interface to write your own `ioctl` API. To
+//! start, create a type that implements [`Ioctl`]. Then, pass it to [`ioctl`]
+//! to make the `ioctl` call.
+
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use crate::fd::{AsFd, BorrowedFd};
+use crate::io::Result;
+
+#[cfg(any(linux_kernel, bsd))]
+use core::mem;
+
+pub use patterns::*;
+
+mod patterns;
+
+#[cfg(linux_kernel)]
+mod linux;
+
+#[cfg(bsd)]
+mod bsd;
+
+#[cfg(linux_kernel)]
+use linux as platform;
+
+#[cfg(bsd)]
+use bsd as platform;
+
+/// Perform an `ioctl` call.
+///
+/// `ioctl` was originally intended to act as a way of modifying the behavior
+/// of files, but has since been adopted as a general purpose system call for
+/// making calls into the kernel. In addition to the default calls exposed by
+/// generic file descriptors, many drivers expose their own `ioctl` calls for
+/// controlling their behavior, some of which are proprietary.
+///
+/// This crate exposes many other `ioctl` interfaces with safe and idiomatic
+/// wrappers, like [`ioctl_fionbio`] and [`ioctl_fionread`]. It is recommended
+/// to use those instead of this function, as they are safer and more
+/// idiomatic. For other cases, implement the [`Ioctl`] API and pass it to this
+/// function.
+///
+/// See documentation for [`Ioctl`] for more information.
+///
+/// [`ioctl_fionbio`]: crate::io::ioctl_fionbio
+/// [`ioctl_fionread`]: crate::io::ioctl_fionread
+///
+/// # Safety
+///
+/// While [`Ioctl`] takes much of the unsafety out of `ioctl` calls, it is
+/// still unsafe to call this code with arbitrary device drivers, as it is up
+/// to the device driver to implement the `ioctl` call correctly. It is on the
+/// onus of the protocol between the user and the driver to ensure that the
+/// `ioctl` call is safe to make.
+///
+/// # References
+/// - [Linux]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [Apple]
+/// - [Solaris]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/ioctl.2.html
+/// [Winsock]: https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ioctlsocket
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=ioctl&sektion=2
+/// [NetBSD]: https://man.netbsd.org/ioctl.2
+/// [OpenBSD]: https://man.openbsd.org/ioctl.2
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/ioctl.2.html
+/// [Solaris]: https://docs.oracle.com/cd/E23824_01/html/821-1463/ioctl-2.html
+/// [illumos]: https://illumos.org/man/2/ioctl
+#[inline]
+pub unsafe fn ioctl<F: AsFd, I: Ioctl>(fd: F, mut ioctl: I) -> Result<I::Output> {
+ let fd = fd.as_fd();
+ let request = I::OPCODE.raw();
+ let arg = ioctl.as_ptr();
+
+ // SAFETY: The variant of `Ioctl` asserts that this is a valid IOCTL call
+ // to make.
+ let output = if I::IS_MUTATING {
+ _ioctl(fd, request, arg)?
+ } else {
+ _ioctl_readonly(fd, request, arg)?
+ };
+
+ // SAFETY: The variant of `Ioctl` asserts that this is a valid pointer to
+ // the output data.
+ I::output_from_ptr(output, arg)
+}
+
+unsafe fn _ioctl(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> Result<IoctlOutput> {
+ crate::backend::io::syscalls::ioctl(fd, request, arg)
+}
+
+unsafe fn _ioctl_readonly(
+ fd: BorrowedFd<'_>,
+ request: RawOpcode,
+ arg: *mut c::c_void,
+) -> Result<IoctlOutput> {
+ crate::backend::io::syscalls::ioctl_readonly(fd, request, arg)
+}
+
+/// A trait defining the properties of an `ioctl` command.
+///
+/// Objects implementing this trait can be passed to [`ioctl`] to make an
+/// `ioctl` call. The contents of the object represent the inputs to the
+/// `ioctl` call. The inputs must be convertible to a pointer through the
+/// `as_ptr` method. In most cases, this involves either casting a number to a
+/// pointer, or creating a pointer to the actual data. The latter case is
+/// necessary for `ioctl` calls that modify userspace data.
+///
+/// # Safety
+///
+/// This trait is unsafe to implement because it is impossible to guarantee
+/// that the `ioctl` call is safe. The `ioctl` call may be proprietary, or it
+/// may be unsafe to call in certain circumstances.
+///
+/// By implementing this trait, you guarantee that:
+///
+/// - The `ioctl` call expects the input provided by `as_ptr` and produces the
+/// output as indicated by `output`.
+/// - That `output_from_ptr` can safely take the pointer from `as_ptr` and cast
+/// it to the correct type, *only* after the `ioctl` call.
+/// - That `OPCODE` uniquely identifies the `ioctl` call.
+/// - That, for whatever platforms you are targeting, the `ioctl` call is safe
+/// to make.
+/// - If `IS_MUTATING` is false, that no userspace data will be modified by the
+/// `ioctl` call.
+pub unsafe trait Ioctl {
+ /// The type of the output data.
+ ///
+ /// Given a pointer, one should be able to construct an instance of this
+ /// type.
+ type Output;
+
+ /// The opcode used by this `ioctl` command.
+ ///
+ /// There are different types of opcode depending on the operation. See
+ /// documentation for the [`Opcode`] struct for more information.
+ const OPCODE: Opcode;
+
+ /// Does the `ioctl` mutate any data in the userspace?
+ ///
+ /// If the `ioctl` call does not mutate any data in the userspace, then
+ /// making this `false` enables optimizations that can make the call
+ /// faster. When in doubt, set this to `true`.
+ ///
+ /// # Safety
+ ///
+ /// This should only be set to `false` if the `ioctl` call does not mutate
+ /// any data in the userspace. Undefined behavior may occur if this is set
+ /// to `false` when it should be `true`.
+ const IS_MUTATING: bool;
+
+ /// Get a pointer to the data to be passed to the `ioctl` command.
+ ///
+ /// See trait-level documentation for more information.
+ fn as_ptr(&mut self) -> *mut c::c_void;
+
+ /// Cast the output data to the correct type.
+ ///
+ /// # Safety
+ ///
+ /// The `extract_output` value must be the resulting value after a
+ /// successful `ioctl` call, and `out` is the direct return value of an
+ /// `ioctl` call that did not fail. In this case `extract_output` is the
+ /// pointer that was passed to the `ioctl` call.
+ unsafe fn output_from_ptr(
+ out: IoctlOutput,
+ extract_output: *mut c::c_void,
+ ) -> Result<Self::Output>;
+}
+
+/// The opcode used by an `Ioctl`.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Opcode {
+ /// The raw opcode.
+ raw: RawOpcode,
+}
+
+impl Opcode {
+ /// Create a new old `Opcode` from a raw opcode.
+ ///
+ /// Rather than being a composition of several attributes, old opcodes are
+ /// just numbers. In general most drivers follow stricter conventions, but
+ /// older drivers may still use this strategy.
+ #[inline]
+ pub const fn old(raw: RawOpcode) -> Self {
+ Self { raw }
+ }
+
+ /// Create a new opcode from a direction, group, number, and size.
+ ///
+ /// This corresponds to the C macro `_IOC(direction, group, number, size)`
+ #[cfg(any(linux_kernel, bsd))]
+ #[inline]
+ pub const fn from_components(
+ direction: Direction,
+ group: u8,
+ number: u8,
+ data_size: usize,
+ ) -> Self {
+ if data_size > RawOpcode::MAX as usize {
+ panic!("data size is too large");
+ }
+
+ Self::old(platform::compose_opcode(
+ direction,
+ group as RawOpcode,
+ number as RawOpcode,
+ data_size as RawOpcode,
+ ))
+ }
+
+ /// Create a new non-mutating opcode from a group, a number, and the type
+ /// of data.
+ ///
+ /// This corresponds to the C macro `_IO(group, number)` when `T` is zero
+ /// sized.
+ #[cfg(any(linux_kernel, bsd))]
+ #[inline]
+ pub const fn none<T>(group: u8, number: u8) -> Self {
+ Self::from_components(Direction::None, group, number, mem::size_of::<T>())
+ }
+
+ /// Create a new reading opcode from a group, a number and the type of
+ /// data.
+ ///
+ /// This corresponds to the C macro `_IOR(group, number, T)`.
+ #[cfg(any(linux_kernel, bsd))]
+ #[inline]
+ pub const fn read<T>(group: u8, number: u8) -> Self {
+ Self::from_components(Direction::Read, group, number, mem::size_of::<T>())
+ }
+
+ /// Create a new writing opcode from a group, a number and the type of
+ /// data.
+ ///
+ /// This corresponds to the C macro `_IOW(group, number, T)`.
+ #[cfg(any(linux_kernel, bsd))]
+ #[inline]
+ pub const fn write<T>(group: u8, number: u8) -> Self {
+ Self::from_components(Direction::Write, group, number, mem::size_of::<T>())
+ }
+
+ /// Create a new reading and writing opcode from a group, a number and the
+ /// type of data.
+ ///
+ /// This corresponds to the C macro `_IOWR(group, number, T)`.
+ #[cfg(any(linux_kernel, bsd))]
+ #[inline]
+ pub const fn read_write<T>(group: u8, number: u8) -> Self {
+ Self::from_components(Direction::ReadWrite, group, number, mem::size_of::<T>())
+ }
+
+ /// Get the raw opcode.
+ #[inline]
+ pub fn raw(self) -> RawOpcode {
+ self.raw
+ }
+}
+
+/// The direction that an `ioctl` is going.
+///
+/// Note that this is relative to userspace. `Read` means reading data from the
+/// kernel, and write means the kernel writing data to userspace.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Direction {
+ /// None of the above.
+ None,
+
+ /// Read data from the kernel.
+ Read,
+
+ /// Write data to the kernel.
+ Write,
+
+ /// Read and write data to the kernel.
+ ReadWrite,
+}
+
+/// The type used by the `ioctl` to signify the output.
+pub type IoctlOutput = c::c_int;
+
+/// The type used by the `ioctl` to signify the command.
+pub type RawOpcode = _RawOpcode;
+
+// Under raw Linux, this is an `unsigned int`.
+#[cfg(linux_raw)]
+type _RawOpcode = c::c_uint;
+
+// On libc Linux with GNU libc or uclibc, this is an `unsigned long`.
+#[cfg(all(
+ not(linux_raw),
+ target_os = "linux",
+ any(target_env = "gnu", target_env = "uclibc")
+))]
+type _RawOpcode = c::c_ulong;
+
+// Musl uses `c_int`.
+#[cfg(all(
+ not(linux_raw),
+ target_os = "linux",
+ not(target_env = "gnu"),
+ not(target_env = "uclibc")
+))]
+type _RawOpcode = c::c_int;
+
+// Android uses `c_int`.
+#[cfg(all(not(linux_raw), target_os = "android"))]
+type _RawOpcode = c::c_int;
+
+// BSD, Haiku, Hurd, Redox, and Vita use `unsigned long`.
+#[cfg(any(
+ bsd,
+ target_os = "redox",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "vita"
+))]
+type _RawOpcode = c::c_ulong;
+
+// AIX, Emscripten, Fuchsia, Solaris, and WASI use a `int`.
+#[cfg(any(
+ solarish,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "emscripten",
+ target_os = "wasi",
+ target_os = "nto"
+))]
+type _RawOpcode = c::c_int;
+
+// ESP-IDF uses a `c_uint`.
+#[cfg(target_os = "espidf")]
+type _RawOpcode = c::c_uint;
+
+// Windows has `ioctlsocket`, which uses `i32`.
+#[cfg(windows)]
+type _RawOpcode = i32;
diff --git a/src/ioctl/patterns.rs b/src/ioctl/patterns.rs
new file mode 100644
index 0000000..6cf7ebd
--- /dev/null
+++ b/src/ioctl/patterns.rs
@@ -0,0 +1,256 @@
+//! Implements typical patterns for `ioctl` usage.
+
+use super::{Ioctl, IoctlOutput, Opcode, RawOpcode};
+
+use crate::backend::c;
+use crate::io::Result;
+
+use core::marker::PhantomData;
+use core::ptr::addr_of_mut;
+use core::{fmt, mem};
+
+/// Implements an `ioctl` with no real arguments.
+pub struct NoArg<Opcode> {
+ /// The opcode.
+ _opcode: PhantomData<Opcode>,
+}
+
+impl<Opcode: CompileTimeOpcode> fmt::Debug for NoArg<Opcode> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("NoArg").field(&Opcode::OPCODE).finish()
+ }
+}
+
+impl<Opcode: CompileTimeOpcode> NoArg<Opcode> {
+ /// Create a new no-argument `ioctl` object.
+ ///
+ /// # Safety
+ ///
+ /// - `Opcode` must provide a valid opcode.
+ #[inline]
+ pub unsafe fn new() -> Self {
+ Self {
+ _opcode: PhantomData,
+ }
+ }
+}
+
+unsafe impl<Opcode: CompileTimeOpcode> Ioctl for NoArg<Opcode> {
+ type Output = ();
+
+ const IS_MUTATING: bool = false;
+ const OPCODE: self::Opcode = Opcode::OPCODE;
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ core::ptr::null_mut()
+ }
+
+ unsafe fn output_from_ptr(_: IoctlOutput, _: *mut c::c_void) -> Result<Self::Output> {
+ Ok(())
+ }
+}
+
+/// Implements the traditional “getter” pattern for `ioctl`s.
+///
+/// Some `ioctl`s just read data into the userspace. As this is a popular
+/// pattern this structure implements it.
+pub struct Getter<Opcode, Output> {
+ /// The output data.
+ output: mem::MaybeUninit<Output>,
+
+ /// The opcode.
+ _opcode: PhantomData<Opcode>,
+}
+
+impl<Opcode: CompileTimeOpcode, Output> fmt::Debug for Getter<Opcode, Output> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("Getter").field(&Opcode::OPCODE).finish()
+ }
+}
+
+impl<Opcode: CompileTimeOpcode, Output> Getter<Opcode, Output> {
+ /// Create a new getter-style `ioctl` object.
+ ///
+ /// # Safety
+ ///
+ /// - `Opcode` must provide a valid opcode.
+ /// - For this opcode, `Output` must be the type that the kernel expects to
+ /// write into.
+ #[inline]
+ pub unsafe fn new() -> Self {
+ Self {
+ output: mem::MaybeUninit::uninit(),
+ _opcode: PhantomData,
+ }
+ }
+}
+
+unsafe impl<Opcode: CompileTimeOpcode, Output> Ioctl for Getter<Opcode, Output> {
+ type Output = Output;
+
+ const IS_MUTATING: bool = true;
+ const OPCODE: self::Opcode = Opcode::OPCODE;
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ self.output.as_mut_ptr().cast()
+ }
+
+ unsafe fn output_from_ptr(_: IoctlOutput, ptr: *mut c::c_void) -> Result<Self::Output> {
+ Ok(ptr.cast::<Output>().read())
+ }
+}
+
+/// Implements the pattern for `ioctl`s where a pointer argument is given to
+/// the `ioctl`.
+///
+/// The opcode must be read-only.
+pub struct Setter<Opcode, Input> {
+ /// The input data.
+ input: Input,
+
+ /// The opcode.
+ _opcode: PhantomData<Opcode>,
+}
+
+impl<Opcode: CompileTimeOpcode, Input: fmt::Debug> fmt::Debug for Setter<Opcode, Input> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("Setter")
+ .field(&Opcode::OPCODE)
+ .field(&self.input)
+ .finish()
+ }
+}
+
+impl<Opcode: CompileTimeOpcode, Input> Setter<Opcode, Input> {
+ /// Create a new pointer setter-style `ioctl` object.
+ ///
+ /// # Safety
+ ///
+ /// - `Opcode` must provide a valid opcode.
+ /// - For this opcode, `Input` must be the type that the kernel expects to
+ /// get.
+ #[inline]
+ pub unsafe fn new(input: Input) -> Self {
+ Self {
+ input,
+ _opcode: PhantomData,
+ }
+ }
+}
+
+unsafe impl<Opcode: CompileTimeOpcode, Input> Ioctl for Setter<Opcode, Input> {
+ type Output = ();
+
+ const IS_MUTATING: bool = false;
+ const OPCODE: self::Opcode = Opcode::OPCODE;
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ addr_of_mut!(self.input).cast::<c::c_void>()
+ }
+
+ unsafe fn output_from_ptr(_: IoctlOutput, _: *mut c::c_void) -> Result<Self::Output> {
+ Ok(())
+ }
+}
+
+/// Implements an “updater” pattern for `ioctl`s.
+///
+/// The ioctl takes a reference to a struct that it reads its input from,
+/// then writes output to the same struct.
+pub struct Updater<'a, Opcode, Value> {
+ /// Reference to input/output data.
+ value: &'a mut Value,
+
+ /// The opcode.
+ _opcode: PhantomData<Opcode>,
+}
+
+impl<'a, Opcode: CompileTimeOpcode, Value> Updater<'a, Opcode, Value> {
+ /// Create a new pointer updater-style `ioctl` object.
+ ///
+ /// # Safety
+ ///
+ /// - `Opcode` must provide a valid opcode.
+ /// - For this opcode, `Value` must be the type that the kernel expects to
+ /// get.
+ #[inline]
+ pub unsafe fn new(value: &'a mut Value) -> Self {
+ Self {
+ value,
+ _opcode: PhantomData,
+ }
+ }
+}
+
+unsafe impl<'a, Opcode: CompileTimeOpcode, T> Ioctl for Updater<'a, Opcode, T> {
+ type Output = ();
+
+ const IS_MUTATING: bool = true;
+ const OPCODE: self::Opcode = Opcode::OPCODE;
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ (self.value as *mut T).cast()
+ }
+
+ unsafe fn output_from_ptr(_output: IoctlOutput, _ptr: *mut c::c_void) -> Result<()> {
+ Ok(())
+ }
+}
+
+/// Trait for something that provides an `ioctl` opcode at compile time.
+pub trait CompileTimeOpcode {
+ /// The opcode.
+ const OPCODE: Opcode;
+}
+
+/// Provides a bad opcode at compile time.
+pub struct BadOpcode<const OPCODE: RawOpcode>;
+
+impl<const OPCODE: RawOpcode> CompileTimeOpcode for BadOpcode<OPCODE> {
+ const OPCODE: Opcode = Opcode::old(OPCODE);
+}
+
+/// Provides a read code at compile time.
+///
+/// This corresponds to the C macro `_IOR(GROUP, NUM, Data)`.
+#[cfg(any(linux_kernel, bsd))]
+pub struct ReadOpcode<const GROUP: u8, const NUM: u8, Data>(Data);
+
+#[cfg(any(linux_kernel, bsd))]
+impl<const GROUP: u8, const NUM: u8, Data> CompileTimeOpcode for ReadOpcode<GROUP, NUM, Data> {
+ const OPCODE: Opcode = Opcode::read::<Data>(GROUP, NUM);
+}
+
+/// Provides a write code at compile time.
+///
+/// This corresponds to the C macro `_IOW(GROUP, NUM, Data)`.
+#[cfg(any(linux_kernel, bsd))]
+pub struct WriteOpcode<const GROUP: u8, const NUM: u8, Data>(Data);
+
+#[cfg(any(linux_kernel, bsd))]
+impl<const GROUP: u8, const NUM: u8, Data> CompileTimeOpcode for WriteOpcode<GROUP, NUM, Data> {
+ const OPCODE: Opcode = Opcode::write::<Data>(GROUP, NUM);
+}
+
+/// Provides a read/write code at compile time.
+///
+/// This corresponds to the C macro `_IOWR(GROUP, NUM, Data)`.
+#[cfg(any(linux_kernel, bsd))]
+pub struct ReadWriteOpcode<const GROUP: u8, const NUM: u8, Data>(Data);
+
+#[cfg(any(linux_kernel, bsd))]
+impl<const GROUP: u8, const NUM: u8, Data> CompileTimeOpcode for ReadWriteOpcode<GROUP, NUM, Data> {
+ const OPCODE: Opcode = Opcode::read_write::<Data>(GROUP, NUM);
+}
+
+/// Provides a `None` code at compile time.
+///
+/// This corresponds to the C macro `_IO(GROUP, NUM)` when `Data` is zero
+/// sized.
+#[cfg(any(linux_kernel, bsd))]
+pub struct NoneOpcode<const GROUP: u8, const NUM: u8, Data>(Data);
+
+#[cfg(any(linux_kernel, bsd))]
+impl<const GROUP: u8, const NUM: u8, Data> CompileTimeOpcode for NoneOpcode<GROUP, NUM, Data> {
+ const OPCODE: Opcode = Opcode::none::<Data>(GROUP, NUM);
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..51fbdba
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,395 @@
+//! `rustix` provides efficient memory-safe and [I/O-safe] wrappers to
+//! POSIX-like, Unix-like, Linux, and Winsock syscall-like APIs, with
+//! configurable backends.
+//!
+//! With rustix, you can write code like this:
+//!
+//! ```
+//! # #[cfg(feature = "net")]
+//! # fn read(sock: std::net::TcpStream, buf: &mut [u8]) -> std::io::Result<()> {
+//! # use rustix::net::RecvFlags;
+//! let nread: usize = rustix::net::recv(&sock, buf, RecvFlags::PEEK)?;
+//! # let _ = nread;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! instead of like this:
+//!
+//! ```
+//! # #[cfg(feature = "net")]
+//! # fn read(sock: std::net::TcpStream, buf: &mut [u8]) -> std::io::Result<()> {
+//! # #[cfg(unix)]
+//! # use std::os::unix::io::AsRawFd;
+//! # #[cfg(target_os = "wasi")]
+//! # use std::os::wasi::io::AsRawFd;
+//! # #[cfg(windows)]
+//! # use windows_sys::Win32::Networking::WinSock as libc;
+//! # #[cfg(windows)]
+//! # use std::os::windows::io::AsRawSocket;
+//! # const MSG_PEEK: i32 = libc::MSG_PEEK;
+//! let nread: usize = unsafe {
+//! #[cfg(any(unix, target_os = "wasi"))]
+//! let raw = sock.as_raw_fd();
+//! #[cfg(windows)]
+//! let raw = sock.as_raw_socket();
+//! match libc::recv(
+//! raw as _,
+//! buf.as_mut_ptr().cast(),
+//! buf.len().try_into().unwrap_or(i32::MAX as _),
+//! MSG_PEEK,
+//! ) {
+//! -1 => return Err(std::io::Error::last_os_error()),
+//! nread => nread as usize,
+//! }
+//! };
+//! # let _ = nread;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! rustix's APIs perform the following tasks:
+//! - Error values are translated to [`Result`]s.
+//! - Buffers are passed as Rust slices.
+//! - Out-parameters are presented as return values.
+//! - Path arguments use [`Arg`], so they accept any string type.
+//! - File descriptors are passed and returned via [`AsFd`] and [`OwnedFd`]
+//! instead of bare integers, ensuring I/O safety.
+//! - Constants use `enum`s and [`bitflags`] types, and enable [support for
+//! externally defined flags].
+//! - Multiplexed functions (eg. `fcntl`, `ioctl`, etc.) are de-multiplexed.
+//! - Variadic functions (eg. `openat`, etc.) are presented as non-variadic.
+//! - Functions that return strings automatically allocate sufficient memory
+//! and retry the syscall as needed to determine the needed length.
+//! - Functions and types which need `l` prefixes or `64` suffixes to enable
+//! large-file support (LFS) are used automatically. File sizes and offsets
+//! are always presented as `u64` and `i64`.
+//! - Behaviors that depend on the sizes of C types like `long` are hidden.
+//! - In some places, more human-friendly and less historical-accident names
+//! are used (and documentation aliases are used so that the original names
+//! can still be searched for).
+//! - Provide y2038 compatibility, on platforms which support this.
+//! - Correct selected platform bugs, such as behavioral differences when
+//! running under seccomp.
+//!
+//! Things they don't do include:
+//! - Detecting whether functions are supported at runtime, except in specific
+//! cases where new interfaces need to be detected to support y2038 and LFS.
+//! - Hiding significant differences between platforms.
+//! - Restricting ambient authorities.
+//! - Imposing sandboxing features such as filesystem path or network address
+//! sandboxing.
+//!
+//! See [`cap-std`], [`system-interface`], and [`io-streams`] for libraries
+//! which do hide significant differences between platforms, and [`cap-std`]
+//! which does perform sandboxing and restricts ambient authorities.
+//!
+//! [`cap-std`]: https://crates.io/crates/cap-std
+//! [`system-interface`]: https://crates.io/crates/system-interface
+//! [`io-streams`]: https://crates.io/crates/io-streams
+//! [`getrandom`]: https://crates.io/crates/getrandom
+//! [`bitflags`]: https://crates.io/crates/bitflags
+//! [`AsFd`]: https://doc.rust-lang.org/stable/std/os/fd/trait.AsFd.html
+//! [`OwnedFd`]: https://doc.rust-lang.org/stable/std/os/fd/struct.OwnedFd.html
+//! [I/O-safe]: https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md
+//! [`Result`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html
+//! [`Arg`]: https://docs.rs/rustix/*/rustix/path/trait.Arg.html
+//! [support for externally defined flags]: https://docs.rs/bitflags/*/bitflags/#externally-defined-flags
+
+#![deny(missing_docs)]
+#![allow(stable_features)]
+#![cfg_attr(linux_raw, deny(unsafe_code))]
+#![cfg_attr(rustc_attrs, feature(rustc_attrs))]
+#![cfg_attr(doc_cfg, feature(doc_cfg))]
+#![cfg_attr(all(wasi_ext, target_os = "wasi", feature = "std"), feature(wasi_ext))]
+#![cfg_attr(core_ffi_c, feature(core_ffi_c))]
+#![cfg_attr(core_c_str, feature(core_c_str))]
+#![cfg_attr(all(feature = "alloc", alloc_c_string), feature(alloc_c_string))]
+#![cfg_attr(all(feature = "alloc", alloc_ffi), feature(alloc_ffi))]
+#![cfg_attr(not(feature = "std"), no_std)]
+#![cfg_attr(feature = "rustc-dep-of-std", feature(ip))]
+#![cfg_attr(feature = "rustc-dep-of-std", allow(internal_features))]
+#![cfg_attr(
+ any(feature = "rustc-dep-of-std", core_intrinsics),
+ feature(core_intrinsics)
+)]
+#![cfg_attr(asm_experimental_arch, feature(asm_experimental_arch))]
+#![cfg_attr(not(feature = "all-apis"), allow(dead_code))]
+// It is common in Linux and libc APIs for types to vary between platforms.
+#![allow(clippy::unnecessary_cast)]
+// It is common in Linux and libc APIs for types to vary between platforms.
+#![allow(clippy::useless_conversion)]
+// Redox and WASI have enough differences that it isn't worth precisely
+// conditionalizing all the `use`s for them. Similar for if we don't have
+// "all-apis".
+#![cfg_attr(
+ any(target_os = "redox", target_os = "wasi", not(feature = "all-apis")),
+ allow(unused_imports)
+)]
+
+#[cfg(all(feature = "alloc", not(feature = "rustc-dep-of-std")))]
+extern crate alloc;
+
+// Use `static_assertions` macros if we have them, or a polyfill otherwise.
+#[cfg(all(test, static_assertions))]
+#[macro_use]
+#[allow(unused_imports)]
+extern crate static_assertions;
+#[cfg(all(test, not(static_assertions)))]
+#[macro_use]
+#[allow(unused_imports)]
+mod static_assertions;
+
+// Internal utilities.
+mod buffer;
+#[cfg(not(windows))]
+#[macro_use]
+pub(crate) mod cstr;
+#[macro_use]
+pub(crate) mod utils;
+// Polyfill for `std` in `no_std` builds.
+#[cfg_attr(feature = "std", path = "maybe_polyfill/std/mod.rs")]
+#[cfg_attr(not(feature = "std"), path = "maybe_polyfill/no_std/mod.rs")]
+pub(crate) mod maybe_polyfill;
+#[cfg(test)]
+#[macro_use]
+pub(crate) mod check_types;
+#[macro_use]
+pub(crate) mod bitcast;
+
+// linux_raw: Weak symbols are used by the use-libc-auxv feature for
+// glibc 2.15 support.
+//
+// libc: Weak symbols are used to call various functions available in some
+// versions of libc and not others.
+#[cfg(any(
+ all(linux_raw, feature = "use-libc-auxv"),
+ all(libc, not(any(windows, target_os = "espidf", target_os = "wasi")))
+))]
+#[macro_use]
+mod weak;
+
+// Pick the backend implementation to use.
+#[cfg_attr(libc, path = "backend/libc/mod.rs")]
+#[cfg_attr(linux_raw, path = "backend/linux_raw/mod.rs")]
+#[cfg_attr(wasi, path = "backend/wasi/mod.rs")]
+mod backend;
+
+/// Export the `*Fd` types and traits that are used in rustix's public API.
+///
+/// Users can use this to avoid needing to import anything else to use the same
+/// versions of these types and traits.
+pub mod fd {
+ use super::backend;
+
+ // Re-export `AsSocket` etc. too, as users can't implement `AsFd` etc. on
+ // Windows due to them having blanket impls on Windows, so users must
+ // implement `AsSocket` etc.
+ #[cfg(windows)]
+ pub use backend::fd::{AsRawSocket, AsSocket, FromRawSocket, IntoRawSocket};
+
+ pub use backend::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
+}
+
+// The public API modules.
+#[cfg(feature = "event")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "event")))]
+pub mod event;
+#[cfg(not(windows))]
+pub mod ffi;
+#[cfg(not(windows))]
+#[cfg(feature = "fs")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+pub mod fs;
+pub mod io;
+#[cfg(linux_kernel)]
+#[cfg(feature = "io_uring")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "io_uring")))]
+pub mod io_uring;
+pub mod ioctl;
+#[cfg(not(any(windows, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+#[cfg(feature = "mm")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "mm")))]
+pub mod mm;
+#[cfg(linux_kernel)]
+#[cfg(feature = "mount")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "mount")))]
+pub mod mount;
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[cfg(feature = "net")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "net")))]
+pub mod net;
+#[cfg(not(any(windows, target_os = "espidf")))]
+#[cfg(feature = "param")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "param")))]
+pub mod param;
+#[cfg(not(windows))]
+#[cfg(any(feature = "fs", feature = "mount", feature = "net"))]
+#[cfg_attr(
+ doc_cfg,
+ doc(cfg(any(feature = "fs", feature = "mount", feature = "net")))
+)]
+pub mod path;
+#[cfg(feature = "pipe")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "pipe")))]
+#[cfg(not(any(windows, target_os = "wasi")))]
+pub mod pipe;
+#[cfg(not(windows))]
+#[cfg(feature = "process")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
+pub mod process;
+#[cfg(feature = "procfs")]
+#[cfg(linux_kernel)]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+pub mod procfs;
+#[cfg(not(windows))]
+#[cfg(not(target_os = "wasi"))]
+#[cfg(feature = "pty")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "pty")))]
+pub mod pty;
+#[cfg(not(windows))]
+#[cfg(feature = "rand")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
+pub mod rand;
+#[cfg(not(any(
+ windows,
+ target_os = "android",
+ target_os = "espidf",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[cfg(feature = "shm")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "shm")))]
+pub mod shm;
+#[cfg(not(windows))]
+#[cfg(feature = "stdio")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "stdio")))]
+pub mod stdio;
+#[cfg(feature = "system")]
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "system")))]
+pub mod system;
+#[cfg(not(any(windows, target_os = "vita")))]
+#[cfg(feature = "termios")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "termios")))]
+pub mod termios;
+#[cfg(not(windows))]
+#[cfg(feature = "thread")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "thread")))]
+pub mod thread;
+#[cfg(not(any(windows, target_os = "espidf")))]
+#[cfg(feature = "time")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "time")))]
+pub mod time;
+
+// "runtime" is also a public API module, but it's only for libc-like users.
+#[cfg(not(windows))]
+#[cfg(feature = "runtime")]
+#[cfg(linux_raw)]
+#[cfg_attr(not(document_experimental_runtime_api), doc(hidden))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "runtime")))]
+pub mod runtime;
+
+// Temporarily provide some mount functions for use in the fs module for
+// backwards compatibility.
+#[cfg(linux_kernel)]
+#[cfg(all(feature = "fs", not(feature = "mount")))]
+pub(crate) mod mount;
+
+// Declare "fs" as a non-public module if "fs" isn't enabled but we need it for
+// reading procfs.
+#[cfg(not(windows))]
+#[cfg(not(feature = "fs"))]
+#[cfg(all(
+ linux_raw,
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+pub(crate) mod fs;
+
+// Similarly, declare `path` as a non-public module if needed.
+#[cfg(not(windows))]
+#[cfg(not(any(feature = "fs", feature = "mount", feature = "net")))]
+#[cfg(all(
+ linux_raw,
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+))]
+pub(crate) mod path;
+
+// Private modules used by multiple public modules.
+#[cfg(not(any(windows, target_os = "espidf")))]
+#[cfg(any(feature = "thread", feature = "time", target_arch = "x86"))]
+mod clockid;
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[cfg(any(
+ feature = "procfs",
+ feature = "process",
+ feature = "runtime",
+ feature = "termios",
+ feature = "thread",
+ all(bsd, feature = "event"),
+ all(linux_kernel, feature = "net")
+))]
+mod pid;
+#[cfg(any(feature = "process", feature = "thread"))]
+#[cfg(linux_kernel)]
+mod prctl;
+#[cfg(not(any(windows, target_os = "espidf", target_os = "wasi")))]
+#[cfg(any(feature = "process", feature = "runtime", all(bsd, feature = "event")))]
+mod signal;
+#[cfg(not(windows))]
+#[cfg(any(
+ feature = "fs",
+ feature = "process",
+ feature = "runtime",
+ feature = "thread",
+ feature = "time",
+ all(
+ linux_raw,
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "process",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+ )
+))]
+mod timespec;
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[cfg(any(
+ feature = "fs",
+ feature = "process",
+ feature = "thread",
+ all(
+ linux_raw,
+ not(feature = "use-libc-auxv"),
+ not(feature = "use-explicitly-provided-auxv"),
+ any(
+ feature = "param",
+ feature = "runtime",
+ feature = "time",
+ target_arch = "x86",
+ )
+ ),
+ all(linux_kernel, feature = "net")
+))]
+mod ugid;
diff --git a/src/maybe_polyfill/no_std/io/mod.rs b/src/maybe_polyfill/no_std/io/mod.rs
new file mode 100644
index 0000000..f0ad750
--- /dev/null
+++ b/src/maybe_polyfill/no_std/io/mod.rs
@@ -0,0 +1,107 @@
+//! The following is derived from Rust's
+//! library/std/src/sys/unix/io.rs
+//! dca3f1b786efd27be3b325ed1e01e247aa589c3b.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+
+#![allow(unsafe_code)]
+use crate::backend::c;
+#[cfg(not(linux_raw))]
+use c::size_t as __kernel_size_t;
+use core::marker::PhantomData;
+use core::slice;
+#[cfg(linux_raw)]
+use linux_raw_sys::general::__kernel_size_t;
+
+/// <https://doc.rust-lang.org/stable/std/io/struct.IoSlice.html>
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct IoSlice<'a> {
+ vec: c::iovec,
+ _p: PhantomData<&'a [u8]>,
+}
+
+impl<'a> IoSlice<'a> {
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSlice.html#method.new>
+ #[inline]
+ pub fn new(buf: &'a [u8]) -> IoSlice<'a> {
+ IoSlice {
+ vec: c::iovec {
+ iov_base: buf.as_ptr() as *mut u8 as *mut c::c_void,
+ iov_len: buf.len() as _,
+ },
+ _p: PhantomData,
+ }
+ }
+
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSlice.html#method.advance>
+ #[inline]
+ pub fn advance(&mut self, n: usize) {
+ if self.vec.iov_len < n as _ {
+ panic!("advancing IoSlice beyond its length");
+ }
+
+ unsafe {
+ // `__kernel_size_t` will always have the same size as `usize`, but it is a `u32` on
+ // 32-bit platforms and `u64` on 64-bit platforms when using `linux_raw` backend
+ self.vec.iov_len -= n as __kernel_size_t;
+ self.vec.iov_base = self.vec.iov_base.add(n);
+ }
+ }
+
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSlice.html#method.as_slice>
+ #[inline]
+ pub fn as_slice(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len as usize) }
+ }
+}
+
+/// <https://doc.rust-lang.org/stable/std/io/struct.IoSliceMut.html>
+#[repr(transparent)]
+pub struct IoSliceMut<'a> {
+ vec: c::iovec,
+ _p: PhantomData<&'a mut [u8]>,
+}
+
+impl<'a> IoSliceMut<'a> {
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSliceMut.html#method.new>
+ #[inline]
+ pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> {
+ IoSliceMut {
+ vec: c::iovec {
+ iov_base: buf.as_mut_ptr() as *mut c::c_void,
+ iov_len: buf.len() as _,
+ },
+ _p: PhantomData,
+ }
+ }
+
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSliceMut.html#method.advance>
+ #[inline]
+ pub fn advance(&mut self, n: usize) {
+ if self.vec.iov_len < n as _ {
+ panic!("advancing IoSliceMut beyond its length");
+ }
+
+ unsafe {
+ // `__kernel_size_t` will always have the same size as `usize`, but it is a `u32` on
+ // 32-bit platforms and `u64` on 64-bit platforms when using `linux_raw` backend
+ self.vec.iov_len -= n as __kernel_size_t;
+ self.vec.iov_base = self.vec.iov_base.add(n);
+ }
+ }
+
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSliceMut.html#method.as_slice>
+ #[inline]
+ pub fn as_slice(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.vec.iov_base as *mut u8, self.vec.iov_len as usize) }
+ }
+
+ /// <https://doc.rust-lang.org/stable/std/io/struct.IoSliceMut.html#method.as_slice_mut>
+ #[inline]
+ pub fn as_mut_slice(&mut self) -> &mut [u8] {
+ unsafe {
+ slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len as usize)
+ }
+ }
+}
diff --git a/src/maybe_polyfill/no_std/mod.rs b/src/maybe_polyfill/no_std/mod.rs
new file mode 100644
index 0000000..84bf5b7
--- /dev/null
+++ b/src/maybe_polyfill/no_std/mod.rs
@@ -0,0 +1,16 @@
+//! Polyfill of parts of the standard library for `no_std` builds.
+//!
+//! All code in this subtree is derived from the standard library and licensed
+//! MIT or Apache 2.0 at your option.
+//!
+//! This implementation is used when `std` is not available and polyfills the
+//! necessary items from `std`. When the `std` feature is specified (so the
+//! standard library is available), the file `src/polyfill/std` is used
+//! instead, which just imports the respective items from `std`.
+
+#[cfg(not(windows))]
+pub mod io;
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[cfg(feature = "net")]
+pub mod net;
+pub mod os;
diff --git a/src/maybe_polyfill/no_std/net/ip_addr.rs b/src/maybe_polyfill/no_std/net/ip_addr.rs
new file mode 100644
index 0000000..81415e9
--- /dev/null
+++ b/src/maybe_polyfill/no_std/net/ip_addr.rs
@@ -0,0 +1,2068 @@
+//! The following is derived from Rust's
+//! library/std/src/net/ip_addr.rs at revision
+//! bd20fc1fd657b32f7aa1d70d8723f04c87f21606.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+//!
+//! This defines `IpAddr`, `Ipv4Addr`, and `Ipv6Addr`. Ideally, these should be
+//! defined in `core`. See [RFC 2832].
+//!
+//! [RFC 2832]: https://github.com/rust-lang/rfcs/pull/2832
+
+#![allow(unsafe_code)]
+
+use core::cmp::Ordering;
+use core::mem::transmute;
+
+/// An IP address, either IPv4 or IPv6.
+///
+/// This enum can contain either an [`Ipv4Addr`] or an [`Ipv6Addr`], see their
+/// respective documentation for more details.
+///
+/// # Examples
+///
+/// ```
+/// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+///
+/// let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+/// let localhost_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
+///
+/// assert_eq!("127.0.0.1".parse(), Ok(localhost_v4));
+/// assert_eq!("::1".parse(), Ok(localhost_v6));
+///
+/// assert_eq!(localhost_v4.is_ipv6(), false);
+/// assert_eq!(localhost_v4.is_ipv4(), true);
+/// ```
+#[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))]
+#[derive(Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
+pub enum IpAddr {
+ /// An IPv4 address.
+ #[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))]
+ V4(#[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))] Ipv4Addr),
+ /// An IPv6 address.
+ #[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))]
+ V6(#[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))] Ipv6Addr),
+}
+
+/// An IPv4 address.
+///
+/// IPv4 addresses are defined as 32-bit integers in [IETF RFC 791].
+/// They are usually represented as four octets.
+///
+/// See [`IpAddr`] for a type encompassing both IPv4 and IPv6 addresses.
+///
+/// [IETF RFC 791]: https://tools.ietf.org/html/rfc791
+///
+/// # Textual representation
+///
+/// `Ipv4Addr` provides a [`FromStr`] implementation. The four octets are in decimal
+/// notation, divided by `.` (this is called "dot-decimal notation").
+/// Notably, octal numbers (which are indicated with a leading `0`) and hexadecimal numbers (which
+/// are indicated with a leading `0x`) are not allowed per [IETF RFC 6943].
+///
+/// [IETF RFC 6943]: https://tools.ietf.org/html/rfc6943#section-3.1.1
+/// [`FromStr`]: core::str::FromStr
+///
+/// # Examples
+///
+/// ```
+/// use std::net::Ipv4Addr;
+///
+/// let localhost = Ipv4Addr::new(127, 0, 0, 1);
+/// assert_eq!("127.0.0.1".parse(), Ok(localhost));
+/// assert_eq!(localhost.is_loopback(), true);
+/// assert!("012.004.002.000".parse::<Ipv4Addr>().is_err()); // all octets are in octal
+/// assert!("0000000.0.0.0".parse::<Ipv4Addr>().is_err()); // first octet is a zero in octal
+/// assert!("0xcb.0x0.0x71.0x00".parse::<Ipv4Addr>().is_err()); // all octets are in hex
+/// ```
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub struct Ipv4Addr {
+ octets: [u8; 4],
+}
+
+/// An IPv6 address.
+///
+/// IPv6 addresses are defined as 128-bit integers in [IETF RFC 4291].
+/// They are usually represented as eight 16-bit segments.
+///
+/// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291
+///
+/// # Embedding IPv4 Addresses
+///
+/// See [`IpAddr`] for a type encompassing both IPv4 and IPv6 addresses.
+///
+/// To assist in the transition from IPv4 to IPv6 two types of IPv6 addresses that embed an IPv4 address were defined:
+/// IPv4-compatible and IPv4-mapped addresses. Of these IPv4-compatible addresses have been officially deprecated.
+///
+/// Both types of addresses are not assigned any special meaning by this implementation,
+/// other than what the relevant standards prescribe. This means that an address like `::ffff:127.0.0.1`,
+/// while representing an IPv4 loopback address, is not itself an IPv6 loopback address; only `::1` is.
+/// To handle these so called "IPv4-in-IPv6" addresses, they have to first be converted to their canonical IPv4 address.
+///
+/// ### IPv4-Compatible IPv6 Addresses
+///
+/// IPv4-compatible IPv6 addresses are defined in [IETF RFC 4291 Section 2.5.5.1], and have been officially deprecated.
+/// The RFC describes the format of an "IPv4-Compatible IPv6 address" as follows:
+///
+/// ```text
+/// | 80 bits | 16 | 32 bits |
+/// +--------------------------------------+--------------------------+
+/// |0000..............................0000|0000| IPv4 address |
+/// +--------------------------------------+----+---------------------+
+/// ```
+/// So `::a.b.c.d` would be an IPv4-compatible IPv6 address representing the IPv4 address `a.b.c.d`.
+///
+/// To convert from an IPv4 address to an IPv4-compatible IPv6 address, use [`Ipv4Addr::to_ipv6_compatible`].
+/// Use [`Ipv6Addr::to_ipv4`] to convert an IPv4-compatible IPv6 address to the canonical IPv4 address.
+///
+/// [IETF RFC 4291 Section 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1
+///
+/// ### IPv4-Mapped IPv6 Addresses
+///
+/// IPv4-mapped IPv6 addresses are defined in [IETF RFC 4291 Section 2.5.5.2].
+/// The RFC describes the format of an "IPv4-Mapped IPv6 address" as follows:
+///
+/// ```text
+/// | 80 bits | 16 | 32 bits |
+/// +--------------------------------------+--------------------------+
+/// |0000..............................0000|FFFF| IPv4 address |
+/// +--------------------------------------+----+---------------------+
+/// ```
+/// So `::ffff:a.b.c.d` would be an IPv4-mapped IPv6 address representing the IPv4 address `a.b.c.d`.
+///
+/// To convert from an IPv4 address to an IPv4-mapped IPv6 address, use [`Ipv4Addr::to_ipv6_mapped`].
+/// Use [`Ipv6Addr::to_ipv4`] to convert an IPv4-mapped IPv6 address to the canonical IPv4 address.
+/// Note that this will also convert the IPv6 loopback address `::1` to `0.0.0.1`. Use
+/// [`Ipv6Addr::to_ipv4_mapped`] to avoid this.
+///
+/// [IETF RFC 4291 Section 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2
+///
+/// # Textual representation
+///
+/// `Ipv6Addr` provides a [`FromStr`] implementation. There are many ways to represent
+/// an IPv6 address in text, but in general, each segments is written in hexadecimal
+/// notation, and segments are separated by `:`. For more information, see
+/// [IETF RFC 5952].
+///
+/// [`FromStr`]: core::str::FromStr
+/// [IETF RFC 5952]: https://tools.ietf.org/html/rfc5952
+///
+/// # Examples
+///
+/// ```
+/// use std::net::Ipv6Addr;
+///
+/// let localhost = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+/// assert_eq!("::1".parse(), Ok(localhost));
+/// assert_eq!(localhost.is_loopback(), true);
+/// ```
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub struct Ipv6Addr {
+ octets: [u8; 16],
+}
+
+/// Scope of an [IPv6 multicast address] as defined in [IETF RFC 7346 section 2].
+///
+/// # Stability Guarantees
+///
+/// Not all possible values for a multicast scope have been assigned.
+/// Future RFCs may introduce new scopes, which will be added as variants to this enum;
+/// because of this the enum is marked as `#[non_exhaustive]`.
+///
+/// # Examples
+/// ```
+/// #![feature(ip)]
+///
+/// use std::net::Ipv6Addr;
+/// use std::net::Ipv6MulticastScope::*;
+///
+/// // An IPv6 multicast address with global scope (`ff0e::`).
+/// let address = Ipv6Addr::new(0xff0e, 0, 0, 0, 0, 0, 0, 0);
+///
+/// // Will print "Global scope".
+/// match address.multicast_scope() {
+/// Some(InterfaceLocal) => println!("Interface-Local scope"),
+/// Some(LinkLocal) => println!("Link-Local scope"),
+/// Some(RealmLocal) => println!("Realm-Local scope"),
+/// Some(AdminLocal) => println!("Admin-Local scope"),
+/// Some(SiteLocal) => println!("Site-Local scope"),
+/// Some(OrganizationLocal) => println!("Organization-Local scope"),
+/// Some(Global) => println!("Global scope"),
+/// Some(_) => println!("Unknown scope"),
+/// None => println!("Not a multicast address!")
+/// }
+///
+/// ```
+///
+/// [IPv6 multicast address]: Ipv6Addr
+/// [IETF RFC 7346 section 2]: https://tools.ietf.org/html/rfc7346#section-2
+#[derive(Copy, PartialEq, Eq, Clone, Hash, Debug)]
+#[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+#[non_exhaustive]
+pub enum Ipv6MulticastScope {
+ /// Interface-Local scope.
+ InterfaceLocal,
+ /// Link-Local scope.
+ LinkLocal,
+ /// Realm-Local scope.
+ RealmLocal,
+ /// Admin-Local scope.
+ AdminLocal,
+ /// Site-Local scope.
+ SiteLocal,
+ /// Organization-Local scope.
+ OrganizationLocal,
+ /// Global scope.
+ Global,
+}
+
+impl IpAddr {
+ /// Returns [`true`] for the special 'unspecified' address.
+ ///
+ /// See the documentation for [`Ipv4Addr::is_unspecified()`] and
+ /// [`Ipv6Addr::is_unspecified()`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)).is_unspecified(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)).is_unspecified(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ip_shared", since = "1.12.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unspecified(&self) -> bool {
+ match self {
+ IpAddr::V4(ip) => ip.is_unspecified(),
+ IpAddr::V6(ip) => ip.is_unspecified(),
+ }
+ }
+
+ /// Returns [`true`] if this is a loopback address.
+ ///
+ /// See the documentation for [`Ipv4Addr::is_loopback()`] and
+ /// [`Ipv6Addr::is_loopback()`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).is_loopback(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1)).is_loopback(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ip_shared", since = "1.12.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_loopback(&self) -> bool {
+ match self {
+ IpAddr::V4(ip) => ip.is_loopback(),
+ IpAddr::V6(ip) => ip.is_loopback(),
+ }
+ }
+
+ /// Returns [`true`] if the address appears to be globally routable.
+ ///
+ /// See the documentation for [`Ipv4Addr::is_global()`] and
+ /// [`Ipv6Addr::is_global()`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(80, 9, 12, 3)).is_global(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0x1c9, 0, 0, 0xafc8, 0, 0x1)).is_global(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ip", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_global(&self) -> bool {
+ match self {
+ IpAddr::V4(ip) => ip.is_global(),
+ IpAddr::V6(ip) => ip.is_global(),
+ }
+ }
+
+ /// Returns [`true`] if this is a multicast address.
+ ///
+ /// See the documentation for [`Ipv4Addr::is_multicast()`] and
+ /// [`Ipv6Addr::is_multicast()`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(224, 254, 0, 0)).is_multicast(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0)).is_multicast(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ip_shared", since = "1.12.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_multicast(&self) -> bool {
+ match self {
+ IpAddr::V4(ip) => ip.is_multicast(),
+ IpAddr::V6(ip) => ip.is_multicast(),
+ }
+ }
+
+ /// Returns [`true`] if this address is in a range designated for documentation.
+ ///
+ /// See the documentation for [`Ipv4Addr::is_documentation()`] and
+ /// [`Ipv6Addr::is_documentation()`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_documentation(), true);
+ /// assert_eq!(
+ /// IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_documentation(),
+ /// true
+ /// );
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ip", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_documentation(&self) -> bool {
+ match self {
+ IpAddr::V4(ip) => ip.is_documentation(),
+ IpAddr::V6(ip) => ip.is_documentation(),
+ }
+ }
+
+ /// Returns [`true`] if this address is in a range designated for benchmarking.
+ ///
+ /// See the documentation for [`Ipv4Addr::is_benchmarking()`] and
+ /// [`Ipv6Addr::is_benchmarking()`] for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(198, 19, 255, 255)).is_benchmarking(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0x2, 0, 0, 0, 0, 0, 0)).is_benchmarking(), true);
+ /// ```
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_benchmarking(&self) -> bool {
+ match self {
+ IpAddr::V4(ip) => ip.is_benchmarking(),
+ IpAddr::V6(ip) => ip.is_benchmarking(),
+ }
+ }
+
+ /// Returns [`true`] if this address is an [`IPv4` address], and [`false`]
+ /// otherwise.
+ ///
+ /// [`IPv4` address]: IpAddr::V4
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_ipv4(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_ipv4(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ipaddr_checker", since = "1.16.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_ipv4(&self) -> bool {
+ matches!(self, IpAddr::V4(_))
+ }
+
+ /// Returns [`true`] if this address is an [`IPv6` address], and [`false`]
+ /// otherwise.
+ ///
+ /// [`IPv6` address]: IpAddr::V6
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 6)).is_ipv6(), false);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)).is_ipv6(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ipaddr_checker", since = "1.16.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_ipv6(&self) -> bool {
+ matches!(self, IpAddr::V6(_))
+ }
+
+ /// Converts this address to an `IpAddr::V4` if it is an IPv4-mapped IPv6 addresses, otherwise it
+ /// return `self` as-is.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).to_canonical().is_loopback(), true);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1)).is_loopback(), false);
+ /// assert_eq!(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1)).to_canonical().is_loopback(), true);
+ /// ```
+ #[inline]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ip", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ pub const fn to_canonical(&self) -> IpAddr {
+ match self {
+ &v4 @ IpAddr::V4(_) => v4,
+ IpAddr::V6(v6) => v6.to_canonical(),
+ }
+ }
+}
+
+impl Ipv4Addr {
+ /// Creates a new IPv4 address from four eight-bit octets.
+ ///
+ /// The result will represent the IP address `a`.`b`.`c`.`d`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::new(127, 0, 0, 1);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_32", since = "1.32.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr {
+ Ipv4Addr {
+ octets: [a, b, c, d],
+ }
+ }
+
+ /// An IPv4 address with the address pointing to localhost: `127.0.0.1`
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::LOCALHOST;
+ /// assert_eq!(addr, Ipv4Addr::new(127, 0, 0, 1));
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "ip_constructors", since = "1.30.0"))]
+ pub const LOCALHOST: Self = Ipv4Addr::new(127, 0, 0, 1);
+
+ /// An IPv4 address representing an unspecified address: `0.0.0.0`
+ ///
+ /// This corresponds to the constant `INADDR_ANY` in other languages.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::UNSPECIFIED;
+ /// assert_eq!(addr, Ipv4Addr::new(0, 0, 0, 0));
+ /// ```
+ #[doc(alias = "INADDR_ANY")]
+ #[cfg_attr(staged_api, stable(feature = "ip_constructors", since = "1.30.0"))]
+ pub const UNSPECIFIED: Self = Ipv4Addr::new(0, 0, 0, 0);
+
+ /// An IPv4 address representing the broadcast address: `255.255.255.255`
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::BROADCAST;
+ /// assert_eq!(addr, Ipv4Addr::new(255, 255, 255, 255));
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "ip_constructors", since = "1.30.0"))]
+ pub const BROADCAST: Self = Ipv4Addr::new(255, 255, 255, 255);
+
+ /// Returns the four eight-bit integers that make up this address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::new(127, 0, 0, 1);
+ /// assert_eq!(addr.octets(), [127, 0, 0, 1]);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn octets(&self) -> [u8; 4] {
+ self.octets
+ }
+
+ /// Returns [`true`] for the special 'unspecified' address (`0.0.0.0`).
+ ///
+ /// This property is defined in _UNIX Network Programming, Second Edition_,
+ /// W. Richard Stevens, p. 891; see also [ip7].
+ ///
+ /// [ip7]: https://man7.org/linux/man-pages/man7/ip.7.html
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(0, 0, 0, 0).is_unspecified(), true);
+ /// assert_eq!(Ipv4Addr::new(45, 22, 13, 197).is_unspecified(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_32", since = "1.32.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ip_shared", since = "1.12.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unspecified(&self) -> bool {
+ u32::from_be_bytes(self.octets) == 0
+ }
+
+ /// Returns [`true`] if this is a loopback address (`127.0.0.0/8`).
+ ///
+ /// This property is defined by [IETF RFC 1122].
+ ///
+ /// [IETF RFC 1122]: https://tools.ietf.org/html/rfc1122
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(127, 0, 0, 1).is_loopback(), true);
+ /// assert_eq!(Ipv4Addr::new(45, 22, 13, 197).is_loopback(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_loopback(&self) -> bool {
+ self.octets()[0] == 127
+ }
+
+ /// Returns [`true`] if this is a private address.
+ ///
+ /// The private address ranges are defined in [IETF RFC 1918] and include:
+ ///
+ /// - `10.0.0.0/8`
+ /// - `172.16.0.0/12`
+ /// - `192.168.0.0/16`
+ ///
+ /// [IETF RFC 1918]: https://tools.ietf.org/html/rfc1918
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(10, 0, 0, 1).is_private(), true);
+ /// assert_eq!(Ipv4Addr::new(10, 10, 10, 10).is_private(), true);
+ /// assert_eq!(Ipv4Addr::new(172, 16, 10, 10).is_private(), true);
+ /// assert_eq!(Ipv4Addr::new(172, 29, 45, 14).is_private(), true);
+ /// assert_eq!(Ipv4Addr::new(172, 32, 0, 2).is_private(), false);
+ /// assert_eq!(Ipv4Addr::new(192, 168, 0, 2).is_private(), true);
+ /// assert_eq!(Ipv4Addr::new(192, 169, 0, 2).is_private(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_private(&self) -> bool {
+ match self.octets() {
+ [10, ..] => true,
+ [172, b, ..] if b >= 16 && b <= 31 => true,
+ [192, 168, ..] => true,
+ _ => false,
+ }
+ }
+
+ /// Returns [`true`] if the address is link-local (`169.254.0.0/16`).
+ ///
+ /// This property is defined by [IETF RFC 3927].
+ ///
+ /// [IETF RFC 3927]: https://tools.ietf.org/html/rfc3927
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(169, 254, 0, 0).is_link_local(), true);
+ /// assert_eq!(Ipv4Addr::new(169, 254, 10, 65).is_link_local(), true);
+ /// assert_eq!(Ipv4Addr::new(16, 89, 10, 65).is_link_local(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_link_local(&self) -> bool {
+ matches!(self.octets(), [169, 254, ..])
+ }
+
+ /// Returns [`true`] if the address appears to be globally reachable
+ /// as specified by the [IANA IPv4 Special-Purpose Address Registry].
+ /// Whether or not an address is practically reachable will depend on your network configuration.
+ ///
+ /// Most IPv4 addresses are globally reachable;
+ /// unless they are specifically defined as *not* globally reachable.
+ ///
+ /// Non-exhaustive list of notable addresses that are not globally reachable:
+ ///
+ /// - The [unspecified address] ([`is_unspecified`](Ipv4Addr::is_unspecified))
+ /// - Addresses reserved for private use ([`is_private`](Ipv4Addr::is_private))
+ /// - Addresses in the shared address space ([`is_shared`](Ipv4Addr::is_shared))
+ /// - Loopback addresses ([`is_loopback`](Ipv4Addr::is_loopback))
+ /// - Link-local addresses ([`is_link_local`](Ipv4Addr::is_link_local))
+ /// - Addresses reserved for documentation ([`is_documentation`](Ipv4Addr::is_documentation))
+ /// - Addresses reserved for benchmarking ([`is_benchmarking`](Ipv4Addr::is_benchmarking))
+ /// - Reserved addresses ([`is_reserved`](Ipv4Addr::is_reserved))
+ /// - The [broadcast address] ([`is_broadcast`](Ipv4Addr::is_broadcast))
+ ///
+ /// For the complete overview of which addresses are globally reachable, see the table at the [IANA IPv4 Special-Purpose Address Registry].
+ ///
+ /// [IANA IPv4 Special-Purpose Address Registry]: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
+ /// [unspecified address]: Ipv4Addr::UNSPECIFIED
+ /// [broadcast address]: Ipv4Addr::BROADCAST
+
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv4Addr;
+ ///
+ /// // Most IPv4 addresses are globally reachable:
+ /// assert_eq!(Ipv4Addr::new(80, 9, 12, 3).is_global(), true);
+ ///
+ /// // However some addresses have been assigned a special meaning
+ /// // that makes them not globally reachable. Some examples are:
+ ///
+ /// // The unspecified address (`0.0.0.0`)
+ /// assert_eq!(Ipv4Addr::UNSPECIFIED.is_global(), false);
+ ///
+ /// // Addresses reserved for private use (`10.0.0.0/8`, `172.16.0.0/12`, 192.168.0.0/16)
+ /// assert_eq!(Ipv4Addr::new(10, 254, 0, 0).is_global(), false);
+ /// assert_eq!(Ipv4Addr::new(192, 168, 10, 65).is_global(), false);
+ /// assert_eq!(Ipv4Addr::new(172, 16, 10, 65).is_global(), false);
+ ///
+ /// // Addresses in the shared address space (`100.64.0.0/10`)
+ /// assert_eq!(Ipv4Addr::new(100, 100, 0, 0).is_global(), false);
+ ///
+ /// // The loopback addresses (`127.0.0.0/8`)
+ /// assert_eq!(Ipv4Addr::LOCALHOST.is_global(), false);
+ ///
+ /// // Link-local addresses (`169.254.0.0/16`)
+ /// assert_eq!(Ipv4Addr::new(169, 254, 45, 1).is_global(), false);
+ ///
+ /// // Addresses reserved for documentation (`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`)
+ /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).is_global(), false);
+ /// assert_eq!(Ipv4Addr::new(198, 51, 100, 65).is_global(), false);
+ /// assert_eq!(Ipv4Addr::new(203, 0, 113, 6).is_global(), false);
+ ///
+ /// // Addresses reserved for benchmarking (`198.18.0.0/15`)
+ /// assert_eq!(Ipv4Addr::new(198, 18, 0, 0).is_global(), false);
+ ///
+ /// // Reserved addresses (`240.0.0.0/4`)
+ /// assert_eq!(Ipv4Addr::new(250, 10, 20, 30).is_global(), false);
+ ///
+ /// // The broadcast address (`255.255.255.255`)
+ /// assert_eq!(Ipv4Addr::BROADCAST.is_global(), false);
+ ///
+ /// // For a complete overview see the IANA IPv4 Special-Purpose Address Registry.
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv4", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_global(&self) -> bool {
+ !(self.octets()[0] == 0 // "This network"
+ || self.is_private()
+ || self.is_shared()
+ || self.is_loopback()
+ || self.is_link_local()
+ // addresses reserved for future protocols (`192.0.0.0/24`)
+ ||(self.octets()[0] == 192 && self.octets()[1] == 0 && self.octets()[2] == 0)
+ || self.is_documentation()
+ || self.is_benchmarking()
+ || self.is_reserved()
+ || self.is_broadcast())
+ }
+
+ /// Returns [`true`] if this address is part of the Shared Address Space defined in
+ /// [IETF RFC 6598] (`100.64.0.0/10`).
+ ///
+ /// [IETF RFC 6598]: https://tools.ietf.org/html/rfc6598
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(100, 64, 0, 0).is_shared(), true);
+ /// assert_eq!(Ipv4Addr::new(100, 127, 255, 255).is_shared(), true);
+ /// assert_eq!(Ipv4Addr::new(100, 128, 0, 0).is_shared(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv4", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_shared(&self) -> bool {
+ self.octets()[0] == 100 && (self.octets()[1] & 0b1100_0000 == 0b0100_0000)
+ }
+
+ /// Returns [`true`] if this address part of the `198.18.0.0/15` range, which is reserved for
+ /// network devices benchmarking. This range is defined in [IETF RFC 2544] as `192.18.0.0`
+ /// through `198.19.255.255` but [errata 423] corrects it to `198.18.0.0/15`.
+ ///
+ /// [IETF RFC 2544]: https://tools.ietf.org/html/rfc2544
+ /// [errata 423]: https://www.rfc-editor.org/errata/eid423
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(198, 17, 255, 255).is_benchmarking(), false);
+ /// assert_eq!(Ipv4Addr::new(198, 18, 0, 0).is_benchmarking(), true);
+ /// assert_eq!(Ipv4Addr::new(198, 19, 255, 255).is_benchmarking(), true);
+ /// assert_eq!(Ipv4Addr::new(198, 20, 0, 0).is_benchmarking(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv4", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_benchmarking(&self) -> bool {
+ self.octets()[0] == 198 && (self.octets()[1] & 0xfe) == 18
+ }
+
+ /// Returns [`true`] if this address is reserved by IANA for future use. [IETF RFC 1112]
+ /// defines the block of reserved addresses as `240.0.0.0/4`. This range normally includes the
+ /// broadcast address `255.255.255.255`, but this implementation explicitly excludes it, since
+ /// it is obviously not reserved for future use.
+ ///
+ /// [IETF RFC 1112]: https://tools.ietf.org/html/rfc1112
+ ///
+ /// # Warning
+ ///
+ /// As IANA assigns new addresses, this method will be
+ /// updated. This may result in non-reserved addresses being
+ /// treated as reserved in code that relies on an outdated version
+ /// of this method.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(240, 0, 0, 0).is_reserved(), true);
+ /// assert_eq!(Ipv4Addr::new(255, 255, 255, 254).is_reserved(), true);
+ ///
+ /// assert_eq!(Ipv4Addr::new(239, 255, 255, 255).is_reserved(), false);
+ /// // The broadcast address is not considered as reserved for future use by this implementation
+ /// assert_eq!(Ipv4Addr::new(255, 255, 255, 255).is_reserved(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv4", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_reserved(&self) -> bool {
+ self.octets()[0] & 240 == 240 && !self.is_broadcast()
+ }
+
+ /// Returns [`true`] if this is a multicast address (`224.0.0.0/4`).
+ ///
+ /// Multicast addresses have a most significant octet between `224` and `239`,
+ /// and is defined by [IETF RFC 5771].
+ ///
+ /// [IETF RFC 5771]: https://tools.ietf.org/html/rfc5771
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(224, 254, 0, 0).is_multicast(), true);
+ /// assert_eq!(Ipv4Addr::new(236, 168, 10, 65).is_multicast(), true);
+ /// assert_eq!(Ipv4Addr::new(172, 16, 10, 65).is_multicast(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_multicast(&self) -> bool {
+ self.octets()[0] >= 224 && self.octets()[0] <= 239
+ }
+
+ /// Returns [`true`] if this is a broadcast address (`255.255.255.255`).
+ ///
+ /// A broadcast address has all octets set to `255` as defined in [IETF RFC 919].
+ ///
+ /// [IETF RFC 919]: https://tools.ietf.org/html/rfc919
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(255, 255, 255, 255).is_broadcast(), true);
+ /// assert_eq!(Ipv4Addr::new(236, 168, 10, 65).is_broadcast(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_broadcast(&self) -> bool {
+ u32::from_be_bytes(self.octets()) == u32::from_be_bytes(Self::BROADCAST.octets())
+ }
+
+ /// Returns [`true`] if this address is in a range designated for documentation.
+ ///
+ /// This is defined in [IETF RFC 5737]:
+ ///
+ /// - `192.0.2.0/24` (TEST-NET-1)
+ /// - `198.51.100.0/24` (TEST-NET-2)
+ /// - `203.0.113.0/24` (TEST-NET-3)
+ ///
+ /// [IETF RFC 5737]: https://tools.ietf.org/html/rfc5737
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).is_documentation(), true);
+ /// assert_eq!(Ipv4Addr::new(198, 51, 100, 65).is_documentation(), true);
+ /// assert_eq!(Ipv4Addr::new(203, 0, 113, 6).is_documentation(), true);
+ /// assert_eq!(Ipv4Addr::new(193, 34, 17, 19).is_documentation(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_documentation(&self) -> bool {
+ matches!(
+ self.octets(),
+ [192, 0, 2, _] | [198, 51, 100, _] | [203, 0, 113, _]
+ )
+ }
+
+ /// Converts this address to an [IPv4-compatible] [`IPv6` address].
+ ///
+ /// `a.b.c.d` becomes `::a.b.c.d`
+ ///
+ /// Note that IPv4-compatible addresses have been officially deprecated.
+ /// If you don't explicitly need an IPv4-compatible address for legacy reasons, consider using `to_ipv6_mapped` instead.
+ ///
+ /// [IPv4-compatible]: Ipv6Addr#ipv4-compatible-ipv6-addresses
+ /// [`IPv6` address]: Ipv6Addr
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(
+ /// Ipv4Addr::new(192, 0, 2, 255).to_ipv6_compatible(),
+ /// Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x2ff)
+ /// );
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub const fn to_ipv6_compatible(&self) -> Ipv6Addr {
+ let [a, b, c, d] = self.octets();
+ Ipv6Addr {
+ octets: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, a, b, c, d],
+ }
+ }
+
+ /// Converts this address to an [IPv4-mapped] [`IPv6` address].
+ ///
+ /// `a.b.c.d` becomes `::ffff:a.b.c.d`
+ ///
+ /// [IPv4-mapped]: Ipv6Addr#ipv4-mapped-ipv6-addresses
+ /// [`IPv6` address]: Ipv6Addr
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).to_ipv6_mapped(),
+ /// Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff));
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub const fn to_ipv6_mapped(&self) -> Ipv6Addr {
+ let [a, b, c, d] = self.octets();
+ Ipv6Addr {
+ octets: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, a, b, c, d],
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_ip", since = "1.16.0"))]
+impl From<Ipv4Addr> for IpAddr {
+ /// Copies this address to a new `IpAddr::V4`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr};
+ ///
+ /// let addr = Ipv4Addr::new(127, 0, 0, 1);
+ ///
+ /// assert_eq!(
+ /// IpAddr::V4(addr),
+ /// IpAddr::from(addr)
+ /// )
+ /// ```
+ #[inline]
+ fn from(ipv4: Ipv4Addr) -> IpAddr {
+ IpAddr::V4(ipv4)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_ip", since = "1.16.0"))]
+impl From<Ipv6Addr> for IpAddr {
+ /// Copies this address to a new `IpAddr::V6`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv6Addr};
+ ///
+ /// let addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff);
+ ///
+ /// assert_eq!(
+ /// IpAddr::V6(addr),
+ /// IpAddr::from(addr)
+ /// );
+ /// ```
+ #[inline]
+ fn from(ipv6: Ipv6Addr) -> IpAddr {
+ IpAddr::V6(ipv6)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialEq<Ipv4Addr> for IpAddr {
+ #[inline]
+ fn eq(&self, other: &Ipv4Addr) -> bool {
+ match self {
+ IpAddr::V4(v4) => v4 == other,
+ IpAddr::V6(_) => false,
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialEq<IpAddr> for Ipv4Addr {
+ #[inline]
+ fn eq(&self, other: &IpAddr) -> bool {
+ match other {
+ IpAddr::V4(v4) => self == v4,
+ IpAddr::V6(_) => false,
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+impl PartialOrd for Ipv4Addr {
+ #[inline]
+ fn partial_cmp(&self, other: &Ipv4Addr) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialOrd<Ipv4Addr> for IpAddr {
+ #[inline]
+ fn partial_cmp(&self, other: &Ipv4Addr) -> Option<Ordering> {
+ match self {
+ IpAddr::V4(v4) => v4.partial_cmp(other),
+ IpAddr::V6(_) => Some(Ordering::Greater),
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialOrd<IpAddr> for Ipv4Addr {
+ #[inline]
+ fn partial_cmp(&self, other: &IpAddr) -> Option<Ordering> {
+ match other {
+ IpAddr::V4(v4) => self.partial_cmp(v4),
+ IpAddr::V6(_) => Some(Ordering::Less),
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+impl Ord for Ipv4Addr {
+ #[inline]
+ fn cmp(&self, other: &Ipv4Addr) -> Ordering {
+ self.octets.cmp(&other.octets)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_u32", since = "1.1.0"))]
+impl From<Ipv4Addr> for u32 {
+ /// Converts an `Ipv4Addr` into a host byte order `u32`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::new(0x12, 0x34, 0x56, 0x78);
+ /// assert_eq!(0x12345678, u32::from(addr));
+ /// ```
+ #[inline]
+ fn from(ip: Ipv4Addr) -> u32 {
+ u32::from_be_bytes(ip.octets)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_u32", since = "1.1.0"))]
+impl From<u32> for Ipv4Addr {
+ /// Converts a host byte order `u32` into an `Ipv4Addr`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::from(0x12345678);
+ /// assert_eq!(Ipv4Addr::new(0x12, 0x34, 0x56, 0x78), addr);
+ /// ```
+ #[inline]
+ fn from(ip: u32) -> Ipv4Addr {
+ Ipv4Addr {
+ octets: ip.to_be_bytes(),
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "from_slice_v4", since = "1.9.0"))]
+impl From<[u8; 4]> for Ipv4Addr {
+ /// Creates an `Ipv4Addr` from a four element byte array.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv4Addr;
+ ///
+ /// let addr = Ipv4Addr::from([13u8, 12u8, 11u8, 10u8]);
+ /// assert_eq!(Ipv4Addr::new(13, 12, 11, 10), addr);
+ /// ```
+ #[inline]
+ fn from(octets: [u8; 4]) -> Ipv4Addr {
+ Ipv4Addr { octets }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_slice", since = "1.17.0"))]
+impl From<[u8; 4]> for IpAddr {
+ /// Creates an `IpAddr::V4` from a four element byte array.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr};
+ ///
+ /// let addr = IpAddr::from([13u8, 12u8, 11u8, 10u8]);
+ /// assert_eq!(IpAddr::V4(Ipv4Addr::new(13, 12, 11, 10)), addr);
+ /// ```
+ #[inline]
+ fn from(octets: [u8; 4]) -> IpAddr {
+ IpAddr::V4(Ipv4Addr::from(octets))
+ }
+}
+
+impl Ipv6Addr {
+ /// Creates a new IPv6 address from eight 16-bit segments.
+ ///
+ /// The result will represent the IP address `a:b:c:d:e:f:g:h`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_32", since = "1.32.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[allow(clippy::too_many_arguments)]
+ #[must_use]
+ #[inline]
+ pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr {
+ let addr16 = [
+ a.to_be(),
+ b.to_be(),
+ c.to_be(),
+ d.to_be(),
+ e.to_be(),
+ f.to_be(),
+ g.to_be(),
+ h.to_be(),
+ ];
+ Ipv6Addr {
+ // All elements in `addr16` are big endian.
+ // SAFETY: `[u16; 8]` is always safe to transmute to `[u8; 16]`.
+ octets: unsafe { transmute::<_, [u8; 16]>(addr16) },
+ }
+ }
+
+ /// An IPv6 address representing localhost: `::1`.
+ ///
+ /// This corresponds to constant `IN6ADDR_LOOPBACK_INIT` or `in6addr_loopback` in other
+ /// languages.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::LOCALHOST;
+ /// assert_eq!(addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
+ /// ```
+ #[doc(alias = "IN6ADDR_LOOPBACK_INIT")]
+ #[doc(alias = "in6addr_loopback")]
+ #[cfg_attr(staged_api, stable(feature = "ip_constructors", since = "1.30.0"))]
+ pub const LOCALHOST: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+
+ /// An IPv6 address representing the unspecified address: `::`
+ ///
+ /// This corresponds to constant `IN6ADDR_ANY_INIT` or `in6addr_any` in other languages.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::UNSPECIFIED;
+ /// assert_eq!(addr, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
+ /// ```
+ #[doc(alias = "IN6ADDR_ANY_INIT")]
+ #[doc(alias = "in6addr_any")]
+ #[cfg_attr(staged_api, stable(feature = "ip_constructors", since = "1.30.0"))]
+ pub const UNSPECIFIED: Self = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
+
+ /// Returns the eight 16-bit segments that make up this address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).segments(),
+ /// [0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff]);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn segments(&self) -> [u16; 8] {
+ // All elements in `self.octets` must be big endian.
+ // SAFETY: `[u8; 16]` is always safe to transmute to `[u16; 8]`.
+ let [a, b, c, d, e, f, g, h] = unsafe { transmute::<_, [u16; 8]>(self.octets) };
+ // We want native endian u16
+ [
+ u16::from_be(a),
+ u16::from_be(b),
+ u16::from_be(c),
+ u16::from_be(d),
+ u16::from_be(e),
+ u16::from_be(f),
+ u16::from_be(g),
+ u16::from_be(h),
+ ]
+ }
+
+ /// Returns [`true`] for the special 'unspecified' address (`::`).
+ ///
+ /// This property is defined in [IETF RFC 4291].
+ ///
+ /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unspecified(), false);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0).is_unspecified(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unspecified(&self) -> bool {
+ u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::UNSPECIFIED.octets())
+ }
+
+ /// Returns [`true`] if this is the [loopback address] (`::1`),
+ /// as defined in [IETF RFC 4291 section 2.5.3].
+ ///
+ /// Contrary to IPv4, in IPv6 there is only one loopback address.
+ ///
+ /// [loopback address]: Ipv6Addr::LOCALHOST
+ /// [IETF RFC 4291 section 2.5.3]: https://tools.ietf.org/html/rfc4291#section-2.5.3
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_loopback(), false);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1).is_loopback(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_loopback(&self) -> bool {
+ u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets())
+ }
+
+ /// Returns [`true`] if the address appears to be globally reachable
+ /// as specified by the [IANA IPv6 Special-Purpose Address Registry].
+ /// Whether or not an address is practically reachable will depend on your network configuration.
+ ///
+ /// Most IPv6 addresses are globally reachable;
+ /// unless they are specifically defined as *not* globally reachable.
+ ///
+ /// Non-exhaustive list of notable addresses that are not globally reachable:
+ /// - The [unspecified address] ([`is_unspecified`](Ipv6Addr::is_unspecified))
+ /// - The [loopback address] ([`is_loopback`](Ipv6Addr::is_loopback))
+ /// - IPv4-mapped addresses
+ /// - Addresses reserved for benchmarking
+ /// - Addresses reserved for documentation ([`is_documentation`](Ipv6Addr::is_documentation))
+ /// - Unique local addresses ([`is_unique_local`](Ipv6Addr::is_unique_local))
+ /// - Unicast addresses with link-local scope ([`is_unicast_link_local`](Ipv6Addr::is_unicast_link_local))
+ ///
+ /// For the complete overview of which addresses are globally reachable, see the table at the [IANA IPv6 Special-Purpose Address Registry].
+ ///
+ /// Note that an address having global scope is not the same as being globally reachable,
+ /// and there is no direct relation between the two concepts: There exist addresses with global scope
+ /// that are not globally reachable (for example unique local addresses),
+ /// and addresses that are globally reachable without having global scope
+ /// (multicast addresses with non-global scope).
+ ///
+ /// [IANA IPv6 Special-Purpose Address Registry]: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
+ /// [unspecified address]: Ipv6Addr::UNSPECIFIED
+ /// [loopback address]: Ipv6Addr::LOCALHOST
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// // Most IPv6 addresses are globally reachable:
+ /// assert_eq!(Ipv6Addr::new(0x26, 0, 0x1c9, 0, 0, 0xafc8, 0x10, 0x1).is_global(), true);
+ ///
+ /// // However some addresses have been assigned a special meaning
+ /// // that makes them not globally reachable. Some examples are:
+ ///
+ /// // The unspecified address (`::`)
+ /// assert_eq!(Ipv6Addr::UNSPECIFIED.is_global(), false);
+ ///
+ /// // The loopback address (`::1`)
+ /// assert_eq!(Ipv6Addr::LOCALHOST.is_global(), false);
+ ///
+ /// // IPv4-mapped addresses (`::ffff:0:0/96`)
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_global(), false);
+ ///
+ /// // Addresses reserved for benchmarking (`2001:2::/48`)
+ /// assert_eq!(Ipv6Addr::new(0x2001, 2, 0, 0, 0, 0, 0, 1,).is_global(), false);
+ ///
+ /// // Addresses reserved for documentation (`2001:db8::/32`)
+ /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1).is_global(), false);
+ ///
+ /// // Unique local addresses (`fc00::/7`)
+ /// assert_eq!(Ipv6Addr::new(0xfc02, 0, 0, 0, 0, 0, 0, 1).is_global(), false);
+ ///
+ /// // Unicast addresses with link-local scope (`fe80::/10`)
+ /// assert_eq!(Ipv6Addr::new(0xfe81, 0, 0, 0, 0, 0, 0, 1).is_global(), false);
+ ///
+ /// // For a complete overview see the IANA IPv6 Special-Purpose Address Registry.
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_global(&self) -> bool {
+ !(self.is_unspecified()
+ || self.is_loopback()
+ // IPv4-mapped Address (`::ffff:0:0/96`)
+ || matches!(self.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
+ // IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
+ || matches!(self.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
+ // Discard-Only Address Block (`100::/64`)
+ || matches!(self.segments(), [0x100, 0, 0, 0, _, _, _, _])
+ // IETF Protocol Assignments (`2001::/23`)
+ || (matches!(self.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
+ && !(
+ // Port Control Protocol Anycast (`2001:1::1`)
+ u128::from_be_bytes(self.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
+ // Traversal Using Relays around NAT Anycast (`2001:1::2`)
+ || u128::from_be_bytes(self.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
+ // AMT (`2001:3::/32`)
+ || matches!(self.segments(), [0x2001, 3, _, _, _, _, _, _])
+ // AS112-v6 (`2001:4:112::/48`)
+ || matches!(self.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
+ // ORCHIDv2 (`2001:20::/28`)
+ || matches!(self.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x2F)
+ ))
+ || self.is_documentation()
+ || self.is_unique_local()
+ || self.is_unicast_link_local())
+ }
+
+ /// Returns [`true`] if this is a unique local address (`fc00::/7`).
+ ///
+ /// This property is defined in [IETF RFC 4193].
+ ///
+ /// [IETF RFC 4193]: https://tools.ietf.org/html/rfc4193
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unique_local(), false);
+ /// assert_eq!(Ipv6Addr::new(0xfc02, 0, 0, 0, 0, 0, 0, 0).is_unique_local(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unique_local(&self) -> bool {
+ (self.segments()[0] & 0xfe00) == 0xfc00
+ }
+
+ /// Returns [`true`] if this is a unicast address, as defined by [IETF RFC 4291].
+ /// Any address that is not a [multicast address] (`ff00::/8`) is unicast.
+ ///
+ /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291
+ /// [multicast address]: Ipv6Addr::is_multicast
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// // The unspecified and loopback addresses are unicast.
+ /// assert_eq!(Ipv6Addr::UNSPECIFIED.is_unicast(), true);
+ /// assert_eq!(Ipv6Addr::LOCALHOST.is_unicast(), true);
+ ///
+ /// // Any address that is not a multicast address (`ff00::/8`) is unicast.
+ /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast(), true);
+ /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).is_unicast(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unicast(&self) -> bool {
+ !self.is_multicast()
+ }
+
+ /// Returns `true` if the address is a unicast address with link-local scope,
+ /// as defined in [RFC 4291].
+ ///
+ /// A unicast address has link-local scope if it has the prefix `fe80::/10`, as per [RFC 4291 section 2.4].
+ /// Note that this encompasses more addresses than those defined in [RFC 4291 section 2.5.6],
+ /// which describes "Link-Local IPv6 Unicast Addresses" as having the following stricter format:
+ ///
+ /// ```text
+ /// | 10 bits | 54 bits | 64 bits |
+ /// +----------+-------------------------+----------------------------+
+ /// |1111111010| 0 | interface ID |
+ /// +----------+-------------------------+----------------------------+
+ /// ```
+ /// So while currently the only addresses with link-local scope an application will encounter are all in `fe80::/64`,
+ /// this might change in the future with the publication of new standards. More addresses in `fe80::/10` could be allocated,
+ /// and those addresses will have link-local scope.
+ ///
+ /// Also note that while [RFC 4291 section 2.5.3] mentions about the [loopback address] (`::1`) that "it is treated as having Link-Local scope",
+ /// this does not mean that the loopback address actually has link-local scope and this method will return `false` on it.
+ ///
+ /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
+ /// [RFC 4291 section 2.4]: https://tools.ietf.org/html/rfc4291#section-2.4
+ /// [RFC 4291 section 2.5.3]: https://tools.ietf.org/html/rfc4291#section-2.5.3
+ /// [RFC 4291 section 2.5.6]: https://tools.ietf.org/html/rfc4291#section-2.5.6
+ /// [loopback address]: Ipv6Addr::LOCALHOST
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// // The loopback address (`::1`) does not actually have link-local scope.
+ /// assert_eq!(Ipv6Addr::LOCALHOST.is_unicast_link_local(), false);
+ ///
+ /// // Only addresses in `fe80::/10` have link-local scope.
+ /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), false);
+ /// assert_eq!(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), true);
+ ///
+ /// // Addresses outside the stricter `fe80::/64` also have link-local scope.
+ /// assert_eq!(Ipv6Addr::new(0xfe80, 0, 0, 1, 0, 0, 0, 0).is_unicast_link_local(), true);
+ /// assert_eq!(Ipv6Addr::new(0xfe81, 0, 0, 0, 0, 0, 0, 0).is_unicast_link_local(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unicast_link_local(&self) -> bool {
+ (self.segments()[0] & 0xffc0) == 0xfe80
+ }
+
+ /// Returns [`true`] if this is an address reserved for documentation
+ /// (`2001:db8::/32`).
+ ///
+ /// This property is defined in [IETF RFC 3849].
+ ///
+ /// [IETF RFC 3849]: https://tools.ietf.org/html/rfc3849
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_documentation(), false);
+ /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_documentation(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_documentation(&self) -> bool {
+ (self.segments()[0] == 0x2001) && (self.segments()[1] == 0xdb8)
+ }
+
+ /// Returns [`true`] if this is an address reserved for benchmarking (`2001:2::/48`).
+ ///
+ /// This property is defined in [IETF RFC 5180], where it is mistakenly specified as covering the range `2001:0200::/48`.
+ /// This is corrected in [IETF RFC Errata 1752] to `2001:0002::/48`.
+ ///
+ /// [IETF RFC 5180]: https://tools.ietf.org/html/rfc5180
+ /// [IETF RFC Errata 1752]: https://www.rfc-editor.org/errata_search.php?eid=1752
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc613, 0x0).is_benchmarking(), false);
+ /// assert_eq!(Ipv6Addr::new(0x2001, 0x2, 0, 0, 0, 0, 0, 0).is_benchmarking(), true);
+ /// ```
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_benchmarking(&self) -> bool {
+ (self.segments()[0] == 0x2001) && (self.segments()[1] == 0x2) && (self.segments()[2] == 0)
+ }
+
+ /// Returns [`true`] if the address is a globally routable unicast address.
+ ///
+ /// The following return false:
+ ///
+ /// - the loopback address
+ /// - the link-local addresses
+ /// - unique local addresses
+ /// - the unspecified address
+ /// - the address range reserved for documentation
+ ///
+ /// This method returns [`true`] for site-local addresses as per [RFC 4291 section 2.5.7]
+ ///
+ /// ```no_rust
+ /// The special behavior of [the site-local unicast] prefix defined in [RFC3513] must no longer
+ /// be supported in new implementations (i.e., new implementations must treat this prefix as
+ /// Global Unicast).
+ /// ```
+ ///
+ /// [RFC 4291 section 2.5.7]: https://tools.ietf.org/html/rfc4291#section-2.5.7
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0).is_unicast_global(), false);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_unicast_global(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_unicast_global(&self) -> bool {
+ self.is_unicast()
+ && !self.is_loopback()
+ && !self.is_unicast_link_local()
+ && !self.is_unique_local()
+ && !self.is_unspecified()
+ && !self.is_documentation()
+ && !self.is_benchmarking()
+ }
+
+ /// Returns the address' multicast scope if the address is multicast.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ ///
+ /// use std::net::{Ipv6Addr, Ipv6MulticastScope};
+ ///
+ /// assert_eq!(
+ /// Ipv6Addr::new(0xff0e, 0, 0, 0, 0, 0, 0, 0).multicast_scope(),
+ /// Some(Ipv6MulticastScope::Global)
+ /// );
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).multicast_scope(), None);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use]
+ #[inline]
+ pub const fn multicast_scope(&self) -> Option<Ipv6MulticastScope> {
+ if self.is_multicast() {
+ match self.segments()[0] & 0x000f {
+ 1 => Some(Ipv6MulticastScope::InterfaceLocal),
+ 2 => Some(Ipv6MulticastScope::LinkLocal),
+ 3 => Some(Ipv6MulticastScope::RealmLocal),
+ 4 => Some(Ipv6MulticastScope::AdminLocal),
+ 5 => Some(Ipv6MulticastScope::SiteLocal),
+ 8 => Some(Ipv6MulticastScope::OrganizationLocal),
+ 14 => Some(Ipv6MulticastScope::Global),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ /// Returns [`true`] if this is a multicast address (`ff00::/8`).
+ ///
+ /// This property is defined by [IETF RFC 4291].
+ ///
+ /// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).is_multicast(), true);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_multicast(), false);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(since = "1.7.0", feature = "ip_17"))]
+ #[must_use]
+ #[inline]
+ pub const fn is_multicast(&self) -> bool {
+ (self.segments()[0] & 0xff00) == 0xff00
+ }
+
+ /// Converts this address to an [`IPv4` address] if it's an [IPv4-mapped] address,
+ /// as defined in [IETF RFC 4291 section 2.5.5.2], otherwise returns [`None`].
+ ///
+ /// `::ffff:a.b.c.d` becomes `a.b.c.d`.
+ /// All addresses *not* starting with `::ffff` will return `None`.
+ ///
+ /// [`IPv4` address]: Ipv4Addr
+ /// [IPv4-mapped]: Ipv6Addr
+ /// [IETF RFC 4291 section 2.5.5.2]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).to_ipv4_mapped(), None);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4_mapped(),
+ /// Some(Ipv4Addr::new(192, 10, 2, 255)));
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_ipv4_mapped(), None);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ipv6_to_ipv4_mapped", since = "1.63.0"))]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub const fn to_ipv4_mapped(&self) -> Option<Ipv4Addr> {
+ match self.octets() {
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => {
+ Some(Ipv4Addr::new(a, b, c, d))
+ }
+ _ => None,
+ }
+ }
+
+ /// Converts this address to an [`IPv4` address] if it is either
+ /// an [IPv4-compatible] address as defined in [IETF RFC 4291 section 2.5.5.1],
+ /// or an [IPv4-mapped] address as defined in [IETF RFC 4291 section 2.5.5.2],
+ /// otherwise returns [`None`].
+ ///
+ /// Note that this will return an [`IPv4` address] for the IPv6 loopback address `::1`. Use
+ /// [`Ipv6Addr::to_ipv4_mapped`] to avoid this.
+ ///
+ /// `::a.b.c.d` and `::ffff:a.b.c.d` become `a.b.c.d`. `::1` becomes `0.0.0.1`.
+ /// All addresses *not* starting with either all zeroes or `::ffff` will return `None`.
+ ///
+ /// [`IPv4` address]: Ipv4Addr
+ /// [IPv4-compatible]: Ipv6Addr#ipv4-compatible-ipv6-addresses
+ /// [IPv4-mapped]: Ipv6Addr#ipv4-mapped-ipv6-addresses
+ /// [IETF RFC 4291 section 2.5.5.1]: https://tools.ietf.org/html/rfc4291#section-2.5.5.1
+ /// [IETF RFC 4291 section 2.5.5.2]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{Ipv4Addr, Ipv6Addr};
+ ///
+ /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).to_ipv4(), None);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4(),
+ /// Some(Ipv4Addr::new(192, 10, 2, 255)));
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_ipv4(),
+ /// Some(Ipv4Addr::new(0, 0, 0, 1)));
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_50", since = "1.50.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub const fn to_ipv4(&self) -> Option<Ipv4Addr> {
+ if let [0, 0, 0, 0, 0, 0 | 0xffff, ab, cd] = self.segments() {
+ let [a, b] = ab.to_be_bytes();
+ let [c, d] = cd.to_be_bytes();
+ Some(Ipv4Addr::new(a, b, c, d))
+ } else {
+ None
+ }
+ }
+
+ /// Converts this address to an `IpAddr::V4` if it is an IPv4-mapped addresses, otherwise it
+ /// returns self wrapped in an `IpAddr::V6`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// #![feature(ip)]
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1).is_loopback(), false);
+ /// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1).to_canonical().is_loopback(), true);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_ipv6", issue = "76205")
+ )]
+ #[cfg_attr(staged_api, unstable(feature = "ip", issue = "27709"))]
+ #[must_use = "this returns the result of the operation, \
+ without modifying the original"]
+ #[inline]
+ pub const fn to_canonical(&self) -> IpAddr {
+ if let Some(mapped) = self.to_ipv4_mapped() {
+ return IpAddr::V4(mapped);
+ }
+ IpAddr::V6(*self)
+ }
+
+ /// Returns the sixteen eight-bit integers the IPv6 address consists of.
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).octets(),
+ /// [255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+ /// ```
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "const_ip_32", since = "1.32.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "ipv6_to_octets", since = "1.12.0"))]
+ #[must_use]
+ #[inline]
+ pub const fn octets(&self) -> [u8; 16] {
+ self.octets
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialEq<IpAddr> for Ipv6Addr {
+ #[inline]
+ fn eq(&self, other: &IpAddr) -> bool {
+ match other {
+ IpAddr::V4(_) => false,
+ IpAddr::V6(v6) => self == v6,
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialEq<Ipv6Addr> for IpAddr {
+ #[inline]
+ fn eq(&self, other: &Ipv6Addr) -> bool {
+ match self {
+ IpAddr::V4(_) => false,
+ IpAddr::V6(v6) => v6 == other,
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+impl PartialOrd for Ipv6Addr {
+ #[inline]
+ fn partial_cmp(&self, other: &Ipv6Addr) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialOrd<Ipv6Addr> for IpAddr {
+ #[inline]
+ fn partial_cmp(&self, other: &Ipv6Addr) -> Option<Ordering> {
+ match self {
+ IpAddr::V4(_) => Some(Ordering::Less),
+ IpAddr::V6(v6) => v6.partial_cmp(other),
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_cmp", since = "1.16.0"))]
+impl PartialOrd<IpAddr> for Ipv6Addr {
+ #[inline]
+ fn partial_cmp(&self, other: &IpAddr) -> Option<Ordering> {
+ match other {
+ IpAddr::V4(_) => Some(Ordering::Greater),
+ IpAddr::V6(v6) => self.partial_cmp(v6),
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+impl Ord for Ipv6Addr {
+ #[inline]
+ fn cmp(&self, other: &Ipv6Addr) -> Ordering {
+ self.segments().cmp(&other.segments())
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "i128", since = "1.26.0"))]
+impl From<Ipv6Addr> for u128 {
+ /// Convert an `Ipv6Addr` into a host byte order `u128`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::new(
+ /// 0x1020, 0x3040, 0x5060, 0x7080,
+ /// 0x90A0, 0xB0C0, 0xD0E0, 0xF00D,
+ /// );
+ /// assert_eq!(0x102030405060708090A0B0C0D0E0F00D_u128, u128::from(addr));
+ /// ```
+ #[inline]
+ fn from(ip: Ipv6Addr) -> u128 {
+ u128::from_be_bytes(ip.octets)
+ }
+}
+#[cfg_attr(staged_api, stable(feature = "i128", since = "1.26.0"))]
+impl From<u128> for Ipv6Addr {
+ /// Convert a host byte order `u128` into an `Ipv6Addr`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::from(0x102030405060708090A0B0C0D0E0F00D_u128);
+ /// assert_eq!(
+ /// Ipv6Addr::new(
+ /// 0x1020, 0x3040, 0x5060, 0x7080,
+ /// 0x90A0, 0xB0C0, 0xD0E0, 0xF00D,
+ /// ),
+ /// addr);
+ /// ```
+ #[inline]
+ fn from(ip: u128) -> Ipv6Addr {
+ Ipv6Addr::from(ip.to_be_bytes())
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ipv6_from_octets", since = "1.9.0"))]
+impl From<[u8; 16]> for Ipv6Addr {
+ /// Creates an `Ipv6Addr` from a sixteen element byte array.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::from([
+ /// 25u8, 24u8, 23u8, 22u8, 21u8, 20u8, 19u8, 18u8,
+ /// 17u8, 16u8, 15u8, 14u8, 13u8, 12u8, 11u8, 10u8,
+ /// ]);
+ /// assert_eq!(
+ /// Ipv6Addr::new(
+ /// 0x1918, 0x1716,
+ /// 0x1514, 0x1312,
+ /// 0x1110, 0x0f0e,
+ /// 0x0d0c, 0x0b0a
+ /// ),
+ /// addr
+ /// );
+ /// ```
+ #[inline]
+ fn from(octets: [u8; 16]) -> Ipv6Addr {
+ Ipv6Addr { octets }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ipv6_from_segments", since = "1.16.0"))]
+impl From<[u16; 8]> for Ipv6Addr {
+ /// Creates an `Ipv6Addr` from an eight element 16-bit array.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::Ipv6Addr;
+ ///
+ /// let addr = Ipv6Addr::from([
+ /// 525u16, 524u16, 523u16, 522u16,
+ /// 521u16, 520u16, 519u16, 518u16,
+ /// ]);
+ /// assert_eq!(
+ /// Ipv6Addr::new(
+ /// 0x20d, 0x20c,
+ /// 0x20b, 0x20a,
+ /// 0x209, 0x208,
+ /// 0x207, 0x206
+ /// ),
+ /// addr
+ /// );
+ /// ```
+ #[inline]
+ fn from(segments: [u16; 8]) -> Ipv6Addr {
+ let [a, b, c, d, e, f, g, h] = segments;
+ Ipv6Addr::new(a, b, c, d, e, f, g, h)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_slice", since = "1.17.0"))]
+impl From<[u8; 16]> for IpAddr {
+ /// Creates an `IpAddr::V6` from a sixteen element byte array.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv6Addr};
+ ///
+ /// let addr = IpAddr::from([
+ /// 25u8, 24u8, 23u8, 22u8, 21u8, 20u8, 19u8, 18u8,
+ /// 17u8, 16u8, 15u8, 14u8, 13u8, 12u8, 11u8, 10u8,
+ /// ]);
+ /// assert_eq!(
+ /// IpAddr::V6(Ipv6Addr::new(
+ /// 0x1918, 0x1716,
+ /// 0x1514, 0x1312,
+ /// 0x1110, 0x0f0e,
+ /// 0x0d0c, 0x0b0a
+ /// )),
+ /// addr
+ /// );
+ /// ```
+ #[inline]
+ fn from(octets: [u8; 16]) -> IpAddr {
+ IpAddr::V6(Ipv6Addr::from(octets))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_slice", since = "1.17.0"))]
+impl From<[u16; 8]> for IpAddr {
+ /// Creates an `IpAddr::V6` from an eight element 16-bit array.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv6Addr};
+ ///
+ /// let addr = IpAddr::from([
+ /// 525u16, 524u16, 523u16, 522u16,
+ /// 521u16, 520u16, 519u16, 518u16,
+ /// ]);
+ /// assert_eq!(
+ /// IpAddr::V6(Ipv6Addr::new(
+ /// 0x20d, 0x20c,
+ /// 0x20b, 0x20a,
+ /// 0x209, 0x208,
+ /// 0x207, 0x206
+ /// )),
+ /// addr
+ /// );
+ /// ```
+ #[inline]
+ fn from(segments: [u16; 8]) -> IpAddr {
+ IpAddr::V6(Ipv6Addr::from(segments))
+ }
+}
diff --git a/src/maybe_polyfill/no_std/net/mod.rs b/src/maybe_polyfill/no_std/net/mod.rs
new file mode 100644
index 0000000..06e8f94
--- /dev/null
+++ b/src/maybe_polyfill/no_std/net/mod.rs
@@ -0,0 +1,6 @@
+mod ip_addr;
+mod socket_addr;
+
+#[allow(unused_imports)]
+pub use self::ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
+pub use self::socket_addr::{SocketAddr, SocketAddrV4, SocketAddrV6};
diff --git a/src/maybe_polyfill/no_std/net/socket_addr.rs b/src/maybe_polyfill/no_std/net/socket_addr.rs
new file mode 100644
index 0000000..053d8f6
--- /dev/null
+++ b/src/maybe_polyfill/no_std/net/socket_addr.rs
@@ -0,0 +1,641 @@
+//! The following is derived from Rust's
+//! library/std/src/net/socket_addr.rs at revision
+//! bd20fc1fd657b32f7aa1d70d8723f04c87f21606.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+//!
+//! This defines `SocketAddr`, `SocketAddrV4`, and `SocketAddrV6` in a
+//! platform-independent way. It is not the native representation.
+
+use super::ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr};
+use core::cmp::Ordering;
+use core::hash;
+
+/// An internet socket address, either IPv4 or IPv6.
+///
+/// Internet socket addresses consist of an [IP address], a 16-bit port number, as well
+/// as possibly some version-dependent additional information. See [`SocketAddrV4`]'s and
+/// [`SocketAddrV6`]'s respective documentation for more details.
+///
+/// The size of a `SocketAddr` instance may vary depending on the target operating
+/// system.
+///
+/// [IP address]: IpAddr
+///
+/// # Examples
+///
+/// ```
+/// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+///
+/// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+///
+/// assert_eq!("127.0.0.1:8080".parse(), Ok(socket));
+/// assert_eq!(socket.port(), 8080);
+/// assert_eq!(socket.is_ipv4(), true);
+/// ```
+#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub enum SocketAddr {
+ /// An IPv4 socket address.
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ V4(#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))] SocketAddrV4),
+ /// An IPv6 socket address.
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ V6(#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))] SocketAddrV6),
+}
+
+/// An IPv4 socket address.
+///
+/// IPv4 socket addresses consist of an [`IPv4` address] and a 16-bit port number, as
+/// stated in [IETF RFC 793].
+///
+/// See [`SocketAddr`] for a type encompassing both IPv4 and IPv6 socket addresses.
+///
+/// The size of a `SocketAddrV4` struct may vary depending on the target operating
+/// system. Do not assume that this type has the same memory layout as the underlying
+/// system representation.
+///
+/// [IETF RFC 793]: https://tools.ietf.org/html/rfc793
+/// [`IPv4` address]: Ipv4Addr
+///
+/// # Examples
+///
+/// ```
+/// use std::net::{Ipv4Addr, SocketAddrV4};
+///
+/// let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
+///
+/// assert_eq!("127.0.0.1:8080".parse(), Ok(socket));
+/// assert_eq!(socket.ip(), &Ipv4Addr::new(127, 0, 0, 1));
+/// assert_eq!(socket.port(), 8080);
+/// ```
+#[derive(Copy, Clone, Eq, PartialEq)]
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub struct SocketAddrV4 {
+ ip: Ipv4Addr,
+ port: u16,
+}
+
+/// An IPv6 socket address.
+///
+/// IPv6 socket addresses consist of an [`IPv6` address], a 16-bit port number, as well
+/// as fields containing the traffic class, the flow label, and a scope identifier
+/// (see [IETF RFC 2553, Section 3.3] for more details).
+///
+/// See [`SocketAddr`] for a type encompassing both IPv4 and IPv6 socket addresses.
+///
+/// The size of a `SocketAddrV6` struct may vary depending on the target operating
+/// system. Do not assume that this type has the same memory layout as the underlying
+/// system representation.
+///
+/// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3
+/// [`IPv6` address]: Ipv6Addr
+///
+/// # Examples
+///
+/// ```
+/// use std::net::{Ipv6Addr, SocketAddrV6};
+///
+/// let socket = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
+///
+/// assert_eq!("[2001:db8::1]:8080".parse(), Ok(socket));
+/// assert_eq!(socket.ip(), &Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
+/// assert_eq!(socket.port(), 8080);
+/// ```
+#[derive(Copy, Clone, Eq, PartialEq)]
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub struct SocketAddrV6 {
+ ip: Ipv6Addr,
+ port: u16,
+ flowinfo: u32,
+ scope_id: u32,
+}
+
+impl SocketAddr {
+ /// Creates a new socket address from an [IP address] and a port number.
+ ///
+ /// [IP address]: IpAddr
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+ /// assert_eq!(socket.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
+ /// assert_eq!(socket.port(), 8080);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))]
+ #[must_use]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn new(ip: IpAddr, port: u16) -> SocketAddr {
+ match ip {
+ IpAddr::V4(a) => SocketAddr::V4(SocketAddrV4::new(a, port)),
+ IpAddr::V6(a) => SocketAddr::V6(SocketAddrV6::new(a, port, 0, 0)),
+ }
+ }
+
+ /// Returns the IP address associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+ /// assert_eq!(socket.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "ip_addr", since = "1.7.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn ip(&self) -> IpAddr {
+ match *self {
+ SocketAddr::V4(ref a) => IpAddr::V4(*a.ip()),
+ SocketAddr::V6(ref a) => IpAddr::V6(*a.ip()),
+ }
+ }
+
+ /// Changes the IP address associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let mut socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+ /// socket.set_ip(IpAddr::V4(Ipv4Addr::new(10, 10, 0, 1)));
+ /// assert_eq!(socket.ip(), IpAddr::V4(Ipv4Addr::new(10, 10, 0, 1)));
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_ip(&mut self, new_ip: IpAddr) {
+ // `match (*self, new_ip)` would have us mutate a copy of self only to throw it away.
+ match (self, new_ip) {
+ (&mut SocketAddr::V4(ref mut a), IpAddr::V4(new_ip)) => a.set_ip(new_ip),
+ (&mut SocketAddr::V6(ref mut a), IpAddr::V6(new_ip)) => a.set_ip(new_ip),
+ (self_, new_ip) => *self_ = Self::new(new_ip, self_.port()),
+ }
+ }
+
+ /// Returns the port number associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+ /// assert_eq!(socket.port(), 8080);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn port(&self) -> u16 {
+ match *self {
+ SocketAddr::V4(ref a) => a.port(),
+ SocketAddr::V6(ref a) => a.port(),
+ }
+ }
+
+ /// Changes the port number associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let mut socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+ /// socket.set_port(1025);
+ /// assert_eq!(socket.port(), 1025);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_port(&mut self, new_port: u16) {
+ match *self {
+ SocketAddr::V4(ref mut a) => a.set_port(new_port),
+ SocketAddr::V6(ref mut a) => a.set_port(new_port),
+ }
+ }
+
+ /// Returns [`true`] if the [IP address] in this `SocketAddr` is an
+ /// [`IPv4` address], and [`false`] otherwise.
+ ///
+ /// [IP address]: IpAddr
+ /// [`IPv4` address]: IpAddr::V4
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
+ /// assert_eq!(socket.is_ipv4(), true);
+ /// assert_eq!(socket.is_ipv6(), false);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_checker", since = "1.16.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn is_ipv4(&self) -> bool {
+ matches!(*self, SocketAddr::V4(_))
+ }
+
+ /// Returns [`true`] if the [IP address] in this `SocketAddr` is an
+ /// [`IPv6` address], and [`false`] otherwise.
+ ///
+ /// [IP address]: IpAddr
+ /// [`IPv6` address]: IpAddr::V6
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{IpAddr, Ipv6Addr, SocketAddr};
+ ///
+ /// let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 65535, 0, 1)), 8080);
+ /// assert_eq!(socket.is_ipv4(), false);
+ /// assert_eq!(socket.is_ipv6(), true);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_checker", since = "1.16.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn is_ipv6(&self) -> bool {
+ matches!(*self, SocketAddr::V6(_))
+ }
+}
+
+impl SocketAddrV4 {
+ /// Creates a new socket address from an [`IPv4` address] and a port number.
+ ///
+ /// [`IPv4` address]: Ipv4Addr
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV4, Ipv4Addr};
+ ///
+ /// let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn new(ip: Ipv4Addr, port: u16) -> SocketAddrV4 {
+ SocketAddrV4 { ip, port }
+ }
+
+ /// Returns the IP address associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV4, Ipv4Addr};
+ ///
+ /// let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
+ /// assert_eq!(socket.ip(), &Ipv4Addr::new(127, 0, 0, 1));
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn ip(&self) -> &Ipv4Addr {
+ &self.ip
+ }
+
+ /// Changes the IP address associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV4, Ipv4Addr};
+ ///
+ /// let mut socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
+ /// socket.set_ip(Ipv4Addr::new(192, 168, 0, 1));
+ /// assert_eq!(socket.ip(), &Ipv4Addr::new(192, 168, 0, 1));
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_ip(&mut self, new_ip: Ipv4Addr) {
+ self.ip = new_ip;
+ }
+
+ /// Returns the port number associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV4, Ipv4Addr};
+ ///
+ /// let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
+ /// assert_eq!(socket.port(), 8080);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn port(&self) -> u16 {
+ self.port
+ }
+
+ /// Changes the port number associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV4, Ipv4Addr};
+ ///
+ /// let mut socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);
+ /// socket.set_port(4242);
+ /// assert_eq!(socket.port(), 4242);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_port(&mut self, new_port: u16) {
+ self.port = new_port;
+ }
+}
+
+impl SocketAddrV6 {
+ /// Creates a new socket address from an [`IPv6` address], a 16-bit port number,
+ /// and the `flowinfo` and `scope_id` fields.
+ ///
+ /// For more information on the meaning and layout of the `flowinfo` and `scope_id`
+ /// parameters, see [IETF RFC 2553, Section 3.3].
+ ///
+ /// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3
+ /// [`IPv6` address]: Ipv6Addr
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[must_use]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn new(ip: Ipv6Addr, port: u16, flowinfo: u32, scope_id: u32) -> SocketAddrV6 {
+ SocketAddrV6 {
+ ip,
+ port,
+ flowinfo,
+ scope_id,
+ }
+ }
+
+ /// Returns the IP address associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
+ /// assert_eq!(socket.ip(), &Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn ip(&self) -> &Ipv6Addr {
+ &self.ip
+ }
+
+ /// Changes the IP address associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let mut socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
+ /// socket.set_ip(Ipv6Addr::new(76, 45, 0, 0, 0, 0, 0, 0));
+ /// assert_eq!(socket.ip(), &Ipv6Addr::new(76, 45, 0, 0, 0, 0, 0, 0));
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_ip(&mut self, new_ip: Ipv6Addr) {
+ self.ip = new_ip;
+ }
+
+ /// Returns the port number associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
+ /// assert_eq!(socket.port(), 8080);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn port(&self) -> u16 {
+ self.port
+ }
+
+ /// Changes the port number associated with this socket address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let mut socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
+ /// socket.set_port(4242);
+ /// assert_eq!(socket.port(), 4242);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_port(&mut self, new_port: u16) {
+ self.port = new_port;
+ }
+
+ /// Returns the flow information associated with this address.
+ ///
+ /// This information corresponds to the `sin6_flowinfo` field in C's `netinet/in.h`,
+ /// as specified in [IETF RFC 2553, Section 3.3].
+ /// It combines information about the flow label and the traffic class as specified
+ /// in [IETF RFC 2460], respectively [Section 6] and [Section 7].
+ ///
+ /// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3
+ /// [IETF RFC 2460]: https://tools.ietf.org/html/rfc2460
+ /// [Section 6]: https://tools.ietf.org/html/rfc2460#section-6
+ /// [Section 7]: https://tools.ietf.org/html/rfc2460#section-7
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 10, 0);
+ /// assert_eq!(socket.flowinfo(), 10);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn flowinfo(&self) -> u32 {
+ self.flowinfo
+ }
+
+ /// Changes the flow information associated with this socket address.
+ ///
+ /// See [`SocketAddrV6::flowinfo`]'s documentation for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let mut socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 10, 0);
+ /// socket.set_flowinfo(56);
+ /// assert_eq!(socket.flowinfo(), 56);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_flowinfo(&mut self, new_flowinfo: u32) {
+ self.flowinfo = new_flowinfo;
+ }
+
+ /// Returns the scope ID associated with this address.
+ ///
+ /// This information corresponds to the `sin6_scope_id` field in C's `netinet/in.h`,
+ /// as specified in [IETF RFC 2553, Section 3.3].
+ ///
+ /// [IETF RFC 2553, Section 3.3]: https://tools.ietf.org/html/rfc2553#section-3.3
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 78);
+ /// assert_eq!(socket.scope_id(), 78);
+ /// ```
+ #[must_use]
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_unstable(feature = "const_socketaddr", issue = "82485")
+ )]
+ pub const fn scope_id(&self) -> u32 {
+ self.scope_id
+ }
+
+ /// Changes the scope ID associated with this socket address.
+ ///
+ /// See [`SocketAddrV6::scope_id`]'s documentation for more details.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::net::{SocketAddrV6, Ipv6Addr};
+ ///
+ /// let mut socket = SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080, 0, 78);
+ /// socket.set_scope_id(42);
+ /// assert_eq!(socket.scope_id(), 42);
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "sockaddr_setters", since = "1.9.0"))]
+ pub fn set_scope_id(&mut self, new_scope_id: u32) {
+ self.scope_id = new_scope_id;
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_ip", since = "1.16.0"))]
+impl From<SocketAddrV4> for SocketAddr {
+ /// Converts a [`SocketAddrV4`] into a [`SocketAddr::V4`].
+ fn from(sock4: SocketAddrV4) -> SocketAddr {
+ SocketAddr::V4(sock4)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "ip_from_ip", since = "1.16.0"))]
+impl From<SocketAddrV6> for SocketAddr {
+ /// Converts a [`SocketAddrV6`] into a [`SocketAddr::V6`].
+ fn from(sock6: SocketAddrV6) -> SocketAddr {
+ SocketAddr::V6(sock6)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "addr_from_into_ip", since = "1.17.0"))]
+impl<I: Into<IpAddr>> From<(I, u16)> for SocketAddr {
+ /// Converts a tuple struct (Into<[`IpAddr`]>, `u16`) into a [`SocketAddr`].
+ ///
+ /// This conversion creates a [`SocketAddr::V4`] for an [`IpAddr::V4`]
+ /// and creates a [`SocketAddr::V6`] for an [`IpAddr::V6`].
+ ///
+ /// `u16` is treated as port of the newly created [`SocketAddr`].
+ fn from(pieces: (I, u16)) -> SocketAddr {
+ SocketAddr::new(pieces.0.into(), pieces.1)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "socketaddr_ordering", since = "1.45.0"))]
+impl PartialOrd for SocketAddrV4 {
+ fn partial_cmp(&self, other: &SocketAddrV4) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "socketaddr_ordering", since = "1.45.0"))]
+impl PartialOrd for SocketAddrV6 {
+ fn partial_cmp(&self, other: &SocketAddrV6) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "socketaddr_ordering", since = "1.45.0"))]
+impl Ord for SocketAddrV4 {
+ fn cmp(&self, other: &SocketAddrV4) -> Ordering {
+ self.ip()
+ .cmp(other.ip())
+ .then(self.port().cmp(&other.port()))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "socketaddr_ordering", since = "1.45.0"))]
+impl Ord for SocketAddrV6 {
+ fn cmp(&self, other: &SocketAddrV6) -> Ordering {
+ self.ip()
+ .cmp(other.ip())
+ .then(self.port().cmp(&other.port()))
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+impl hash::Hash for SocketAddrV4 {
+ fn hash<H: hash::Hasher>(&self, s: &mut H) {
+ (self.port, self.ip).hash(s)
+ }
+}
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+impl hash::Hash for SocketAddrV6 {
+ fn hash<H: hash::Hasher>(&self, s: &mut H) {
+ (self.port, &self.ip, self.flowinfo, self.scope_id).hash(s)
+ }
+}
diff --git a/src/maybe_polyfill/no_std/os/fd/mod.rs b/src/maybe_polyfill/no_std/os/fd/mod.rs
new file mode 100644
index 0000000..ea55953
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/fd/mod.rs
@@ -0,0 +1,25 @@
+//! The following is derived from Rust's
+//! library/std/src/os/fd/mod.rs at revision
+//! fa68e73e9947be8ffc5b3b46d899e4953a44e7e9.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+//!
+//! Owned and borrowed Unix-like file descriptors.
+//!
+//! This module is supported on Unix platforms and WASI, which both use a
+//! similar file descriptor system for referencing OS resources.
+
+#![cfg_attr(staged_api, stable(feature = "os_fd", since = "1.66.0"))]
+#![deny(unsafe_op_in_unsafe_fn)]
+
+// `RawFd`, `AsRawFd`, etc.
+mod raw;
+
+// `OwnedFd`, `AsFd`, etc.
+mod owned;
+
+// Export the types and traits for the public API.
+#[cfg_attr(staged_api, stable(feature = "os_fd", since = "1.66.0"))]
+pub use owned::*;
+#[cfg_attr(staged_api, stable(feature = "os_fd", since = "1.66.0"))]
+pub use raw::*;
diff --git a/src/maybe_polyfill/no_std/os/fd/owned.rs b/src/maybe_polyfill/no_std/os/fd/owned.rs
new file mode 100644
index 0000000..d765c1d
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/fd/owned.rs
@@ -0,0 +1,294 @@
+//! The following is derived from Rust's
+//! library/std/src/os/fd/owned.rs at revision
+//! 334a54cd83191f38ad8046ed94c45de735c86c65.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+//!
+//! Owned and borrowed Unix-like file descriptors.
+
+#![cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+#![deny(unsafe_op_in_unsafe_fn)]
+#![allow(unsafe_code)]
+
+use super::raw::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::io::close;
+use core::fmt;
+use core::marker::PhantomData;
+use core::mem::forget;
+
+/// A borrowed file descriptor.
+///
+/// This has a lifetime parameter to tie it to the lifetime of something that owns the file
+/// descriptor. For the duration of that lifetime, it is guaranteed that nobody will close the file
+/// descriptor.
+///
+/// This uses `repr(transparent)` and has the representation of a host file
+/// descriptor, so it can be used in FFI in places where a file descriptor is
+/// passed as an argument, it is not captured or consumed, and it never has the
+/// value `-1`.
+///
+/// This type's `.to_owned()` implementation returns another `BorrowedFd`
+/// rather than an `OwnedFd`. It just makes a trivial copy of the raw file
+/// descriptor, which is then borrowed under the same lifetime.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+#[cfg_attr(rustc_attrs, rustc_layout_scalar_valid_range_start(0))]
+// libstd/os/raw/mod.rs assures me that every libstd-supported platform has a
+// 32-bit c_int. Below is -2, in two's complement, but that only works out
+// because c_int is 32 bits.
+#[cfg_attr(rustc_attrs, rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE))]
+#[cfg_attr(rustc_attrs, rustc_nonnull_optimization_guaranteed)]
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+pub struct BorrowedFd<'fd> {
+ fd: RawFd,
+ _phantom: PhantomData<&'fd OwnedFd>,
+}
+
+/// An owned file descriptor.
+///
+/// This closes the file descriptor on drop. It is guaranteed that nobody else will close the file
+/// descriptor.
+///
+/// This uses `repr(transparent)` and has the representation of a host file
+/// descriptor, so it can be used in FFI in places where a file descriptor is
+/// passed as a consumed argument or returned as an owned value, and it never
+/// has the value `-1`.
+#[repr(transparent)]
+#[cfg_attr(rustc_attrs, rustc_layout_scalar_valid_range_start(0))]
+// libstd/os/raw/mod.rs assures me that every libstd-supported platform has a
+// 32-bit c_int. Below is -2, in two's complement, but that only works out
+// because c_int is 32 bits.
+#[cfg_attr(rustc_attrs, rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE))]
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+#[cfg_attr(rustc_attrs, rustc_nonnull_optimization_guaranteed)]
+pub struct OwnedFd {
+ fd: RawFd,
+}
+
+impl BorrowedFd<'_> {
+ /// Return a `BorrowedFd` holding the given raw file descriptor.
+ ///
+ /// # Safety
+ ///
+ /// The resource pointed to by `fd` must remain open for the duration of
+ /// the returned `BorrowedFd`, and it must not have the value `-1`.
+ #[inline]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "io_safety", since = "1.63.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+ pub const unsafe fn borrow_raw(fd: RawFd) -> Self {
+ assert!(fd != u32::MAX as RawFd);
+ // SAFETY: we just asserted that the value is in the valid range and isn't `-1` (the only value bigger than `0xFF_FF_FF_FE` unsigned)
+ #[allow(unused_unsafe)]
+ unsafe {
+ Self {
+ fd,
+ _phantom: PhantomData,
+ }
+ }
+ }
+}
+
+impl OwnedFd {
+ /// Creates a new `OwnedFd` instance that shares the same underlying file handle
+ /// as the existing `OwnedFd` instance.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn try_clone(&self) -> crate::io::Result<Self> {
+ // We want to atomically duplicate this file descriptor and set the
+ // CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
+ // is a POSIX flag that was added to Linux in 2.6.24.
+ #[cfg(not(target_os = "espidf"))]
+ let fd = crate::io::fcntl_dupfd_cloexec(self, 0)?;
+
+ // For ESP-IDF, F_DUPFD is used instead, because the CLOEXEC semantics
+ // will never be supported, as this is a bare metal framework with
+ // no capabilities for multi-process execution. While F_DUPFD is also
+ // not supported yet, it might be (currently it returns ENOSYS).
+ #[cfg(target_os = "espidf")]
+ let fd = crate::io::fcntl_dupfd(self)?;
+
+ Ok(fd.into())
+ }
+
+ /// Creates a new `OwnedFd` instance that shares the same underlying file handle
+ /// as the existing `OwnedFd` instance.
+ #[cfg(target_arch = "wasm32")]
+ pub fn try_clone(&self) -> crate::io::Result<Self> {
+ Err(crate::io::Errno::NOSYS)
+ }
+}
+
+impl BorrowedFd<'_> {
+ /// Creates a new `OwnedFd` instance that shares the same underlying file
+ /// description as the existing `BorrowedFd` instance.
+ #[cfg(not(any(target_arch = "wasm32", target_os = "hermit")))]
+ #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+ pub fn try_clone_to_owned(&self) -> crate::io::Result<OwnedFd> {
+ // Avoid using file descriptors below 3 as they are used for stdio
+
+ // We want to atomically duplicate this file descriptor and set the
+ // CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
+ // is a POSIX flag that was added to Linux in 2.6.24.
+ #[cfg(not(target_os = "espidf"))]
+ let fd = crate::io::fcntl_dupfd_cloexec(self, 3)?;
+
+ // For ESP-IDF, F_DUPFD is used instead, because the CLOEXEC semantics
+ // will never be supported, as this is a bare metal framework with
+ // no capabilities for multi-process execution. While F_DUPFD is also
+ // not supported yet, it might be (currently it returns ENOSYS).
+ #[cfg(target_os = "espidf")]
+ let fd = crate::io::fcntl_dupfd(self, 3)?;
+
+ Ok(fd)
+ }
+
+ /// Creates a new `OwnedFd` instance that shares the same underlying file
+ /// description as the existing `BorrowedFd` instance.
+ #[cfg(any(target_arch = "wasm32", target_os = "hermit"))]
+ #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+ pub fn try_clone_to_owned(&self) -> crate::io::Result<OwnedFd> {
+ Err(crate::io::Errno::NOSYS)
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl AsRawFd for BorrowedFd<'_> {
+ #[inline]
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl AsRawFd for OwnedFd {
+ #[inline]
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl IntoRawFd for OwnedFd {
+ #[inline]
+ fn into_raw_fd(self) -> RawFd {
+ let fd = self.fd;
+ forget(self);
+ fd
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl FromRawFd for OwnedFd {
+ /// Constructs a new instance of `Self` from the given raw file descriptor.
+ ///
+ /// # Safety
+ ///
+ /// The resource pointed to by `fd` must be open and suitable for assuming
+ /// [ownership][io-safety]. The resource must not require any cleanup other than `close`.
+ ///
+ /// [io-safety]: io#io-safety
+ #[inline]
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ assert_ne!(fd, u32::MAX as RawFd);
+ // SAFETY: we just asserted that the value is in the valid range and isn't `-1` (the only value bigger than `0xFF_FF_FF_FE` unsigned)
+ #[allow(unused_unsafe)]
+ unsafe {
+ Self { fd }
+ }
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl Drop for OwnedFd {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ // Errors are ignored when closing a file descriptor. The reason
+ // for this is that if an error occurs we don't actually know if
+ // the file descriptor was closed or not, and if we retried (for
+ // something like EINTR), we might close another valid file
+ // descriptor opened after we closed ours.
+ close(self.fd as _);
+ }
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl fmt::Debug for BorrowedFd<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("BorrowedFd").field("fd", &self.fd).finish()
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl fmt::Debug for OwnedFd {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("OwnedFd").field("fd", &self.fd).finish()
+ }
+}
+
+/// A trait to borrow the file descriptor from an underlying object.
+///
+/// This is only available on unix platforms and must be imported in order to
+/// call the method. Windows platforms have a corresponding `AsHandle` and
+/// `AsSocket` set of traits.
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+pub trait AsFd {
+ /// Borrows the file descriptor.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # #![feature(io_safety)]
+ /// use std::fs::File;
+ /// # use std::io;
+ /// # #[cfg(target_os = "wasi")]
+ /// # use std::os::wasi::io::{AsFd, BorrowedFd};
+ /// # #[cfg(unix)]
+ /// # use std::os::unix::io::{AsFd, BorrowedFd};
+ ///
+ /// let mut f = File::open("foo.txt")?;
+ /// # #[cfg(any(unix, target_os = "wasi"))]
+ /// let borrowed_fd: BorrowedFd<'_> = f.as_fd();
+ /// # Ok::<(), io::Error>(())
+ /// ```
+ #[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+ fn as_fd(&self) -> BorrowedFd<'_>;
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl<T: AsFd> AsFd for &T {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ T::as_fd(self)
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl<T: AsFd> AsFd for &mut T {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ T::as_fd(self)
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl AsFd for BorrowedFd<'_> {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ *self
+ }
+}
+
+#[cfg_attr(staged_api, unstable(feature = "io_safety", issue = "87074"))]
+impl AsFd for OwnedFd {
+ #[inline]
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ // SAFETY: `OwnedFd` and `BorrowedFd` have the same validity
+ // invariants, and the `BorrowedFd` is bounded by the lifetime
+ // of `&self`.
+ unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
+ }
+}
diff --git a/src/maybe_polyfill/no_std/os/fd/raw.rs b/src/maybe_polyfill/no_std/os/fd/raw.rs
new file mode 100644
index 0000000..8f6b75a
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/fd/raw.rs
@@ -0,0 +1,164 @@
+//! The following is derived from Rust's
+//! library/std/src/os/fd/raw.rs at revision
+//! 334a54cd83191f38ad8046ed94c45de735c86c65.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+//!
+//! Raw Unix-like file descriptors.
+
+#![cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+
+/// Raw file descriptors.
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub type RawFd = c::c_int;
+
+/// A trait to extract the raw file descriptor from an underlying object.
+///
+/// This is only available on unix and WASI platforms and must be imported in
+/// order to call the method. Windows platforms have a corresponding
+/// `AsRawHandle` and `AsRawSocket` set of traits.
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub trait AsRawFd {
+ /// Extracts the raw file descriptor.
+ ///
+ /// This function is typically used to **borrow** an owned file descriptor.
+ /// When used in this way, this method does **not** pass ownership of the
+ /// raw file descriptor to the caller, and the file descriptor is only
+ /// guaranteed to be valid while the original object has not yet been
+ /// destroyed.
+ ///
+ /// However, borrowing is not strictly required. See [`AsFd::as_fd`]
+ /// for an API which strictly borrows a file descriptor.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use std::fs::File;
+ /// # use std::io;
+ /// #[cfg(unix)]
+ /// use std::os::unix::io::{AsRawFd, RawFd};
+ /// #[cfg(target_os = "wasi")]
+ /// use std::os::wasi::io::{AsRawFd, RawFd};
+ ///
+ /// let mut f = File::open("foo.txt")?;
+ /// // `raw_fd` is only valid as long as `f` exists.
+ /// #[cfg(any(unix, target_os = "wasi"))]
+ /// let raw_fd: RawFd = f.as_raw_fd();
+ /// # Ok::<(), io::Error>(())
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ fn as_raw_fd(&self) -> RawFd;
+}
+
+/// A trait to express the ability to construct an object from a raw file
+/// descriptor.
+#[cfg_attr(staged_api, stable(feature = "from_raw_os", since = "1.1.0"))]
+pub trait FromRawFd {
+ /// Constructs a new instance of `Self` from the given raw file
+ /// descriptor.
+ ///
+ /// This function is typically used to **consume ownership** of the
+ /// specified file descriptor. When used in this way, the returned object
+ /// will take responsibility for closing it when the object goes out of
+ /// scope.
+ ///
+ /// However, consuming ownership is not strictly required. Use a
+ /// [`From<OwnedFd>::from`] implementation for an API which strictly
+ /// consumes ownership.
+ ///
+ /// # Safety
+ ///
+ /// The `fd` passed in must be an [owned file descriptor][io-safety];
+ /// in particular, it must be open.
+ ///
+ /// [io-safety]: io#io-safety
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use std::fs::File;
+ /// # use std::io;
+ /// #[cfg(unix)]
+ /// use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
+ /// #[cfg(target_os = "wasi")]
+ /// use std::os::wasi::io::{FromRawFd, IntoRawFd, RawFd};
+ ///
+ /// let f = File::open("foo.txt")?;
+ /// # #[cfg(any(unix, target_os = "wasi"))]
+ /// let raw_fd: RawFd = f.into_raw_fd();
+ /// // SAFETY: no other functions should call `from_raw_fd`, so there
+ /// // is only one owner for the file descriptor.
+ /// # #[cfg(any(unix, target_os = "wasi"))]
+ /// let f = unsafe { File::from_raw_fd(raw_fd) };
+ /// # Ok::<(), io::Error>(())
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "from_raw_os", since = "1.1.0"))]
+ unsafe fn from_raw_fd(fd: RawFd) -> Self;
+}
+
+/// A trait to express the ability to consume an object and acquire ownership of
+/// its raw file descriptor.
+#[cfg_attr(staged_api, stable(feature = "into_raw_os", since = "1.4.0"))]
+pub trait IntoRawFd {
+ /// Consumes this object, returning the raw underlying file descriptor.
+ ///
+ /// This function is typically used to **transfer ownership** of the underlying
+ /// file descriptor to the caller. When used in this way, callers are then the unique
+ /// owners of the file descriptor and must close it once it's no longer needed.
+ ///
+ /// However, transferring ownership is not strictly required. Use a
+ /// [`Into<OwnedFd>::into`] implementation for an API which strictly
+ /// transfers ownership.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use std::fs::File;
+ /// # use std::io;
+ /// #[cfg(unix)]
+ /// use std::os::unix::io::{IntoRawFd, RawFd};
+ /// #[cfg(target_os = "wasi")]
+ /// use std::os::wasi::io::{IntoRawFd, RawFd};
+ ///
+ /// let f = File::open("foo.txt")?;
+ /// #[cfg(any(unix, target_os = "wasi"))]
+ /// let raw_fd: RawFd = f.into_raw_fd();
+ /// # Ok::<(), io::Error>(())
+ /// ```
+ #[cfg_attr(staged_api, stable(feature = "into_raw_os", since = "1.4.0"))]
+ fn into_raw_fd(self) -> RawFd;
+}
+
+#[cfg_attr(
+ staged_api,
+ stable(feature = "raw_fd_reflexive_traits", since = "1.48.0")
+)]
+impl AsRawFd for RawFd {
+ #[inline]
+ fn as_raw_fd(&self) -> RawFd {
+ *self
+ }
+}
+#[cfg_attr(
+ staged_api,
+ stable(feature = "raw_fd_reflexive_traits", since = "1.48.0")
+)]
+impl IntoRawFd for RawFd {
+ #[inline]
+ fn into_raw_fd(self) -> RawFd {
+ self
+ }
+}
+#[cfg_attr(
+ staged_api,
+ stable(feature = "raw_fd_reflexive_traits", since = "1.48.0")
+)]
+impl FromRawFd for RawFd {
+ #[inline]
+ unsafe fn from_raw_fd(fd: RawFd) -> RawFd {
+ fd
+ }
+}
diff --git a/src/maybe_polyfill/no_std/os/mod.rs b/src/maybe_polyfill/no_std/os/mod.rs
new file mode 100644
index 0000000..67f41f5
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/mod.rs
@@ -0,0 +1,4 @@
+#[cfg(any(unix, target_os = "wasi"))]
+pub mod fd;
+#[cfg(windows)]
+pub mod windows;
diff --git a/src/maybe_polyfill/no_std/os/windows/io/mod.rs b/src/maybe_polyfill/no_std/os/windows/io/mod.rs
new file mode 100644
index 0000000..77abd03
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/windows/io/mod.rs
@@ -0,0 +1,5 @@
+mod raw;
+mod socket;
+
+pub use raw::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
+pub use socket::{AsSocket, BorrowedSocket, OwnedSocket};
diff --git a/src/maybe_polyfill/no_std/os/windows/io/raw.rs b/src/maybe_polyfill/no_std/os/windows/io/raw.rs
new file mode 100644
index 0000000..1e73e00
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/windows/io/raw.rs
@@ -0,0 +1,71 @@
+//! The following is derived from Rust's
+//! library/std/src/os/windows/io/raw.rs
+//! at revision
+//! 4f9b394c8a24803e57ba892fa00e539742ebafc0.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+
+use super::super::raw;
+
+/// Raw SOCKETs.
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub type RawSocket = raw::SOCKET;
+
+/// Extracts raw sockets.
+#[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+pub trait AsRawSocket {
+ /// Extracts the raw socket.
+ ///
+ /// This function is typically used to **borrow** an owned socket.
+ /// When used in this way, this method does **not** pass ownership of the
+ /// raw socket to the caller, and the socket is only guaranteed
+ /// to be valid while the original object has not yet been destroyed.
+ ///
+ /// However, borrowing is not strictly required. See [`AsSocket::as_socket`]
+ /// for an API which strictly borrows a socket.
+ #[cfg_attr(staged_api, stable(feature = "rust1", since = "1.0.0"))]
+ fn as_raw_socket(&self) -> RawSocket;
+}
+
+/// Creates I/O objects from raw sockets.
+#[cfg_attr(staged_api, stable(feature = "from_raw_os", since = "1.1.0"))]
+pub trait FromRawSocket {
+ /// Constructs a new I/O object from the specified raw socket.
+ ///
+ /// This function is typically used to **consume ownership** of the socket
+ /// given, passing responsibility for closing the socket to the returned
+ /// object. When used in this way, the returned object
+ /// will take responsibility for closing it when the object goes out of
+ /// scope.
+ ///
+ /// However, consuming ownership is not strictly required. Use a
+ /// `From<OwnedSocket>::from` implementation for an API which strictly
+ /// consumes ownership.
+ ///
+ /// # Safety
+ ///
+ /// The `socket` passed in must:
+ /// - be a valid an open socket,
+ /// - be a socket that may be freed via [`closesocket`].
+ ///
+ /// [`closesocket`]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-closesocket
+ #[cfg_attr(staged_api, stable(feature = "from_raw_os", since = "1.1.0"))]
+ unsafe fn from_raw_socket(sock: RawSocket) -> Self;
+}
+
+/// A trait to express the ability to consume an object and acquire ownership of
+/// its raw `SOCKET`.
+#[cfg_attr(staged_api, stable(feature = "into_raw_os", since = "1.4.0"))]
+pub trait IntoRawSocket {
+ /// Consumes this object, returning the raw underlying socket.
+ ///
+ /// This function is typically used to **transfer ownership** of the underlying
+ /// socket to the caller. When used in this way, callers are then the unique
+ /// owners of the socket and must close it once it's no longer needed.
+ ///
+ /// However, transferring ownership is not strictly required. Use a
+ /// `Into<OwnedSocket>::into` implementation for an API which strictly
+ /// transfers ownership.
+ #[cfg_attr(staged_api, stable(feature = "into_raw_os", since = "1.4.0"))]
+ fn into_raw_socket(self) -> RawSocket;
+}
diff --git a/src/maybe_polyfill/no_std/os/windows/io/socket.rs b/src/maybe_polyfill/no_std/os/windows/io/socket.rs
new file mode 100644
index 0000000..bc637aa
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/windows/io/socket.rs
@@ -0,0 +1,199 @@
+//! The following is derived from Rust's
+//! library/std/src/os/windows/io/socket.rs
+//! at revision
+//! 4f9b394c8a24803e57ba892fa00e539742ebafc0.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+
+use super::raw::*;
+use crate::backend::c;
+use crate::backend::fd::LibcFd as LibcSocket;
+use core::fmt;
+use core::marker::PhantomData;
+use core::mem::forget;
+
+/// A borrowed socket.
+///
+/// This has a lifetime parameter to tie it to the lifetime of something that
+/// owns the socket.
+///
+/// This uses `repr(transparent)` and has the representation of a host socket,
+/// so it can be used in FFI in places where a socket is passed as an argument,
+/// it is not captured or consumed, and it never has the value
+/// `INVALID_SOCKET`.
+///
+/// This type's `.to_owned()` implementation returns another `BorrowedSocket`
+/// rather than an `OwnedSocket`. It just makes a trivial copy of the raw
+/// socket, which is then borrowed under the same lifetime.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+#[cfg_attr(staged_api, rustc_layout_scalar_valid_range_start(0))]
+// This is -2, in two's complement. -1 is `INVALID_SOCKET`.
+#[cfg_attr(
+ all(staged_api, target_pointer_width = "32"),
+ rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)
+)]
+#[cfg_attr(
+ all(staged_api, target_pointer_width = "64"),
+ rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FF_FF_FF_FF_FE)
+)]
+#[cfg_attr(staged_api, rustc_nonnull_optimization_guaranteed)]
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+pub struct BorrowedSocket<'socket> {
+ socket: RawSocket,
+ _phantom: PhantomData<&'socket OwnedSocket>,
+}
+
+/// An owned socket.
+///
+/// This closes the socket on drop.
+///
+/// This uses `repr(transparent)` and has the representation of a host socket,
+/// so it can be used in FFI in places where a socket is passed as a consumed
+/// argument or returned as an owned value, and it never has the value
+/// `INVALID_SOCKET`.
+#[repr(transparent)]
+#[cfg_attr(staged_api, rustc_layout_scalar_valid_range_start(0))]
+// This is -2, in two's complement. -1 is `INVALID_SOCKET`.
+#[cfg_attr(
+ all(staged_api, target_pointer_width = "32"),
+ rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)
+)]
+#[cfg_attr(
+ all(staged_api, target_pointer_width = "64"),
+ rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FF_FF_FF_FF_FE)
+)]
+#[cfg_attr(staged_api, rustc_nonnull_optimization_guaranteed)]
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+pub struct OwnedSocket {
+ socket: RawSocket,
+}
+
+impl BorrowedSocket<'_> {
+ /// Return a `BorrowedSocket` holding the given raw socket.
+ ///
+ /// # Safety
+ ///
+ /// The resource pointed to by `raw` must remain open for the duration of
+ /// the returned `BorrowedSocket`, and it must not have the value
+ /// `INVALID_SOCKET`.
+ #[inline]
+ #[cfg_attr(
+ staged_api,
+ rustc_const_stable(feature = "io_safety", since = "1.63.0")
+ )]
+ #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+ pub const unsafe fn borrow_raw(socket: RawSocket) -> Self {
+ assert!(socket != c::INVALID_SOCKET as RawSocket);
+ Self {
+ socket,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl AsRawSocket for BorrowedSocket<'_> {
+ #[inline]
+ fn as_raw_socket(&self) -> RawSocket {
+ self.socket
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl AsRawSocket for OwnedSocket {
+ #[inline]
+ fn as_raw_socket(&self) -> RawSocket {
+ self.socket
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl IntoRawSocket for OwnedSocket {
+ #[inline]
+ fn into_raw_socket(self) -> RawSocket {
+ let socket = self.socket;
+ forget(self);
+ socket
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl FromRawSocket for OwnedSocket {
+ #[inline]
+ unsafe fn from_raw_socket(socket: RawSocket) -> Self {
+ debug_assert_ne!(socket, c::INVALID_SOCKET as RawSocket);
+ Self { socket }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl Drop for OwnedSocket {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ let _ = c::closesocket(self.socket as LibcSocket);
+ }
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl fmt::Debug for BorrowedSocket<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("BorrowedSocket")
+ .field("socket", &self.socket)
+ .finish()
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl fmt::Debug for OwnedSocket {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("OwnedSocket")
+ .field("socket", &self.socket)
+ .finish()
+ }
+}
+
+/// A trait to borrow the socket from an underlying object.
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+pub trait AsSocket {
+ /// Borrows the socket.
+ #[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+ fn as_socket(&self) -> BorrowedSocket<'_>;
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl<T: AsSocket> AsSocket for &T {
+ #[inline]
+ fn as_socket(&self) -> BorrowedSocket<'_> {
+ T::as_socket(self)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl<T: AsSocket> AsSocket for &mut T {
+ #[inline]
+ fn as_socket(&self) -> BorrowedSocket<'_> {
+ T::as_socket(self)
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl AsSocket for BorrowedSocket<'_> {
+ #[inline]
+ fn as_socket(&self) -> BorrowedSocket<'_> {
+ *self
+ }
+}
+
+#[cfg_attr(staged_api, stable(feature = "io_safety", since = "1.63.0"))]
+impl AsSocket for OwnedSocket {
+ #[inline]
+ fn as_socket(&self) -> BorrowedSocket<'_> {
+ // Safety: `OwnedSocket` and `BorrowedSocket` have the same validity
+ // invariants, and the `BorrowdSocket` is bounded by the lifetime
+ // of `&self`.
+ unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
+ }
+}
diff --git a/src/maybe_polyfill/no_std/os/windows/mod.rs b/src/maybe_polyfill/no_std/os/windows/mod.rs
new file mode 100644
index 0000000..02f3894
--- /dev/null
+++ b/src/maybe_polyfill/no_std/os/windows/mod.rs
@@ -0,0 +1,19 @@
+//! The following is derived from Rust's
+//! library/std/src/os/windows/raw.rs,
+//! library/std/src/os/windows/io/raw.rs and
+//! library/std/src/os/windows/io/socket.rs
+//! at revision
+//! 4f9b394c8a24803e57ba892fa00e539742ebafc0.
+//!
+//! All code in this file is licensed MIT or Apache 2.0 at your option.
+
+mod raw {
+ #[cfg(target_pointer_width = "32")]
+ #[cfg_attr(staged_api, stable(feature = "raw_ext", since = "1.1.0"))]
+ pub type SOCKET = u32;
+ #[cfg(target_pointer_width = "64")]
+ #[cfg_attr(staged_api, stable(feature = "raw_ext", since = "1.1.0"))]
+ pub type SOCKET = u64;
+}
+
+pub mod io;
diff --git a/src/maybe_polyfill/std/mod.rs b/src/maybe_polyfill/std/mod.rs
new file mode 100644
index 0000000..bcaceb9
--- /dev/null
+++ b/src/maybe_polyfill/std/mod.rs
@@ -0,0 +1,43 @@
+//! Imports from `std` that would be polyfilled for `no_std` builds (see
+//! `src/polyfill/no_std`).
+//!
+//! This implementation is used when `std` is available and just imports the
+//! necessary items from `std`. For `no_std` builds, the file
+//! `src/polyfill/no_std` is used instead, which doesn't depend on the standard
+//! library.
+
+#[cfg(not(windows))]
+pub mod io {
+ pub use std::io::{IoSlice, IoSliceMut};
+}
+
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[cfg(feature = "net")]
+pub mod net {
+ pub use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
+}
+
+pub mod os {
+ pub mod fd {
+ // Change to use `std::os::fd` when MSRV becomes 1.66 or higher.
+
+ #[cfg(unix)]
+ pub use std::os::unix::io::{
+ AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd,
+ };
+ #[cfg(target_os = "wasi")]
+ pub use std::os::wasi::io::{
+ AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd,
+ };
+ }
+
+ #[cfg(windows)]
+ pub mod windows {
+ pub mod io {
+ pub use std::os::windows::io::{
+ AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket,
+ RawSocket,
+ };
+ }
+ }
+}
diff --git a/src/mm/madvise.rs b/src/mm/madvise.rs
new file mode 100644
index 0000000..4015d12
--- /dev/null
+++ b/src/mm/madvise.rs
@@ -0,0 +1,49 @@
+//! The `madvise` function.
+//!
+//! # Safety
+//!
+//! `madvise` operates on a raw pointer. Some forms of `madvise` may
+//! mutate the memory or have other side effects.
+#![allow(unsafe_code)]
+
+use crate::{backend, io};
+use core::ffi::c_void;
+
+pub use backend::mm::types::Advice;
+
+/// `posix_madvise(addr, len, advice)`—Declares an expected access pattern
+/// for a memory-mapped file.
+///
+/// # Safety
+///
+/// `addr` must be a valid pointer to memory that is appropriate to call
+/// `posix_madvise` on. Some forms of `advice` may mutate the memory or evoke a
+/// variety of side-effects on the mapping and/or the file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux `madvise`]
+/// - [Linux `posix_madvise`]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_madvise.html
+/// [Linux `madvise`]: https://man7.org/linux/man-pages/man2/madvise.2.html
+/// [Linux `posix_madvise`]: https://man7.org/linux/man-pages/man3/posix_madvise.3.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/madvise.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=madvise&sektion=2
+/// [NetBSD]: https://man.netbsd.org/madvise.2
+/// [OpenBSD]: https://man.openbsd.org/madvise.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=madvise&section=2
+/// [illumos]: https://illumos.org/man/3C/madvise
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Memory_002dmapped-I_002fO.html#index-madvise
+#[inline]
+#[doc(alias = "posix_madvise")]
+pub unsafe fn madvise(addr: *mut c_void, len: usize, advice: Advice) -> io::Result<()> {
+ backend::mm::syscalls::madvise(addr, len, advice)
+}
diff --git a/src/mm/mmap.rs b/src/mm/mmap.rs
new file mode 100644
index 0000000..9413340
--- /dev/null
+++ b/src/mm/mmap.rs
@@ -0,0 +1,409 @@
+//! The `mmap` API.
+//!
+//! # Safety
+//!
+//! `mmap` and related functions manipulate raw pointers and have special
+//! semantics and are wildly unsafe.
+#![allow(unsafe_code)]
+
+use crate::{backend, io};
+use backend::fd::AsFd;
+use core::ffi::c_void;
+
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+pub use backend::mm::types::MlockAllFlags;
+#[cfg(linux_kernel)]
+pub use backend::mm::types::MlockFlags;
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+pub use backend::mm::types::MremapFlags;
+pub use backend::mm::types::{MapFlags, MprotectFlags, ProtFlags};
+
+impl MapFlags {
+ /// Create `MAP_HUGETLB` with provided size of huge page.
+ ///
+ /// Under the hood it computes
+ /// `MAP_HUGETLB | (huge_page_size_log2 << MAP_HUGE_SHIFT)`.
+ /// `huge_page_size_log2` denotes logarithm of huge page size to use and
+ /// should be between 16 and 63 (inclusive).
+ ///
+ /// ```
+ /// use rustix::mm::MapFlags;
+ ///
+ /// let f = MapFlags::hugetlb_with_size_log2(30).unwrap();
+ /// assert_eq!(f, MapFlags::HUGETLB | MapFlags::HUGE_1GB);
+ /// ```
+ #[cfg(feature = "linux-raw-sys")]
+ pub const fn hugetlb_with_size_log2(huge_page_size_log2: u32) -> Option<Self> {
+ use linux_raw_sys::general::{MAP_HUGETLB, MAP_HUGE_SHIFT};
+ if 16 <= huge_page_size_log2 && huge_page_size_log2 <= 63 {
+ let bits = MAP_HUGETLB | (huge_page_size_log2 << MAP_HUGE_SHIFT);
+ Self::from_bits(bits)
+ } else {
+ None
+ }
+ }
+}
+
+/// `mmap(ptr, len, prot, flags, fd, offset)`—Create a file-backed memory
+/// mapping.
+///
+/// For anonymous mappings (`MAP_ANON`/`MAP_ANONYMOUS`), see
+/// [`mmap_anonymous`].
+///
+/// # Safety
+///
+/// Raw pointers and lots of special semantics.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mmap.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mmap.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=mmap&sektion=2
+/// [NetBSD]: https://man.netbsd.org/mmap.2
+/// [OpenBSD]: https://man.openbsd.org/mmap.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=mmap&section=2
+/// [illumos]: https://illumos.org/man/2/mmap
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Memory_002dmapped-I_002fO.html#index-mmap
+#[inline]
+pub unsafe fn mmap<Fd: AsFd>(
+ ptr: *mut c_void,
+ len: usize,
+ prot: ProtFlags,
+ flags: MapFlags,
+ fd: Fd,
+ offset: u64,
+) -> io::Result<*mut c_void> {
+ backend::mm::syscalls::mmap(ptr, len, prot, flags, fd.as_fd(), offset)
+}
+
+/// `mmap(ptr, len, prot, MAP_ANONYMOUS | flags, -1, 0)`—Create an anonymous
+/// memory mapping.
+///
+/// For file-backed mappings, see [`mmap`].
+///
+/// # Safety
+///
+/// Raw pointers and lots of special semantics.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mmap.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mmap.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=mmap&sektion=2
+/// [NetBSD]: https://man.netbsd.org/mmap.2
+/// [OpenBSD]: https://man.openbsd.org/mmap.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=mmap&section=2
+/// [illumos]: https://illumos.org/man/2/mmap
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Memory_002dmapped-I_002fO.html#index-mmap
+#[inline]
+#[doc(alias = "mmap")]
+pub unsafe fn mmap_anonymous(
+ ptr: *mut c_void,
+ len: usize,
+ prot: ProtFlags,
+ flags: MapFlags,
+) -> io::Result<*mut c_void> {
+ backend::mm::syscalls::mmap_anonymous(ptr, len, prot, flags)
+}
+
+/// `munmap(ptr, len)`—Remove a memory mapping.
+///
+/// # Safety
+///
+/// Raw pointers and lots of special semantics.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/munmap.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/munmap.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/munmap.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=munmap&sektion=2
+/// [NetBSD]: https://man.netbsd.org/munmap.2
+/// [OpenBSD]: https://man.openbsd.org/munmap.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=munmap&section=2
+/// [illumos]: https://illumos.org/man/2/munmap
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Memory_002dmapped-I_002fO.html#index-munmap
+#[inline]
+pub unsafe fn munmap(ptr: *mut c_void, len: usize) -> io::Result<()> {
+ backend::mm::syscalls::munmap(ptr, len)
+}
+
+/// `mremap(old_address, old_size, new_size, flags)`—Resize, modify, and/or
+/// move a memory mapping.
+///
+/// For moving a mapping to a fixed address (`MREMAP_FIXED`), see
+/// [`mremap_fixed`].
+///
+/// # Safety
+///
+/// Raw pointers and lots of special semantics.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mremap.2.html
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+#[inline]
+pub unsafe fn mremap(
+ old_address: *mut c_void,
+ old_size: usize,
+ new_size: usize,
+ flags: MremapFlags,
+) -> io::Result<*mut c_void> {
+ backend::mm::syscalls::mremap(old_address, old_size, new_size, flags)
+}
+
+/// `mremap(old_address, old_size, new_size, MREMAP_FIXED | flags)`—Resize,
+/// modify, and/or move a memory mapping to a specific address.
+///
+/// For `mremap` without moving to a specific address, see [`mremap`].
+/// [`mremap_fixed`].
+///
+/// # Safety
+///
+/// Raw pointers and lots of special semantics.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mremap.2.html
+#[cfg(any(target_os = "emscripten", target_os = "linux"))]
+#[inline]
+#[doc(alias = "mremap")]
+pub unsafe fn mremap_fixed(
+ old_address: *mut c_void,
+ old_size: usize,
+ new_size: usize,
+ flags: MremapFlags,
+ new_address: *mut c_void,
+) -> io::Result<*mut c_void> {
+ backend::mm::syscalls::mremap_fixed(old_address, old_size, new_size, flags, new_address)
+}
+
+/// `mprotect(ptr, len, flags)`—Change the protection flags of a region of
+/// memory.
+///
+/// # Safety
+///
+/// Raw pointers and lots of special semantics.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mprotect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mprotect.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=mprotect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/mprotect.2
+/// [OpenBSD]: https://man.openbsd.org/mprotect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=mprotect&section=2
+/// [illumos]: https://illumos.org/man/2/mprotect
+#[inline]
+pub unsafe fn mprotect(ptr: *mut c_void, len: usize, flags: MprotectFlags) -> io::Result<()> {
+ backend::mm::syscalls::mprotect(ptr, len, flags)
+}
+
+/// `mlock(ptr, len)`—Lock memory into RAM.
+///
+/// # Safety
+///
+/// This function operates on raw pointers, but it should only be used on
+/// memory which the caller owns. Technically, locking memory shouldn't violate
+/// any invariants, but since unlocking it can violate invariants, this
+/// function is also unsafe for symmetry.
+///
+/// Some implementations implicitly round the memory region out to the nearest
+/// page boundaries, so this function may lock more memory than explicitly
+/// requested if the memory isn't page-aligned. Other implementations fail if
+/// the memory isn't page-aligned.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mlock.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mlock.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=mlock&sektion=2
+/// [NetBSD]: https://man.netbsd.org/mlock.2
+/// [OpenBSD]: https://man.openbsd.org/mlock.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=mlock&section=2
+/// [illumos]: https://illumos.org/man/3C/mlock
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Page-Lock-Functions.html#index-mlock
+#[inline]
+pub unsafe fn mlock(ptr: *mut c_void, len: usize) -> io::Result<()> {
+ backend::mm::syscalls::mlock(ptr, len)
+}
+
+/// `mlock2(ptr, len, flags)`—Lock memory into RAM, with flags.
+///
+/// `mlock_with` is the same as [`mlock`] but adds an additional flags operand.
+///
+/// # Safety
+///
+/// This function operates on raw pointers, but it should only be used on
+/// memory which the caller owns. Technically, locking memory shouldn't violate
+/// any invariants, but since unlocking it can violate invariants, this
+/// function is also unsafe for symmetry.
+///
+/// Some implementations implicitly round the memory region out to the nearest
+/// page boundaries, so this function may lock more memory than explicitly
+/// requested if the memory isn't page-aligned.
+///
+/// # References
+/// - [Linux]
+/// - [glibc]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mlock2.2.html
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Page-Lock-Functions.html#index-mlock2
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "mlock2")]
+pub unsafe fn mlock_with(ptr: *mut c_void, len: usize, flags: MlockFlags) -> io::Result<()> {
+ backend::mm::syscalls::mlock_with(ptr, len, flags)
+}
+
+/// `munlock(ptr, len)`—Unlock memory.
+///
+/// # Safety
+///
+/// This function operates on raw pointers, but it should only be used on
+/// memory which the caller owns, to avoid compromising the `mlock` invariants
+/// of other unrelated code in the process.
+///
+/// Some implementations implicitly round the memory region out to the nearest
+/// page boundaries, so this function may unlock more memory than explicitly
+/// requested if the memory isn't page-aligned.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/munlock.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/munlock.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/munlock.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=munlock&sektion=2
+/// [NetBSD]: https://man.netbsd.org/munlock.2
+/// [OpenBSD]: https://man.openbsd.org/munlock.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=munlock&section=2
+/// [illumos]: https://illumos.org/man/3C/munlock
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Page-Lock-Functions.html#index-munlock
+#[inline]
+pub unsafe fn munlock(ptr: *mut c_void, len: usize) -> io::Result<()> {
+ backend::mm::syscalls::munlock(ptr, len)
+}
+
+/// 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.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/mlockall.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=mlockall&sektion=2
+/// [NetBSD]: https://man.netbsd.org/mlockall.2
+/// [OpenBSD]: https://man.openbsd.org/mlockall.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=mlockall&section=2
+/// [illumos]: https://illumos.org/man/3C/mlockall
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Page-Lock-Functions.html#index-mlockall
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+#[inline]
+pub fn mlockall(flags: MlockAllFlags) -> io::Result<()> {
+ backend::mm::syscalls::mlockall(flags)
+}
+
+/// Unlocks all pages mapped into the address space of the calling process.
+///
+/// # Warnings
+///
+/// This function is aware of all the memory pages in the process, as if it
+/// were a debugger. It unlocks all the pages, which could potentially
+/// compromise security assumptions made by code about memory it has
+/// encapsulated.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/munlockall.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/munlockall.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=munlockall&sektion=2
+/// [NetBSD]: https://man.netbsd.org/munlockall.2
+/// [OpenBSD]: https://man.openbsd.org/munlockall.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=munlockall&section=2
+/// [illumos]: https://illumos.org/man/3C/munlockall
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Page-Lock-Functions.html#index-munlockall
+#[cfg(any(linux_kernel, freebsdlike, netbsdlike))]
+#[inline]
+pub fn munlockall() -> io::Result<()> {
+ backend::mm::syscalls::munlockall()
+}
diff --git a/src/mm/mod.rs b/src/mm/mod.rs
new file mode 100644
index 0000000..7505cac
--- /dev/null
+++ b/src/mm/mod.rs
@@ -0,0 +1,15 @@
+//! Memory map operations.
+
+#[cfg(not(target_os = "redox"))]
+mod madvise;
+mod mmap;
+mod msync;
+#[cfg(linux_kernel)]
+mod userfaultfd;
+
+#[cfg(not(target_os = "redox"))]
+pub use madvise::{madvise, Advice};
+pub use mmap::*;
+pub use msync::{msync, MsyncFlags};
+#[cfg(linux_kernel)]
+pub use userfaultfd::{userfaultfd, UserfaultfdFlags};
diff --git a/src/mm/msync.rs b/src/mm/msync.rs
new file mode 100644
index 0000000..c61d7c3
--- /dev/null
+++ b/src/mm/msync.rs
@@ -0,0 +1,46 @@
+//! The `msync` function.
+//!
+//! # Safety
+//!
+//! `msync` operates on a raw pointer. Some forms of `msync` may mutate the
+//! memory or have other side effects.
+#![allow(unsafe_code)]
+
+use crate::{backend, io};
+use core::ffi::c_void;
+
+pub use backend::mm::types::MsyncFlags;
+
+/// `msync(addr, len, flags)`—Synchronizes a memory-mapping with its backing
+/// storage.
+///
+/// # Safety
+///
+/// `addr` must be a valid pointer to memory that is appropriate to call
+/// `msync` on. Some forms of `msync` may mutate the memory or evoke a variety
+/// of side-effects on the mapping and/or the file.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/msync.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/msync.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/msync.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=msync&sektion=2
+/// [NetBSD]: https://man.netbsd.org/msync.2
+/// [OpenBSD]: https://man.openbsd.org/msync.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=msync&section=2
+/// [illumos]: https://illumos.org/man/3C/msync
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Memory_002dmapped-I_002fO.html#index-msync
+#[inline]
+pub unsafe fn msync(addr: *mut c_void, len: usize, flags: MsyncFlags) -> io::Result<()> {
+ backend::mm::syscalls::msync(addr, len, flags)
+}
diff --git a/src/mm/userfaultfd.rs b/src/mm/userfaultfd.rs
new file mode 100644
index 0000000..201d547
--- /dev/null
+++ b/src/mm/userfaultfd.rs
@@ -0,0 +1,30 @@
+//! The Linux `userfaultfd` API.
+//!
+//! # Safety
+//!
+//! Calling `userfaultfd` is safe, but the returned file descriptor lets users
+//! observe and manipulate process memory in magical ways.
+#![allow(unsafe_code)]
+
+use crate::fd::OwnedFd;
+use crate::{backend, io};
+
+pub use backend::mm::types::UserfaultfdFlags;
+
+/// `userfaultfd(flags)`
+///
+/// # Safety
+///
+/// The call itself is safe, but the returned file descriptor lets users
+/// observe and manipulate process memory in magical ways.
+///
+/// # References
+/// - [Linux]
+/// - [Linux userfaultfd]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/userfaultfd.2.html
+/// [Linux userfaultfd]: https://www.kernel.org/doc/Documentation/vm/userfaultfd.txt
+#[inline]
+pub unsafe fn userfaultfd(flags: UserfaultfdFlags) -> io::Result<OwnedFd> {
+ backend::mm::syscalls::userfaultfd(flags)
+}
diff --git a/src/mount/fsopen.rs b/src/mount/fsopen.rs
new file mode 100644
index 0000000..54ba401
--- /dev/null
+++ b/src/mount/fsopen.rs
@@ -0,0 +1,219 @@
+//! `fsopen` and related functions in Linux's `mount` API.
+
+use crate::backend::mount::types::{
+ FsMountFlags, FsOpenFlags, FsPickFlags, MountAttrFlags, MoveMountFlags, OpenTreeFlags,
+};
+use crate::fd::{BorrowedFd, OwnedFd};
+use crate::{backend, io, path};
+
+/// `fsopen(fs_name, flags)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsopen.md
+#[inline]
+pub fn fsopen<Fs: path::Arg>(fs_name: Fs, flags: FsOpenFlags) -> io::Result<OwnedFd> {
+ fs_name.into_with_c_str(|fs_name| backend::mount::syscalls::fsopen(fs_name, flags))
+}
+
+/// `fsmount(fs_fd, flags, attr_flags)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsmount.md
+#[inline]
+pub fn fsmount(
+ fs_fd: BorrowedFd<'_>,
+ flags: FsMountFlags,
+ attr_flags: MountAttrFlags,
+) -> io::Result<OwnedFd> {
+ backend::mount::syscalls::fsmount(fs_fd, flags, attr_flags)
+}
+
+/// `move_mount(from_dfd, from_pathname, to_dfd, to_pathname, flags)`
+///
+/// This is not the same as `mount` with the `MS_MOVE` flag. If you want to
+/// use that, use [`mount_move`] instead.
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [`mount_move`]: crate::mount::mount_move
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/move_mount.md
+#[inline]
+pub fn move_mount<From: path::Arg, To: path::Arg>(
+ from_dfd: BorrowedFd<'_>,
+ from_pathname: From,
+ to_dfd: BorrowedFd<'_>,
+ to_pathname: To,
+ flags: MoveMountFlags,
+) -> io::Result<()> {
+ from_pathname.into_with_c_str(|from_pathname| {
+ to_pathname.into_with_c_str(|to_pathname| {
+ backend::mount::syscalls::move_mount(
+ from_dfd,
+ from_pathname,
+ to_dfd,
+ to_pathname,
+ flags,
+ )
+ })
+ })
+}
+
+/// `open_tree(dfd, filename, flags)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/open_tree.md
+#[inline]
+pub fn open_tree<Path: path::Arg>(
+ dfd: BorrowedFd<'_>,
+ filename: Path,
+ flags: OpenTreeFlags,
+) -> io::Result<OwnedFd> {
+ filename.into_with_c_str(|filename| backend::mount::syscalls::open_tree(dfd, filename, flags))
+}
+
+/// `fspick(dfd, path, flags)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fspick.md
+#[inline]
+pub fn fspick<Path: path::Arg>(
+ dfd: BorrowedFd<'_>,
+ path: Path,
+ flags: FsPickFlags,
+) -> io::Result<OwnedFd> {
+ path.into_with_c_str(|path| backend::mount::syscalls::fspick(dfd, path, flags))
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_SET_FLAG, key, NULL, 0)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_set_flag<Key: path::Arg>(fs_fd: BorrowedFd<'_>, key: Key) -> io::Result<()> {
+ key.into_with_c_str(|key| backend::mount::syscalls::fsconfig_set_flag(fs_fd, key))
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_SET_STRING, key, value, 0)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_set_string<Key: path::Arg, Value: path::Arg>(
+ fs_fd: BorrowedFd<'_>,
+ key: Key,
+ value: Value,
+) -> io::Result<()> {
+ key.into_with_c_str(|key| {
+ value.into_with_c_str(|value| {
+ backend::mount::syscalls::fsconfig_set_string(fs_fd, key, value)
+ })
+ })
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_SET_BINARY, key, value, value.len())`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_set_binary<Key: path::Arg>(
+ fs_fd: BorrowedFd<'_>,
+ key: Key,
+ value: &[u8],
+) -> io::Result<()> {
+ key.into_with_c_str(|key| backend::mount::syscalls::fsconfig_set_binary(fs_fd, key, value))
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_SET_PATH, key, path, fd)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_set_path<Key: path::Arg, Path: path::Arg>(
+ fs_fd: BorrowedFd<'_>,
+ key: Key,
+ path: Path,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ key.into_with_c_str(|key| {
+ path.into_with_c_str(|path| {
+ backend::mount::syscalls::fsconfig_set_path(fs_fd, key, path, fd)
+ })
+ })
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_SET_PATH_EMPTY, key, "", fd)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_set_path_empty<Key: path::Arg>(
+ fs_fd: BorrowedFd<'_>,
+ key: Key,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ key.into_with_c_str(|key| backend::mount::syscalls::fsconfig_set_path_empty(fs_fd, key, fd))
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_SET_FD, key, NULL, fd)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_set_fd<Key: path::Arg>(
+ fs_fd: BorrowedFd<'_>,
+ key: Key,
+ fd: BorrowedFd<'_>,
+) -> io::Result<()> {
+ key.into_with_c_str(|key| backend::mount::syscalls::fsconfig_set_fd(fs_fd, key, fd))
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_CMD_CREATE, key, NULL, 0)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_create(fs_fd: BorrowedFd<'_>) -> io::Result<()> {
+ backend::mount::syscalls::fsconfig_create(fs_fd)
+}
+
+/// `fsconfig(fs_fd, FSCONFIG_CMD_RECONFIGURE, key, NULL, 0)`
+///
+/// # References
+/// - [Unfinished draft]
+///
+/// [Unfinished draft]: https://github.com/sunfishcode/linux-mount-api-documentation/blob/main/fsconfig.md
+#[inline]
+#[doc(alias = "fsconfig")]
+pub fn fsconfig_reconfigure(fs_fd: BorrowedFd<'_>) -> io::Result<()> {
+ backend::mount::syscalls::fsconfig_reconfigure(fs_fd)
+}
diff --git a/src/mount/mod.rs b/src/mount/mod.rs
new file mode 100644
index 0000000..9b4f6da
--- /dev/null
+++ b/src/mount/mod.rs
@@ -0,0 +1,19 @@
+//! Linux `mount` API.
+
+// The `mount` module includes the `mount` function and related
+// functions which were originally defined in `rustix::fs` but are
+// now replaced by deprecated aliases. After the next semver bump,
+// we can remove the aliases and all the `#[cfg(feature = "mount")]`
+// here and in src/backend/*/mount.
+//
+// The `fsopen` module includes `fsopen` and related functions.
+
+#[cfg(feature = "mount")]
+mod fsopen;
+mod mount_unmount;
+mod types;
+
+#[cfg(feature = "mount")]
+pub use fsopen::*;
+pub use mount_unmount::*;
+pub use types::*;
diff --git a/src/mount/mount_unmount.rs b/src/mount/mount_unmount.rs
new file mode 100644
index 0000000..ebb5173
--- /dev/null
+++ b/src/mount/mount_unmount.rs
@@ -0,0 +1,175 @@
+//! Linux `mount`.
+
+use crate::backend::mount::types::{
+ InternalMountFlags, MountFlags, MountFlagsArg, MountPropagationFlags, UnmountFlags,
+};
+use crate::{backend, io, path};
+
+/// `mount(source, target, filesystemtype, mountflags, data)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mount.2.html
+#[inline]
+pub fn mount<Source: path::Arg, Target: path::Arg, Fs: path::Arg, Data: path::Arg>(
+ source: Source,
+ target: Target,
+ file_system_type: Fs,
+ flags: MountFlags,
+ data: Data,
+) -> io::Result<()> {
+ source.into_with_c_str(|source| {
+ target.into_with_c_str(|target| {
+ file_system_type.into_with_c_str(|file_system_type| {
+ data.into_with_c_str(|data| {
+ backend::mount::syscalls::mount(
+ Some(source),
+ target,
+ Some(file_system_type),
+ MountFlagsArg(flags.bits()),
+ Some(data),
+ )
+ })
+ })
+ })
+ })
+}
+
+/// `mount(NULL, target, NULL, MS_REMOUNT | mountflags, data)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mount.2.html
+#[inline]
+#[doc(alias = "mount")]
+#[doc(alias = "MS_REMOUNT")]
+pub fn mount_remount<Target: path::Arg, Data: path::Arg>(
+ target: Target,
+ flags: MountFlags,
+ data: Data,
+) -> io::Result<()> {
+ target.into_with_c_str(|target| {
+ data.into_with_c_str(|data| {
+ backend::mount::syscalls::mount(
+ None,
+ target,
+ None,
+ MountFlagsArg(InternalMountFlags::REMOUNT.bits() | flags.bits()),
+ Some(data),
+ )
+ })
+ })
+}
+
+/// `mount(source, target, NULL, MS_BIND, NULL)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mount.2.html
+#[inline]
+#[doc(alias = "mount")]
+#[doc(alias = "MS_BIND")]
+pub fn mount_bind<Source: path::Arg, Target: path::Arg>(
+ source: Source,
+ target: Target,
+) -> io::Result<()> {
+ source.into_with_c_str(|source| {
+ target.into_with_c_str(|target| {
+ backend::mount::syscalls::mount(
+ Some(source),
+ target,
+ None,
+ MountFlagsArg(MountFlags::BIND.bits()),
+ None,
+ )
+ })
+ })
+}
+
+/// `mount(source, target, NULL, MS_BIND | MS_REC, NULL)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mount.2.html
+#[inline]
+#[doc(alias = "mount")]
+#[doc(alias = "MS_REC")]
+pub fn mount_recursive_bind<Source: path::Arg, Target: path::Arg>(
+ source: Source,
+ target: Target,
+) -> io::Result<()> {
+ source.into_with_c_str(|source| {
+ target.into_with_c_str(|target| {
+ backend::mount::syscalls::mount(
+ Some(source),
+ target,
+ None,
+ MountFlagsArg(MountFlags::BIND.bits() | MountPropagationFlags::REC.bits()),
+ None,
+ )
+ })
+ })
+}
+
+/// `mount(NULL, target, NULL, mountflags, NULL)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/mount.2.html
+#[inline]
+#[doc(alias = "mount")]
+pub fn mount_change<Target: path::Arg>(
+ target: Target,
+ flags: MountPropagationFlags,
+) -> io::Result<()> {
+ target.into_with_c_str(|target| {
+ backend::mount::syscalls::mount(None, target, None, MountFlagsArg(flags.bits()), None)
+ })
+}
+
+/// `mount(source, target, NULL, MS_MOVE, NULL)`
+///
+/// This is not the same as the `move_mount` syscall. If you want to use that,
+/// use [`move_mount`] instead.
+///
+/// # References
+/// - [Linux]
+///
+/// [`move_mount`]: crate::mount::move_mount
+/// [Linux]: https://man7.org/linux/man-pages/man2/mount.2.html
+#[inline]
+#[doc(alias = "mount")]
+#[doc(alias = "MS_MOVE")]
+pub fn mount_move<Source: path::Arg, Target: path::Arg>(
+ source: Source,
+ target: Target,
+) -> io::Result<()> {
+ source.into_with_c_str(|source| {
+ target.into_with_c_str(|target| {
+ backend::mount::syscalls::mount(
+ Some(source),
+ target,
+ None,
+ MountFlagsArg(InternalMountFlags::MOVE.bits()),
+ None,
+ )
+ })
+ })
+}
+
+/// `umount2(target, flags)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/umount.2.html
+#[inline]
+#[doc(alias = "umount", alias = "umount2")]
+pub fn unmount<Target: path::Arg>(target: Target, flags: UnmountFlags) -> io::Result<()> {
+ target.into_with_c_str(|target| backend::mount::syscalls::unmount(target, flags))
+}
diff --git a/src/mount/types.rs b/src/mount/types.rs
new file mode 100644
index 0000000..6096e76
--- /dev/null
+++ b/src/mount/types.rs
@@ -0,0 +1 @@
+pub use crate::backend::mount::types::*;
diff --git a/src/net/mod.rs b/src/net/mod.rs
new file mode 100644
index 0000000..7ec8bc6
--- /dev/null
+++ b/src/net/mod.rs
@@ -0,0 +1,33 @@
+//! Network-related operations.
+//!
+//! On Windows, one must call [`wsa_startup`] in the process before calling any
+//! of these APIs. [`wsa_cleanup`] may be used in the process if these APIs are
+//! no longer needed.
+//!
+//! [`wsa_startup`]: https://docs.rs/rustix/*/x86_64-pc-windows-msvc/rustix/net/fn.wsa_startup.html
+//! [`wsa_cleanup`]: https://docs.rs/rustix/*/x86_64-pc-windows-msvc/rustix/net/fn.wsa_cleanup.html
+
+mod send_recv;
+mod socket;
+mod socket_addr_any;
+#[cfg(not(any(windows, target_os = "wasi")))]
+mod socketpair;
+mod types;
+#[cfg(windows)]
+mod wsa;
+
+#[cfg(linux_kernel)]
+pub mod netdevice;
+pub mod sockopt;
+
+pub use crate::maybe_polyfill::net::{
+ IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6,
+};
+pub use send_recv::*;
+pub use socket::*;
+pub use socket_addr_any::{SocketAddrAny, SocketAddrStorage};
+#[cfg(not(any(windows, target_os = "wasi")))]
+pub use socketpair::socketpair;
+pub use types::*;
+#[cfg(windows)]
+pub use wsa::{wsa_cleanup, wsa_startup};
diff --git a/src/net/netdevice.rs b/src/net/netdevice.rs
new file mode 100644
index 0000000..960c8a0
--- /dev/null
+++ b/src/net/netdevice.rs
@@ -0,0 +1,99 @@
+//! Low-level Linux network device access
+//!
+//! The methods in this module take a socket's file descriptor to communicate with
+//! the kernel in their ioctl call:
+//! - glibc uses an `AF_UNIX`, `AF_INET`, or `AF_INET6` socket.
+//! The address family itself does not matter and glibc tries the next address family if socket creation with one fails.
+//! - Android (bionic) uses an `AF_INET` socket.
+//! - Both create the socket with `SOCK_DGRAM|SOCK_CLOEXEC` type/flag.
+//! - The [man-pages] specify, that the ioctl calls "can be used on any socket's file descriptor regardless of the
+//! family or type".
+//!
+//! # References
+//! - [Linux]
+//!
+//! [man-pages]: https://man7.org/linux/man-pages/man7/netdevice.7.html
+//! [Linux]: https://man7.org/linux/man-pages/man7/netdevice.7.html
+
+#[cfg(feature = "alloc")]
+use crate::alloc::string::String;
+use crate::fd::AsFd;
+use crate::io;
+
+/// `ioctl(fd, SIOCGIFINDEX, ifreq)`—Returns the interface index for a given name.
+///
+/// See the [module-level documentation] for information about `fd` usage.
+///
+/// # References
+/// - [Linux]
+///
+/// [module-level documentation]: self
+/// [Linux]: https://man7.org/linux/man-pages/man7/netdevice.7.html
+#[inline]
+#[doc(alias = "SIOCGIFINDEX")]
+pub fn name_to_index(fd: impl AsFd, if_name: &str) -> io::Result<u32> {
+ crate::backend::net::netdevice::name_to_index(fd, if_name)
+}
+
+/// `ioctl(fd, SIOCGIFNAME, ifreq)`—Returns the interface name for a given index.
+///
+/// See the [module-level documentation] for information about `fd` usage.
+///
+/// # References
+/// - [Linux]
+///
+/// [module-level documentation]: self
+/// [Linux]: https://man7.org/linux/man-pages/man7/netdevice.7.html
+#[inline]
+#[doc(alias = "SIOCGIFNAME")]
+#[cfg(feature = "alloc")]
+pub fn index_to_name(fd: impl AsFd, index: u32) -> io::Result<String> {
+ crate::backend::net::netdevice::index_to_name(fd, index)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::backend::net::netdevice::{index_to_name, name_to_index};
+ use crate::net::{AddressFamily, SocketFlags, SocketType};
+
+ #[test]
+ fn test_name_to_index() {
+ let fd = crate::net::socket_with(
+ AddressFamily::INET,
+ SocketType::DGRAM,
+ SocketFlags::CLOEXEC,
+ None,
+ )
+ .unwrap();
+
+ let loopback_index = std::fs::read_to_string("/sys/class/net/lo/ifindex")
+ .unwrap()
+ .as_str()
+ .split_at(1)
+ .0
+ .parse::<u32>()
+ .unwrap();
+ assert_eq!(Ok(loopback_index), name_to_index(fd, "lo"));
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn test_index_to_name() {
+ let fd = crate::net::socket_with(
+ AddressFamily::INET,
+ SocketType::DGRAM,
+ SocketFlags::CLOEXEC,
+ None,
+ )
+ .unwrap();
+
+ let loopback_index = std::fs::read_to_string("/sys/class/net/lo/ifindex")
+ .unwrap()
+ .as_str()
+ .split_at(1)
+ .0
+ .parse::<u32>()
+ .unwrap();
+ assert_eq!(Ok("lo".to_owned()), index_to_name(fd, loopback_index));
+ }
+}
diff --git a/src/net/send_recv/mod.rs b/src/net/send_recv/mod.rs
new file mode 100644
index 0000000..1ae4fdb
--- /dev/null
+++ b/src/net/send_recv/mod.rs
@@ -0,0 +1,403 @@
+//! `recv`, `send`, and variants.
+
+#![allow(unsafe_code)]
+
+use crate::buffer::split_init;
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+#[cfg(unix)]
+use crate::net::SocketAddrUnix;
+use crate::net::{SocketAddr, SocketAddrAny, SocketAddrV4, SocketAddrV6};
+use crate::{backend, io};
+use backend::fd::{AsFd, BorrowedFd};
+use core::mem::MaybeUninit;
+
+pub use backend::net::send_recv::{RecvFlags, SendFlags};
+
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+mod msg;
+
+#[cfg(not(any(
+ windows,
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub use msg::*;
+
+/// `recv(fd, buf, flags)`—Reads data from a socket.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendrecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/recv.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/recv.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-recv
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2
+/// [NetBSD]: https://man.netbsd.org/recv.2
+/// [OpenBSD]: https://man.openbsd.org/recv.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=recv&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/recv
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Receiving-Data.html
+#[inline]
+pub fn recv<Fd: AsFd>(fd: Fd, buf: &mut [u8], flags: RecvFlags) -> io::Result<usize> {
+ unsafe { backend::net::syscalls::recv(fd.as_fd(), buf.as_mut_ptr(), buf.len(), flags) }
+}
+
+/// `recv(fd, buf, flags)`—Reads data from a socket.
+///
+/// This is equivalent to [`recv`], except that it can read into uninitialized
+/// memory. It returns the slice that was initialized by this function and the
+/// slice that remains uninitialized.
+#[inline]
+pub fn recv_uninit<Fd: AsFd>(
+ fd: Fd,
+ buf: &mut [MaybeUninit<u8>],
+ flags: RecvFlags,
+) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
+ let length = unsafe {
+ backend::net::syscalls::recv(fd.as_fd(), buf.as_mut_ptr() as *mut u8, buf.len(), flags)
+ };
+
+ Ok(unsafe { split_init(buf, length?) })
+}
+
+/// `send(fd, buf, flags)`—Writes data to a socket.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendrecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/send.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/send.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2
+/// [NetBSD]: https://man.netbsd.org/send.2
+/// [OpenBSD]: https://man.openbsd.org/send.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=send&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/send
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Sending-Data.html
+#[inline]
+pub fn send<Fd: AsFd>(fd: Fd, buf: &[u8], flags: SendFlags) -> io::Result<usize> {
+ backend::net::syscalls::send(fd.as_fd(), buf, flags)
+}
+
+/// `recvfrom(fd, buf, flags, addr, len)`—Reads data from a socket and
+/// returns the sender address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendtorecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/recvfrom.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/recvfrom.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-recvfrom
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=recvfrom&sektion=2
+/// [NetBSD]: https://man.netbsd.org/recvfrom.2
+/// [OpenBSD]: https://man.openbsd.org/recvfrom.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=recvfrom&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/recvfrom
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Receiving-Datagrams.html
+#[inline]
+pub fn recvfrom<Fd: AsFd>(
+ fd: Fd,
+ buf: &mut [u8],
+ flags: RecvFlags,
+) -> io::Result<(usize, Option<SocketAddrAny>)> {
+ unsafe { backend::net::syscalls::recvfrom(fd.as_fd(), buf.as_mut_ptr(), buf.len(), flags) }
+}
+
+/// `recvfrom(fd, buf, flags, addr, len)`—Reads data from a socket and
+/// returns the sender address.
+///
+/// This is equivalent to [`recvfrom`], except that it can read into
+/// uninitialized memory. It returns the slice that was initialized by this
+/// function and the slice that remains uninitialized.
+#[allow(clippy::type_complexity)]
+#[inline]
+pub fn recvfrom_uninit<Fd: AsFd>(
+ fd: Fd,
+ buf: &mut [MaybeUninit<u8>],
+ flags: RecvFlags,
+) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>], Option<SocketAddrAny>)> {
+ let (length, addr) = unsafe {
+ backend::net::syscalls::recvfrom(fd.as_fd(), buf.as_mut_ptr() as *mut u8, buf.len(), flags)?
+ };
+ let (init, uninit) = unsafe { split_init(buf, length) };
+ Ok((init, uninit, addr))
+}
+
+/// `sendto(fd, buf, flags, addr)`—Writes data to a socket to a specific IP
+/// address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendtorecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendto.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendto&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendto.2
+/// [OpenBSD]: https://man.openbsd.org/sendto.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendto&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendto
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Sending-Datagrams.html
+pub fn sendto<Fd: AsFd>(
+ fd: Fd,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddr,
+) -> io::Result<usize> {
+ _sendto(fd.as_fd(), buf, flags, addr)
+}
+
+fn _sendto(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddr,
+) -> io::Result<usize> {
+ match addr {
+ SocketAddr::V4(v4) => backend::net::syscalls::sendto_v4(fd, buf, flags, v4),
+ SocketAddr::V6(v6) => backend::net::syscalls::sendto_v6(fd, buf, flags, v6),
+ }
+}
+
+/// `sendto(fd, buf, flags, addr)`—Writes data to a socket to a specific
+/// address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendtorecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendto.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendto&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendto.2
+/// [OpenBSD]: https://man.openbsd.org/sendto.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendto&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendto
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Sending-Datagrams.html
+pub fn sendto_any<Fd: AsFd>(
+ fd: Fd,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrAny,
+) -> io::Result<usize> {
+ _sendto_any(fd.as_fd(), buf, flags, addr)
+}
+
+fn _sendto_any(
+ fd: BorrowedFd<'_>,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrAny,
+) -> io::Result<usize> {
+ match addr {
+ SocketAddrAny::V4(v4) => backend::net::syscalls::sendto_v4(fd, buf, flags, v4),
+ SocketAddrAny::V6(v6) => backend::net::syscalls::sendto_v6(fd, buf, flags, v6),
+ #[cfg(unix)]
+ SocketAddrAny::Unix(unix) => backend::net::syscalls::sendto_unix(fd, buf, flags, unix),
+ #[cfg(target_os = "linux")]
+ SocketAddrAny::Xdp(xdp) => backend::net::syscalls::sendto_xdp(fd, buf, flags, xdp),
+ }
+}
+
+/// `sendto(fd, buf, flags, addr, sizeof(struct sockaddr_in))`—Writes data to
+/// a socket to a specific IPv4 address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendtorecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendto.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendto&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendto.2
+/// [OpenBSD]: https://man.openbsd.org/sendto.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendto&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendto
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Sending-Datagrams.html
+#[inline]
+#[doc(alias = "sendto")]
+pub fn sendto_v4<Fd: AsFd>(
+ fd: Fd,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrV4,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendto_v4(fd.as_fd(), buf, flags, addr)
+}
+
+/// `sendto(fd, buf, flags, addr, sizeof(struct sockaddr_in6))`—Writes data
+/// to a socket to a specific IPv6 address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendtorecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendto.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendto&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendto.2
+/// [OpenBSD]: https://man.openbsd.org/sendto.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendto&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendto
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Sending-Datagrams.html
+#[inline]
+#[doc(alias = "sendto")]
+pub fn sendto_v6<Fd: AsFd>(
+ fd: Fd,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrV6,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendto_v6(fd.as_fd(), buf, flags, addr)
+}
+
+/// `sendto(fd, buf, flags, addr, sizeof(struct sockaddr_un))`—Writes data to
+/// a socket to a specific Unix-domain socket address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#sendtorecv
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendto.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendto&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendto.2
+/// [OpenBSD]: https://man.openbsd.org/sendto.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendto&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendto
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Sending-Datagrams.html
+#[cfg(unix)]
+#[inline]
+#[doc(alias = "sendto")]
+pub fn sendto_unix<Fd: AsFd>(
+ fd: Fd,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrUnix,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendto_unix(fd.as_fd(), buf, flags, addr)
+}
+
+/// `sendto(fd, buf, flags, addr, sizeof(struct sockaddr_xdp))`—Writes data
+/// to a socket to a specific XDP address.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "sendto")]
+pub fn sendto_xdp<Fd: AsFd>(
+ fd: Fd,
+ buf: &[u8],
+ flags: SendFlags,
+ addr: &SocketAddrXdp,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendto_xdp(fd.as_fd(), buf, flags, addr)
+}
diff --git a/src/net/send_recv/msg.rs b/src/net/send_recv/msg.rs
new file mode 100644
index 0000000..629e465
--- /dev/null
+++ b/src/net/send_recv/msg.rs
@@ -0,0 +1,988 @@
+//! [`recvmsg`], [`sendmsg`], and related functions.
+
+#![allow(unsafe_code)]
+
+use crate::backend::{self, c};
+use crate::fd::{AsFd, BorrowedFd, OwnedFd};
+use crate::io::{self, IoSlice, IoSliceMut};
+#[cfg(linux_kernel)]
+use crate::net::UCred;
+
+use core::iter::FusedIterator;
+use core::marker::PhantomData;
+use core::mem::{align_of, size_of, size_of_val, take};
+#[cfg(linux_kernel)]
+use core::ptr::addr_of;
+use core::{ptr, slice};
+
+use super::{RecvFlags, SendFlags, SocketAddrAny, SocketAddrV4, SocketAddrV6};
+
+/// Macro for defining the amount of space to allocate in a buffer for use with
+/// [`RecvAncillaryBuffer::new`] and [`SendAncillaryBuffer::new`].
+///
+/// # Examples
+///
+/// Allocate a buffer for a single file descriptor:
+/// ```
+/// # use rustix::cmsg_space;
+/// let mut space = [0; rustix::cmsg_space!(ScmRights(1))];
+/// ```
+///
+/// Allocate a buffer for credentials:
+/// ```
+/// # #[cfg(linux_kernel)]
+/// # {
+/// # use rustix::cmsg_space;
+/// let mut space = [0; rustix::cmsg_space!(ScmCredentials(1))];
+/// # }
+/// ```
+///
+/// Allocate a buffer for two file descriptors and credentials:
+/// ```
+/// # #[cfg(linux_kernel)]
+/// # {
+/// # use rustix::cmsg_space;
+/// let mut space = [0; rustix::cmsg_space!(ScmRights(2), ScmCredentials(1))];
+/// # }
+/// ```
+#[macro_export]
+macro_rules! cmsg_space {
+ // Base Rules
+ (ScmRights($len:expr)) => {
+ $crate::net::__cmsg_space(
+ $len * ::core::mem::size_of::<$crate::fd::BorrowedFd<'static>>(),
+ )
+ };
+ (ScmCredentials($len:expr)) => {
+ $crate::net::__cmsg_space(
+ $len * ::core::mem::size_of::<$crate::net::UCred>(),
+ )
+ };
+
+ // Combo Rules
+ ($firstid:ident($firstex:expr), $($restid:ident($restex:expr)),*) => {{
+ // We only have to add `cmsghdr` alignment once; all other times we can
+ // use `cmsg_aligned_space`.
+ let sum = $crate::cmsg_space!($firstid($firstex));
+ $(
+ let sum = sum + $crate::cmsg_aligned_space!($restid($restex));
+ )*
+ sum
+ }};
+}
+
+/// Like `cmsg_space`, but doesn't add padding for `cmsghdr` alignment.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! cmsg_aligned_space {
+ // Base Rules
+ (ScmRights($len:expr)) => {
+ $crate::net::__cmsg_aligned_space(
+ $len * ::core::mem::size_of::<$crate::fd::BorrowedFd<'static>>(),
+ )
+ };
+ (ScmCredentials($len:expr)) => {
+ $crate::net::__cmsg_aligned_space(
+ $len * ::core::mem::size_of::<$crate::net::UCred>(),
+ )
+ };
+
+ // Combo Rules
+ ($firstid:ident($firstex:expr), $($restid:ident($restex:expr)),*) => {{
+ let sum = cmsg_aligned_space!($firstid($firstex));
+ $(
+ let sum = sum + cmsg_aligned_space!($restid($restex));
+ )*
+ sum
+ }};
+}
+
+#[doc(hidden)]
+pub const fn __cmsg_space(len: usize) -> usize {
+ // Add `align_of::<c::cmsghdr>()` so that we can align the user-provided
+ // `&[u8]` to the required alignment boundary.
+ let len = len + align_of::<c::cmsghdr>();
+
+ __cmsg_aligned_space(len)
+}
+
+#[doc(hidden)]
+pub const fn __cmsg_aligned_space(len: usize) -> usize {
+ // Convert `len` to `u32` for `CMSG_SPACE`. This would be `try_into()` if
+ // we could call that in a `const fn`.
+ let converted_len = len as u32;
+ if converted_len as usize != len {
+ unreachable!(); // `CMSG_SPACE` size overflow
+ }
+
+ unsafe { c::CMSG_SPACE(converted_len) as usize }
+}
+
+/// Ancillary message for [`sendmsg`], [`sendmsg_v4`], [`sendmsg_v6`],
+/// [`sendmsg_unix`], and [`sendmsg_any`].
+#[non_exhaustive]
+pub enum SendAncillaryMessage<'slice, 'fd> {
+ /// Send file descriptors.
+ #[doc(alias = "SCM_RIGHTS")]
+ ScmRights(&'slice [BorrowedFd<'fd>]),
+ /// Send process credentials.
+ #[cfg(linux_kernel)]
+ #[doc(alias = "SCM_CREDENTIAL")]
+ ScmCredentials(UCred),
+}
+
+impl SendAncillaryMessage<'_, '_> {
+ /// Get the maximum size of an ancillary message.
+ ///
+ /// This can be helpful in determining the size of the buffer you allocate.
+ pub const fn size(&self) -> usize {
+ match self {
+ Self::ScmRights(slice) => cmsg_space!(ScmRights(slice.len())),
+ #[cfg(linux_kernel)]
+ Self::ScmCredentials(_) => cmsg_space!(ScmCredentials(1)),
+ }
+ }
+}
+
+/// Ancillary message for [`recvmsg`].
+#[non_exhaustive]
+pub enum RecvAncillaryMessage<'a> {
+ /// Received file descriptors.
+ #[doc(alias = "SCM_RIGHTS")]
+ ScmRights(AncillaryIter<'a, OwnedFd>),
+ /// Received process credentials.
+ #[cfg(linux_kernel)]
+ #[doc(alias = "SCM_CREDENTIALS")]
+ ScmCredentials(UCred),
+}
+
+/// Buffer for sending ancillary messages with [`sendmsg`], [`sendmsg_v4`],
+/// [`sendmsg_v6`], [`sendmsg_unix`], and [`sendmsg_any`].
+///
+/// Use the [`push`] function to add messages to send.
+///
+/// [`push`]: SendAncillaryBuffer::push
+pub struct SendAncillaryBuffer<'buf, 'slice, 'fd> {
+ /// Raw byte buffer for messages.
+ buffer: &'buf mut [u8],
+
+ /// The amount of the buffer that is used.
+ length: usize,
+
+ /// Phantom data for lifetime of `&'slice [BorrowedFd<'fd>]`.
+ _phantom: PhantomData<&'slice [BorrowedFd<'fd>]>,
+}
+
+impl<'buf> From<&'buf mut [u8]> for SendAncillaryBuffer<'buf, '_, '_> {
+ fn from(buffer: &'buf mut [u8]) -> Self {
+ Self::new(buffer)
+ }
+}
+
+impl Default for SendAncillaryBuffer<'_, '_, '_> {
+ fn default() -> Self {
+ Self {
+ buffer: &mut [],
+ length: 0,
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<'buf, 'slice, 'fd> SendAncillaryBuffer<'buf, 'slice, 'fd> {
+ /// Create a new, empty `SendAncillaryBuffer` from a raw byte buffer.
+ ///
+ /// The buffer size may be computed with [`cmsg_space`], or it may be
+ /// zero for an empty buffer, however in that case, consider `default()`
+ /// instead, or even using [`send`] instead of `sendmsg`.
+ ///
+ /// # Examples
+ ///
+ /// Allocate a buffer for a single file descriptor:
+ /// ```
+ /// # use rustix::cmsg_space;
+ /// # use rustix::net::SendAncillaryBuffer;
+ /// let mut space = [0; rustix::cmsg_space!(ScmRights(1))];
+ /// let mut cmsg_buffer = SendAncillaryBuffer::new(&mut space);
+ /// ```
+ ///
+ /// Allocate a buffer for credentials:
+ /// ```
+ /// # #[cfg(linux_kernel)]
+ /// # {
+ /// # use rustix::cmsg_space;
+ /// # use rustix::net::SendAncillaryBuffer;
+ /// let mut space = [0; rustix::cmsg_space!(ScmCredentials(1))];
+ /// let mut cmsg_buffer = SendAncillaryBuffer::new(&mut space);
+ /// # }
+ /// ```
+ ///
+ /// Allocate a buffer for two file descriptors and credentials:
+ /// ```
+ /// # #[cfg(linux_kernel)]
+ /// # {
+ /// # use rustix::cmsg_space;
+ /// # use rustix::net::SendAncillaryBuffer;
+ /// let mut space = [0; rustix::cmsg_space!(ScmRights(2), ScmCredentials(1))];
+ /// let mut cmsg_buffer = SendAncillaryBuffer::new(&mut space);
+ /// # }
+ /// ```
+ ///
+ /// [`send`]: crate::net::send
+ #[inline]
+ pub fn new(buffer: &'buf mut [u8]) -> Self {
+ Self {
+ buffer: align_for_cmsghdr(buffer),
+ length: 0,
+ _phantom: PhantomData,
+ }
+ }
+
+ /// Returns a pointer to the message data.
+ pub(crate) fn as_control_ptr(&mut self) -> *mut u8 {
+ // When the length is zero, we may be using a `&[]` address, which may
+ // be an invalid but non-null pointer, and on some platforms, that
+ // causes `sendmsg` to fail with `EFAULT` or `EINVAL`
+ #[cfg(not(linux_kernel))]
+ if self.length == 0 {
+ return core::ptr::null_mut();
+ }
+
+ self.buffer.as_mut_ptr()
+ }
+
+ /// Returns the length of the message data.
+ pub(crate) fn control_len(&self) -> usize {
+ self.length
+ }
+
+ /// Delete all messages from the buffer.
+ pub fn clear(&mut self) {
+ self.length = 0;
+ }
+
+ /// Add an ancillary message to the buffer.
+ ///
+ /// Returns `true` if the message was added successfully.
+ pub fn push(&mut self, msg: SendAncillaryMessage<'slice, 'fd>) -> bool {
+ match msg {
+ SendAncillaryMessage::ScmRights(fds) => {
+ let fds_bytes =
+ unsafe { slice::from_raw_parts(fds.as_ptr().cast::<u8>(), size_of_val(fds)) };
+ self.push_ancillary(fds_bytes, c::SOL_SOCKET as _, c::SCM_RIGHTS as _)
+ }
+ #[cfg(linux_kernel)]
+ SendAncillaryMessage::ScmCredentials(ucred) => {
+ let ucred_bytes = unsafe {
+ slice::from_raw_parts(addr_of!(ucred).cast::<u8>(), size_of_val(&ucred))
+ };
+ self.push_ancillary(ucred_bytes, c::SOL_SOCKET as _, c::SCM_CREDENTIALS as _)
+ }
+ }
+ }
+
+ /// Pushes an ancillary message to the buffer.
+ fn push_ancillary(&mut self, source: &[u8], cmsg_level: c::c_int, cmsg_type: c::c_int) -> bool {
+ macro_rules! leap {
+ ($e:expr) => {{
+ match ($e) {
+ Some(x) => x,
+ None => return false,
+ }
+ }};
+ }
+
+ // Calculate the length of the message.
+ let source_len = leap!(u32::try_from(source.len()).ok());
+
+ // Calculate the new length of the buffer.
+ let additional_space = unsafe { c::CMSG_SPACE(source_len) };
+ let new_length = leap!(self.length.checked_add(additional_space as usize));
+ let buffer = leap!(self.buffer.get_mut(..new_length));
+
+ // Fill the new part of the buffer with zeroes.
+ buffer[self.length..new_length].fill(0);
+ self.length = new_length;
+
+ // Get the last header in the buffer.
+ let last_header = leap!(messages::Messages::new(buffer).last());
+
+ // Set the header fields.
+ last_header.cmsg_len = unsafe { c::CMSG_LEN(source_len) } as _;
+ last_header.cmsg_level = cmsg_level;
+ last_header.cmsg_type = cmsg_type;
+
+ // Get the pointer to the payload and copy the data.
+ unsafe {
+ let payload = c::CMSG_DATA(last_header);
+ ptr::copy_nonoverlapping(source.as_ptr(), payload, source_len as _);
+ }
+
+ true
+ }
+}
+
+impl<'slice, 'fd> Extend<SendAncillaryMessage<'slice, 'fd>>
+ for SendAncillaryBuffer<'_, 'slice, 'fd>
+{
+ fn extend<T: IntoIterator<Item = SendAncillaryMessage<'slice, 'fd>>>(&mut self, iter: T) {
+ // TODO: This could be optimized to add every message in one go.
+ iter.into_iter().all(|msg| self.push(msg));
+ }
+}
+
+/// Buffer for receiving ancillary messages with [`recvmsg`].
+///
+/// Use the [`drain`] function to iterate over the received messages.
+///
+/// [`drain`]: RecvAncillaryBuffer::drain
+#[derive(Default)]
+pub struct RecvAncillaryBuffer<'buf> {
+ /// Raw byte buffer for messages.
+ buffer: &'buf mut [u8],
+
+ /// The portion of the buffer we've read from already.
+ read: usize,
+
+ /// The amount of the buffer that is used.
+ length: usize,
+}
+
+impl<'buf> From<&'buf mut [u8]> for RecvAncillaryBuffer<'buf> {
+ fn from(buffer: &'buf mut [u8]) -> Self {
+ Self::new(buffer)
+ }
+}
+
+impl<'buf> RecvAncillaryBuffer<'buf> {
+ /// Create a new, empty `RecvAncillaryBuffer` from a raw byte buffer.
+ ///
+ /// The buffer size may be computed with [`cmsg_space`], or it may be
+ /// zero for an empty buffer, however in that case, consider `default()`
+ /// instead, or even using [`recv`] instead of `recvmsg`.
+ ///
+ /// # Examples
+ ///
+ /// Allocate a buffer for a single file descriptor:
+ /// ```
+ /// # use rustix::cmsg_space;
+ /// # use rustix::net::RecvAncillaryBuffer;
+ /// let mut space = [0; rustix::cmsg_space!(ScmRights(1))];
+ /// let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut space);
+ /// ```
+ ///
+ /// Allocate a buffer for credentials:
+ /// ```
+ /// # #[cfg(linux_kernel)]
+ /// # {
+ /// # use rustix::cmsg_space;
+ /// # use rustix::net::RecvAncillaryBuffer;
+ /// let mut space = [0; rustix::cmsg_space!(ScmCredentials(1))];
+ /// let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut space);
+ /// # }
+ /// ```
+ ///
+ /// Allocate a buffer for two file descriptors and credentials:
+ /// ```
+ /// # #[cfg(linux_kernel)]
+ /// # {
+ /// # use rustix::cmsg_space;
+ /// # use rustix::net::RecvAncillaryBuffer;
+ /// let mut space = [0; rustix::cmsg_space!(ScmRights(2), ScmCredentials(1))];
+ /// let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut space);
+ /// # }
+ /// ```
+ ///
+ /// [`recv`]: crate::net::recv
+ #[inline]
+ pub fn new(buffer: &'buf mut [u8]) -> Self {
+ Self {
+ buffer: align_for_cmsghdr(buffer),
+ read: 0,
+ length: 0,
+ }
+ }
+
+ /// Returns a pointer to the message data.
+ pub(crate) fn as_control_ptr(&mut self) -> *mut u8 {
+ // When the length is zero, we may be using a `&[]` address, which may
+ // be an invalid but non-null pointer, and on some platforms, that
+ // causes `sendmsg` to fail with `EFAULT` or `EINVAL`
+ #[cfg(not(linux_kernel))]
+ if self.buffer.is_empty() {
+ return core::ptr::null_mut();
+ }
+
+ self.buffer.as_mut_ptr()
+ }
+
+ /// Returns the length of the message data.
+ pub(crate) fn control_len(&self) -> usize {
+ self.buffer.len()
+ }
+
+ /// Set the length of the message data.
+ ///
+ /// # Safety
+ ///
+ /// The buffer must be filled with valid message data.
+ pub(crate) unsafe fn set_control_len(&mut self, len: usize) {
+ self.length = len;
+ self.read = 0;
+ }
+
+ /// Delete all messages from the buffer.
+ pub(crate) fn clear(&mut self) {
+ self.drain().for_each(drop);
+ }
+
+ /// Drain all messages from the buffer.
+ pub fn drain(&mut self) -> AncillaryDrain<'_> {
+ AncillaryDrain {
+ messages: messages::Messages::new(&mut self.buffer[self.read..][..self.length]),
+ read: &mut self.read,
+ length: &mut self.length,
+ }
+ }
+}
+
+impl Drop for RecvAncillaryBuffer<'_> {
+ fn drop(&mut self) {
+ self.clear();
+ }
+}
+
+/// Return a slice of `buffer` starting at the first `cmsghdr` alignment
+/// boundary.
+#[inline]
+fn align_for_cmsghdr(buffer: &mut [u8]) -> &mut [u8] {
+ // If the buffer is empty, we won't be writing anything into it, so it
+ // doesn't need to be aligned.
+ if buffer.is_empty() {
+ return buffer;
+ }
+
+ let align = align_of::<c::cmsghdr>();
+ let addr = buffer.as_ptr() as usize;
+ let adjusted = (addr + (align - 1)) & align.wrapping_neg();
+ &mut buffer[adjusted - addr..]
+}
+
+/// An iterator that drains messages from a [`RecvAncillaryBuffer`].
+pub struct AncillaryDrain<'buf> {
+ /// Inner iterator over messages.
+ messages: messages::Messages<'buf>,
+
+ /// Increment the number of messages we've read.
+ read: &'buf mut usize,
+
+ /// Decrement the total length.
+ length: &'buf mut usize,
+}
+
+impl<'buf> AncillaryDrain<'buf> {
+ /// A closure that converts a message into a [`RecvAncillaryMessage`].
+ fn cvt_msg(
+ read: &mut usize,
+ length: &mut usize,
+ msg: &c::cmsghdr,
+ ) -> Option<RecvAncillaryMessage<'buf>> {
+ unsafe {
+ // Advance the `read` pointer.
+ let msg_len = msg.cmsg_len as usize;
+ *read += msg_len;
+ *length -= msg_len;
+
+ // Get a pointer to the payload.
+ let payload = c::CMSG_DATA(msg);
+ let payload_len = msg.cmsg_len as usize - c::CMSG_LEN(0) as usize;
+
+ // Get a mutable slice of the payload.
+ let payload: &'buf mut [u8] = slice::from_raw_parts_mut(payload, payload_len);
+
+ // Determine what type it is.
+ let (level, msg_type) = (msg.cmsg_level, msg.cmsg_type);
+ match (level as _, msg_type as _) {
+ (c::SOL_SOCKET, c::SCM_RIGHTS) => {
+ // Create an iterator that reads out the file descriptors.
+ let fds = AncillaryIter::new(payload);
+
+ Some(RecvAncillaryMessage::ScmRights(fds))
+ }
+ #[cfg(linux_kernel)]
+ (c::SOL_SOCKET, c::SCM_CREDENTIALS) => {
+ if payload_len >= size_of::<UCred>() {
+ let ucred = payload.as_ptr().cast::<UCred>().read_unaligned();
+ Some(RecvAncillaryMessage::ScmCredentials(ucred))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ }
+ }
+}
+
+impl<'buf> Iterator for AncillaryDrain<'buf> {
+ type Item = RecvAncillaryMessage<'buf>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let read = &mut self.read;
+ let length = &mut self.length;
+ self.messages.find_map(|ev| Self::cvt_msg(read, length, ev))
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let (_, max) = self.messages.size_hint();
+ (0, max)
+ }
+
+ fn fold<B, F>(self, init: B, f: F) -> B
+ where
+ Self: Sized,
+ F: FnMut(B, Self::Item) -> B,
+ {
+ let read = self.read;
+ let length = self.length;
+ self.messages
+ .filter_map(|ev| Self::cvt_msg(read, length, ev))
+ .fold(init, f)
+ }
+
+ fn count(self) -> usize {
+ let read = self.read;
+ let length = self.length;
+ self.messages
+ .filter_map(|ev| Self::cvt_msg(read, length, ev))
+ .count()
+ }
+
+ fn last(self) -> Option<Self::Item>
+ where
+ Self: Sized,
+ {
+ let read = self.read;
+ let length = self.length;
+ self.messages
+ .filter_map(|ev| Self::cvt_msg(read, length, ev))
+ .last()
+ }
+
+ fn collect<B: FromIterator<Self::Item>>(self) -> B
+ where
+ Self: Sized,
+ {
+ let read = self.read;
+ let length = self.length;
+ self.messages
+ .filter_map(|ev| Self::cvt_msg(read, length, ev))
+ .collect()
+ }
+}
+
+impl FusedIterator for AncillaryDrain<'_> {}
+
+/// `sendmsg(msghdr)`—Sends a message on a socket.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendmsg.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendmsg.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendmsg.2
+/// [OpenBSD]: https://man.openbsd.org/sendmsg.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendmsg&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendmsg
+#[inline]
+pub fn sendmsg(
+ socket: impl AsFd,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ flags: SendFlags,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendmsg(socket.as_fd(), iov, control, flags)
+}
+
+/// `sendmsg(msghdr)`—Sends a message on a socket to a specific IPv4 address.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendmsg.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendmsg.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendmsg.2
+/// [OpenBSD]: https://man.openbsd.org/sendmsg.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendmsg&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendmsg
+#[inline]
+pub fn sendmsg_v4(
+ socket: impl AsFd,
+ addr: &SocketAddrV4,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ flags: SendFlags,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendmsg_v4(socket.as_fd(), addr, iov, control, flags)
+}
+
+/// `sendmsg(msghdr)`—Sends a message on a socket to a specific IPv6 address.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendmsg.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendmsg.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendmsg.2
+/// [OpenBSD]: https://man.openbsd.org/sendmsg.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendmsg&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendmsg
+#[inline]
+pub fn sendmsg_v6(
+ socket: impl AsFd,
+ addr: &SocketAddrV6,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ flags: SendFlags,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendmsg_v6(socket.as_fd(), addr, iov, control, flags)
+}
+
+/// `sendmsg(msghdr)`—Sends a message on a socket to a specific Unix-domain
+/// address.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendmsg.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendmsg.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendmsg.2
+/// [OpenBSD]: https://man.openbsd.org/sendmsg.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendmsg&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendmsg
+#[inline]
+#[cfg(unix)]
+pub fn sendmsg_unix(
+ socket: impl AsFd,
+ addr: &super::SocketAddrUnix,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ flags: SendFlags,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendmsg_unix(socket.as_fd(), addr, iov, control, flags)
+}
+
+/// `sendmsg(msghdr)`—Sends a message on a socket to a specific XDP address.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendmsg.2.html
+#[inline]
+#[cfg(target_os = "linux")]
+pub fn sendmsg_xdp(
+ socket: impl AsFd,
+ addr: &super::SocketAddrXdp,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ flags: SendFlags,
+) -> io::Result<usize> {
+ backend::net::syscalls::sendmsg_xdp(socket.as_fd(), addr, iov, control, flags)
+}
+
+/// `sendmsg(msghdr)`—Sends a message on a socket to a specific address.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendmsg.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sendmsg.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=sendmsg&sektion=2
+/// [NetBSD]: https://man.netbsd.org/sendmsg.2
+/// [OpenBSD]: https://man.openbsd.org/sendmsg.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sendmsg&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/sendmsg
+#[inline]
+pub fn sendmsg_any(
+ socket: impl AsFd,
+ addr: Option<&SocketAddrAny>,
+ iov: &[IoSlice<'_>],
+ control: &mut SendAncillaryBuffer<'_, '_, '_>,
+ flags: SendFlags,
+) -> io::Result<usize> {
+ match addr {
+ None => backend::net::syscalls::sendmsg(socket.as_fd(), iov, control, flags),
+ Some(SocketAddrAny::V4(addr)) => {
+ backend::net::syscalls::sendmsg_v4(socket.as_fd(), addr, iov, control, flags)
+ }
+ Some(SocketAddrAny::V6(addr)) => {
+ backend::net::syscalls::sendmsg_v6(socket.as_fd(), addr, iov, control, flags)
+ }
+ #[cfg(unix)]
+ Some(SocketAddrAny::Unix(addr)) => {
+ backend::net::syscalls::sendmsg_unix(socket.as_fd(), addr, iov, control, flags)
+ }
+ #[cfg(target_os = "linux")]
+ Some(SocketAddrAny::Xdp(addr)) => {
+ backend::net::syscalls::sendmsg_xdp(socket.as_fd(), addr, iov, control, flags)
+ }
+ }
+}
+
+/// `recvmsg(msghdr)`—Receives a message from a socket.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/recvmsg.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/recvmsg.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=recvmsg&sektion=2
+/// [NetBSD]: https://man.netbsd.org/recvmsg.2
+/// [OpenBSD]: https://man.openbsd.org/recvmsg.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=recvmsg&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/recvmsg
+#[inline]
+pub fn recvmsg(
+ socket: impl AsFd,
+ iov: &mut [IoSliceMut<'_>],
+ control: &mut RecvAncillaryBuffer<'_>,
+ flags: RecvFlags,
+) -> io::Result<RecvMsgReturn> {
+ backend::net::syscalls::recvmsg(socket.as_fd(), iov, control, flags)
+}
+
+/// The result of a successful [`recvmsg`] call.
+pub struct RecvMsgReturn {
+ /// The number of bytes received.
+ pub bytes: usize,
+
+ /// The flags received.
+ pub flags: RecvFlags,
+
+ /// The address of the socket we received from, if any.
+ pub address: Option<SocketAddrAny>,
+}
+
+/// An iterator over data in an ancillary buffer.
+pub struct AncillaryIter<'data, T> {
+ /// The data we're iterating over.
+ data: &'data mut [u8],
+
+ /// The raw data we're removing.
+ _marker: PhantomData<T>,
+}
+
+impl<'data, T> AncillaryIter<'data, T> {
+ /// Create a new iterator over data in an ancillary buffer.
+ ///
+ /// # Safety
+ ///
+ /// The buffer must contain valid ancillary data.
+ unsafe fn new(data: &'data mut [u8]) -> Self {
+ assert_eq!(data.len() % size_of::<T>(), 0);
+
+ Self {
+ data,
+ _marker: PhantomData,
+ }
+ }
+}
+
+impl<'data, T> Drop for AncillaryIter<'data, T> {
+ fn drop(&mut self) {
+ self.for_each(drop);
+ }
+}
+
+impl<T> Iterator for AncillaryIter<'_, T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // See if there is a next item.
+ if self.data.len() < size_of::<T>() {
+ return None;
+ }
+
+ // Get the next item.
+ let item = unsafe { self.data.as_ptr().cast::<T>().read_unaligned() };
+
+ // Move forward.
+ let data = take(&mut self.data);
+ self.data = &mut data[size_of::<T>()..];
+
+ Some(item)
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let len = self.len();
+ (len, Some(len))
+ }
+
+ fn count(self) -> usize {
+ self.len()
+ }
+
+ fn last(mut self) -> Option<Self::Item> {
+ self.next_back()
+ }
+}
+
+impl<T> FusedIterator for AncillaryIter<'_, T> {}
+
+impl<T> ExactSizeIterator for AncillaryIter<'_, T> {
+ fn len(&self) -> usize {
+ self.data.len() / size_of::<T>()
+ }
+}
+
+impl<T> DoubleEndedIterator for AncillaryIter<'_, T> {
+ fn next_back(&mut self) -> Option<Self::Item> {
+ // See if there is a next item.
+ if self.data.len() < size_of::<T>() {
+ return None;
+ }
+
+ // Get the next item.
+ let item = unsafe {
+ let ptr = self.data.as_ptr().add(self.data.len() - size_of::<T>());
+ ptr.cast::<T>().read_unaligned()
+ };
+
+ // Move forward.
+ let len = self.data.len();
+ let data = take(&mut self.data);
+ self.data = &mut data[..len - size_of::<T>()];
+
+ Some(item)
+ }
+}
+
+mod messages {
+ use crate::backend::c;
+ use crate::backend::net::msghdr;
+ use core::iter::FusedIterator;
+ use core::marker::PhantomData;
+ use core::ptr::NonNull;
+
+ /// An iterator over the messages in an ancillary buffer.
+ pub(super) struct Messages<'buf> {
+ /// The message header we're using to iterate over the messages.
+ msghdr: c::msghdr,
+
+ /// The current pointer to the next message header to return.
+ ///
+ /// This has a lifetime of `'buf`.
+ header: Option<NonNull<c::cmsghdr>>,
+
+ /// Capture the original lifetime of the buffer.
+ _buffer: PhantomData<&'buf mut [u8]>,
+ }
+
+ impl<'buf> Messages<'buf> {
+ /// Create a new iterator over messages from a byte buffer.
+ pub(super) fn new(buf: &'buf mut [u8]) -> Self {
+ let msghdr = {
+ let mut h = msghdr::zero_msghdr();
+ h.msg_control = buf.as_mut_ptr().cast();
+ h.msg_controllen = buf.len().try_into().expect("buffer too large for msghdr");
+ h
+ };
+
+ // Get the first header.
+ let header = NonNull::new(unsafe { c::CMSG_FIRSTHDR(&msghdr) });
+
+ Self {
+ msghdr,
+ header,
+ _buffer: PhantomData,
+ }
+ }
+ }
+
+ impl<'a> Iterator for Messages<'a> {
+ type Item = &'a mut c::cmsghdr;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ // Get the current header.
+ let header = self.header?;
+
+ // Get the next header.
+ self.header = NonNull::new(unsafe { c::CMSG_NXTHDR(&self.msghdr, header.as_ptr()) });
+
+ // If the headers are equal, we're done.
+ if Some(header) == self.header {
+ self.header = None;
+ }
+
+ // SAFETY: The lifetime of `header` is tied to this.
+ Some(unsafe { &mut *header.as_ptr() })
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ if self.header.is_some() {
+ // The remaining buffer *could* be filled with zero-length
+ // messages.
+ let max_size = unsafe { c::CMSG_LEN(0) } as usize;
+ let remaining_count = self.msghdr.msg_controllen as usize / max_size;
+ (1, Some(remaining_count))
+ } else {
+ (0, Some(0))
+ }
+ }
+ }
+
+ impl FusedIterator for Messages<'_> {}
+}
diff --git a/src/net/socket.rs b/src/net/socket.rs
new file mode 100644
index 0000000..bf1aa7c
--- /dev/null
+++ b/src/net/socket.rs
@@ -0,0 +1,769 @@
+use crate::fd::OwnedFd;
+use crate::net::{SocketAddr, SocketAddrAny, SocketAddrV4, SocketAddrV6};
+use crate::{backend, io};
+use backend::fd::{AsFd, BorrowedFd};
+
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+pub use crate::net::{AddressFamily, Protocol, Shutdown, SocketFlags, SocketType};
+#[cfg(unix)]
+pub use backend::net::addr::SocketAddrUnix;
+
+/// `socket(domain, type_, protocol)`—Creates a socket.
+///
+/// POSIX guarantees that `socket` will use the lowest unused file descriptor,
+/// however it is not safe in general to rely on this, as file descriptors may
+/// be unexpectedly allocated on other threads or in libraries.
+///
+/// To pass extra flags such as [`SocketFlags::CLOEXEC`] or
+/// [`SocketFlags::NONBLOCK`], use [`socket_with`].
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#socket
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/socket.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/socket.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2
+/// [NetBSD]: https://man.netbsd.org/socket.2
+/// [OpenBSD]: https://man.openbsd.org/socket.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=socket&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/socket
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Creating-a-Socket.html
+#[inline]
+pub fn socket(
+ domain: AddressFamily,
+ type_: SocketType,
+ protocol: Option<Protocol>,
+) -> io::Result<OwnedFd> {
+ backend::net::syscalls::socket(domain, type_, protocol)
+}
+
+/// `socket_with(domain, type_ | flags, protocol)`—Creates a socket, with
+/// flags.
+///
+/// POSIX guarantees that `socket` will use the lowest unused file descriptor,
+/// however it is not safe in general to rely on this, as file descriptors may
+/// be unexpectedly allocated on other threads or in libraries.
+///
+/// `socket_with` is the same as [`socket`] but adds an additional flags
+/// operand.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#socket
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/socket.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/socket.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2
+/// [NetBSD]: https://man.netbsd.org/socket.2
+/// [OpenBSD]: https://man.openbsd.org/socket.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=socket&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/socket
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Creating-a-Socket.html
+#[doc(alias("socket"))]
+#[inline]
+pub fn socket_with(
+ domain: AddressFamily,
+ type_: SocketType,
+ flags: SocketFlags,
+ protocol: Option<Protocol>,
+) -> io::Result<OwnedFd> {
+ backend::net::syscalls::socket_with(domain, type_, flags, protocol)
+}
+
+/// `bind(sockfd, addr)`—Binds a socket to an IP address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#bind
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/bind.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2
+/// [NetBSD]: https://man.netbsd.org/bind.2
+/// [OpenBSD]: https://man.openbsd.org/bind.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=bind&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/bind
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Setting-Address.html
+pub fn bind<Fd: AsFd>(sockfd: Fd, addr: &SocketAddr) -> io::Result<()> {
+ _bind(sockfd.as_fd(), addr)
+}
+
+fn _bind(sockfd: BorrowedFd<'_>, addr: &SocketAddr) -> io::Result<()> {
+ match addr {
+ SocketAddr::V4(v4) => backend::net::syscalls::bind_v4(sockfd, v4),
+ SocketAddr::V6(v6) => backend::net::syscalls::bind_v6(sockfd, v6),
+ }
+}
+
+/// `bind(sockfd, addr)`—Binds a socket to an address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#bind
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/bind.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2
+/// [NetBSD]: https://man.netbsd.org/bind.2
+/// [OpenBSD]: https://man.openbsd.org/bind.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=bind&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/bind
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Setting-Address.html
+#[doc(alias = "bind")]
+pub fn bind_any<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrAny) -> io::Result<()> {
+ _bind_any(sockfd.as_fd(), addr)
+}
+
+fn _bind_any(sockfd: BorrowedFd<'_>, addr: &SocketAddrAny) -> io::Result<()> {
+ match addr {
+ SocketAddrAny::V4(v4) => backend::net::syscalls::bind_v4(sockfd, v4),
+ SocketAddrAny::V6(v6) => backend::net::syscalls::bind_v6(sockfd, v6),
+ #[cfg(unix)]
+ SocketAddrAny::Unix(unix) => backend::net::syscalls::bind_unix(sockfd, unix),
+ #[cfg(target_os = "linux")]
+ SocketAddrAny::Xdp(xdp) => backend::net::syscalls::bind_xdp(sockfd, xdp),
+ }
+}
+
+/// `bind(sockfd, addr, sizeof(struct sockaddr_in))`—Binds a socket to an
+/// IPv4 address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#bind
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/bind.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2
+/// [NetBSD]: https://man.netbsd.org/bind.2
+/// [OpenBSD]: https://man.openbsd.org/bind.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=bind&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/bind
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Setting-Address.html
+#[inline]
+#[doc(alias = "bind")]
+pub fn bind_v4<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrV4) -> io::Result<()> {
+ backend::net::syscalls::bind_v4(sockfd.as_fd(), addr)
+}
+
+/// `bind(sockfd, addr, sizeof(struct sockaddr_in6))`—Binds a socket to an
+/// IPv6 address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#bind
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/bind.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2
+/// [NetBSD]: https://man.netbsd.org/bind.2
+/// [OpenBSD]: https://man.openbsd.org/bind.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=bind&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/bind
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Setting-Address.html
+#[inline]
+#[doc(alias = "bind")]
+pub fn bind_v6<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrV6) -> io::Result<()> {
+ backend::net::syscalls::bind_v6(sockfd.as_fd(), addr)
+}
+
+/// `bind(sockfd, addr, sizeof(struct sockaddr_un))`—Binds a socket to a
+/// Unix-domain address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#bind
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/bind.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2
+/// [NetBSD]: https://man.netbsd.org/bind.2
+/// [OpenBSD]: https://man.openbsd.org/bind.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=bind&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/bind
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Setting-Address.html
+#[cfg(unix)]
+#[inline]
+#[doc(alias = "bind")]
+pub fn bind_unix<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrUnix) -> io::Result<()> {
+ backend::net::syscalls::bind_unix(sockfd.as_fd(), addr)
+}
+
+/// `bind(sockfd, addr, sizeof(struct sockaddr_un))`—Binds a socket to a XDP address.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "bind")]
+pub fn bind_xdp<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrXdp) -> io::Result<()> {
+ backend::net::syscalls::bind_xdp(sockfd.as_fd(), addr)
+}
+
+/// `connect(sockfd, addr)`—Initiates a connection to an IP address.
+///
+/// On Windows, a non-blocking socket returns [`Errno::WOULDBLOCK`] if the
+/// connection cannot be completed immediately, rather than
+/// `Errno::INPROGRESS`.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/connect.2
+/// [OpenBSD]: https://man.openbsd.org/connect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/connect
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
+/// [`Errno::WOULDBLOCK`]: io::Errno::WOULDBLOCK
+pub fn connect<Fd: AsFd>(sockfd: Fd, addr: &SocketAddr) -> io::Result<()> {
+ _connect(sockfd.as_fd(), addr)
+}
+
+fn _connect(sockfd: BorrowedFd<'_>, addr: &SocketAddr) -> io::Result<()> {
+ match addr {
+ SocketAddr::V4(v4) => backend::net::syscalls::connect_v4(sockfd, v4),
+ SocketAddr::V6(v6) => backend::net::syscalls::connect_v6(sockfd, v6),
+ }
+}
+
+/// `connect(sockfd, addr)`—Initiates a connection.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/connect.2
+/// [OpenBSD]: https://man.openbsd.org/connect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/connect
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
+#[doc(alias = "connect")]
+pub fn connect_any<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrAny) -> io::Result<()> {
+ _connect_any(sockfd.as_fd(), addr)
+}
+
+fn _connect_any(sockfd: BorrowedFd<'_>, addr: &SocketAddrAny) -> io::Result<()> {
+ match addr {
+ SocketAddrAny::V4(v4) => backend::net::syscalls::connect_v4(sockfd, v4),
+ SocketAddrAny::V6(v6) => backend::net::syscalls::connect_v6(sockfd, v6),
+ #[cfg(unix)]
+ SocketAddrAny::Unix(unix) => backend::net::syscalls::connect_unix(sockfd, unix),
+ #[cfg(target_os = "linux")]
+ SocketAddrAny::Xdp(_) => Err(io::Errno::OPNOTSUPP),
+ }
+}
+
+/// `connect(sockfd, addr, sizeof(struct sockaddr_in))`—Initiates a
+/// connection to an IPv4 address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/connect.2
+/// [OpenBSD]: https://man.openbsd.org/connect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/connect
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
+#[inline]
+#[doc(alias = "connect")]
+pub fn connect_v4<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrV4) -> io::Result<()> {
+ backend::net::syscalls::connect_v4(sockfd.as_fd(), addr)
+}
+
+/// `connect(sockfd, addr, sizeof(struct sockaddr_in6))`—Initiates a
+/// connection to an IPv6 address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/connect.2
+/// [OpenBSD]: https://man.openbsd.org/connect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/connect
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
+#[inline]
+#[doc(alias = "connect")]
+pub fn connect_v6<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrV6) -> io::Result<()> {
+ backend::net::syscalls::connect_v6(sockfd.as_fd(), addr)
+}
+
+/// `connect(sockfd, addr, sizeof(struct sockaddr_un))`—Initiates a
+/// connection to a Unix-domain address.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/connect.2
+/// [OpenBSD]: https://man.openbsd.org/connect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/connect
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
+#[cfg(unix)]
+#[inline]
+#[doc(alias = "connect")]
+pub fn connect_unix<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrUnix) -> io::Result<()> {
+ backend::net::syscalls::connect_unix(sockfd.as_fd(), addr)
+}
+
+/// `connect(sockfd, {.sa_family = AF_UNSPEC}, sizeof(struct sockaddr))`
+/// — Dissolve the socket's association.
+///
+/// On UDP sockets, BSD platforms report [`Errno::AFNOSUPPORT`] or
+/// [`Errno::INVAL`] even if the disconnect was successful.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
+/// [NetBSD]: https://man.netbsd.org/connect.2
+/// [OpenBSD]: https://man.openbsd.org/connect.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/connect
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
+/// [`Errno::AFNOSUPPORT`]: io::Errno::AFNOSUPPORT
+/// [`Errno::INVAL`]: io::Errno::INVAL
+#[inline]
+#[doc(alias = "connect")]
+pub fn connect_unspec<Fd: AsFd>(sockfd: Fd) -> io::Result<()> {
+ backend::net::syscalls::connect_unspec(sockfd.as_fd())
+}
+
+/// `listen(fd, backlog)`—Enables listening for incoming connections.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#listen
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/listen.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/listen.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2
+/// [NetBSD]: https://man.netbsd.org/listen.2
+/// [OpenBSD]: https://man.openbsd.org/listen.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=listen&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/listen
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Listening.html
+#[inline]
+pub fn listen<Fd: AsFd>(sockfd: Fd, backlog: i32) -> io::Result<()> {
+ backend::net::syscalls::listen(sockfd.as_fd(), backlog)
+}
+
+/// `accept(fd, NULL, NULL)`—Accepts an incoming connection.
+///
+/// Use [`acceptfrom`] to retrieve the peer address.
+///
+/// POSIX guarantees that `accept` will use the lowest unused file descriptor,
+/// however it is not safe in general to rely on this, as file descriptors may
+/// be unexpectedly allocated on other threads or in libraries.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#acceptthank-you-for-calling-port-3490.
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/accept.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/accept.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2
+/// [NetBSD]: https://man.netbsd.org/accept.2
+/// [OpenBSD]: https://man.openbsd.org/accept.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=accept&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/accept
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Accepting-Connections.html
+#[inline]
+pub fn accept<Fd: AsFd>(sockfd: Fd) -> io::Result<OwnedFd> {
+ backend::net::syscalls::accept(sockfd.as_fd())
+}
+
+/// `accept4(fd, NULL, NULL, flags)`—Accepts an incoming connection, with
+/// flags.
+///
+/// Use [`acceptfrom_with`] to retrieve the peer address.
+///
+/// Even though POSIX guarantees that this will use the lowest unused file
+/// descriptor, it is not safe in general to rely on this, as file descriptors
+/// may be unexpectedly allocated on other threads or in libraries.
+///
+/// `accept_with` is the same as [`accept`] but adds an additional flags
+/// operand.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/accept4.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=accept4&sektion=2
+/// [NetBSD]: https://man.netbsd.org/accept4.2
+/// [OpenBSD]: https://man.openbsd.org/accept4.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=accept4&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/accept4
+#[inline]
+#[doc(alias = "accept4")]
+pub fn accept_with<Fd: AsFd>(sockfd: Fd, flags: SocketFlags) -> io::Result<OwnedFd> {
+ backend::net::syscalls::accept_with(sockfd.as_fd(), flags)
+}
+
+/// `accept(fd, &addr, &len)`—Accepts an incoming connection and returns the
+/// peer address.
+///
+/// Use [`accept`] if the peer address isn't needed.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#acceptthank-you-for-calling-port-3490.
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/accept.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/accept.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2
+/// [NetBSD]: https://man.netbsd.org/accept.2
+/// [OpenBSD]: https://man.openbsd.org/accept.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=accept&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/accept
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Accepting-Connections.html
+#[inline]
+#[doc(alias = "accept")]
+pub fn acceptfrom<Fd: AsFd>(sockfd: Fd) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ backend::net::syscalls::acceptfrom(sockfd.as_fd())
+}
+
+/// `accept4(fd, &addr, &len, flags)`—Accepts an incoming connection and
+/// returns the peer address, with flags.
+///
+/// Use [`accept_with`] if the peer address isn't needed.
+///
+/// `acceptfrom_with` is the same as [`acceptfrom`] but adds an additional
+/// flags operand.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/accept4.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=accept4&sektion=2
+/// [NetBSD]: https://man.netbsd.org/accept4.2
+/// [OpenBSD]: https://man.openbsd.org/accept4.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=accept4&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/accept4
+#[inline]
+#[doc(alias = "accept4")]
+pub fn acceptfrom_with<Fd: AsFd>(
+ sockfd: Fd,
+ flags: SocketFlags,
+) -> io::Result<(OwnedFd, Option<SocketAddrAny>)> {
+ backend::net::syscalls::acceptfrom_with(sockfd.as_fd(), flags)
+}
+
+/// `shutdown(fd, how)`—Closes the read and/or write sides of a stream.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#close-and-shutdownget-outta-my-face
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/shutdown.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/shutdown.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-shutdown
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=shutdown&sektion=2
+/// [NetBSD]: https://man.netbsd.org/shutdown.2
+/// [OpenBSD]: https://man.openbsd.org/shutdown.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=shutdown&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/shutdown
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Closing-a-Socket.html
+#[inline]
+pub fn shutdown<Fd: AsFd>(sockfd: Fd, how: Shutdown) -> io::Result<()> {
+ backend::net::syscalls::shutdown(sockfd.as_fd(), how)
+}
+
+/// `getsockname(fd, addr, len)`—Returns the address a socket is bound to.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getsockname.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getsockname.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-getsockname
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=getsockname&sektion=2
+/// [NetBSD]: https://man.netbsd.org/getsockname.2
+/// [OpenBSD]: https://man.openbsd.org/getsockname.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=getsockname&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/getsockname
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Reading-Address.html
+#[inline]
+pub fn getsockname<Fd: AsFd>(sockfd: Fd) -> io::Result<SocketAddrAny> {
+ backend::net::syscalls::getsockname(sockfd.as_fd())
+}
+
+/// `getpeername(fd, addr, len)`—Returns the address a socket is connected
+/// to.
+///
+/// # References
+/// - [Beej's Guide to Network Programming]
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [Winsock]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#getpeernamewho-are-you
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpeername.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getpeername.2.html
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-getpeername
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2
+/// [NetBSD]: https://man.netbsd.org/getpeername.2
+/// [OpenBSD]: https://man.openbsd.org/getpeername.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=getpeername&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/getpeername
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Who-is-Connected.html
+#[inline]
+pub fn getpeername<Fd: AsFd>(sockfd: Fd) -> io::Result<Option<SocketAddrAny>> {
+ backend::net::syscalls::getpeername(sockfd.as_fd())
+}
diff --git a/src/net/socket_addr_any.rs b/src/net/socket_addr_any.rs
new file mode 100644
index 0000000..3be80a3
--- /dev/null
+++ b/src/net/socket_addr_any.rs
@@ -0,0 +1,122 @@
+//! A socket address for any kind of socket.
+//!
+//! This is similar to [`std::net::SocketAddr`], but also supports Unix-domain
+//! socket addresses on Unix.
+//!
+//! # Safety
+//!
+//! The `read` and `write` functions allow decoding and encoding from and to
+//! OS-specific socket address representations in memory.
+#![allow(unsafe_code)]
+
+#[cfg(target_os = "linux")]
+use crate::net::xdp::SocketAddrXdp;
+#[cfg(unix)]
+use crate::net::SocketAddrUnix;
+use crate::net::{AddressFamily, SocketAddr, SocketAddrV4, SocketAddrV6};
+use crate::{backend, io};
+#[cfg(feature = "std")]
+use core::fmt;
+
+pub use backend::net::addr::SocketAddrStorage;
+
+/// `struct sockaddr_storage` as a Rust enum.
+#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[doc(alias = "sockaddr")]
+#[non_exhaustive]
+pub enum SocketAddrAny {
+ /// `struct sockaddr_in`
+ V4(SocketAddrV4),
+ /// `struct sockaddr_in6`
+ V6(SocketAddrV6),
+ /// `struct sockaddr_un`
+ #[cfg(unix)]
+ Unix(SocketAddrUnix),
+ /// `struct sockaddr_xdp`
+ #[cfg(target_os = "linux")]
+ Xdp(SocketAddrXdp),
+}
+
+impl From<SocketAddr> for SocketAddrAny {
+ #[inline]
+ fn from(from: SocketAddr) -> Self {
+ match from {
+ SocketAddr::V4(v4) => Self::V4(v4),
+ SocketAddr::V6(v6) => Self::V6(v6),
+ }
+ }
+}
+
+impl From<SocketAddrV4> for SocketAddrAny {
+ #[inline]
+ fn from(from: SocketAddrV4) -> Self {
+ Self::V4(from)
+ }
+}
+
+impl From<SocketAddrV6> for SocketAddrAny {
+ #[inline]
+ fn from(from: SocketAddrV6) -> Self {
+ Self::V6(from)
+ }
+}
+
+#[cfg(unix)]
+impl From<SocketAddrUnix> for SocketAddrAny {
+ #[inline]
+ fn from(from: SocketAddrUnix) -> Self {
+ Self::Unix(from)
+ }
+}
+
+impl SocketAddrAny {
+ /// Return the address family of this socket address.
+ #[inline]
+ pub const fn address_family(&self) -> AddressFamily {
+ match self {
+ Self::V4(_) => AddressFamily::INET,
+ Self::V6(_) => AddressFamily::INET6,
+ #[cfg(unix)]
+ Self::Unix(_) => AddressFamily::UNIX,
+ #[cfg(target_os = "linux")]
+ Self::Xdp(_) => AddressFamily::XDP,
+ }
+ }
+
+ /// Writes a platform-specific encoding of this socket address to
+ /// the memory pointed to by `storage`, and returns the number of
+ /// bytes used.
+ ///
+ /// # Safety
+ ///
+ /// `storage` must point to valid memory for encoding the socket
+ /// address.
+ pub unsafe fn write(&self, storage: *mut SocketAddrStorage) -> usize {
+ backend::net::write_sockaddr::write_sockaddr(self, storage)
+ }
+
+ /// Reads a platform-specific encoding of a socket address from
+ /// the memory pointed to by `storage`, which uses `len` bytes.
+ ///
+ /// # Safety
+ ///
+ /// `storage` must point to valid memory for decoding a socket
+ /// address.
+ pub unsafe fn read(storage: *const SocketAddrStorage, len: usize) -> io::Result<Self> {
+ backend::net::read_sockaddr::read_sockaddr(storage, len)
+ }
+}
+
+#[cfg(feature = "std")]
+impl fmt::Debug for SocketAddrAny {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::V4(v4) => v4.fmt(fmt),
+ Self::V6(v6) => v6.fmt(fmt),
+ #[cfg(unix)]
+ Self::Unix(unix) => unix.fmt(fmt),
+ #[cfg(target_os = "linux")]
+ Self::Xdp(xdp) => xdp.fmt(fmt),
+ }
+ }
+}
diff --git a/src/net/socketpair.rs b/src/net/socketpair.rs
new file mode 100644
index 0000000..7228e71
--- /dev/null
+++ b/src/net/socketpair.rs
@@ -0,0 +1,36 @@
+use crate::fd::OwnedFd;
+use crate::net::{AddressFamily, Protocol, SocketFlags, SocketType};
+use crate::{backend, io};
+
+/// `socketpair(domain, type_ | accept_flags, protocol)`—Create a pair of
+/// sockets that are connected to each other.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/socketpair.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/socketpair.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=socketpair&sektion=2
+/// [NetBSD]: https://man.netbsd.org/socketpair.2
+/// [OpenBSD]: https://man.openbsd.org/socketpair.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=socketpair&section=2
+/// [illumos]: https://illumos.org/man/3SOCKET/socketpair
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Socket-Pairs.html
+#[inline]
+pub fn socketpair(
+ domain: AddressFamily,
+ type_: SocketType,
+ flags: SocketFlags,
+ protocol: Option<Protocol>,
+) -> io::Result<(OwnedFd, OwnedFd)> {
+ backend::net::syscalls::socketpair(domain, type_, flags, protocol)
+}
diff --git a/src/net/sockopt.rs b/src/net/sockopt.rs
new file mode 100644
index 0000000..47ab1a5
--- /dev/null
+++ b/src/net/sockopt.rs
@@ -0,0 +1,1482 @@
+//! `getsockopt` and `setsockopt` functions.
+//!
+//! In the rustix API, there is a separate function for each option, so that it
+//! can be given an option-specific type signature.
+//!
+//! # References for all `get_*` functions:
+//!
+//! - [POSIX `getsockopt`]
+//! - [Linux `getsockopt`]
+//! - [Winsock `getsockopt`]
+//! - [Apple `getsockopt`]
+//! - [FreeBSD `getsockopt`]
+//! - [NetBSD `getsockopt`]
+//! - [OpenBSD `getsockopt`]
+//! - [DragonFly BSD `getsockopt`]
+//! - [illumos `getsockopt`]
+//! - [glibc `getsockopt`]
+//!
+//! [POSIX `getsockopt`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html
+//! [Linux `getsockopt`]: https://man7.org/linux/man-pages/man2/getsockopt.2.html
+//! [Winsock `getsockopt`]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-getsockopt
+//! [Apple `getsockopt`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getsockopt.2.html
+//! [FreeBSD `getsockopt`]: https://man.freebsd.org/cgi/man.cgi?query=getsockopt&sektion=2
+//! [NetBSD `getsockopt`]: https://man.netbsd.org/getsockopt.2
+//! [OpenBSD `getsockopt`]: https://man.openbsd.org/getsockopt.2
+//! [DragonFly BSD `getsockopt`]: https://man.dragonflybsd.org/?command=getsockopt&section=2
+//! [illumos `getsockopt`]: https://illumos.org/man/3SOCKET/getsockopt
+//! [glibc `getsockopt`]: https://www.gnu.org/software/libc/manual/html_node/Socket-Option-Functions.html
+//!
+//! # References for all `set_*` functions:
+//!
+//! - [POSIX `setsockopt`]
+//! - [Linux `setsockopt`]
+//! - [Winsock `setsockopt`]
+//! - [Apple `setsockopt`]
+//! - [FreeBSD `setsockopt`]
+//! - [NetBSD `setsockopt`]
+//! - [OpenBSD `setsockopt`]
+//! - [DragonFly BSD `setsockopt`]
+//! - [illumos `setsockopt`]
+//! - [glibc `setsockopt`]
+//!
+//! [POSIX `setsockopt`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html
+//! [Linux `setsockopt`]: https://man7.org/linux/man-pages/man2/setsockopt.2.html
+//! [Winsock `setsockopt`]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-setsockopt
+//! [Apple `setsockopt`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setsockopt.2.html
+//! [FreeBSD `setsockopt`]: https://man.freebsd.org/cgi/man.cgi?query=setsockopt&sektion=2
+//! [NetBSD `setsockopt`]: https://man.netbsd.org/setsockopt.2
+//! [OpenBSD `setsockopt`]: https://man.openbsd.org/setsockopt.2
+//! [DragonFly BSD `setsockopt`]: https://man.dragonflybsd.org/?command=setsockopt&section=2
+//! [illumos `setsockopt`]: https://illumos.org/man/3SOCKET/setsockopt
+//! [glibc `setsockopt`]: https://www.gnu.org/software/libc/manual/html_node/Socket-Option-Functions.html
+//!
+//! # References for `get_socket_*` and `set_socket_*` functions:
+//!
+//! - [References for all `get_*` functions]
+//! - [References for all `set_*` functions]
+//! - [POSIX `sys/socket.h`]
+//! - [Linux `socket`]
+//! - [Winsock `SOL_SOCKET` options]
+//! - [glibc `SOL_SOCKET` Options]
+//!
+//! [POSIX `sys/socket.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_socket.h.html
+//! [Linux `socket`]: https://man7.org/linux/man-pages/man7/socket.7.html
+//! [Winsock `SOL_SOCKET` options]: https://docs.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options
+//! [glibc `SOL_SOCKET` options]: https://www.gnu.org/software/libc/manual/html_node/Socket_002dLevel-Options.html
+//!
+//! # References for `get_ip_*` and `set_ip_*` functions:
+//!
+//! - [References for all `get_*` functions]
+//! - [References for all `set_*` functions]
+//! - [POSIX `netinet/in.h`]
+//! - [Linux `ip`]
+//! - [Winsock `IPPROTO_IP` options]
+//! - [Apple `ip`]
+//! - [FreeBSD `ip`]
+//! - [NetBSD `ip`]
+//! - [OpenBSD `ip`]
+//! - [DragonFly BSD `ip`]
+//! - [illumos `ip`]
+//!
+//! [POSIX `netinet/in.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/netinet_in.h.html
+//! [Linux `ip`]: https://man7.org/linux/man-pages/man7/ip.7.html
+//! [Winsock `IPPROTO_IP` options]: https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
+//! [Apple `ip`]: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/man/man4/ip.4.auto.html
+//! [FreeBSD `ip`]: https://man.freebsd.org/cgi/man.cgi?query=ip&sektion=4
+//! [NetBSD `ip`]: https://man.netbsd.org/ip.4
+//! [OpenBSD `ip`]: https://man.openbsd.org/ip.4
+//! [DragonFly BSD `ip`]: https://man.dragonflybsd.org/?command=ip&section=4
+//! [illumos `ip`]: https://illumos.org/man/4P/ip
+//!
+//! # References for `get_ipv6_*` and `set_ipv6_*` functions:
+//!
+//! - [References for all `get_*` functions]
+//! - [References for all `set_*` functions]
+//! - [POSIX `netinet/in.h`]
+//! - [Linux `ipv6`]
+//! - [Winsock `IPPROTO_IPV6` options]
+//! - [Apple `ip6`]
+//! - [FreeBSD `ip6`]
+//! - [NetBSD `ip6`]
+//! - [OpenBSD `ip6`]
+//! - [DragonFly BSD `ip6`]
+//! - [illumos `ip6`]
+//!
+//! [POSIX `netinet/in.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/netinet_in.h.html
+//! [Linux `ipv6`]: https://man7.org/linux/man-pages/man7/ipv6.7.html
+//! [Winsock `IPPROTO_IPV6` options]: https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ipv6-socket-options
+//! [Apple `ip6`]: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/man/man4/ip6.4.auto.html
+//! [FreeBSD `ip6`]: https://man.freebsd.org/cgi/man.cgi?query=ip6&sektion=4
+//! [NetBSD `ip6`]: https://man.netbsd.org/ip6.4
+//! [OpenBSD `ip6`]: https://man.openbsd.org/ip6.4
+//! [DragonFly BSD `ip6`]: https://man.dragonflybsd.org/?command=ip6&section=4
+//! [illumos `ip6`]: https://illumos.org/man/4P/ip6
+//!
+//! # References for `get_tcp_*` and `set_tcp_*` functions:
+//!
+//! - [References for all `get_*` functions]
+//! - [References for all `set_*` functions]
+//! - [POSIX `netinet/tcp.h`]
+//! - [Linux `tcp`]
+//! - [Winsock `IPPROTO_TCP` options]
+//! - [Apple `tcp`]
+//! - [FreeBSD `tcp`]
+//! - [NetBSD `tcp`]
+//! - [OpenBSD `tcp`]
+//! - [DragonFly BSD `tcp`]
+//! - [illumos `tcp`]
+//!
+//! [POSIX `netinet/tcp.h`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/netinet_tcp.h.html
+//! [Linux `tcp`]: https://man7.org/linux/man-pages/man7/tcp.7.html
+//! [Winsock `IPPROTO_TCP` options]: https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-tcp-socket-options
+//! [Apple `tcp`]: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/man/man4/tcp.4.auto.html
+//! [FreeBSD `tcp`]: https://man.freebsd.org/cgi/man.cgi?query=tcp&sektion=4
+//! [NetBSD `tcp`]: https://man.netbsd.org/tcp.4
+//! [OpenBSD `tcp`]: https://man.openbsd.org/tcp.4
+//! [DragonFly BSD `tcp`]: https://man.dragonflybsd.org/?command=tcp&section=4
+//! [illumos `tcp`]: https://illumos.org/man/4P/tcp
+//!
+//! [References for all `get_*` functions]: #references-for-all-get_-functions
+//! [References for all `set_*` functions]: #references-for-all-set_-functions
+
+#![doc(alias = "getsockopt")]
+#![doc(alias = "setsockopt")]
+
+#[cfg(target_os = "linux")]
+use crate::net::xdp::{XdpMmapOffsets, XdpOptionsFlags, 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 = "fuchsia"))]
+use crate::net::SocketAddrV4;
+#[cfg(linux_kernel)]
+use crate::net::SocketAddrV6;
+use crate::net::{Ipv4Addr, Ipv6Addr, SocketType};
+use crate::{backend, io};
+#[cfg(feature = "alloc")]
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+use alloc::string::String;
+use backend::c;
+use backend::fd::AsFd;
+use core::time::Duration;
+
+/// Timeout identifier for use with [`set_socket_timeout`] and
+/// [`get_socket_timeout`].
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u32)]
+pub enum Timeout {
+ /// `SO_RCVTIMEO`—Timeout for receiving.
+ Recv = c::SO_RCVTIMEO as _,
+
+ /// `SO_SNDTIMEO`—Timeout for sending.
+ Send = c::SO_SNDTIMEO as _,
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_TYPE)`—Returns the type of a socket.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_TYPE")]
+pub fn get_socket_type<Fd: AsFd>(fd: Fd) -> io::Result<SocketType> {
+ backend::net::sockopt::get_socket_type(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, value)`—Set whether local
+/// addresses may be reused in `bind`.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_REUSEADDR")]
+pub fn set_socket_reuseaddr<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_reuseaddr(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_REUSEADDR)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_REUSEADDR")]
+pub fn get_socket_reuseaddr<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_reuseaddr(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_BROADCAST, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_BROADCAST")]
+pub fn set_socket_broadcast<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_broadcast(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_BROADCAST)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_BROADCAST")]
+pub fn get_socket_broadcast<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_broadcast(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_LINGER, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_LINGER")]
+pub fn set_socket_linger<Fd: AsFd>(fd: Fd, value: Option<Duration>) -> io::Result<()> {
+ backend::net::sockopt::set_socket_linger(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_LINGER)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_LINGER")]
+pub fn get_socket_linger<Fd: AsFd>(fd: Fd) -> io::Result<Option<Duration>> {
+ backend::net::sockopt::get_socket_linger(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_PASSCRED, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "SO_PASSCRED")]
+pub fn set_socket_passcred<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_passcred(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_PASSCRED)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "SO_PASSCRED")]
+pub fn get_socket_passcred<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_passcred(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, id, value)`—Set the sending or receiving
+/// timeout.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_RCVTIMEO")]
+#[doc(alias = "SO_SNDTIMEO")]
+pub fn set_socket_timeout<Fd: AsFd>(
+ fd: Fd,
+ id: Timeout,
+ value: Option<Duration>,
+) -> io::Result<()> {
+ backend::net::sockopt::set_socket_timeout(fd.as_fd(), id, value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, id)`—Get the sending or receiving timeout.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_RCVTIMEO")]
+#[doc(alias = "SO_SNDTIMEO")]
+pub fn get_socket_timeout<Fd: AsFd>(fd: Fd, id: Timeout) -> io::Result<Option<Duration>> {
+ backend::net::sockopt::get_socket_timeout(fd.as_fd(), id)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_ERROR)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_ERROR")]
+pub fn get_socket_error<Fd: AsFd>(fd: Fd) -> io::Result<Result<(), io::Errno>> {
+ backend::net::sockopt::get_socket_error(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(any(apple, freebsdlike, target_os = "netbsd"))]
+#[doc(alias = "SO_NOSIGPIPE")]
+#[inline]
+pub fn get_socket_nosigpipe<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_nosigpipe(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(any(apple, freebsdlike, target_os = "netbsd"))]
+#[doc(alias = "SO_NOSIGPIPE")]
+#[inline]
+pub fn set_socket_nosigpipe<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_nosigpipe(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_KEEPALIVE")]
+pub fn set_socket_keepalive<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_keepalive(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_KEEPALIVE")]
+pub fn get_socket_keepalive<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_keepalive(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_RCVBUF, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_RCVBUF")]
+pub fn set_socket_recv_buffer_size<Fd: AsFd>(fd: Fd, value: usize) -> io::Result<()> {
+ backend::net::sockopt::set_socket_recv_buffer_size(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_RCVBUF)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_RCVBUF")]
+pub fn get_socket_recv_buffer_size<Fd: AsFd>(fd: Fd) -> io::Result<usize> {
+ backend::net::sockopt::get_socket_recv_buffer_size(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_SNDBUF, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_SNDBUF")]
+pub fn set_socket_send_buffer_size<Fd: AsFd>(fd: Fd, value: usize) -> io::Result<()> {
+ backend::net::sockopt::set_socket_send_buffer_size(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_SNDBUF)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_SNDBUF")]
+pub fn get_socket_send_buffer_size<Fd: AsFd>(fd: Fd) -> io::Result<usize> {
+ backend::net::sockopt::get_socket_send_buffer_size(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_DOMAIN)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[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",
+)))]
+#[inline]
+#[doc(alias = "SO_DOMAIN")]
+pub fn get_socket_domain<Fd: AsFd>(fd: Fd) -> io::Result<AddressFamily> {
+ backend::net::sockopt::get_socket_domain(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(not(apple))] // Apple platforms declare the constant, but do not actually implement it.
+#[inline]
+#[doc(alias = "SO_ACCEPTCONN")]
+pub fn get_socket_acceptconn<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_acceptconn(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_OOBINLINE")]
+pub fn set_socket_oobinline<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_oobinline(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_OOBINLINE)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "SO_OOBINLINE")]
+pub fn get_socket_oobinline<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_oobinline(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(not(any(solarish, windows)))]
+#[cfg(not(windows))]
+#[inline]
+#[doc(alias = "SO_REUSEPORT")]
+pub fn set_socket_reuseport<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_reuseport(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_REUSEPORT)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(not(any(solarish, windows)))]
+#[inline]
+#[doc(alias = "SO_REUSEPORT")]
+pub fn get_socket_reuseport<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_reuseport(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(target_os = "freebsd")]
+#[inline]
+#[doc(alias = "SO_REUSEPORT_LB")]
+pub fn set_socket_reuseport_lb<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_socket_reuseport_lb(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(target_os = "freebsd")]
+#[inline]
+#[doc(alias = "SO_REUSEPORT_LB")]
+pub fn get_socket_reuseport_lb<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_socket_reuseport_lb(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_PROTOCOL)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(any(
+ linux_kernel,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "openbsd",
+ target_os = "redox",
+ target_env = "newlib"
+))]
+#[inline]
+#[doc(alias = "SO_PROTOCOL")]
+pub fn get_socket_protocol<Fd: AsFd>(fd: Fd) -> io::Result<Option<Protocol>> {
+ backend::net::sockopt::get_socket_protocol(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_COOKIE)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "SO_COOKIE")]
+pub fn get_socket_cookie<Fd: AsFd>(fd: Fd) -> io::Result<u64> {
+ backend::net::sockopt::get_socket_cookie(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "SO_INCOMING_CPU")]
+pub fn get_socket_incoming_cpu<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_socket_incoming_cpu(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "SO_INCOMING_CPU")]
+pub fn set_socket_incoming_cpu<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_socket_incoming_cpu(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_TTL, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_socket_-and-set_socket_-functions
+#[inline]
+#[doc(alias = "IP_TTL")]
+pub fn set_ip_ttl<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_ip_ttl(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IP, IP_TTL)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_TTL")]
+pub fn get_ip_ttl<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_ip_ttl(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_V6ONLY")]
+pub fn set_ipv6_v6only<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_v6only(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_V6ONLY")]
+pub fn get_ipv6_v6only<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ipv6_v6only(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_MULTICAST_LOOP")]
+pub fn set_ip_multicast_loop<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ip_multicast_loop(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_MULTICAST_LOOP")]
+pub fn get_ip_multicast_loop<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ip_multicast_loop(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_MULTICAST_TTL")]
+pub fn set_ip_multicast_ttl<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_ip_multicast_ttl(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_MULTICAST_TTL")]
+pub fn get_ip_multicast_ttl<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_ip_multicast_ttl(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_MULTICAST_LOOP")]
+pub fn set_ipv6_multicast_loop<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_multicast_loop(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_MULTICAST_LOOP")]
+pub fn get_ipv6_multicast_loop<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ipv6_multicast_loop(fd.as_fd())
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_UNICAST_HOPS")]
+pub fn get_ipv6_unicast_hops<Fd: AsFd>(fd: Fd) -> io::Result<u8> {
+ backend::net::sockopt::get_ipv6_unicast_hops(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_UNICAST_HOPS")]
+pub fn set_ipv6_unicast_hops<Fd: AsFd>(fd: Fd, value: Option<u8>) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_unicast_hops(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_MULTICAST_HOPS")]
+pub fn set_ipv6_multicast_hops<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_multicast_hops(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_MULTICAST_HOPS")]
+pub fn get_ipv6_multicast_hops<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_ipv6_multicast_hops(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, multiaddr, interface)`
+///
+/// This is similar to [`set_ip_add_membership`] but always sets `ifindex`
+/// value to zero.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_ADD_MEMBERSHIP")]
+pub fn set_ip_add_membership<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ip_add_membership(fd.as_fd(), multiaddr, interface)
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, multiaddr, address,
+/// ifindex)`
+///
+/// This is similar to [`set_ip_add_membership_with_ifindex`] but additionally
+/// allows a `ifindex` value to be given.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(
+ apple,
+ freebsdlike,
+ linux_like,
+ target_os = "fuchsia",
+ target_os = "openbsd"
+))]
+#[inline]
+#[doc(alias = "IP_ADD_MEMBERSHIP")]
+pub fn set_ip_add_membership_with_ifindex<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv4Addr,
+ address: &Ipv4Addr,
+ ifindex: i32,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ip_add_membership_with_ifindex(
+ fd.as_fd(),
+ multiaddr,
+ address,
+ ifindex,
+ )
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
+#[inline]
+#[doc(alias = "IP_ADD_SOURCE_MEMBERSHIP")]
+pub fn set_ip_add_source_membership<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+ sourceaddr: &Ipv4Addr,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ip_add_source_membership(
+ fd.as_fd(),
+ multiaddr,
+ interface,
+ sourceaddr,
+ )
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(apple, freebsdlike, linux_like, solarish, target_os = "aix"))]
+#[inline]
+#[doc(alias = "IP_DROP_SOURCE_MEMBERSHIP")]
+pub fn set_ip_drop_source_membership<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+ sourceaddr: &Ipv4Addr,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ip_drop_source_membership(
+ fd.as_fd(),
+ multiaddr,
+ interface,
+ sourceaddr,
+ )
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, multiaddr, interface)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_JOIN_GROUP")]
+#[doc(alias = "IPV6_ADD_MEMBERSHIP")]
+pub fn set_ipv6_add_membership<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv6Addr,
+ interface: u32,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_add_membership(fd.as_fd(), multiaddr, interface)
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, multiaddr, interface)`
+///
+/// This is similar to [`set_ip_drop_membership`] but always sets `ifindex`
+/// value to zero.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[inline]
+#[doc(alias = "IP_DROP_MEMBERSHIP")]
+pub fn set_ip_drop_membership<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv4Addr,
+ interface: &Ipv4Addr,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ip_drop_membership(fd.as_fd(), multiaddr, interface)
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, multiaddr, interface)`
+///
+/// This is similar to [`set_ip_drop_membership_with_ifindex`] but additionally
+/// allows a `ifindex` value to be given.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(
+ apple,
+ freebsdlike,
+ linux_like,
+ target_os = "fuchsia",
+ target_os = "openbsd"
+))]
+#[inline]
+#[doc(alias = "IP_DROP_MEMBERSHIP")]
+pub fn set_ip_drop_membership_with_ifindex<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv4Addr,
+ address: &Ipv4Addr,
+ ifindex: i32,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ip_drop_membership_with_ifindex(
+ fd.as_fd(),
+ multiaddr,
+ address,
+ ifindex,
+ )
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, multiaddr, interface)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[inline]
+#[doc(alias = "IPV6_LEAVE_GROUP")]
+#[doc(alias = "IPV6_DROP_MEMBERSHIP")]
+pub fn set_ipv6_drop_membership<Fd: AsFd>(
+ fd: Fd,
+ multiaddr: &Ipv6Addr,
+ interface: u32,
+) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_drop_membership(fd.as_fd(), multiaddr, interface)
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_TOS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_env = "newlib"
+))]
+#[inline]
+#[doc(alias = "IP_TOS")]
+pub fn set_ip_tos<Fd: AsFd>(fd: Fd, value: u8) -> io::Result<()> {
+ backend::net::sockopt::set_ip_tos(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IP, IP_TOS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_env = "newlib"
+))]
+#[inline]
+#[doc(alias = "IP_TOS")]
+pub fn get_ip_tos<Fd: AsFd>(fd: Fd) -> io::Result<u8> {
+ backend::net::sockopt::get_ip_tos(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_RECVTOS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "IP_RECVTOS")]
+pub fn set_ip_recvtos<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ip_recvtos(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IP, IP_RECVTOS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ip_-and-set_ip_-functions
+#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "IP_RECVTOS")]
+pub fn get_ip_recvtos<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ip_recvtos(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "nto"
+))]
+#[inline]
+#[doc(alias = "IPV6_RECVTCLASS")]
+pub fn set_ipv6_recvtclass<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_recvtclass(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(any(
+ bsd,
+ linux_like,
+ target_os = "aix",
+ target_os = "fuchsia",
+ target_os = "nto"
+))]
+#[inline]
+#[doc(alias = "IPV6_RECVTCLASS")]
+pub fn get_ipv6_recvtclass<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ipv6_recvtclass(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IP, IP_FREEBIND, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "IP_FREEBIND")]
+pub fn set_ip_freebind<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ip_freebind(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IP, IP_FREEBIND)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "IP_FREEBIND")]
+pub fn get_ip_freebind<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ip_freebind(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "IPV6_FREEBIND")]
+pub fn set_ipv6_freebind<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_freebind(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_FREEBIND)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "IPV6_FREEBIND")]
+pub fn get_ipv6_freebind<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_ipv6_freebind(fd.as_fd())
+}
+
+/// `getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST)`
+///
+/// Even though this corresponnds to a `SO_*` constant, it is an `IPPROTO_IP`
+/// option.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "SO_ORIGINAL_DST")]
+pub fn get_ip_original_dst<Fd: AsFd>(fd: Fd) -> io::Result<SocketAddrV4> {
+ backend::net::sockopt::get_ip_original_dst(fd.as_fd())
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST)`
+///
+/// Even though this corresponnds to a `IP6T_*` constant, it is an
+/// `IPPROTO_IPV6` option.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(linux_kernel)]
+#[inline]
+#[doc(alias = "IP6T_SO_ORIGINAL_DST")]
+pub fn get_ipv6_original_dst<Fd: AsFd>(fd: Fd) -> io::Result<SocketAddrV6> {
+ backend::net::sockopt::get_ipv6_original_dst(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+)))]
+#[inline]
+#[doc(alias = "IPV6_TCLASS")]
+pub fn set_ipv6_tclass<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_ipv6_tclass(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_ipv6_-and-set_ipv6_-functions
+#[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+)))]
+#[inline]
+#[doc(alias = "IPV6_TCLASS")]
+pub fn get_ipv6_tclass<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_ipv6_tclass(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[inline]
+#[doc(alias = "TCP_NODELAY")]
+pub fn set_tcp_nodelay<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_nodelay(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_NODELAY)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[inline]
+#[doc(alias = "TCP_NODELAY")]
+pub fn get_tcp_nodelay<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_tcp_nodelay(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+#[inline]
+#[doc(alias = "TCP_KEEPCNT")]
+pub fn set_tcp_keepcnt<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_keepcnt(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+#[inline]
+#[doc(alias = "TCP_KEEPCNT")]
+pub fn get_tcp_keepcnt<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_tcp_keepcnt(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, value)`
+///
+/// `TCP_KEEPALIVE` on Apple platforms.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+#[inline]
+#[doc(alias = "TCP_KEEPIDLE")]
+pub fn set_tcp_keepidle<Fd: AsFd>(fd: Fd, value: Duration) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_keepidle(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE)`
+///
+/// `TCP_KEEPALIVE` on Apple platforms.
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+#[inline]
+#[doc(alias = "TCP_KEEPIDLE")]
+pub fn get_tcp_keepidle<Fd: AsFd>(fd: Fd) -> io::Result<Duration> {
+ backend::net::sockopt::get_tcp_keepidle(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+#[inline]
+#[doc(alias = "TCP_KEEPINTVL")]
+pub fn set_tcp_keepintvl<Fd: AsFd>(fd: Fd, value: Duration) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_keepintvl(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "nto")))]
+#[inline]
+#[doc(alias = "TCP_KEEPINTVL")]
+pub fn get_tcp_keepintvl<Fd: AsFd>(fd: Fd) -> io::Result<Duration> {
+ backend::net::sockopt::get_tcp_keepintvl(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_USER_TIMEOUT")]
+pub fn set_tcp_user_timeout<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_user_timeout(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_USER_TIMEOUT")]
+pub fn get_tcp_user_timeout<Fd: AsFd>(fd: Fd) -> io::Result<u32> {
+ backend::net::sockopt::get_tcp_user_timeout(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_QUICKACK")]
+pub fn set_tcp_quickack<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_quickack(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_QUICKACK)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_QUICKACK")]
+pub fn get_tcp_quickack<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_tcp_quickack(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+#[inline]
+#[doc(alias = "TCP_CONGESTION")]
+pub fn set_tcp_congestion<Fd: AsFd>(fd: Fd, value: &str) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_congestion(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_CONGESTION)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(feature = "alloc")]
+#[cfg(any(
+ linux_like,
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "illumos"
+))]
+#[inline]
+#[doc(alias = "TCP_CONGESTION")]
+pub fn get_tcp_congestion<Fd: AsFd>(fd: Fd) -> io::Result<String> {
+ backend::net::sockopt::get_tcp_congestion(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_THIN_LINEAR_TIMEOUTS")]
+pub fn set_tcp_thin_linear_timeouts<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_thin_linear_timeouts(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_THIN_LINEAR_TIMEOUTS")]
+pub fn get_tcp_thin_linear_timeouts<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_tcp_thin_linear_timeouts(fd.as_fd())
+}
+
+/// `setsockopt(fd, IPPROTO_TCP, TCP_CORK, value)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, solarish, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_CORK")]
+pub fn set_tcp_cork<Fd: AsFd>(fd: Fd, value: bool) -> io::Result<()> {
+ backend::net::sockopt::set_tcp_cork(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, IPPROTO_TCP, TCP_CORK)`
+///
+/// See the [module-level documentation] for more.
+///
+/// [module-level documentation]: self#references-for-get_tcp_-and-set_tcp_-functions
+#[cfg(any(linux_like, solarish, target_os = "fuchsia"))]
+#[inline]
+#[doc(alias = "TCP_CORK")]
+pub fn get_tcp_cork<Fd: AsFd>(fd: Fd) -> io::Result<bool> {
+ backend::net::sockopt::get_tcp_cork(fd.as_fd())
+}
+
+/// Get credentials of Unix domain socket peer process
+///
+/// # References
+/// - [Linux `unix`]
+///
+/// [Linux `unix`]: https://man7.org/linux/man-pages/man7/unix.7.html
+#[cfg(linux_kernel)]
+#[doc(alias = "SO_PEERCRED")]
+pub fn get_socket_peercred<Fd: AsFd>(fd: Fd) -> io::Result<super::UCred> {
+ backend::net::sockopt::get_socket_peercred(fd.as_fd())
+}
+
+/// `setsockopt(fd, SOL_XDP, XDP_UMEM_REG, value)`
+///
+/// On kernel versions only supporting v1, the flags are ignored.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-umem-reg-setsockopt
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_UMEM_REG")]
+pub fn set_xdp_umem_reg<Fd: AsFd>(fd: Fd, value: XdpUmemReg) -> io::Result<()> {
+ backend::net::sockopt::set_xdp_umem_reg(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, SOL_XDP, XDP_UMEM_FILL_RING, value)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-rx-tx-umem-fill-umem-completion-ring-setsockopts
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_UMEM_FILL_RING")]
+pub fn set_xdp_umem_fill_ring_size<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_xdp_umem_fill_ring_size(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, SOL_XDP, XDP_UMEM_COMPLETION_RING, value)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-rx-tx-umem-fill-umem-completion-ring-setsockopts
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_UMEM_COMPLETION_RING")]
+pub fn set_xdp_umem_completion_ring_size<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_xdp_umem_completion_ring_size(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, SOL_XDP, XDP_TX_RING, value)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-rx-tx-umem-fill-umem-completion-ring-setsockopts
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_TX_RING")]
+pub fn set_xdp_tx_ring_size<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_xdp_tx_ring_size(fd.as_fd(), value)
+}
+
+/// `setsockopt(fd, SOL_XDP, XDP_RX_RING, value)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-rx-tx-umem-fill-umem-completion-ring-setsockopts
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_RX_RING")]
+pub fn set_xdp_rx_ring_size<Fd: AsFd>(fd: Fd, value: u32) -> io::Result<()> {
+ backend::net::sockopt::set_xdp_rx_ring_size(fd.as_fd(), value)
+}
+
+/// `getsockopt(fd, SOL_XDP, XDP_MMAP_OFFSETS)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_MMAP_OFFSETS")]
+pub fn get_xdp_mmap_offsets<Fd: AsFd>(fd: Fd) -> io::Result<XdpMmapOffsets> {
+ backend::net::sockopt::get_xdp_mmap_offsets(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_XDP, XDP_STATISTICS)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-statistics-getsockopt
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_STATISTICS")]
+pub fn get_xdp_statistics<Fd: AsFd>(fd: Fd) -> io::Result<XdpStatistics> {
+ backend::net::sockopt::get_xdp_statistics(fd.as_fd())
+}
+
+/// `getsockopt(fd, SOL_XDP, XDP_OPTIONS)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://www.kernel.org/doc/html/next/networking/af_xdp.html#xdp-options-getsockopt
+#[cfg(target_os = "linux")]
+#[doc(alias = "XDP_OPTIONS")]
+pub fn get_xdp_options<Fd: AsFd>(fd: Fd) -> io::Result<XdpOptionsFlags> {
+ backend::net::sockopt::get_xdp_options(fd.as_fd())
+}
+
+#[test]
+fn test_sizes() {
+ use c::c_int;
+
+ // Backend code needs to cast these to `c_int` so make sure that cast
+ // isn't lossy.
+ assert_eq_size!(Timeout, c_int);
+}
diff --git a/src/net/types.rs b/src/net/types.rs
new file mode 100644
index 0000000..dc0b752
--- /dev/null
+++ b/src/net/types.rs
@@ -0,0 +1,1788 @@
+//! Types and constants for `rustix::net`.
+
+use crate::backend::c;
+use bitflags::bitflags;
+
+/// A type for holding raw integer socket types.
+pub type RawSocketType = u32;
+
+/// `SOCK_*` constants for use with [`socket`].
+///
+/// [`socket`]: crate::net::socket()
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct SocketType(pub(crate) RawSocketType);
+
+#[rustfmt::skip]
+impl SocketType {
+ /// `SOCK_STREAM`
+ pub const STREAM: Self = Self(c::SOCK_STREAM as _);
+
+ /// `SOCK_DGRAM`
+ pub const DGRAM: Self = Self(c::SOCK_DGRAM as _);
+
+ /// `SOCK_SEQPACKET`
+ #[cfg(not(target_os = "espidf"))]
+ pub const SEQPACKET: Self = Self(c::SOCK_SEQPACKET as _);
+
+ /// `SOCK_RAW`
+ #[cfg(not(target_os = "espidf"))]
+ pub const RAW: Self = Self(c::SOCK_RAW as _);
+
+ /// `SOCK_RDM`
+ #[cfg(not(any(target_os = "espidf", target_os = "haiku")))]
+ pub const RDM: Self = Self(c::SOCK_RDM as _);
+
+ /// Constructs a `SocketType` from a raw integer.
+ #[inline]
+ pub const fn from_raw(raw: RawSocketType) -> Self {
+ Self(raw)
+ }
+
+ /// Returns the raw integer for this `SocketType`.
+ #[inline]
+ pub const fn as_raw(self) -> RawSocketType {
+ self.0
+ }
+}
+
+/// A type for holding raw integer address families.
+pub type RawAddressFamily = c::sa_family_t;
+
+/// `AF_*` constants for use with [`socket`], [`socket_with`], and
+/// [`socketpair`].
+///
+/// [`socket`]: crate::net::socket()
+/// [`socket_with`]: crate::net::socket_with
+/// [`socketpair`]: crate::net::socketpair()
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct AddressFamily(pub(crate) RawAddressFamily);
+
+#[rustfmt::skip]
+#[allow(non_upper_case_globals)]
+impl AddressFamily {
+ /// `AF_UNSPEC`
+ pub const UNSPEC: Self = Self(c::AF_UNSPEC as _);
+ /// `AF_INET`
+ ///
+ /// # References
+ /// - [Linux]
+ ///
+ /// [Linux]: https://man7.org/linux/man-pages/man7/ip.7.html
+ pub const INET: Self = Self(c::AF_INET as _);
+ /// `AF_INET6`
+ ///
+ /// # References
+ /// - [Linux]
+ ///
+ /// [Linux]: https://man7.org/linux/man-pages/man7/ipv6.7.html
+ pub const INET6: Self = Self(c::AF_INET6 as _);
+ /// `AF_NETLINK`
+ ///
+ /// # References
+ /// - [Linux]
+ ///
+ /// [Linux]: https://man7.org/linux/man-pages/man7/netlink.7.html
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const NETLINK: Self = Self(c::AF_NETLINK as _);
+ /// `AF_UNIX`, aka `AF_LOCAL`
+ #[doc(alias = "LOCAL")]
+ pub const UNIX: Self = Self(c::AF_UNIX as _);
+ /// `AF_AX25`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const AX25: Self = Self(c::AF_AX25 as _);
+ /// `AF_IPX`
+ #[cfg(not(any(
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "vita",
+ )))]
+ pub const IPX: Self = Self(c::AF_IPX as _);
+ /// `AF_APPLETALK`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ pub const APPLETALK: Self = Self(c::AF_APPLETALK as _);
+ /// `AF_NETROM`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const NETROM: Self = Self(c::AF_NETROM as _);
+ /// `AF_BRIDGE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const BRIDGE: Self = Self(c::AF_BRIDGE as _);
+ /// `AF_ATMPVC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const ATMPVC: Self = Self(c::AF_ATMPVC as _);
+ /// `AF_X25`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const X25: Self = Self(c::AF_X25 as _);
+ /// `AF_ROSE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const ROSE: Self = Self(c::AF_ROSE as _);
+ /// `AF_DECnet`
+ #[cfg(not(any(target_os = "espidf", target_os = "haiku", target_os = "vita")))]
+ pub const DECnet: Self = Self(c::AF_DECnet as _);
+ /// `AF_NETBEUI`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const NETBEUI: Self = Self(c::AF_NETBEUI as _);
+ /// `AF_SECURITY`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const SECURITY: Self = Self(c::AF_SECURITY as _);
+ /// `AF_KEY`
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const KEY: Self = Self(c::AF_KEY as _);
+ /// `AF_PACKET`
+ ///
+ /// # References
+ /// - [Linux]
+ ///
+ /// [Linux]: https://man7.org/linux/man-pages/man7/packet.7.html
+ #[cfg(not(any(
+ bsd,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const PACKET: Self = Self(c::AF_PACKET as _);
+ /// `AF_ASH`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const ASH: Self = Self(c::AF_ASH as _);
+ /// `AF_ECONET`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const ECONET: Self = Self(c::AF_ECONET as _);
+ /// `AF_ATMSVC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const ATMSVC: Self = Self(c::AF_ATMSVC as _);
+ /// `AF_RDS`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const RDS: Self = Self(c::AF_RDS as _);
+ /// `AF_SNA`
+ #[cfg(not(any(target_os = "espidf", target_os = "haiku", target_os = "vita")))]
+ pub const SNA: Self = Self(c::AF_SNA as _);
+ /// `AF_IRDA`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const IRDA: Self = Self(c::AF_IRDA as _);
+ /// `AF_PPPOX`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const PPPOX: Self = Self(c::AF_PPPOX as _);
+ /// `AF_WANPIPE`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const WANPIPE: Self = Self(c::AF_WANPIPE as _);
+ /// `AF_LLC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const LLC: Self = Self(c::AF_LLC as _);
+ /// `AF_CAN`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const CAN: Self = Self(c::AF_CAN as _);
+ /// `AF_TIPC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const TIPC: Self = Self(c::AF_TIPC as _);
+ /// `AF_BLUETOOTH`
+ #[cfg(not(any(
+ apple,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "vita",
+ )))]
+ pub const BLUETOOTH: Self = Self(c::AF_BLUETOOTH as _);
+ /// `AF_IUCV`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const IUCV: Self = Self(c::AF_IUCV as _);
+ /// `AF_RXRPC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const RXRPC: Self = Self(c::AF_RXRPC as _);
+ /// `AF_ISDN`
+ #[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita",
+ )))]
+ pub const ISDN: Self = Self(c::AF_ISDN as _);
+ /// `AF_PHONET`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const PHONET: Self = Self(c::AF_PHONET as _);
+ /// `AF_IEEE802154`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const IEEE802154: Self = Self(c::AF_IEEE802154 as _);
+ /// `AF_802`
+ #[cfg(solarish)]
+ pub const EIGHT_ZERO_TWO: Self = Self(c::AF_802 as _);
+ #[cfg(target_os = "fuchsia")]
+ /// `AF_ALG`
+ pub const ALG: Self = Self(c::AF_ALG as _);
+ #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "nto"))]
+ /// `AF_ARP`
+ pub const ARP: Self = Self(c::AF_ARP as _);
+ /// `AF_ATM`
+ #[cfg(freebsdlike)]
+ pub const ATM: Self = Self(c::AF_ATM as _);
+ /// `AF_CAIF`
+ #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia"))]
+ pub const CAIF: Self = Self(c::AF_CAIF as _);
+ /// `AF_CCITT`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const CCITT: Self = Self(c::AF_CCITT as _);
+ /// `AF_CHAOS`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const CHAOS: Self = Self(c::AF_CHAOS as _);
+ /// `AF_CNT`
+ #[cfg(any(bsd, target_os = "nto"))]
+ pub const CNT: Self = Self(c::AF_CNT as _);
+ /// `AF_COIP`
+ #[cfg(any(bsd, target_os = "nto"))]
+ pub const COIP: Self = Self(c::AF_COIP as _);
+ /// `AF_DATAKIT`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const DATAKIT: Self = Self(c::AF_DATAKIT as _);
+ /// `AF_DLI`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "haiku", target_os = "nto"))]
+ pub const DLI: Self = Self(c::AF_DLI as _);
+ /// `AF_E164`
+ #[cfg(any(bsd, target_os = "nto"))]
+ pub const E164: Self = Self(c::AF_E164 as _);
+ /// `AF_ECMA`
+ #[cfg(any(apple, freebsdlike, solarish, target_os = "aix", target_os = "nto", target_os = "openbsd"))]
+ pub const ECMA: Self = Self(c::AF_ECMA as _);
+ /// `AF_ENCAP`
+ #[cfg(target_os = "openbsd")]
+ pub const ENCAP: Self = Self(c::AF_ENCAP as _);
+ /// `AF_FILE`
+ #[cfg(solarish)]
+ pub const FILE: Self = Self(c::AF_FILE as _);
+ /// `AF_GOSIP`
+ #[cfg(solarish)]
+ pub const GOSIP: Self = Self(c::AF_GOSIP as _);
+ /// `AF_HYLINK`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const HYLINK: Self = Self(c::AF_HYLINK as _);
+ /// `AF_IB`
+ #[cfg(any(target_os = "emscripten", target_os = "fuchsia"))]
+ pub const IB: Self = Self(c::AF_IB as _);
+ /// `AF_IMPLINK`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const IMPLINK: Self = Self(c::AF_IMPLINK as _);
+ /// `AF_IEEE80211`
+ #[cfg(any(apple, freebsdlike, target_os = "netbsd"))]
+ pub const IEEE80211: Self = Self(c::AF_IEEE80211 as _);
+ /// `AF_INET6_SDP`
+ #[cfg(target_os = "freebsd")]
+ pub const INET6_SDP: Self = Self(c::AF_INET6_SDP as _);
+ /// `AF_INET_OFFLOAD`
+ #[cfg(solarish)]
+ pub const INET_OFFLOAD: Self = Self(c::AF_INET_OFFLOAD as _);
+ /// `AF_INET_SDP`
+ #[cfg(target_os = "freebsd")]
+ pub const INET_SDP: Self = Self(c::AF_INET_SDP as _);
+ /// `AF_INTF`
+ #[cfg(target_os = "aix")]
+ pub const INTF: Self = Self(c::AF_INTF as _);
+ /// `AF_ISO`
+ #[cfg(any(bsd, target_os = "aix", target_os = "nto"))]
+ pub const ISO: Self = Self(c::AF_ISO as _);
+ /// `AF_LAT`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const LAT: Self = Self(c::AF_LAT as _);
+ /// `AF_LINK`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "haiku", target_os = "nto"))]
+ pub const LINK: Self = Self(c::AF_LINK as _);
+ /// `AF_MPLS`
+ #[cfg(any(netbsdlike, target_os = "dragonfly", target_os = "emscripten", target_os = "fuchsia"))]
+ pub const MPLS: Self = Self(c::AF_MPLS as _);
+ /// `AF_NATM`
+ #[cfg(any(bsd, target_os = "nto"))]
+ pub const NATM: Self = Self(c::AF_NATM as _);
+ /// `AF_NBS`
+ #[cfg(solarish)]
+ pub const NBS: Self = Self(c::AF_NBS as _);
+ /// `AF_NCA`
+ #[cfg(solarish)]
+ pub const NCA: Self = Self(c::AF_NCA as _);
+ /// `AF_NDD`
+ #[cfg(target_os = "aix")]
+ pub const NDD: Self = Self(c::AF_NDD as _);
+ /// `AF_NDRV`
+ #[cfg(apple)]
+ pub const NDRV: Self = Self(c::AF_NDRV as _);
+ /// `AF_NETBIOS`
+ #[cfg(any(apple, freebsdlike))]
+ pub const NETBIOS: Self = Self(c::AF_NETBIOS as _);
+ /// `AF_NETGRAPH`
+ #[cfg(freebsdlike)]
+ pub const NETGRAPH: Self = Self(c::AF_NETGRAPH as _);
+ /// `AF_NIT`
+ #[cfg(solarish)]
+ pub const NIT: Self = Self(c::AF_NIT as _);
+ /// `AF_NOTIFY`
+ #[cfg(target_os = "haiku")]
+ pub const NOTIFY: Self = Self(c::AF_NOTIFY as _);
+ /// `AF_NFC`
+ #[cfg(any(target_os = "emscripten", target_os = "fuchsia"))]
+ pub const NFC: Self = Self(c::AF_NFC as _);
+ /// `AF_NS`
+ #[cfg(any(apple, solarish, netbsdlike, target_os = "aix", target_os = "nto"))]
+ pub const NS: Self = Self(c::AF_NS as _);
+ /// `AF_OROUTE`
+ #[cfg(target_os = "netbsd")]
+ pub const OROUTE: Self = Self(c::AF_OROUTE as _);
+ /// `AF_OSI`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const OSI: Self = Self(c::AF_OSI as _);
+ /// `AF_OSINET`
+ #[cfg(solarish)]
+ pub const OSINET: Self = Self(c::AF_OSINET as _);
+ /// `AF_POLICY`
+ #[cfg(solarish)]
+ pub const POLICY: Self = Self(c::AF_POLICY as _);
+ /// `AF_PPP`
+ #[cfg(apple)]
+ pub const PPP: Self = Self(c::AF_PPP as _);
+ /// `AF_PUP`
+ #[cfg(any(bsd, solarish, target_os = "aix", target_os = "nto"))]
+ pub const PUP: Self = Self(c::AF_PUP as _);
+ /// `AF_RIF`
+ #[cfg(target_os = "aix")]
+ pub const RIF: Self = Self(c::AF_RIF as _);
+ /// `AF_ROUTE`
+ #[cfg(any(bsd, solarish, target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "haiku", target_os = "nto"))]
+ pub const ROUTE: Self = Self(c::AF_ROUTE as _);
+ /// `AF_SCLUSTER`
+ #[cfg(target_os = "freebsd")]
+ pub const SCLUSTER: Self = Self(c::AF_SCLUSTER as _);
+ /// `AF_SIP`
+ #[cfg(any(apple, target_os = "freebsd", target_os = "openbsd"))]
+ pub const SIP: Self = Self(c::AF_SIP as _);
+ /// `AF_SLOW`
+ #[cfg(target_os = "freebsd")]
+ pub const SLOW: Self = Self(c::AF_SLOW as _);
+ /// `AF_SYS_CONTROL`
+ #[cfg(apple)]
+ pub const SYS_CONTROL: Self = Self(c::AF_SYS_CONTROL as _);
+ /// `AF_SYSTEM`
+ #[cfg(apple)]
+ pub const SYSTEM: Self = Self(c::AF_SYSTEM as _);
+ /// `AF_TRILL`
+ #[cfg(solarish)]
+ pub const TRILL: Self = Self(c::AF_TRILL as _);
+ /// `AF_UTUN`
+ #[cfg(apple)]
+ pub const UTUN: Self = Self(c::AF_UTUN as _);
+ /// `AF_VSOCK`
+ #[cfg(any(apple, target_os = "emscripten", target_os = "fuchsia"))]
+ pub const VSOCK: Self = Self(c::AF_VSOCK as _);
+ /// `AF_XDP`
+ #[cfg(target_os = "linux")]
+ pub const XDP: Self = Self(c::AF_XDP as _);
+
+ /// Constructs a `AddressFamily` from a raw integer.
+ #[inline]
+ pub const fn from_raw(raw: RawAddressFamily) -> Self {
+ Self(raw)
+ }
+
+ /// Returns the raw integer for this `AddressFamily`.
+ #[inline]
+ pub const fn as_raw(self) -> RawAddressFamily {
+ self.0
+ }
+}
+
+/// A type for holding raw integer protocols.
+pub type RawProtocol = core::num::NonZeroU32;
+
+const fn new_raw_protocol(u: u32) -> RawProtocol {
+ match RawProtocol::new(u) {
+ Some(p) => p,
+ None => panic!("new_raw_protocol: protocol must be non-zero"),
+ }
+}
+
+/// `IPPROTO_*` and other constants for use with [`socket`], [`socket_with`],
+/// and [`socketpair`] when a nondefault value is desired. See the [`ipproto`],
+/// [`sysproto`], and [`netlink`] modules for possible values.
+///
+/// For the default values, such as `IPPROTO_IP` or `NETLINK_ROUTE`, pass
+/// `None` as the `protocol` argument in these functions.
+///
+/// [`socket`]: crate::net::socket()
+/// [`socket_with`]: crate::net::socket_with
+/// [`socketpair`]: crate::net::socketpair()
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+#[doc(alias = "IPPROTO_IP")]
+#[doc(alias = "NETLINK_ROUTE")]
+pub struct Protocol(pub(crate) RawProtocol);
+
+/// `IPPROTO_*` constants.
+///
+/// For `IPPROTO_IP`, pass `None` as the `protocol` argument.
+pub mod ipproto {
+ use super::{new_raw_protocol, Protocol};
+ use crate::backend::c;
+
+ /// `IPPROTO_ICMP`
+ pub const ICMP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_ICMP as _));
+ /// `IPPROTO_IGMP`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const IGMP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_IGMP as _));
+ /// `IPPROTO_IPIP`
+ #[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const IPIP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_IPIP as _));
+ /// `IPPROTO_TCP`
+ pub const TCP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_TCP as _));
+ /// `IPPROTO_EGP`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const EGP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_EGP as _));
+ /// `IPPROTO_PUP`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const PUP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_PUP as _));
+ /// `IPPROTO_UDP`
+ pub const UDP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_UDP as _));
+ /// `IPPROTO_IDP`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const IDP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_IDP as _));
+ /// `IPPROTO_TP`
+ #[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const TP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_TP as _));
+ /// `IPPROTO_DCCP`
+ #[cfg(not(any(
+ apple,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "openbsd",
+ target_os = "vita",
+ )))]
+ pub const DCCP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_DCCP as _));
+ /// `IPPROTO_IPV6`
+ pub const IPV6: Protocol = Protocol(new_raw_protocol(c::IPPROTO_IPV6 as _));
+ /// `IPPROTO_RSVP`
+ #[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const RSVP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_RSVP as _));
+ /// `IPPROTO_GRE`
+ #[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const GRE: Protocol = Protocol(new_raw_protocol(c::IPPROTO_GRE as _));
+ /// `IPPROTO_ESP`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const ESP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_ESP as _));
+ /// `IPPROTO_AH`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const AH: Protocol = Protocol(new_raw_protocol(c::IPPROTO_AH as _));
+ /// `IPPROTO_MTP`
+ #[cfg(not(any(
+ solarish,
+ netbsdlike,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const MTP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_MTP as _));
+ /// `IPPROTO_BEETPH`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const BEETPH: Protocol = Protocol(new_raw_protocol(c::IPPROTO_BEETPH as _));
+ /// `IPPROTO_ENCAP`
+ #[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita",
+ )))]
+ pub const ENCAP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_ENCAP as _));
+ /// `IPPROTO_PIM`
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const PIM: Protocol = Protocol(new_raw_protocol(c::IPPROTO_PIM as _));
+ /// `IPPROTO_COMP`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const COMP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_COMP as _));
+ /// `IPPROTO_SCTP`
+ #[cfg(not(any(
+ solarish,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "openbsd",
+ target_os = "vita",
+ )))]
+ pub const SCTP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_SCTP as _));
+ /// `IPPROTO_UDPLITE`
+ #[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const UDPLITE: Protocol = Protocol(new_raw_protocol(c::IPPROTO_UDPLITE as _));
+ /// `IPPROTO_MPLS`
+ #[cfg(not(any(
+ apple,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const MPLS: Protocol = Protocol(new_raw_protocol(c::IPPROTO_MPLS as _));
+ /// `IPPROTO_ETHERNET`
+ #[cfg(linux_kernel)]
+ pub const ETHERNET: Protocol = Protocol(new_raw_protocol(c::IPPROTO_ETHERNET as _));
+ /// `IPPROTO_RAW`
+ #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
+ pub const RAW: Protocol = Protocol(new_raw_protocol(c::IPPROTO_RAW as _));
+ /// `IPPROTO_MPTCP`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ windows,
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const MPTCP: Protocol = Protocol(new_raw_protocol(c::IPPROTO_MPTCP as _));
+ /// `IPPROTO_FRAGMENT`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const FRAGMENT: Protocol = Protocol(new_raw_protocol(c::IPPROTO_FRAGMENT as _));
+ /// `IPPROTO_ICMPV6`
+ pub const ICMPV6: Protocol = Protocol(new_raw_protocol(c::IPPROTO_ICMPV6 as _));
+ /// `IPPROTO_MH`
+ #[cfg(not(any(
+ apple,
+ netbsdlike,
+ solarish,
+ windows,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ pub const MH: Protocol = Protocol(new_raw_protocol(c::IPPROTO_MH as _));
+ /// `IPPROTO_ROUTING`
+ #[cfg(not(any(
+ solarish,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "vita"
+ )))]
+ pub const ROUTING: Protocol = Protocol(new_raw_protocol(c::IPPROTO_ROUTING as _));
+}
+
+/// `SYSPROTO_*` constants.
+pub mod sysproto {
+ #[cfg(apple)]
+ use {
+ super::{new_raw_protocol, Protocol},
+ crate::backend::c,
+ };
+
+ /// `SYSPROTO_EVENT`
+ #[cfg(apple)]
+ pub const EVENT: Protocol = Protocol(new_raw_protocol(c::SYSPROTO_EVENT as _));
+
+ /// `SYSPROTO_CONTROL`
+ #[cfg(apple)]
+ pub const CONTROL: Protocol = Protocol(new_raw_protocol(c::SYSPROTO_CONTROL as _));
+}
+
+/// `NETLINK_*` constants.
+///
+/// For `NETLINK_ROUTE`, pass `None` as the `protocol` argument.
+pub mod netlink {
+ #[cfg(linux_kernel)]
+ use {
+ super::{new_raw_protocol, Protocol},
+ crate::backend::c,
+ };
+
+ /// `NETLINK_UNUSED`
+ #[cfg(linux_kernel)]
+ pub const UNUSED: Protocol = Protocol(new_raw_protocol(c::NETLINK_UNUSED as _));
+ /// `NETLINK_USERSOCK`
+ #[cfg(linux_kernel)]
+ pub const USERSOCK: Protocol = Protocol(new_raw_protocol(c::NETLINK_USERSOCK as _));
+ /// `NETLINK_FIREWALL`
+ #[cfg(linux_kernel)]
+ pub const FIREWALL: Protocol = Protocol(new_raw_protocol(c::NETLINK_FIREWALL as _));
+ /// `NETLINK_SOCK_DIAG`
+ #[cfg(linux_kernel)]
+ pub const SOCK_DIAG: Protocol = Protocol(new_raw_protocol(c::NETLINK_SOCK_DIAG as _));
+ /// `NETLINK_NFLOG`
+ #[cfg(linux_kernel)]
+ pub const NFLOG: Protocol = Protocol(new_raw_protocol(c::NETLINK_NFLOG as _));
+ /// `NETLINK_XFRM`
+ #[cfg(linux_kernel)]
+ pub const XFRM: Protocol = Protocol(new_raw_protocol(c::NETLINK_XFRM as _));
+ /// `NETLINK_SELINUX`
+ #[cfg(linux_kernel)]
+ pub const SELINUX: Protocol = Protocol(new_raw_protocol(c::NETLINK_SELINUX as _));
+ /// `NETLINK_ISCSI`
+ #[cfg(linux_kernel)]
+ pub const ISCSI: Protocol = Protocol(new_raw_protocol(c::NETLINK_ISCSI as _));
+ /// `NETLINK_AUDIT`
+ #[cfg(linux_kernel)]
+ pub const AUDIT: Protocol = Protocol(new_raw_protocol(c::NETLINK_AUDIT as _));
+ /// `NETLINK_FIB_LOOKUP`
+ #[cfg(linux_kernel)]
+ pub const FIB_LOOKUP: Protocol = Protocol(new_raw_protocol(c::NETLINK_FIB_LOOKUP as _));
+ /// `NETLINK_CONNECTOR`
+ #[cfg(linux_kernel)]
+ pub const CONNECTOR: Protocol = Protocol(new_raw_protocol(c::NETLINK_CONNECTOR as _));
+ /// `NETLINK_NETFILTER`
+ #[cfg(linux_kernel)]
+ pub const NETFILTER: Protocol = Protocol(new_raw_protocol(c::NETLINK_NETFILTER as _));
+ /// `NETLINK_IP6_FW`
+ #[cfg(linux_kernel)]
+ pub const IP6_FW: Protocol = Protocol(new_raw_protocol(c::NETLINK_IP6_FW as _));
+ /// `NETLINK_DNRTMSG`
+ #[cfg(linux_kernel)]
+ pub const DNRTMSG: Protocol = Protocol(new_raw_protocol(c::NETLINK_DNRTMSG as _));
+ /// `NETLINK_KOBJECT_UEVENT`
+ #[cfg(linux_kernel)]
+ pub const KOBJECT_UEVENT: Protocol = Protocol(new_raw_protocol(c::NETLINK_KOBJECT_UEVENT as _));
+ /// `NETLINK_GENERIC`
+ // This is defined on FreeBSD too, but it has the value 0, so it doesn't
+ // fit in or `NonZeroU32`. It's unclear whether FreeBSD intends
+ // `NETLINK_GENERIC` to be the default when Linux has `NETLINK_ROUTE`
+ // as the default.
+ #[cfg(linux_kernel)]
+ pub const GENERIC: Protocol = Protocol(new_raw_protocol(c::NETLINK_GENERIC as _));
+ /// `NETLINK_SCSITRANSPORT`
+ #[cfg(linux_kernel)]
+ pub const SCSITRANSPORT: Protocol = Protocol(new_raw_protocol(c::NETLINK_SCSITRANSPORT as _));
+ /// `NETLINK_ECRYPTFS`
+ #[cfg(linux_kernel)]
+ pub const ECRYPTFS: Protocol = Protocol(new_raw_protocol(c::NETLINK_ECRYPTFS as _));
+ /// `NETLINK_RDMA`
+ #[cfg(linux_kernel)]
+ pub const RDMA: Protocol = Protocol(new_raw_protocol(c::NETLINK_RDMA as _));
+ /// `NETLINK_CRYPTO`
+ #[cfg(linux_kernel)]
+ pub const CRYPTO: Protocol = Protocol(new_raw_protocol(c::NETLINK_CRYPTO as _));
+ /// `NETLINK_INET_DIAG`
+ #[cfg(linux_kernel)]
+ pub const INET_DIAG: Protocol = Protocol(new_raw_protocol(c::NETLINK_INET_DIAG as _));
+ /// `NETLINK_ADD_MEMBERSHIP`
+ #[cfg(linux_kernel)]
+ pub const ADD_MEMBERSHIP: Protocol = Protocol(new_raw_protocol(c::NETLINK_ADD_MEMBERSHIP as _));
+ /// `NETLINK_DROP_MEMBERSHIP`
+ #[cfg(linux_kernel)]
+ pub const DROP_MEMBERSHIP: Protocol =
+ Protocol(new_raw_protocol(c::NETLINK_DROP_MEMBERSHIP as _));
+ /// `NETLINK_PKTINFO`
+ #[cfg(linux_kernel)]
+ pub const PKTINFO: Protocol = Protocol(new_raw_protocol(c::NETLINK_PKTINFO as _));
+ /// `NETLINK_BROADCAST_ERROR`
+ #[cfg(linux_kernel)]
+ pub const BROADCAST_ERROR: Protocol =
+ Protocol(new_raw_protocol(c::NETLINK_BROADCAST_ERROR as _));
+ /// `NETLINK_NO_ENOBUFS`
+ #[cfg(linux_kernel)]
+ pub const NO_ENOBUFS: Protocol = Protocol(new_raw_protocol(c::NETLINK_NO_ENOBUFS as _));
+ /// `NETLINK_RX_RING`
+ #[cfg(linux_kernel)]
+ pub const RX_RING: Protocol = Protocol(new_raw_protocol(c::NETLINK_RX_RING as _));
+ /// `NETLINK_TX_RING`
+ #[cfg(linux_kernel)]
+ pub const TX_RING: Protocol = Protocol(new_raw_protocol(c::NETLINK_TX_RING as _));
+ /// `NETLINK_LISTEN_ALL_NSID`
+ #[cfg(linux_kernel)]
+ pub const LISTEN_ALL_NSID: Protocol =
+ Protocol(new_raw_protocol(c::NETLINK_LISTEN_ALL_NSID as _));
+ /// `NETLINK_LIST_MEMBERSHIPS`
+ #[cfg(linux_kernel)]
+ pub const LIST_MEMBERSHIPS: Protocol =
+ Protocol(new_raw_protocol(c::NETLINK_LIST_MEMBERSHIPS as _));
+ /// `NETLINK_CAP_ACK`
+ #[cfg(linux_kernel)]
+ pub const CAP_ACK: Protocol = Protocol(new_raw_protocol(c::NETLINK_CAP_ACK as _));
+ /// `NETLINK_EXT_ACK`
+ #[cfg(linux_kernel)]
+ pub const EXT_ACK: Protocol = Protocol(new_raw_protocol(c::NETLINK_EXT_ACK as _));
+ /// `NETLINK_GET_STRICT_CHK`
+ #[cfg(linux_kernel)]
+ pub const GET_STRICT_CHK: Protocol = Protocol(new_raw_protocol(c::NETLINK_GET_STRICT_CHK as _));
+}
+
+/// `ETH_P_*` constants.
+// These are translated into 16-bit big-endian form because that's what the
+// [`AddressFamily::PACKET`] address family [expects].
+//
+// [expects]: https://man7.org/linux/man-pages/man7/packet.7.html
+pub mod eth {
+ #[cfg(linux_kernel)]
+ use {
+ super::{new_raw_protocol, Protocol},
+ crate::backend::c,
+ };
+
+ /// `ETH_P_LOOP`
+ #[cfg(linux_kernel)]
+ pub const LOOP: Protocol = Protocol(new_raw_protocol((c::ETH_P_LOOP as u16).to_be() as u32));
+ /// `ETH_P_PUP`
+ #[cfg(linux_kernel)]
+ pub const PUP: Protocol = Protocol(new_raw_protocol((c::ETH_P_PUP as u16).to_be() as u32));
+ /// `ETH_P_PUPAT`
+ #[cfg(linux_kernel)]
+ pub const PUPAT: Protocol = Protocol(new_raw_protocol((c::ETH_P_PUPAT as u16).to_be() as u32));
+ /// `ETH_P_TSN`
+ #[cfg(linux_kernel)]
+ pub const TSN: Protocol = Protocol(new_raw_protocol((c::ETH_P_TSN as u16).to_be() as u32));
+ /// `ETH_P_ERSPAN2`
+ #[cfg(linux_kernel)]
+ pub const ERSPAN2: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ERSPAN2 as u16).to_be() as u32));
+ /// `ETH_P_IP`
+ #[cfg(linux_kernel)]
+ pub const IP: Protocol = Protocol(new_raw_protocol((c::ETH_P_IP as u16).to_be() as u32));
+ /// `ETH_P_X25`
+ #[cfg(linux_kernel)]
+ pub const X25: Protocol = Protocol(new_raw_protocol((c::ETH_P_X25 as u16).to_be() as u32));
+ /// `ETH_P_ARP`
+ #[cfg(linux_kernel)]
+ pub const ARP: Protocol = Protocol(new_raw_protocol((c::ETH_P_ARP as u16).to_be() as u32));
+ /// `ETH_P_BPQ`
+ #[cfg(linux_kernel)]
+ pub const BPQ: Protocol = Protocol(new_raw_protocol((c::ETH_P_BPQ as u16).to_be() as u32));
+ /// `ETH_P_IEEEPUP`
+ #[cfg(linux_kernel)]
+ pub const IEEEPUP: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_IEEEPUP as u16).to_be() as u32));
+ /// `ETH_P_IEEEPUPAT`
+ #[cfg(linux_kernel)]
+ pub const IEEEPUPAT: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_IEEEPUPAT as u16).to_be() as u32));
+ /// `ETH_P_BATMAN`
+ #[cfg(linux_kernel)]
+ pub const BATMAN: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_BATMAN as u16).to_be() as u32));
+ /// `ETH_P_DEC`
+ #[cfg(linux_kernel)]
+ pub const DEC: Protocol = Protocol(new_raw_protocol((c::ETH_P_DEC as u16).to_be() as u32));
+ /// `ETH_P_DNA_DL`
+ #[cfg(linux_kernel)]
+ pub const DNA_DL: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_DNA_DL as u16).to_be() as u32));
+ /// `ETH_P_DNA_RC`
+ #[cfg(linux_kernel)]
+ pub const DNA_RC: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_DNA_RC as u16).to_be() as u32));
+ /// `ETH_P_DNA_RT`
+ #[cfg(linux_kernel)]
+ pub const DNA_RT: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_DNA_RT as u16).to_be() as u32));
+ /// `ETH_P_LAT`
+ #[cfg(linux_kernel)]
+ pub const LAT: Protocol = Protocol(new_raw_protocol((c::ETH_P_LAT as u16).to_be() as u32));
+ /// `ETH_P_DIAG`
+ #[cfg(linux_kernel)]
+ pub const DIAG: Protocol = Protocol(new_raw_protocol((c::ETH_P_DIAG as u16).to_be() as u32));
+ /// `ETH_P_CUST`
+ #[cfg(linux_kernel)]
+ pub const CUST: Protocol = Protocol(new_raw_protocol((c::ETH_P_CUST as u16).to_be() as u32));
+ /// `ETH_P_SCA`
+ #[cfg(linux_kernel)]
+ pub const SCA: Protocol = Protocol(new_raw_protocol((c::ETH_P_SCA as u16).to_be() as u32));
+ /// `ETH_P_TEB`
+ #[cfg(linux_kernel)]
+ pub const TEB: Protocol = Protocol(new_raw_protocol((c::ETH_P_TEB as u16).to_be() as u32));
+ /// `ETH_P_RARP`
+ #[cfg(linux_kernel)]
+ pub const RARP: Protocol = Protocol(new_raw_protocol((c::ETH_P_RARP as u16).to_be() as u32));
+ /// `ETH_P_ATALK`
+ #[cfg(linux_kernel)]
+ pub const ATALK: Protocol = Protocol(new_raw_protocol((c::ETH_P_ATALK as u16).to_be() as u32));
+ /// `ETH_P_AARP`
+ #[cfg(linux_kernel)]
+ pub const AARP: Protocol = Protocol(new_raw_protocol((c::ETH_P_AARP as u16).to_be() as u32));
+ /// `ETH_P_8021Q`
+ #[cfg(linux_kernel)]
+ pub const P_8021Q: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_8021Q as u16).to_be() as u32));
+ /// `ETH_P_ERSPAN`
+ #[cfg(linux_kernel)]
+ pub const ERSPAN: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ERSPAN as u16).to_be() as u32));
+ /// `ETH_P_IPX`
+ #[cfg(linux_kernel)]
+ pub const IPX: Protocol = Protocol(new_raw_protocol((c::ETH_P_IPX as u16).to_be() as u32));
+ /// `ETH_P_IPV6`
+ #[cfg(linux_kernel)]
+ pub const IPV6: Protocol = Protocol(new_raw_protocol((c::ETH_P_IPV6 as u16).to_be() as u32));
+ /// `ETH_P_PAUSE`
+ #[cfg(linux_kernel)]
+ pub const PAUSE: Protocol = Protocol(new_raw_protocol((c::ETH_P_PAUSE as u16).to_be() as u32));
+ /// `ETH_P_SLOW`
+ #[cfg(linux_kernel)]
+ pub const SLOW: Protocol = Protocol(new_raw_protocol((c::ETH_P_SLOW as u16).to_be() as u32));
+ /// `ETH_P_WCCP`
+ #[cfg(linux_kernel)]
+ pub const WCCP: Protocol = Protocol(new_raw_protocol((c::ETH_P_WCCP as u16).to_be() as u32));
+ /// `ETH_P_MPLS_UC`
+ #[cfg(linux_kernel)]
+ pub const MPLS_UC: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_MPLS_UC as u16).to_be() as u32));
+ /// `ETH_P_MPLS_MC`
+ #[cfg(linux_kernel)]
+ pub const MPLS_MC: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_MPLS_MC as u16).to_be() as u32));
+ /// `ETH_P_ATMMPOA`
+ #[cfg(linux_kernel)]
+ pub const ATMMPOA: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ATMMPOA as u16).to_be() as u32));
+ /// `ETH_P_PPP_DISC`
+ #[cfg(linux_kernel)]
+ pub const PPP_DISC: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PPP_DISC as u16).to_be() as u32));
+ /// `ETH_P_PPP_SES`
+ #[cfg(linux_kernel)]
+ pub const PPP_SES: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PPP_SES as u16).to_be() as u32));
+ /// `ETH_P_LINK_CTL`
+ #[cfg(linux_kernel)]
+ pub const LINK_CTL: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_LINK_CTL as u16).to_be() as u32));
+ /// `ETH_P_ATMFATE`
+ #[cfg(linux_kernel)]
+ pub const ATMFATE: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ATMFATE as u16).to_be() as u32));
+ /// `ETH_P_PAE`
+ #[cfg(linux_kernel)]
+ pub const PAE: Protocol = Protocol(new_raw_protocol((c::ETH_P_PAE as u16).to_be() as u32));
+ /// `ETH_P_PROFINET`
+ #[cfg(linux_kernel)]
+ pub const PROFINET: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PROFINET as u16).to_be() as u32));
+ /// `ETH_P_REALTEK`
+ #[cfg(linux_kernel)]
+ pub const REALTEK: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_REALTEK as u16).to_be() as u32));
+ /// `ETH_P_AOE`
+ #[cfg(linux_kernel)]
+ pub const AOE: Protocol = Protocol(new_raw_protocol((c::ETH_P_AOE as u16).to_be() as u32));
+ /// `ETH_P_ETHERCAT`
+ #[cfg(linux_kernel)]
+ pub const ETHERCAT: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ETHERCAT as u16).to_be() as u32));
+ /// `ETH_P_8021AD`
+ #[cfg(linux_kernel)]
+ pub const P_8021AD: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_8021AD as u16).to_be() as u32));
+ /// `ETH_P_802_EX1`
+ #[cfg(linux_kernel)]
+ pub const P_802_EX1: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_802_EX1 as u16).to_be() as u32));
+ /// `ETH_P_PREAUTH`
+ #[cfg(linux_kernel)]
+ pub const PREAUTH: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PREAUTH as u16).to_be() as u32));
+ /// `ETH_P_TIPC`
+ #[cfg(linux_kernel)]
+ pub const TIPC: Protocol = Protocol(new_raw_protocol((c::ETH_P_TIPC as u16).to_be() as u32));
+ /// `ETH_P_LLDP`
+ #[cfg(linux_kernel)]
+ pub const LLDP: Protocol = Protocol(new_raw_protocol((c::ETH_P_LLDP as u16).to_be() as u32));
+ /// `ETH_P_MRP`
+ #[cfg(linux_kernel)]
+ pub const MRP: Protocol = Protocol(new_raw_protocol((c::ETH_P_MRP as u16).to_be() as u32));
+ /// `ETH_P_MACSEC`
+ #[cfg(linux_kernel)]
+ pub const MACSEC: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_MACSEC as u16).to_be() as u32));
+ /// `ETH_P_8021AH`
+ #[cfg(linux_kernel)]
+ pub const P_8021AH: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_8021AH as u16).to_be() as u32));
+ /// `ETH_P_MVRP`
+ #[cfg(linux_kernel)]
+ pub const MVRP: Protocol = Protocol(new_raw_protocol((c::ETH_P_MVRP as u16).to_be() as u32));
+ /// `ETH_P_1588`
+ #[cfg(linux_kernel)]
+ pub const P_1588: Protocol = Protocol(new_raw_protocol((c::ETH_P_1588 as u16).to_be() as u32));
+ /// `ETH_P_NCSI`
+ #[cfg(linux_kernel)]
+ pub const NCSI: Protocol = Protocol(new_raw_protocol((c::ETH_P_NCSI as u16).to_be() as u32));
+ /// `ETH_P_PRP`
+ #[cfg(linux_kernel)]
+ pub const PRP: Protocol = Protocol(new_raw_protocol((c::ETH_P_PRP as u16).to_be() as u32));
+ /// `ETH_P_CFM`
+ #[cfg(linux_kernel)]
+ pub const CFM: Protocol = Protocol(new_raw_protocol((c::ETH_P_CFM as u16).to_be() as u32));
+ /// `ETH_P_FCOE`
+ #[cfg(linux_kernel)]
+ pub const FCOE: Protocol = Protocol(new_raw_protocol((c::ETH_P_FCOE as u16).to_be() as u32));
+ /// `ETH_P_IBOE`
+ #[cfg(linux_kernel)]
+ pub const IBOE: Protocol = Protocol(new_raw_protocol((c::ETH_P_IBOE as u16).to_be() as u32));
+ /// `ETH_P_TDLS`
+ #[cfg(linux_kernel)]
+ pub const TDLS: Protocol = Protocol(new_raw_protocol((c::ETH_P_TDLS as u16).to_be() as u32));
+ /// `ETH_P_FIP`
+ #[cfg(linux_kernel)]
+ pub const FIP: Protocol = Protocol(new_raw_protocol((c::ETH_P_FIP as u16).to_be() as u32));
+ /// `ETH_P_80221`
+ #[cfg(linux_kernel)]
+ pub const P_80221: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_80221 as u16).to_be() as u32));
+ /// `ETH_P_HSR`
+ #[cfg(linux_kernel)]
+ pub const HSR: Protocol = Protocol(new_raw_protocol((c::ETH_P_HSR as u16).to_be() as u32));
+ /// `ETH_P_NSH`
+ #[cfg(linux_kernel)]
+ pub const NSH: Protocol = Protocol(new_raw_protocol((c::ETH_P_NSH as u16).to_be() as u32));
+ /// `ETH_P_LOOPBACK`
+ #[cfg(linux_kernel)]
+ pub const LOOPBACK: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_LOOPBACK as u16).to_be() as u32));
+ /// `ETH_P_QINQ1`
+ #[cfg(linux_kernel)]
+ pub const QINQ1: Protocol = Protocol(new_raw_protocol((c::ETH_P_QINQ1 as u16).to_be() as u32));
+ /// `ETH_P_QINQ2`
+ #[cfg(linux_kernel)]
+ pub const QINQ2: Protocol = Protocol(new_raw_protocol((c::ETH_P_QINQ2 as u16).to_be() as u32));
+ /// `ETH_P_QINQ3`
+ #[cfg(linux_kernel)]
+ pub const QINQ3: Protocol = Protocol(new_raw_protocol((c::ETH_P_QINQ3 as u16).to_be() as u32));
+ /// `ETH_P_EDSA`
+ #[cfg(linux_kernel)]
+ pub const EDSA: Protocol = Protocol(new_raw_protocol((c::ETH_P_EDSA as u16).to_be() as u32));
+ /// `ETH_P_DSA_8021Q`
+ #[cfg(linux_kernel)]
+ pub const DSA_8021Q: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_DSA_8021Q as u16).to_be() as u32));
+ /// `ETH_P_DSA_A5PSW`
+ #[cfg(linux_kernel)]
+ pub const DSA_A5PSW: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_DSA_A5PSW as u16).to_be() as u32));
+ /// `ETH_P_IFE`
+ #[cfg(linux_kernel)]
+ pub const IFE: Protocol = Protocol(new_raw_protocol((c::ETH_P_IFE as u16).to_be() as u32));
+ /// `ETH_P_AF_IUCV`
+ #[cfg(linux_kernel)]
+ pub const AF_IUCV: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_AF_IUCV as u16).to_be() as u32));
+ /// `ETH_P_802_3_MIN`
+ #[cfg(linux_kernel)]
+ pub const P_802_3_MIN: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_802_3_MIN as u16).to_be() as u32));
+ /// `ETH_P_802_3`
+ #[cfg(linux_kernel)]
+ pub const P_802_3: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_802_3 as u16).to_be() as u32));
+ /// `ETH_P_AX25`
+ #[cfg(linux_kernel)]
+ pub const AX25: Protocol = Protocol(new_raw_protocol((c::ETH_P_AX25 as u16).to_be() as u32));
+ /// `ETH_P_ALL`
+ #[cfg(linux_kernel)]
+ pub const ALL: Protocol = Protocol(new_raw_protocol((c::ETH_P_ALL as u16).to_be() as u32));
+ /// `ETH_P_802_2`
+ #[cfg(linux_kernel)]
+ pub const P_802_2: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_802_2 as u16).to_be() as u32));
+ /// `ETH_P_SNAP`
+ #[cfg(linux_kernel)]
+ pub const SNAP: Protocol = Protocol(new_raw_protocol((c::ETH_P_SNAP as u16).to_be() as u32));
+ /// `ETH_P_DDCMP`
+ #[cfg(linux_kernel)]
+ pub const DDCMP: Protocol = Protocol(new_raw_protocol((c::ETH_P_DDCMP as u16).to_be() as u32));
+ /// `ETH_P_WAN_PPP`
+ #[cfg(linux_kernel)]
+ pub const WAN_PPP: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_WAN_PPP as u16).to_be() as u32));
+ /// `ETH_P_PPP_MP`
+ #[cfg(linux_kernel)]
+ pub const PPP_MP: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PPP_MP as u16).to_be() as u32));
+ /// `ETH_P_LOCALTALK`
+ #[cfg(linux_kernel)]
+ pub const LOCALTALK: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_LOCALTALK as u16).to_be() as u32));
+ /// `ETH_P_CAN`
+ #[cfg(linux_kernel)]
+ pub const CAN: Protocol = Protocol(new_raw_protocol((c::ETH_P_CAN as u16).to_be() as u32));
+ /// `ETH_P_CANFD`
+ #[cfg(linux_kernel)]
+ pub const CANFD: Protocol = Protocol(new_raw_protocol((c::ETH_P_CANFD as u16).to_be() as u32));
+ /// `ETH_P_CANXL`
+ #[cfg(linux_kernel)]
+ pub const CANXL: Protocol = Protocol(new_raw_protocol((c::ETH_P_CANXL as u16).to_be() as u32));
+ /// `ETH_P_PPPTALK`
+ #[cfg(linux_kernel)]
+ pub const PPPTALK: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PPPTALK as u16).to_be() as u32));
+ /// `ETH_P_TR_802_2`
+ #[cfg(linux_kernel)]
+ pub const TR_802_2: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_TR_802_2 as u16).to_be() as u32));
+ /// `ETH_P_MOBITEX`
+ #[cfg(linux_kernel)]
+ pub const MOBITEX: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_MOBITEX as u16).to_be() as u32));
+ /// `ETH_P_CONTROL`
+ #[cfg(linux_kernel)]
+ pub const CONTROL: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_CONTROL as u16).to_be() as u32));
+ /// `ETH_P_IRDA`
+ #[cfg(linux_kernel)]
+ pub const IRDA: Protocol = Protocol(new_raw_protocol((c::ETH_P_IRDA as u16).to_be() as u32));
+ /// `ETH_P_ECONET`
+ #[cfg(linux_kernel)]
+ pub const ECONET: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ECONET as u16).to_be() as u32));
+ /// `ETH_P_HDLC`
+ #[cfg(linux_kernel)]
+ pub const HDLC: Protocol = Protocol(new_raw_protocol((c::ETH_P_HDLC as u16).to_be() as u32));
+ /// `ETH_P_ARCNET`
+ #[cfg(linux_kernel)]
+ pub const ARCNET: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_ARCNET as u16).to_be() as u32));
+ /// `ETH_P_DSA`
+ #[cfg(linux_kernel)]
+ pub const DSA: Protocol = Protocol(new_raw_protocol((c::ETH_P_DSA as u16).to_be() as u32));
+ /// `ETH_P_TRAILER`
+ #[cfg(linux_kernel)]
+ pub const TRAILER: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_TRAILER as u16).to_be() as u32));
+ /// `ETH_P_PHONET`
+ #[cfg(linux_kernel)]
+ pub const PHONET: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_PHONET as u16).to_be() as u32));
+ /// `ETH_P_IEEE802154`
+ #[cfg(linux_kernel)]
+ pub const IEEE802154: Protocol =
+ Protocol(new_raw_protocol((c::ETH_P_IEEE802154 as u16).to_be() as u32));
+ /// `ETH_P_CAIF`
+ #[cfg(linux_kernel)]
+ pub const CAIF: Protocol = Protocol(new_raw_protocol((c::ETH_P_CAIF as u16).to_be() as u32));
+ /// `ETH_P_XDSA`
+ #[cfg(linux_kernel)]
+ pub const XDSA: Protocol = Protocol(new_raw_protocol((c::ETH_P_XDSA as u16).to_be() as u32));
+ /// `ETH_P_MAP`
+ #[cfg(linux_kernel)]
+ pub const MAP: Protocol = Protocol(new_raw_protocol((c::ETH_P_MAP as u16).to_be() as u32));
+ /// `ETH_P_MCTP`
+ #[cfg(linux_kernel)]
+ pub const MCTP: Protocol = Protocol(new_raw_protocol((c::ETH_P_MCTP as u16).to_be() as u32));
+}
+
+#[rustfmt::skip]
+impl Protocol {
+ /// Constructs a `Protocol` from a raw integer.
+ #[inline]
+ pub const fn from_raw(raw: RawProtocol) -> Self {
+ Self(raw)
+ }
+
+ /// Returns the raw integer for this `Protocol`.
+ #[inline]
+ pub const fn as_raw(self) -> RawProtocol {
+ self.0
+ }
+}
+
+/// `SHUT_*` constants for use with [`shutdown`].
+///
+/// [`shutdown`]: crate::net::shutdown
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u32)]
+pub enum Shutdown {
+ /// `SHUT_RD`—Disable further read operations.
+ Read = c::SHUT_RD as _,
+ /// `SHUT_WR`—Disable further write operations.
+ Write = c::SHUT_WR as _,
+ /// `SHUT_RDWR`—Disable further read and write operations.
+ ReadWrite = c::SHUT_RDWR as _,
+}
+
+bitflags! {
+ /// `SOCK_*` constants for use with [`socket_with`], [`accept_with`] and
+ /// [`acceptfrom_with`].
+ ///
+ /// [`socket_with`]: crate::net::socket_with
+ /// [`accept_with`]: crate::net::accept_with
+ /// [`acceptfrom_with`]: crate::net::acceptfrom_with
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SocketFlags: c::c_uint {
+ /// `SOCK_NONBLOCK`
+ #[cfg(not(any(
+ apple,
+ windows,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ )))]
+ const NONBLOCK = bitcast!(c::SOCK_NONBLOCK);
+
+ /// `SOCK_CLOEXEC`
+ #[cfg(not(any(apple, windows, target_os = "aix", target_os = "haiku")))]
+ const CLOEXEC = bitcast!(c::SOCK_CLOEXEC);
+
+ // This deliberately lacks a `const _ = !0`, so that users can use
+ // `from_bits_truncate` to extract the `SocketFlags` from a flags
+ // value that also includes a `SocketType`.
+ }
+}
+
+/// `AF_XDP` related types and constants.
+#[cfg(target_os = "linux")]
+pub mod xdp {
+ use super::{bitflags, c};
+
+ bitflags! {
+ /// `XDP_OPTIONS_*` constants returned by [`get_xdp_options`].
+ ///
+ /// [`get_xdp_options`]: crate::net::sockopt::get_xdp_options
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpOptionsFlags: u32 {
+ /// `XDP_OPTIONS_ZEROCOPY`
+ const XDP_OPTIONS_ZEROCOPY = bitcast!(c::XDP_OPTIONS_ZEROCOPY);
+ }
+ }
+
+ // Constant needs to be cast because bindgen does generate a u32 but the struct expects a u16.
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L15-L44
+ bitflags! {
+ /// `XDP_*` constants for use in [`SockaddrXdp`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
+ pub struct SockaddrXdpFlags: u16 {
+ /// `XDP_SHARED_UMEM`
+ const XDP_SHARED_UMEM = bitcast!(c::XDP_SHARED_UMEM as u16);
+ /// `XDP_COPY`
+ const XDP_COPY = bitcast!(c::XDP_COPY as u16);
+ /// `XDP_COPY`
+ const XDP_ZEROCOPY = bitcast!(c::XDP_ZEROCOPY as u16);
+ /// `XDP_USE_NEED_WAKEUP`
+ const XDP_USE_NEED_WAKEUP = bitcast!(c::XDP_USE_NEED_WAKEUP as u16);
+ // requires kernel 6.6
+ /// `XDP_USE_SG`
+ const XDP_USE_SG = bitcast!(c::XDP_USE_SG as u16);
+ }
+ }
+
+ bitflags! {
+ /// `XDP_RING_*` constants for use in fill and/or Tx ring.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpRingFlags: u32 {
+ /// `XDP_RING_NEED_WAKEUP`
+ const XDP_RING_NEED_WAKEUP = bitcast!(c::XDP_RING_NEED_WAKEUP);
+ }
+ }
+
+ bitflags! {
+ /// `XDP_UMEM_*` constants for use in [`XdpUmemReg`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpUmemRegFlags: u32 {
+ /// `XDP_UMEM_UNALIGNED_CHUNK_FLAG`
+ const XDP_UMEM_UNALIGNED_CHUNK_FLAG = bitcast!(c::XDP_UMEM_UNALIGNED_CHUNK_FLAG);
+ }
+ }
+
+ /// A XDP socket address.
+ ///
+ /// Used to bind to XDP socket.
+ ///
+ /// Not ABI compatible with `struct sockaddr_xdp`
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L38-L44
+ #[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
+ pub struct SocketAddrXdp {
+ /// Flags.
+ sxdp_flags: SockaddrXdpFlags,
+ /// Interface index.
+ sxdp_ifindex: u32,
+ /// Queue ID.
+ sxdp_queue_id: u32,
+ /// Shared UMEM file descriptor.
+ sxdp_shared_umem_fd: u32,
+ }
+
+ impl SocketAddrXdp {
+ /// Construct a new XDP address.
+ #[inline]
+ pub fn new(
+ flags: SockaddrXdpFlags,
+ interface_index: u32,
+ queue_id: u32,
+ share_umem_fd: u32,
+ ) -> Self {
+ Self {
+ sxdp_flags: flags,
+ sxdp_ifindex: interface_index,
+ sxdp_queue_id: queue_id,
+ sxdp_shared_umem_fd: share_umem_fd,
+ }
+ }
+
+ /// Return flags.
+ #[inline]
+ pub fn flags(&self) -> SockaddrXdpFlags {
+ self.sxdp_flags
+ }
+
+ /// Set flags.
+ #[inline]
+ pub fn set_flags(&mut self, flags: SockaddrXdpFlags) {
+ self.sxdp_flags = flags;
+ }
+
+ /// Return interface index.
+ #[inline]
+ pub fn interface_index(&self) -> u32 {
+ self.sxdp_ifindex
+ }
+
+ /// Set interface index.
+ #[inline]
+ pub fn set_interface_index(&mut self, interface_index: u32) {
+ self.sxdp_ifindex = interface_index;
+ }
+
+ /// Return queue ID.
+ #[inline]
+ pub fn queue_id(&self) -> u32 {
+ self.sxdp_queue_id
+ }
+
+ /// Set queue ID.
+ #[inline]
+ pub fn set_queue_id(&mut self, queue_id: u32) {
+ self.sxdp_queue_id = queue_id;
+ }
+
+ /// Return shared UMEM file descriptor.
+ #[inline]
+ pub fn shared_umem_fd(&self) -> u32 {
+ self.sxdp_shared_umem_fd
+ }
+
+ /// Set shared UMEM file descriptor.
+ #[inline]
+ pub fn set_shared_umem_fd(&mut self, shared_umem_fd: u32) {
+ self.sxdp_shared_umem_fd = shared_umem_fd;
+ }
+ }
+
+ /// XDP ring offset.
+ ///
+ /// Used to mmap rings from kernel.
+ ///
+ /// Not ABI compatible with `struct xdp_ring_offset`.
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L49-L54
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpRingOffset {
+ /// Producer offset.
+ pub producer: u64,
+ /// Consumer offset.
+ pub consumer: u64,
+ /// Descriptors offset.
+ pub desc: u64,
+ /// Flags offset.
+ ///
+ /// Is `None` if the kernel version (<5.4) does not yet support flags.
+ pub flags: Option<u64>,
+ }
+
+ /// XDP mmap offsets.
+ ///
+ /// Not ABI compatible with `struct xdp_mmap_offsets`
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L56-L61
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpMmapOffsets {
+ /// Rx ring offsets.
+ pub rx: XdpRingOffset,
+ /// Tx ring offsets.
+ pub tx: XdpRingOffset,
+ /// Fill ring offsets.
+ pub fr: XdpRingOffset,
+ /// Completion ring offsets.
+ pub cr: XdpRingOffset,
+ }
+
+ /// XDP umem registration.
+ ///
+ /// `struct xdp_umem_reg`
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L73-L79
+ #[repr(C)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpUmemReg {
+ /// Start address of UMEM.
+ pub addr: u64,
+ /// Umem length in bytes.
+ pub len: u64,
+ /// Chunk size in bytes.
+ pub chunk_size: u32,
+ /// Headroom in bytes.
+ pub headroom: u32,
+ /// Flags.
+ ///
+ /// Requires kernel version 5.4.
+ pub flags: XdpUmemRegFlags,
+ }
+
+ /// XDP statistics.
+ ///
+ /// Not ABI compatible with `struct xdp_statistics`
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L81-L88
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpStatistics {
+ /// Rx dropped.
+ pub rx_dropped: u64,
+ /// Rx invalid descriptors.
+ pub rx_invalid_descs: u64,
+ /// Tx invalid descriptors.
+ pub tx_invalid_descs: u64,
+ /// Rx ring full.
+ ///
+ /// Is `None` if the kernel version (<5.9) does not yet support flags.
+ pub rx_ring_full: Option<u64>,
+ /// Rx fill ring empty descriptors.
+ ///
+ /// Is `None` if the kernel version (<5.9) does not yet support flags.
+ pub rx_fill_ring_empty_descs: Option<u64>,
+ /// Tx ring empty descriptors.
+ ///
+ /// Is `None` if the kernel version (<5.9) does not yet support flags.
+ pub tx_ring_empty_descs: Option<u64>,
+ }
+
+ /// XDP options.
+ ///
+ /// Requires kernel version 5.3.
+ /// `struct xdp_options`
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L90-L92
+ #[repr(C)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpOptions {
+ /// Flags.
+ pub flags: XdpOptionsFlags,
+ }
+
+ /// XDP rx/tx frame descriptor.
+ ///
+ /// `struct xdp_desc`
+ // https://github.com/torvalds/linux/blob/v6.6/include/uapi/linux/if_xdp.h#L109-L113
+ #[repr(C)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct XdpDesc {
+ /// Offset from the start of the UMEM.
+ pub addr: u64,
+ /// Length of packet in bytes.
+ pub len: u32,
+ /// Options.
+ pub options: XdpDescOptions,
+ }
+
+ #[cfg(target_os = "linux")]
+ bitflags! {
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ /// `XDP_*` constants for use in [`XdpDesc`].
+ ///
+ /// Requires kernel version 6.6.
+ pub struct XdpDescOptions: u32 {
+ /// `XDP_PKT_CONTD`
+ const XDP_PKT_CONTD = bitcast!(c::XDP_PKT_CONTD);
+ }
+ }
+
+ /// Offset for mmapping rx ring.
+ pub const XDP_PGOFF_RX_RING: u64 = c::XDP_PGOFF_RX_RING as u64;
+ /// Offset for mmapping tx ring.
+ pub const XDP_PGOFF_TX_RING: u64 = c::XDP_PGOFF_TX_RING as u64;
+ /// Offset for mmapping fill ring.
+ pub const XDP_UMEM_PGOFF_FILL_RING: u64 = c::XDP_UMEM_PGOFF_FILL_RING;
+ /// Offset for mmapping completion ring.
+ pub const XDP_UMEM_PGOFF_COMPLETION_RING: u64 = c::XDP_UMEM_PGOFF_COMPLETION_RING;
+
+ /// Offset used to shift the [`XdpDesc`] addr to the right to extract the address offset in
+ /// unaligned mode.
+ pub const XSK_UNALIGNED_BUF_OFFSET_SHIFT: u64 = c::XSK_UNALIGNED_BUF_OFFSET_SHIFT as u64;
+ /// Mask used to binary `and` the [`XdpDesc`] addr to extract the address without the offset
+ /// carried in the upper 16 bits of the address in unaligned mode.
+ pub const XSK_UNALIGNED_BUF_ADDR_MASK: u64 = c::XSK_UNALIGNED_BUF_ADDR_MASK;
+}
+
+/// UNIX credentials of socket peer, for use with [`get_socket_peercred`]
+/// [`SendAncillaryMessage::ScmCredentials`] and
+/// [`RecvAncillaryMessage::ScmCredentials`].
+///
+/// [`get_socket_peercred`]: crate::net::sockopt::get_socket_peercred
+/// [`SendAncillaryMessage::ScmCredentials`]: crate::net::SendAncillaryMessage::ScmCredentials
+/// [`RecvAncillaryMessage::ScmCredentials`]: crate::net::RecvAncillaryMessage::ScmCredentials
+#[cfg(linux_kernel)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(C)]
+pub struct UCred {
+ /// Process ID of peer
+ pub pid: crate::pid::Pid,
+ /// User ID of peer
+ pub uid: crate::ugid::Uid,
+ /// Group ID of peer
+ pub gid: crate::ugid::Gid,
+}
+
+#[test]
+fn test_sizes() {
+ use crate::backend::c;
+ use c::c_int;
+ use core::mem::transmute;
+
+ // Backend code needs to cast these to `c_int` so make sure that cast isn't
+ // lossy.
+ assert_eq_size!(RawProtocol, c_int);
+ assert_eq_size!(Protocol, c_int);
+ assert_eq_size!(Option<RawProtocol>, c_int);
+ assert_eq_size!(Option<Protocol>, c_int);
+ assert_eq_size!(RawSocketType, c_int);
+ assert_eq_size!(SocketType, c_int);
+ assert_eq_size!(SocketFlags, c_int);
+
+ // Rustix doesn't depend on `Option<Protocol>` matching the ABI of a raw
+ // integer for correctness, but it should work nonetheless.
+ #[allow(unsafe_code)]
+ unsafe {
+ let t: Option<Protocol> = None;
+ assert_eq!(0_u32, transmute::<Option<Protocol>, u32>(t));
+
+ let t: Option<Protocol> = Some(Protocol::from_raw(RawProtocol::new(4567).unwrap()));
+ assert_eq!(4567_u32, transmute::<Option<Protocol>, u32>(t));
+ }
+
+ #[cfg(linux_kernel)]
+ assert_eq_size!(UCred, libc::ucred);
+
+ #[cfg(target_os = "linux")]
+ assert_eq_size!(super::xdp::XdpUmemReg, c::xdp_umem_reg);
+ #[cfg(target_os = "linux")]
+ assert_eq_size!(super::xdp::XdpOptions, c::xdp_options);
+ #[cfg(target_os = "linux")]
+ assert_eq_size!(super::xdp::XdpDesc, c::xdp_desc);
+}
diff --git a/src/net/wsa.rs b/src/net/wsa.rs
new file mode 100644
index 0000000..0ad4db5
--- /dev/null
+++ b/src/net/wsa.rs
@@ -0,0 +1,49 @@
+use crate::io;
+use core::mem::MaybeUninit;
+use windows_sys::Win32::Networking::WinSock::{WSACleanup, WSAGetLastError, WSAStartup, WSADATA};
+
+/// `WSAStartup()`—Initialize process-wide Windows support for sockets.
+///
+/// On Windows, it's necessary to initialize the sockets subsystem before
+/// using sockets APIs. The function performs the necessary initialization.
+///
+/// # References
+/// - [Winsock]
+///
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsastartup
+pub fn wsa_startup() -> io::Result<WSADATA> {
+ // Request version 2.2, which has been the latest version since far older
+ // versions of Windows than we support here. For more information about
+ // the version, see [here].
+ //
+ // [here]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsastartup#remarks
+ let version = 0x202;
+ let mut data = MaybeUninit::uninit();
+ unsafe {
+ let ret = WSAStartup(version, data.as_mut_ptr());
+ if ret == 0 {
+ Ok(data.assume_init())
+ } else {
+ Err(io::Errno::from_raw_os_error(WSAGetLastError()))
+ }
+ }
+}
+
+/// `WSACleanup()`—Clean up process-wide Windows support for sockets.
+///
+/// In a program where `init` is called, if sockets are no longer necessary,
+/// this function releases associated resources.
+///
+/// # References
+/// - [Winsock]
+///
+/// [Winsock]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsacleanup
+pub fn wsa_cleanup() -> io::Result<()> {
+ unsafe {
+ if WSACleanup() == 0 {
+ Ok(())
+ } else {
+ Err(io::Errno::from_raw_os_error(WSAGetLastError()))
+ }
+ }
+}
diff --git a/src/param/auxv.rs b/src/param/auxv.rs
new file mode 100644
index 0000000..19f93dc
--- /dev/null
+++ b/src/param/auxv.rs
@@ -0,0 +1,87 @@
+use crate::backend;
+#[cfg(any(
+ linux_raw,
+ any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+ )
+))]
+use crate::ffi::CStr;
+
+/// `sysconf(_SC_PAGESIZE)`—Returns the process' page size.
+///
+/// Also known as `getpagesize`.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux `sysconf`]
+/// - [Linux `getpagesize`]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html
+/// [Linux `sysconf`]: https://man7.org/linux/man-pages/man3/sysconf.3.html
+/// [Linux `getpagesize`]: https://man7.org/linux/man-pages/man2/getpagesize.2.html
+#[inline]
+#[doc(alias = "_SC_PAGESIZE")]
+#[doc(alias = "_SC_PAGE_SIZE")]
+#[doc(alias = "getpagesize")]
+pub fn page_size() -> usize {
+ backend::param::auxv::page_size()
+}
+
+/// `sysconf(_SC_CLK_TCK)`—Returns the process' clock ticks per second.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/sysconf.3.html
+#[cfg(not(any(target_os = "vita", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "_SC_CLK_TCK")]
+pub fn clock_ticks_per_second() -> u64 {
+ backend::param::auxv::clock_ticks_per_second()
+}
+
+/// `(getauxval(AT_HWCAP), getauxval(AT_HWCAP2)`—Returns the Linux "hwcap"
+/// data.
+///
+/// Return the Linux `AT_HWCAP` and `AT_HWCAP2` values passed to the
+/// current process. Returns 0 for each value if it is not available.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/getauxval.3.html
+#[cfg(any(
+ linux_raw,
+ any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+ )
+))]
+#[inline]
+pub fn linux_hwcap() -> (usize, usize) {
+ backend::param::auxv::linux_hwcap()
+}
+
+/// `getauxval(AT_EXECFN)`—Returns the Linux "execfn" string.
+///
+/// Return the string that Linux has recorded as the filesystem path to the
+/// executable. Returns an empty string if the string is not available.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/getauxval.3.html
+#[cfg(any(
+ linux_raw,
+ any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+ )
+))]
+#[inline]
+pub fn linux_execfn() -> &'static CStr {
+ backend::param::auxv::linux_execfn()
+}
diff --git a/src/param/init.rs b/src/param/init.rs
new file mode 100644
index 0000000..09d7c96
--- /dev/null
+++ b/src/param/init.rs
@@ -0,0 +1,23 @@
+//! rustix's `init` function.
+//!
+//! # Safety
+//!
+//! When "use-explicitly-provided-auxv" is enabled, the `init` function must be
+//! called before any other function in this module. It is unsafe because it
+//! operates on raw pointers.
+#![allow(unsafe_code)]
+
+use crate::backend;
+
+/// Initialize process-wide state.
+///
+/// # Safety
+///
+/// This must be passed a pointer to the original environment variable block
+/// set up by the OS at process startup, and it must be called before any
+/// other rustix functions are called.
+#[inline]
+#[doc(hidden)]
+pub unsafe fn init(envp: *mut *mut u8) {
+ backend::param::auxv::init(envp)
+}
diff --git a/src/param/mod.rs b/src/param/mod.rs
new file mode 100644
index 0000000..f0de9d4
--- /dev/null
+++ b/src/param/mod.rs
@@ -0,0 +1,14 @@
+//! Process parameters.
+//!
+//! These values correspond to `sysconf` in POSIX, and the auxv array in Linux.
+//! Despite the POSIX name “sysconf”, these aren't *system* configuration
+//! parameters; they're *process* configuration parameters, as they may differ
+//! between different processes on the same system.
+
+mod auxv;
+#[cfg(all(feature = "use-explicitly-provided-auxv", not(libc)))]
+mod init;
+
+pub use auxv::*;
+#[cfg(all(feature = "use-explicitly-provided-auxv", not(libc)))]
+pub use init::init;
diff --git a/src/path/arg.rs b/src/path/arg.rs
new file mode 100644
index 0000000..f71125d
--- /dev/null
+++ b/src/path/arg.rs
@@ -0,0 +1,1099 @@
+//! Convenient and efficient string argument passing.
+//!
+//! This module defines the `Arg` trait and implements it for several common
+//! string types. This allows users to pass any of these string types directly
+//! to rustix APIs with string arguments, and it allows rustix to implement
+//! NUL-termination without the need for copying where possible.
+
+use crate::ffi::CStr;
+use crate::io;
+#[cfg(feature = "itoa")]
+use crate::path::DecInt;
+use crate::path::SMALL_PATH_BUFFER_SIZE;
+#[cfg(all(feature = "alloc", feature = "itoa"))]
+use alloc::borrow::ToOwned;
+use core::mem::MaybeUninit;
+use core::{ptr, slice, str};
+#[cfg(feature = "std")]
+use std::ffi::{OsStr, OsString};
+#[cfg(all(feature = "std", target_os = "hermit"))]
+use std::os::hermit::ext::ffi::{OsStrExt, OsStringExt};
+#[cfg(all(feature = "std", unix))]
+use std::os::unix::ffi::{OsStrExt, OsStringExt};
+#[cfg(all(feature = "std", target_os = "vxworks"))]
+use std::os::vxworks::ext::ffi::{OsStrExt, OsStringExt};
+#[cfg(all(feature = "std", target_os = "wasi"))]
+use std::os::wasi::ffi::{OsStrExt, OsStringExt};
+#[cfg(feature = "std")]
+use std::path::{Component, Components, Iter, Path, PathBuf};
+#[cfg(feature = "alloc")]
+use {crate::ffi::CString, alloc::borrow::Cow};
+#[cfg(feature = "alloc")]
+use {alloc::string::String, alloc::vec::Vec};
+
+/// A trait for passing path arguments.
+///
+/// This is similar to [`AsRef`]`<`[`Path`]`>`, but is implemented for more
+/// kinds of strings and can convert into more kinds of strings.
+///
+/// # Examples
+///
+/// ```
+/// # #[cfg(any(feature = "fs", feature = "net"))]
+/// use rustix::ffi::CStr;
+/// use rustix::io;
+/// # #[cfg(any(feature = "fs", feature = "net"))]
+/// use rustix::path::Arg;
+///
+/// # #[cfg(any(feature = "fs", feature = "net"))]
+/// pub fn touch<P: Arg>(path: P) -> io::Result<()> {
+/// let path = path.into_c_str()?;
+/// _touch(&path)
+/// }
+///
+/// # #[cfg(any(feature = "fs", feature = "net"))]
+/// fn _touch(path: &CStr) -> io::Result<()> {
+/// // implementation goes here
+/// Ok(())
+/// }
+/// ```
+///
+/// Users can then call `touch("foo")`, `touch(cstr!("foo"))`,
+/// `touch(Path::new("foo"))`, or many other things.
+///
+/// [`AsRef`]: std::convert::AsRef
+pub trait Arg {
+ /// Returns a view of this string as a string slice.
+ fn as_str(&self) -> io::Result<&str>;
+
+ /// Returns a potentially-lossy rendering of this string as a
+ /// `Cow<'_, str>`.
+ #[cfg(feature = "alloc")]
+ fn to_string_lossy(&self) -> Cow<'_, str>;
+
+ /// Returns a view of this string as a maybe-owned [`CStr`].
+ #[cfg(feature = "alloc")]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>;
+
+ /// Consumes `self` and returns a view of this string as a maybe-owned
+ /// [`CStr`].
+ #[cfg(feature = "alloc")]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b;
+
+ /// Runs a closure with `self` passed in as a `&CStr`.
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>;
+}
+
+impl Arg for &str {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ Ok(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ Cow::Borrowed(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(*self).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl Arg for &String {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ Ok(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ Cow::Borrowed(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(String::as_str(self)).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ self.as_str().into_c_str()
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl Arg for String {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ Ok(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ Cow::Borrowed(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_str()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(&CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Arg for &OsStr {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ OsStr::to_string_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Arg for &OsString {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ OsString::as_os_str(self).to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_os_str().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(OsString::as_os_str(self).as_bytes())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ self.as_os_str().into_c_str()
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Arg for OsString {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.as_os_str().to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_os_str().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.into_vec()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(&CString::new(self.into_vec()).map_err(|_cstr_err| io::Errno::INVAL)?)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Arg for &Path {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.as_os_str().to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ Path::to_string_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_os_str().as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Arg for &PathBuf {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ PathBuf::as_path(self)
+ .as_os_str()
+ .to_str()
+ .ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_path().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(PathBuf::as_path(self).as_os_str().as_bytes())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ self.as_path().into_c_str()
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_os_str().as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+impl Arg for PathBuf {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.as_os_str().to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_os_str().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.into_os_string().into_vec()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(
+ &CString::new(self.into_os_string().into_vec())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ )
+ }
+}
+
+impl Arg for &CStr {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ CStr::to_string_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Borrowed(self))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Borrowed(self))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(self)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl Arg for &CString {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ unimplemented!()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ unimplemented!()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Borrowed(self))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Borrowed(self))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(self)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl Arg for CString {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ CStr::to_string_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Borrowed(self))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(self))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(&self)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl<'a> Arg for Cow<'a, str> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ Ok(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ Cow::Borrowed(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_ref()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ match self {
+ Cow::Owned(s) => CString::new(s),
+ Cow::Borrowed(s) => CString::new(s),
+ }
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg(feature = "alloc")]
+impl<'a> Arg for Cow<'a, OsStr> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ (**self).to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ (**self).to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ match self {
+ Cow::Owned(os) => CString::new(os.into_vec()),
+ Cow::Borrowed(os) => CString::new(os.as_bytes()),
+ }
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl<'a> Arg for Cow<'a, CStr> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ let borrow: &CStr = core::borrow::Borrow::borrow(self);
+ borrow.to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Borrowed(self))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(self)
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(&self)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Arg for Component<'a> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.as_os_str().to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_os_str().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_os_str().as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Arg for Components<'a> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.as_path().to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_path().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_path().as_os_str().as_bytes())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.as_path().as_os_str().as_bytes())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_path().as_os_str().as_bytes(), f)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Arg for Iter<'a> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ self.as_path().to_str().ok_or(io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ self.as_path().to_string_lossy()
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_path().as_os_str().as_bytes())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.as_path().as_os_str().as_bytes())
+ .map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self.as_path().as_os_str().as_bytes(), f)
+ }
+}
+
+impl Arg for &[u8] {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ String::from_utf8_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(*self).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self, f)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl Arg for &Vec<u8> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ String::from_utf8_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ with_c_str(self, f)
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl Arg for Vec<u8> {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ String::from_utf8_lossy(self)
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Owned(
+ CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(
+ CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
+ ))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(&CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?)
+ }
+}
+
+#[cfg(feature = "itoa")]
+impl Arg for DecInt {
+ #[inline]
+ fn as_str(&self) -> io::Result<&str> {
+ Ok(self.as_str())
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn to_string_lossy(&self) -> Cow<'_, str> {
+ Cow::Borrowed(self.as_str())
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
+ Ok(Cow::Borrowed(self.as_c_str()))
+ }
+
+ #[cfg(feature = "alloc")]
+ #[inline]
+ fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
+ where
+ Self: 'b,
+ {
+ Ok(Cow::Owned(self.as_c_str().to_owned()))
+ }
+
+ #[inline]
+ fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
+ where
+ Self: Sized,
+ F: FnOnce(&CStr) -> io::Result<T>,
+ {
+ f(self.as_c_str())
+ }
+}
+
+/// Runs a closure with `bytes` passed in as a `&CStr`.
+#[allow(unsafe_code, clippy::int_plus_one)]
+#[inline]
+fn with_c_str<T, F>(bytes: &[u8], f: F) -> io::Result<T>
+where
+ F: FnOnce(&CStr) -> io::Result<T>,
+{
+ // Most paths are less than `SMALL_PATH_BUFFER_SIZE` long. The rest can go
+ // through the dynamic allocation path. If you're opening many files in a
+ // directory with a long path, consider opening the directory and using
+ // `openat` to open the files under it, which will avoid this, and is often
+ // faster in the OS as well.
+
+ // Test with >= so that we have room for the trailing NUL.
+ if bytes.len() >= SMALL_PATH_BUFFER_SIZE {
+ return with_c_str_slow_path(bytes, f);
+ }
+
+ // Taken from
+ // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
+ let mut buf = MaybeUninit::<[u8; SMALL_PATH_BUFFER_SIZE]>::uninit();
+ let buf_ptr = buf.as_mut_ptr().cast::<u8>();
+
+ // This helps test our safety condition below.
+ debug_assert!(bytes.len() + 1 <= SMALL_PATH_BUFFER_SIZE);
+
+ // SAFETY: `bytes.len() < SMALL_PATH_BUFFER_SIZE` which means we have space
+ // for `bytes.len() + 1` u8s:
+ unsafe {
+ ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
+ buf_ptr.add(bytes.len()).write(0);
+ }
+
+ // SAFETY: We just wrote the bytes above and they will remain valid for the
+ // duration of `f` b/c buf doesn't get dropped until the end of the
+ // function.
+ match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) }) {
+ Ok(s) => f(s),
+ Err(_) => Err(io::Errno::INVAL),
+ }
+}
+
+/// The slow path which handles any length. In theory OS's only support up to
+/// `PATH_MAX`, but we let the OS enforce that.
+#[allow(unsafe_code, clippy::int_plus_one)]
+#[cold]
+fn with_c_str_slow_path<T, F>(bytes: &[u8], f: F) -> io::Result<T>
+where
+ F: FnOnce(&CStr) -> io::Result<T>,
+{
+ #[cfg(feature = "alloc")]
+ {
+ f(&CString::new(bytes).map_err(|_cstr_err| io::Errno::INVAL)?)
+ }
+
+ #[cfg(not(feature = "alloc"))]
+ {
+ #[cfg(all(libc, not(target_os = "wasi")))]
+ const LARGE_PATH_BUFFER_SIZE: usize = libc::PATH_MAX as usize;
+ #[cfg(linux_raw)]
+ const LARGE_PATH_BUFFER_SIZE: usize = linux_raw_sys::general::PATH_MAX as usize;
+ #[cfg(target_os = "wasi")]
+ const LARGE_PATH_BUFFER_SIZE: usize = 4096 as usize; // TODO: upstream this
+
+ // Taken from
+ // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
+ let mut buf = MaybeUninit::<[u8; LARGE_PATH_BUFFER_SIZE]>::uninit();
+ let buf_ptr = buf.as_mut_ptr().cast::<u8>();
+
+ // This helps test our safety condition below.
+ if bytes.len() + 1 > LARGE_PATH_BUFFER_SIZE {
+ return Err(io::Errno::NAMETOOLONG);
+ }
+
+ // SAFETY: `bytes.len() < LARGE_PATH_BUFFER_SIZE` which means we have
+ // space for `bytes.len() + 1` u8s:
+ unsafe {
+ ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
+ buf_ptr.add(bytes.len()).write(0);
+ }
+
+ // SAFETY: We just wrote the bytes above and they will remain valid for
+ // the duration of `f` b/c buf doesn't get dropped until the end of the
+ // function.
+ match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) })
+ {
+ Ok(s) => f(s),
+ Err(_) => Err(io::Errno::INVAL),
+ }
+ }
+}
diff --git a/src/path/dec_int.rs b/src/path/dec_int.rs
new file mode 100644
index 0000000..e9c46f4
--- /dev/null
+++ b/src/path/dec_int.rs
@@ -0,0 +1,120 @@
+//! Efficient decimal integer formatting.
+//!
+//! # Safety
+//!
+//! This uses `CStr::from_bytes_with_nul_unchecked` and
+//! `str::from_utf8_unchecked`on the buffer that it filled itself.
+#![allow(unsafe_code)]
+
+use crate::backend::fd::{AsFd, AsRawFd};
+use crate::ffi::CStr;
+use core::fmt::Write;
+use itoa::{Buffer, Integer};
+#[cfg(all(feature = "std", unix))]
+use std::os::unix::ffi::OsStrExt;
+#[cfg(all(feature = "std", target_os = "wasi"))]
+use std::os::wasi::ffi::OsStrExt;
+#[cfg(feature = "std")]
+use {core::fmt, std::ffi::OsStr, std::path::Path};
+
+/// Format an integer into a decimal `Path` component, without constructing a
+/// temporary `PathBuf` or `String`.
+///
+/// This is used for opening paths such as `/proc/self/fd/<fd>` on Linux.
+///
+/// # Examples
+///
+/// ```
+/// # #[cfg(any(feature = "fs", feature = "net"))]
+/// use rustix::path::DecInt;
+///
+/// # #[cfg(any(feature = "fs", feature = "net"))]
+/// assert_eq!(
+/// format!("hello {}", DecInt::new(9876).as_ref().display()),
+/// "hello 9876"
+/// );
+/// ```
+#[derive(Clone)]
+pub struct DecInt {
+ // 20 `u8`s is enough to hold the decimal ASCII representation of any
+ // `u64`, and we add one for a NUL terminator for `as_c_str`.
+ buf: [u8; 20 + 1],
+ len: usize,
+}
+
+impl DecInt {
+ /// Construct a new path component from an integer.
+ #[inline]
+ pub fn new<Int: Integer>(i: Int) -> Self {
+ let mut me = DecIntWriter(Self {
+ buf: [0; 20 + 1],
+ len: 0,
+ });
+ let mut buf = Buffer::new();
+ me.write_str(buf.format(i)).unwrap();
+ me.0
+ }
+
+ /// Construct a new path component from a file descriptor.
+ #[inline]
+ pub fn from_fd<Fd: AsFd>(fd: Fd) -> Self {
+ Self::new(fd.as_fd().as_raw_fd())
+ }
+
+ /// Return the raw byte buffer as a `&str`.
+ #[inline]
+ pub fn as_str(&self) -> &str {
+ // SAFETY: `DecInt` always holds a formatted decimal number, so it's
+ // always valid UTF-8.
+ unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
+ }
+
+ /// Return the raw byte buffer as a `&CStr`.
+ #[inline]
+ pub fn as_c_str(&self) -> &CStr {
+ let bytes_with_nul = &self.buf[..=self.len];
+ debug_assert!(CStr::from_bytes_with_nul(bytes_with_nul).is_ok());
+
+ // SAFETY: `self.buf` holds a single decimal ASCII representation and
+ // at least one extra NUL byte.
+ unsafe { CStr::from_bytes_with_nul_unchecked(bytes_with_nul) }
+ }
+
+ /// Return the raw byte buffer.
+ #[inline]
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.buf[..self.len]
+ }
+}
+
+struct DecIntWriter(DecInt);
+
+impl core::fmt::Write for DecIntWriter {
+ #[inline]
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ match self.0.buf.get_mut(self.0.len..self.0.len + s.len()) {
+ Some(slice) => {
+ slice.copy_from_slice(s.as_bytes());
+ self.0.len += s.len();
+ Ok(())
+ }
+ None => Err(core::fmt::Error),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl AsRef<Path> for DecInt {
+ #[inline]
+ fn as_ref(&self) -> &Path {
+ let as_os_str: &OsStr = OsStrExt::from_bytes(&self.buf[..self.len]);
+ Path::new(as_os_str)
+ }
+}
+
+#[cfg(feature = "std")]
+impl fmt::Debug for DecInt {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.as_str().fmt(fmt)
+ }
+}
diff --git a/src/path/mod.rs b/src/path/mod.rs
new file mode 100644
index 0000000..19bf2c7
--- /dev/null
+++ b/src/path/mod.rs
@@ -0,0 +1,12 @@
+//! Filesystem path operations.
+
+mod arg;
+#[cfg(feature = "itoa")]
+mod dec_int;
+
+pub use arg::Arg;
+#[cfg(feature = "itoa")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "itoa")))]
+pub use dec_int::DecInt;
+
+pub(crate) const SMALL_PATH_BUFFER_SIZE: usize = 256;
diff --git a/src/pid.rs b/src/pid.rs
new file mode 100644
index 0000000..5f2b9ad
--- /dev/null
+++ b/src/pid.rs
@@ -0,0 +1,104 @@
+//! The `Pid` type.
+
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use core::num::NonZeroI32;
+
+/// A process identifier as a raw integer.
+pub type RawPid = c::pid_t;
+
+/// `pid_t`—A non-zero Unix process ID.
+///
+/// This is a pid, and not a pidfd. It is not a file descriptor, and the
+/// process it refers to could disappear at any time and be replaced by
+/// another, unrelated, process.
+#[repr(transparent)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
+pub struct Pid(NonZeroI32);
+
+impl Pid {
+ /// A `Pid` corresponding to the init process (pid 1).
+ pub const INIT: Self = Self(match NonZeroI32::new(1) {
+ Some(n) => n,
+ None => panic!("unreachable"),
+ });
+
+ /// Converts a `RawPid` into a `Pid`.
+ ///
+ /// Returns `Some` for positive `RawPid`s. Otherwise, returns `None`.
+ ///
+ /// This is safe because a `Pid` is a number without any guarantees for the
+ /// kernel. Non-child `Pid`s are always racy for any syscalls, but can only
+ /// cause logic errors. If you want race-free access to or control of
+ /// non-child processes, please consider other mechanisms like [pidfd] on
+ /// Linux.
+ ///
+ /// [pidfd]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
+ #[inline]
+ pub const fn from_raw(raw: RawPid) -> Option<Self> {
+ if raw > 0 {
+ // SAFETY: We just checked that `raw > 0`.
+ unsafe { Some(Self::from_raw_unchecked(raw)) }
+ } else {
+ None
+ }
+ }
+
+ /// Converts a known positive `RawPid` into a `Pid`.
+ ///
+ /// # Safety
+ ///
+ /// The caller must guarantee `raw` is positive.
+ #[inline]
+ pub const unsafe fn from_raw_unchecked(raw: RawPid) -> Self {
+ debug_assert!(raw > 0);
+ Self(NonZeroI32::new_unchecked(raw))
+ }
+
+ /// Creates a `Pid` holding the ID of the given child process.
+ #[cfg(feature = "std")]
+ #[inline]
+ pub fn from_child(child: &std::process::Child) -> Self {
+ let id = child.id();
+ // SAFETY: We know the returned ID is valid because it came directly
+ // from an OS API.
+ unsafe { Self::from_raw_unchecked(id as i32) }
+ }
+
+ /// Converts a `Pid` into a `NonZeroI32`.
+ #[inline]
+ pub const fn as_raw_nonzero(self) -> NonZeroI32 {
+ self.0
+ }
+
+ /// Converts an `Option<Pid>` into a `RawPid`.
+ #[inline]
+ pub fn as_raw(pid: Option<Self>) -> RawPid {
+ pid.map_or(0, |pid| pid.0.get())
+ }
+
+ /// Test whether this pid represents the init process (pid 1).
+ #[inline]
+ pub const fn is_init(self) -> bool {
+ self.0.get() == Self::INIT.0.get()
+ }
+}
+
+#[test]
+fn test_sizes() {
+ use core::mem::transmute;
+
+ assert_eq_size!(RawPid, NonZeroI32);
+ assert_eq_size!(RawPid, Pid);
+ assert_eq_size!(RawPid, Option<Pid>);
+
+ // Rustix doesn't depend on `Option<Pid>` matching the ABI of a raw integer
+ // for correctness, but it should work nonetheless.
+ const_assert_eq!(0 as RawPid, unsafe {
+ transmute::<Option<Pid>, RawPid>(None)
+ });
+ const_assert_eq!(4567 as RawPid, unsafe {
+ transmute::<Option<Pid>, RawPid>(Some(Pid::from_raw_unchecked(4567)))
+ });
+}
diff --git a/src/pipe.rs b/src/pipe.rs
new file mode 100644
index 0000000..b0a9783
--- /dev/null
+++ b/src/pipe.rs
@@ -0,0 +1,220 @@
+//! `pipe` and related APIs.
+//!
+//! # Safety
+//!
+//! `vmsplice` is an unsafe function.
+
+#![allow(unsafe_code)]
+
+use crate::fd::OwnedFd;
+use crate::{backend, io};
+#[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+use backend::c;
+#[cfg(linux_kernel)]
+use backend::fd::AsFd;
+
+#[cfg(not(apple))]
+pub use backend::pipe::types::PipeFlags;
+
+#[cfg(linux_kernel)]
+pub use backend::pipe::types::{IoSliceRaw, SpliceFlags};
+
+/// `PIPE_BUF`—The maximum length at which writes to a pipe are atomic.
+///
+/// # References
+/// - [Linux]
+/// - [POSIX]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man7/pipe.7.html
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
+#[cfg(not(any(
+ solarish,
+ windows,
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+pub const PIPE_BUF: usize = c::PIPE_BUF;
+
+/// `pipe()`—Creates a pipe.
+///
+/// This function creates a pipe and returns two file descriptors, for the
+/// reading and writing ends of the pipe, respectively.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/pipe.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/pipe.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=pipe&sektion=2
+/// [NetBSD]: https://man.netbsd.org/pipe.2
+/// [OpenBSD]: https://man.openbsd.org/pipe.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=pipe&section=2
+/// [illumos]: https://illumos.org/man/2/pipe
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Creating-a-Pipe.html
+#[inline]
+pub fn pipe() -> io::Result<(OwnedFd, OwnedFd)> {
+ backend::pipe::syscalls::pipe()
+}
+
+/// `pipe2(flags)`—Creates a pipe, with flags.
+///
+/// This function creates a pipe and returns two file descriptors, for the
+/// reading and writing ends of the pipe, respectively.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/pipe2.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=pipe2&sektion=2
+/// [NetBSD]: https://man.netbsd.org/pipe2.2
+/// [OpenBSD]: https://man.openbsd.org/pipe2.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=pipe2&section=2
+/// [illumos]: https://illumos.org/man/2/pipe2
+#[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "espidf",
+ target_os = "haiku",
+ target_os = "nto"
+)))]
+#[inline]
+#[doc(alias = "pipe2")]
+pub fn pipe_with(flags: PipeFlags) -> io::Result<(OwnedFd, OwnedFd)> {
+ backend::pipe::syscalls::pipe_with(flags)
+}
+
+/// `splice(fd_in, off_in, fd_out, off_out, len, flags)`—Transfer data
+/// between a file and a pipe.
+///
+/// This function transfers up to `len` bytes of data from the file descriptor
+/// `fd_in` to the file descriptor `fd_out`, where one of the file descriptors
+/// must refer to a pipe.
+///
+/// `off_*` must be `None` if the corresponding fd refers to a pipe. Otherwise
+/// its value points to the starting offset to the file, from which the data is
+/// read/written. On success, the number of bytes read/written is added to the
+/// offset.
+///
+/// Passing `None` causes the read/write to start from the file offset, and the
+/// file offset is adjusted appropriately.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/splice.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn splice<FdIn: AsFd, FdOut: AsFd>(
+ fd_in: FdIn,
+ off_in: Option<&mut u64>,
+ fd_out: FdOut,
+ off_out: Option<&mut u64>,
+ len: usize,
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ backend::pipe::syscalls::splice(fd_in.as_fd(), off_in, fd_out.as_fd(), off_out, len, flags)
+}
+
+/// `vmsplice(fd, bufs, flags)`—Transfer data between memory and a pipe.
+///
+/// If `fd` is the write end of the pipe, the function maps the memory pointer
+/// at by `bufs` to the pipe.
+///
+/// If `fd` is the read end of the pipe, the function writes data from the pipe
+/// to said memory.
+///
+/// # Safety
+///
+/// If the memory must not be mutated (such as when `bufs` were originally
+/// immutable slices), it is up to the caller to ensure that the write end of
+/// the pipe is placed in `fd`.
+///
+/// Additionally if `SpliceFlags::GIFT` is set, the caller must also ensure
+/// that the contents of `bufs` in never modified following the call, and that
+/// all of the pointers in `bufs` are page aligned, and the lengths are
+/// multiples of a page size in bytes.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/vmsplice.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub unsafe fn vmsplice<PipeFd: AsFd>(
+ fd: PipeFd,
+ bufs: &[IoSliceRaw<'_>],
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ backend::pipe::syscalls::vmsplice(fd.as_fd(), bufs, flags)
+}
+
+/// `tee(fd_in, fd_out, len, flags)`—Copy data between pipes without
+/// consuming it.
+///
+/// This reads up to `len` bytes from `in_fd` without consuming them, and
+/// writes them to `out_fd`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/tee.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn tee<FdIn: AsFd, FdOut: AsFd>(
+ fd_in: FdIn,
+ fd_out: FdOut,
+ len: usize,
+ flags: SpliceFlags,
+) -> io::Result<usize> {
+ backend::pipe::syscalls::tee(fd_in.as_fd(), fd_out.as_fd(), len, flags)
+}
+
+/// `fnctl(fd, F_GETPIPE_SZ)`—Return the buffer capacity of a pipe.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn fcntl_getpipe_size<Fd: AsFd>(fd: Fd) -> io::Result<usize> {
+ backend::pipe::syscalls::fcntl_getpipe_sz(fd.as_fd())
+}
+
+/// `fnctl(fd, F_SETPIPE_SZ)`—Set the buffer capacity of a pipe.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn fcntl_setpipe_size<Fd: AsFd>(fd: Fd, size: usize) -> io::Result<()> {
+ backend::pipe::syscalls::fcntl_setpipe_sz(fd.as_fd(), size)
+}
diff --git a/src/prctl.rs b/src/prctl.rs
new file mode 100644
index 0000000..0ea8303
--- /dev/null
+++ b/src/prctl.rs
@@ -0,0 +1,71 @@
+//! Helper functions for `prctl` syscalls.
+
+#![allow(unsafe_code)]
+
+use crate::backend::c::{c_int, c_void};
+use crate::backend::prctl::syscalls;
+use crate::io;
+use crate::utils::as_mut_ptr;
+use bitflags::bitflags;
+use core::mem::MaybeUninit;
+use core::ptr::null_mut;
+
+bitflags! {
+ /// `PR_PAC_AP*`.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PointerAuthenticationKeys: u32 {
+ /// `PR_PAC_APIAKEY`—Instruction authentication key `A`.
+ const INSTRUCTION_AUTHENTICATION_KEY_A = linux_raw_sys::prctl::PR_PAC_APIAKEY;
+ /// `PR_PAC_APIBKEY`—Instruction authentication key `B`.
+ const INSTRUCTION_AUTHENTICATION_KEY_B = linux_raw_sys::prctl::PR_PAC_APIBKEY;
+ /// `PR_PAC_APDAKEY`—Data authentication key `A`.
+ const DATA_AUTHENTICATION_KEY_A = linux_raw_sys::prctl::PR_PAC_APDAKEY;
+ /// `PR_PAC_APDBKEY`—Data authentication key `B`.
+ const DATA_AUTHENTICATION_KEY_B = linux_raw_sys::prctl::PR_PAC_APDBKEY;
+ /// `PR_PAC_APGAKEY`—Generic authentication `A` key.
+ const GENERIC_AUTHENTICATION_KEY_A = linux_raw_sys::prctl::PR_PAC_APGAKEY;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn prctl_1arg(option: c_int) -> io::Result<c_int> {
+ const NULL: *mut c_void = null_mut();
+ syscalls::prctl(option, NULL, NULL, NULL, NULL)
+}
+
+#[inline]
+pub(crate) unsafe fn prctl_2args(option: c_int, arg2: *mut c_void) -> io::Result<c_int> {
+ const NULL: *mut c_void = null_mut();
+ syscalls::prctl(option, arg2, NULL, NULL, NULL)
+}
+
+#[inline]
+pub(crate) unsafe fn prctl_3args(
+ option: c_int,
+ arg2: *mut c_void,
+ arg3: *mut c_void,
+) -> io::Result<c_int> {
+ syscalls::prctl(option, arg2, arg3, null_mut(), null_mut())
+}
+
+#[inline]
+pub(crate) unsafe fn prctl_get_at_arg2_optional<P>(option: i32) -> io::Result<P> {
+ let mut value: MaybeUninit<P> = MaybeUninit::uninit();
+ prctl_2args(option, value.as_mut_ptr().cast())?;
+ Ok(value.assume_init())
+}
+
+#[inline]
+pub(crate) unsafe fn prctl_get_at_arg2<P, T>(option: i32) -> io::Result<T>
+where
+ P: Default,
+ T: TryFrom<P, Error = io::Errno>,
+{
+ let mut value: P = Default::default();
+ prctl_2args(option, as_mut_ptr(&mut value).cast())?;
+ TryFrom::try_from(value)
+}
diff --git a/src/process/chdir.rs b/src/process/chdir.rs
new file mode 100644
index 0000000..a68352f
--- /dev/null
+++ b/src/process/chdir.rs
@@ -0,0 +1,97 @@
+#[cfg(not(target_os = "fuchsia"))]
+use crate::backend::fd::AsFd;
+#[cfg(feature = "fs")]
+use crate::path;
+#[cfg(any(feature = "fs", not(target_os = "fuchsia")))]
+use crate::{backend, io};
+#[cfg(all(feature = "alloc", feature = "fs"))]
+use {
+ crate::ffi::{CStr, CString},
+ crate::path::SMALL_PATH_BUFFER_SIZE,
+ alloc::vec::Vec,
+};
+
+/// `chdir(path)`—Change the current working directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/chdir.2.html
+#[inline]
+#[cfg(feature = "fs")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+pub fn chdir<P: path::Arg>(path: P) -> io::Result<()> {
+ path.into_with_c_str(backend::process::syscalls::chdir)
+}
+
+/// `fchdir(fd)`—Change the current working directory.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchdir.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fchdir.2.html
+#[cfg(not(target_os = "fuchsia"))]
+#[inline]
+pub fn fchdir<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::process::syscalls::fchdir(fd.as_fd())
+}
+
+/// `getCWD`—Return the current working directory.
+///
+/// If `reuse` already has available capacity, reuse it if possible.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/getcwd.3.html
+#[cfg(all(feature = "alloc", feature = "fs"))]
+#[cfg(not(target_os = "wasi"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+#[inline]
+pub fn getcwd<B: Into<Vec<u8>>>(reuse: B) -> io::Result<CString> {
+ _getcwd(reuse.into())
+}
+
+#[cfg(all(feature = "alloc", feature = "fs"))]
+#[allow(unsafe_code)]
+fn _getcwd(mut buffer: Vec<u8>) -> io::Result<CString> {
+ buffer.clear();
+ buffer.reserve(SMALL_PATH_BUFFER_SIZE);
+
+ loop {
+ match backend::process::syscalls::getcwd(buffer.spare_capacity_mut()) {
+ Err(io::Errno::RANGE) => {
+ // Use `Vec` reallocation strategy to grow capacity
+ // exponentially.
+ buffer.reserve(buffer.capacity() + 1);
+ }
+ Ok(_) => {
+ // SAFETY:
+ // - "These functions return a null-terminated string"
+ // - [POSIX definition 3.375: String]: "A contiguous sequence
+ // of bytes terminated by and including the first null byte."
+ //
+ // Thus, there will be a single NUL byte at the end of the
+ // string.
+ //
+ // [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_375
+ unsafe {
+ buffer.set_len(
+ CStr::from_ptr(buffer.as_ptr().cast())
+ .to_bytes_with_nul()
+ .len(),
+ );
+
+ return Ok(CString::from_vec_with_nul_unchecked(buffer));
+ }
+ }
+ Err(errno) => return Err(errno),
+ }
+ }
+}
diff --git a/src/process/chroot.rs b/src/process/chroot.rs
new file mode 100644
index 0000000..a4fd8d8
--- /dev/null
+++ b/src/process/chroot.rs
@@ -0,0 +1,16 @@
+#[cfg(feature = "fs")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+use crate::{backend, io, path};
+
+/// `chroot(path)`—Change the process root directory.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/chroot.2.html
+#[cfg(feature = "fs")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+#[inline]
+pub fn chroot<P: path::Arg>(path: P) -> io::Result<()> {
+ path.into_with_c_str(backend::process::syscalls::chroot)
+}
diff --git a/src/process/exit.rs b/src/process/exit.rs
new file mode 100644
index 0000000..95b78c4
--- /dev/null
+++ b/src/process/exit.rs
@@ -0,0 +1,36 @@
+use crate::backend;
+
+/// `EXIT_SUCCESS` for use with [`exit`].
+///
+/// [`exit`]: std::process::exit
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdlib.h.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/exit.3.html
+pub const EXIT_SUCCESS: i32 = backend::c::EXIT_SUCCESS;
+
+/// `EXIT_FAILURE` for use with [`exit`].
+///
+/// [`exit`]: std::process::exit
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdlib.h.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/exit.3.html
+pub const EXIT_FAILURE: i32 = backend::c::EXIT_FAILURE;
+
+/// The exit status used by a process terminated with a [`Signal::Abort`]
+/// signal.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://tldp.org/LDP/abs/html/exitcodes.html
+/// [`Signal::Abort`]: crate::process::Signal::Abort
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub const EXIT_SIGNALED_SIGABRT: i32 = backend::c::EXIT_SIGNALED_SIGABRT;
diff --git a/src/process/id.rs b/src/process/id.rs
new file mode 100644
index 0000000..1a49dc5
--- /dev/null
+++ b/src/process/id.rs
@@ -0,0 +1,232 @@
+//! Unix user, group, and process identifiers.
+//!
+//! # Safety
+//!
+//! The `Uid`, `Gid`, and `Pid` types can be constructed from raw integers,
+//! which is marked unsafe because actual OS's assign special meaning to some
+//! integer values.
+#![allow(unsafe_code)]
+
+use crate::{backend, io};
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+#[cfg(linux_kernel)]
+use backend::process::types::RawCpuid;
+
+/// The raw integer value of a Unix user ID.
+pub use crate::ugid::RawUid;
+
+/// The raw integer value of a Unix group ID.
+pub use crate::ugid::RawGid;
+
+/// The raw integer value of a Unix process ID.
+pub use crate::pid::RawPid;
+
+pub use crate::pid::Pid;
+pub use crate::ugid::{Gid, Uid};
+
+/// A Linux CPU ID.
+#[cfg(linux_kernel)]
+#[repr(transparent)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
+pub struct Cpuid(RawCpuid);
+
+#[cfg(linux_kernel)]
+impl Cpuid {
+ /// Converts a `RawCpuid` into a `Cpuid`.
+ ///
+ /// # Safety
+ ///
+ /// `raw` must be the value of a valid Linux CPU ID.
+ #[inline]
+ pub const unsafe fn from_raw(raw: RawCpuid) -> Self {
+ Self(raw)
+ }
+
+ /// Converts a `Cpuid` into a `RawCpuid`.
+ #[inline]
+ pub const fn as_raw(self) -> RawCpuid {
+ self.0
+ }
+}
+
+/// `getuid()`—Returns the process' real user ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getuid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getuid.2.html
+#[inline]
+#[must_use]
+pub fn getuid() -> Uid {
+ backend::ugid::syscalls::getuid()
+}
+
+/// `geteuid()`—Returns the process' effective user ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/geteuid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/geteuid.2.html
+#[inline]
+#[must_use]
+pub fn geteuid() -> Uid {
+ backend::ugid::syscalls::geteuid()
+}
+
+/// `getgid()`—Returns the process' real group ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getgid.2.html
+#[inline]
+#[must_use]
+pub fn getgid() -> Gid {
+ backend::ugid::syscalls::getgid()
+}
+
+/// `getegid()`—Returns the process' effective group ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getegid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getegid.2.html
+#[inline]
+#[must_use]
+pub fn getegid() -> Gid {
+ backend::ugid::syscalls::getegid()
+}
+
+/// `getpid()`—Returns the process' ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpid.2.html
+#[inline]
+#[must_use]
+pub fn getpid() -> Pid {
+ backend::pid::syscalls::getpid()
+}
+
+/// `getppid()`—Returns the parent process' ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getppid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getppid.2.html
+#[inline]
+#[must_use]
+pub fn getppid() -> Option<Pid> {
+ backend::process::syscalls::getppid()
+}
+
+/// `getpgid(pid)`—Returns the process group ID of the given process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpgid.2.html
+#[inline]
+pub fn getpgid(pid: Option<Pid>) -> io::Result<Pid> {
+ backend::process::syscalls::getpgid(pid)
+}
+
+/// `setpgid(pid, pgid)`—Sets the process group ID of the given process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setpgid.2.html
+#[inline]
+pub fn setpgid(pid: Option<Pid>, pgid: Option<Pid>) -> io::Result<()> {
+ backend::process::syscalls::setpgid(pid, pgid)
+}
+
+/// `getpgrp()`—Returns the process' group ID.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgrp.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpgrp.2.html
+#[inline]
+#[must_use]
+pub fn getpgrp() -> Pid {
+ backend::process::syscalls::getpgrp()
+}
+
+/// `getsid(pid)`—Get the session ID of the given process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getsid.2.html
+#[cfg(not(target_os = "redox"))]
+#[inline]
+pub fn getsid(pid: Option<Pid>) -> io::Result<Pid> {
+ backend::process::syscalls::getsid(pid)
+}
+
+/// `setsid()`—Create a new session.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setsid.2.html
+#[inline]
+pub fn setsid() -> io::Result<Pid> {
+ backend::process::syscalls::setsid()
+}
+
+/// `getgroups()`—Return a list of the current user's groups.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgroups.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getgroups.2.html
+#[cfg(feature = "alloc")]
+pub fn getgroups() -> io::Result<Vec<Gid>> {
+ // This code would benefit from having a better way to read into
+ // uninitialized memory, but that requires `unsafe`.
+ let mut buffer = Vec::with_capacity(8);
+ buffer.resize(buffer.capacity(), Gid::ROOT);
+
+ loop {
+ let ngroups = backend::process::syscalls::getgroups(&mut buffer)?;
+
+ let ngroups = ngroups as usize;
+ assert!(ngroups <= buffer.len());
+ if ngroups < buffer.len() {
+ buffer.resize(ngroups, Gid::ROOT);
+ return Ok(buffer);
+ }
+ // Use `Vec` reallocation strategy to grow capacity exponentially.
+ buffer.reserve(1);
+ buffer.resize(buffer.capacity(), Gid::ROOT);
+ }
+}
diff --git a/src/process/ioctl.rs b/src/process/ioctl.rs
new file mode 100644
index 0000000..5afc766
--- /dev/null
+++ b/src/process/ioctl.rs
@@ -0,0 +1,52 @@
+//! Process-oriented `ioctl`s.
+//!
+//! # Safety
+//!
+//! This module invokes `ioctl`s.
+
+#![allow(unsafe_code)]
+
+use crate::{backend, io, ioctl};
+use backend::c;
+use backend::fd::AsFd;
+
+/// `ioctl(fd, TIOCSCTTY, 0)`—Sets the controlling terminal for the process.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=tty&sektion=4
+/// [NetBSD]: https://man.netbsd.org/tty.4
+/// [OpenBSD]: https://man.openbsd.org/tty.4
+#[cfg(not(any(windows, target_os = "aix", target_os = "redox", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TIOCSCTTY")]
+pub fn ioctl_tiocsctty<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ unsafe { ioctl::ioctl(fd, Tiocsctty) }
+}
+
+#[cfg(not(any(windows, target_os = "aix", target_os = "redox", target_os = "wasi")))]
+struct Tiocsctty;
+
+#[cfg(not(any(windows, target_os = "aix", target_os = "redox", target_os = "wasi")))]
+unsafe impl ioctl::Ioctl for Tiocsctty {
+ type Output = ();
+
+ const IS_MUTATING: bool = false;
+ const OPCODE: ioctl::Opcode = ioctl::Opcode::old(c::TIOCSCTTY as ioctl::RawOpcode);
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ (&0u32) as *const u32 as *mut c::c_void
+ }
+
+ unsafe fn output_from_ptr(
+ _: ioctl::IoctlOutput,
+ _: *mut c::c_void,
+ ) -> io::Result<Self::Output> {
+ Ok(())
+ }
+}
diff --git a/src/process/kill.rs b/src/process/kill.rs
new file mode 100644
index 0000000..01d5380
--- /dev/null
+++ b/src/process/kill.rs
@@ -0,0 +1,97 @@
+use crate::process::Pid;
+use crate::{backend, io};
+
+pub use crate::signal::Signal;
+
+/// `kill(pid, sig)`—Sends a signal to a process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/kill.2.html
+#[inline]
+#[doc(alias = "kill")]
+pub fn kill_process(pid: Pid, sig: Signal) -> io::Result<()> {
+ backend::process::syscalls::kill_process(pid, sig)
+}
+
+/// `kill(-pid, sig)`—Sends a signal to all processes in a process group.
+///
+/// If `pid` is 1, this sends a signal to all processes the current process has
+/// permission to send signals to, except process `1`, possibly other
+/// system-specific processes, and on some systems, the current process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/kill.2.html
+#[inline]
+#[doc(alias = "kill")]
+pub fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> {
+ backend::process::syscalls::kill_process_group(pid, sig)
+}
+
+/// `kill(0, sig)`—Sends a signal to all processes in the current process
+/// group.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/kill.2.html
+#[inline]
+#[doc(alias = "kill")]
+pub fn kill_current_process_group(sig: Signal) -> io::Result<()> {
+ backend::process::syscalls::kill_current_process_group(sig)
+}
+
+/// `kill(pid, 0)`—Check validity of pid and permissions to send signals to
+/// the process, without actually sending any signals.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/kill.2.html
+#[inline]
+#[doc(alias = "kill")]
+pub fn test_kill_process(pid: Pid) -> io::Result<()> {
+ backend::process::syscalls::test_kill_process(pid)
+}
+
+/// `kill(-pid, 0)`—Check validity of pid and permissions to send signals to
+/// all processes in the process group, without actually sending any signals.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/kill.2.html
+#[inline]
+#[doc(alias = "kill")]
+pub fn test_kill_process_group(pid: Pid) -> io::Result<()> {
+ backend::process::syscalls::test_kill_process_group(pid)
+}
+
+/// `kill(0, 0)`—Check validity of pid and permissions to send signals to the
+/// all processes in the current process group, without actually sending any
+/// signals.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/kill.2.html
+#[inline]
+#[doc(alias = "kill")]
+pub fn test_kill_current_process_group() -> io::Result<()> {
+ backend::process::syscalls::test_kill_current_process_group()
+}
diff --git a/src/process/membarrier.rs b/src/process/membarrier.rs
new file mode 100644
index 0000000..26be07d
--- /dev/null
+++ b/src/process/membarrier.rs
@@ -0,0 +1,92 @@
+//! The Linux `membarrier` syscall.
+
+use crate::process::Cpuid;
+use crate::{backend, io};
+
+pub use backend::process::types::MembarrierCommand;
+
+#[cfg(linux_kernel)]
+bitflags::bitflags! {
+ /// A result from [`membarrier_query`].
+ ///
+ /// These flags correspond to values of [`MembarrierCommand`] which are
+ /// supported in the OS.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct MembarrierQuery: u32 {
+ /// `MEMBARRIER_CMD_GLOBAL` (also known as `MEMBARRIER_CMD_SHARED`)
+ #[doc(alias = "SHARED")]
+ #[doc(alias = "MEMBARRIER_CMD_SHARED")]
+ const GLOBAL = MembarrierCommand::Global as _;
+ /// `MEMBARRIER_CMD_GLOBAL_EXPEDITED`
+ const GLOBAL_EXPEDITED = MembarrierCommand::GlobalExpedited as _;
+ /// `MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED`
+ const REGISTER_GLOBAL_EXPEDITED = MembarrierCommand::RegisterGlobalExpedited as _;
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED`
+ const PRIVATE_EXPEDITED = MembarrierCommand::PrivateExpedited as _;
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED`
+ const REGISTER_PRIVATE_EXPEDITED = MembarrierCommand::RegisterPrivateExpedited as _;
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE`
+ const PRIVATE_EXPEDITED_SYNC_CORE = MembarrierCommand::PrivateExpeditedSyncCore as _;
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE`
+ const REGISTER_PRIVATE_EXPEDITED_SYNC_CORE = MembarrierCommand::RegisterPrivateExpeditedSyncCore as _;
+ /// `MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ` (since Linux 5.10)
+ const PRIVATE_EXPEDITED_RSEQ = MembarrierCommand::PrivateExpeditedRseq as _;
+ /// `MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ` (since Linux 5.10)
+ const REGISTER_PRIVATE_EXPEDITED_RSEQ = MembarrierCommand::RegisterPrivateExpeditedRseq as _;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(linux_kernel)]
+impl MembarrierQuery {
+ /// Test whether this query result contains the given command.
+ #[inline]
+ pub fn contains_command(self, cmd: MembarrierCommand) -> bool {
+ // `MembarrierCommand` is an enum that only contains values also valid
+ // in `MembarrierQuery`.
+ self.contains(Self::from_bits_retain(cmd as _))
+ }
+}
+
+/// `membarrier(MEMBARRIER_CMD_QUERY, 0, 0)`—Query the supported `membarrier`
+/// commands.
+///
+/// This function doesn't return a `Result` because it always succeeds; if the
+/// underlying OS doesn't support the `membarrier` syscall, it returns an empty
+/// `MembarrierQuery` value.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/membarrier.2.html
+#[inline]
+#[doc(alias = "MEMBARRIER_CMD_QUERY")]
+pub fn membarrier_query() -> MembarrierQuery {
+ backend::process::syscalls::membarrier_query()
+}
+
+/// `membarrier(cmd, 0, 0)`—Perform a memory barrier.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/membarrier.2.html
+#[inline]
+pub fn membarrier(cmd: MembarrierCommand) -> io::Result<()> {
+ backend::process::syscalls::membarrier(cmd)
+}
+
+/// `membarrier(cmd, MEMBARRIER_CMD_FLAG_CPU, cpu)`—Perform a memory barrier
+/// with a specific CPU.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/membarrier.2.html
+#[inline]
+pub fn membarrier_cpu(cmd: MembarrierCommand, cpu: Cpuid) -> io::Result<()> {
+ backend::process::syscalls::membarrier_cpu(cmd, cpu)
+}
diff --git a/src/process/mod.rs b/src/process/mod.rs
new file mode 100644
index 0000000..5fbc1f3
--- /dev/null
+++ b/src/process/mod.rs
@@ -0,0 +1,80 @@
+//! Process-associated operations.
+
+#[cfg(not(target_os = "wasi"))]
+mod chdir;
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+mod chroot;
+mod exit;
+#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
+mod id;
+#[cfg(not(any(target_os = "aix", target_os = "espidf", target_os = "vita")))]
+mod ioctl;
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+mod kill;
+#[cfg(linux_kernel)]
+mod membarrier;
+#[cfg(target_os = "linux")]
+mod pidfd;
+#[cfg(target_os = "linux")]
+mod pidfd_getfd;
+#[cfg(linux_kernel)]
+mod prctl;
+#[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+// WASI doesn't have [gs]etpriority.
+mod priority;
+#[cfg(freebsdlike)]
+mod procctl;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+mod rlimit;
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+mod sched;
+mod sched_yield;
+#[cfg(not(target_os = "wasi"))] // WASI doesn't have umask.
+mod umask;
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+mod wait;
+
+#[cfg(not(target_os = "wasi"))]
+pub use chdir::*;
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+pub use chroot::*;
+pub use exit::*;
+#[cfg(not(target_os = "wasi"))]
+pub use id::*;
+#[cfg(not(any(target_os = "aix", target_os = "espidf", target_os = "vita")))]
+pub use ioctl::*;
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub use kill::*;
+#[cfg(linux_kernel)]
+pub use membarrier::*;
+#[cfg(target_os = "linux")]
+pub use pidfd::*;
+#[cfg(target_os = "linux")]
+pub use pidfd_getfd::*;
+#[cfg(linux_kernel)]
+pub use prctl::*;
+#[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+pub use priority::*;
+#[cfg(freebsdlike)]
+pub use procctl::*;
+#[cfg(not(any(
+ target_os = "espidf",
+ target_os = "fuchsia",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+pub use rlimit::*;
+#[cfg(any(freebsdlike, linux_kernel, target_os = "fuchsia"))]
+pub use sched::*;
+pub use sched_yield::sched_yield;
+#[cfg(not(target_os = "wasi"))]
+pub use umask::*;
+#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
+pub use wait::*;
diff --git a/src/process/pidfd.rs b/src/process/pidfd.rs
new file mode 100644
index 0000000..abebaf2
--- /dev/null
+++ b/src/process/pidfd.rs
@@ -0,0 +1,30 @@
+use crate::fd::OwnedFd;
+use crate::process::Pid;
+use crate::{backend, io};
+
+bitflags::bitflags! {
+ /// `PIDFD_*` flags for use with [`pidfd_open`].
+ ///
+ /// [`pidfd_open`]: crate::process::pidfd_open
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PidfdFlags: backend::c::c_uint {
+ /// `PIDFD_NONBLOCK`.
+ const NONBLOCK = backend::c::PIDFD_NONBLOCK;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `syscall(SYS_pidfd_open, pid, flags)`—Creates a file descriptor for a
+/// process.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
+#[inline]
+pub fn pidfd_open(pid: Pid, flags: PidfdFlags) -> io::Result<OwnedFd> {
+ backend::process::syscalls::pidfd_open(pid, flags)
+}
diff --git a/src/process/pidfd_getfd.rs b/src/process/pidfd_getfd.rs
new file mode 100644
index 0000000..20e5163
--- /dev/null
+++ b/src/process/pidfd_getfd.rs
@@ -0,0 +1,56 @@
+//! The [`pidfd_getfd`] function and supporting types.
+
+#![allow(unsafe_code)]
+use crate::fd::OwnedFd;
+use crate::{backend, io};
+use backend::fd::{AsFd, RawFd};
+
+/// Raw file descriptor in another process.
+///
+/// A distinct type alias is used here to inform the user that normal file
+/// descriptors from the calling process should not be used. The provided file
+/// descriptor is used by the kernel as the index into the file descriptor
+/// table of an entirely different process.
+pub type ForeignRawFd = RawFd;
+
+bitflags::bitflags! {
+ /// All flags are reserved for future use.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PidfdGetfdFlags: backend::c::c_uint {
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `syscall(SYS_pidfd_getfd, pidfd, flags)`—Obtain a duplicate of another
+/// process' file descriptor.
+///
+/// # References
+/// - [Linux]
+///
+/// # Warning
+///
+/// This function is generally safe for the calling process, but it can impact
+/// the target process in unexpected ways. If you want to ensure that Rust I/O
+/// safety assumptions continue to hold in the target process, then the target
+/// process must have communicated the file description number to the calling
+/// process from a value of a type that implements `AsRawFd`, and the target
+/// process must not drop that value until after the calling process has
+/// returned from `pidfd_getfd`.
+///
+/// When `pidfd_getfd` is used to debug the target, or the target is not a Rust
+/// application, or `pidfd_getfd` is used in any other way, then extra care
+/// should be taken to avoid unexpected behaviour or crashes.
+///
+/// For further details, see the references above.
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html
+#[inline]
+pub fn pidfd_getfd<Fd: AsFd>(
+ pidfd: Fd,
+ targetfd: ForeignRawFd,
+ flags: PidfdGetfdFlags,
+) -> io::Result<OwnedFd> {
+ backend::process::syscalls::pidfd_getfd(pidfd.as_fd(), targetfd, flags)
+}
diff --git a/src/process/prctl.rs b/src/process/prctl.rs
new file mode 100644
index 0000000..830abc1
--- /dev/null
+++ b/src/process/prctl.rs
@@ -0,0 +1,1151 @@
+//! Bindings for the Linux `prctl` system call.
+//!
+//! There are similarities (but also differences) with FreeBSD's `procctl`
+//! system call, whose interface is located in the `procctl.rs` file.
+
+#![allow(unsafe_code)]
+
+use core::mem::size_of;
+use core::ptr::{null, null_mut, NonNull};
+
+use bitflags::bitflags;
+
+use crate::backend::c::{c_int, c_uint, c_void};
+use crate::backend::prctl::syscalls;
+use crate::fd::{AsRawFd, BorrowedFd};
+use crate::ffi::CStr;
+use crate::io;
+use crate::prctl::*;
+use crate::process::{Pid, RawPid};
+use crate::signal::Signal;
+use crate::utils::{as_mut_ptr, as_ptr};
+
+//
+// PR_GET_PDEATHSIG/PR_SET_PDEATHSIG
+//
+
+const PR_GET_PDEATHSIG: c_int = 2;
+
+/// Get the current value of the parent process death signal.
+///
+/// # References
+/// - [Linux: `prctl(PR_GET_PDEATHSIG,...)`]
+/// - [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]
+///
+/// [Linux: `prctl(PR_GET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+#[doc(alias = "PR_GET_PDEATHSIG")]
+pub fn parent_process_death_signal() -> io::Result<Option<Signal>> {
+ unsafe { prctl_get_at_arg2_optional::<c_int>(PR_GET_PDEATHSIG) }.map(Signal::from_raw)
+}
+
+const PR_SET_PDEATHSIG: c_int = 1;
+
+/// Set the parent-death signal of the calling process.
+///
+/// # References
+/// - [Linux: `prctl(PR_SET_PDEATHSIG,...)`]
+/// - [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]
+///
+/// [Linux: `prctl(PR_SET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+#[doc(alias = "PR_SET_PDEATHSIG")]
+pub fn set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()> {
+ let signal = signal.map_or(0_usize, |signal| signal as usize);
+ unsafe { prctl_2args(PR_SET_PDEATHSIG, signal as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_DUMPABLE/PR_SET_DUMPABLE
+//
+
+const PR_GET_DUMPABLE: c_int = 3;
+
+const SUID_DUMP_DISABLE: i32 = 0;
+const SUID_DUMP_USER: i32 = 1;
+const SUID_DUMP_ROOT: i32 = 2;
+
+/// `SUID_DUMP_*` values for use with [`dumpable_behavior`] and
+/// [`set_dumpable_behavior`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum DumpableBehavior {
+ /// Not dumpable.
+ #[doc(alias = "SUID_DUMP_DISABLE")]
+ NotDumpable = SUID_DUMP_DISABLE,
+ /// Dumpable.
+ #[doc(alias = "SUID_DUMP_USER")]
+ Dumpable = SUID_DUMP_USER,
+ /// Dumpable but only readable by root.
+ #[doc(alias = "SUID_DUMP_ROOT")]
+ DumpableReadableOnlyByRoot = SUID_DUMP_ROOT,
+}
+
+impl TryFrom<i32> for DumpableBehavior {
+ type Error = io::Errno;
+
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
+ match value {
+ SUID_DUMP_DISABLE => Ok(Self::NotDumpable),
+ SUID_DUMP_USER => Ok(Self::Dumpable),
+ SUID_DUMP_ROOT => Ok(Self::DumpableReadableOnlyByRoot),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get the current state of the calling process' `dumpable` attribute.
+///
+/// # References
+/// - [`prctl(PR_GET_DUMPABLE,...)`]
+///
+/// [`prctl(PR_GET_DUMPABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_DUMPABLE")]
+pub fn dumpable_behavior() -> io::Result<DumpableBehavior> {
+ unsafe { prctl_1arg(PR_GET_DUMPABLE) }.and_then(TryInto::try_into)
+}
+
+const PR_SET_DUMPABLE: c_int = 4;
+
+/// Set the state of the `dumpable` attribute, which determines whether the
+/// process can be traced and whether core dumps are produced for the calling
+/// process upon delivery of a signal whose default behavior is to produce a
+/// core dump.
+///
+/// A similar function with the same name is available on FreeBSD (as part of
+/// the `procctl` interface), but it has an extra argument which allows to
+/// select a process other then the current process.
+///
+/// # References
+/// - [`prctl(PR_SET_DUMPABLE,...)`]
+///
+/// [`prctl(PR_SET_DUMPABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_DUMPABLE")]
+pub fn set_dumpable_behavior(config: DumpableBehavior) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_DUMPABLE, config as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_UNALIGN/PR_SET_UNALIGN
+//
+
+const PR_GET_UNALIGN: c_int = 5;
+
+bitflags! {
+ /// `PR_UNALIGN_*` flags for use with [`unaligned_access_control`] and
+ /// [`set_unaligned_access_control`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UnalignedAccessControl: u32 {
+ /// Silently fix up unaligned user accesses.
+ #[doc(alias = "NOPRINT")]
+ #[doc(alias = "PR_UNALIGN_NOPRINT")]
+ const NO_PRINT = 1;
+ /// Generate a [`Signal::Bus`] signal on unaligned user access.
+ #[doc(alias = "PR_UNALIGN_SIGBUS")]
+ const SIGBUS = 2;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// Get unaligned access control bits.
+///
+/// # References
+/// - [`prctl(PR_GET_UNALIGN,...)`]
+///
+/// [`prctl(PR_GET_UNALIGN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_UNALIGN")]
+pub fn unaligned_access_control() -> io::Result<UnalignedAccessControl> {
+ let r = unsafe { prctl_get_at_arg2_optional::<c_uint>(PR_GET_UNALIGN)? };
+ UnalignedAccessControl::from_bits(r).ok_or(io::Errno::RANGE)
+}
+
+const PR_SET_UNALIGN: c_int = 6;
+
+/// Set unaligned access control bits.
+///
+/// # References
+/// - [`prctl(PR_SET_UNALIGN,...)`]
+///
+/// [`prctl(PR_SET_UNALIGN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_UNALIGN")]
+pub fn set_unaligned_access_control(config: UnalignedAccessControl) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_UNALIGN, config.bits() as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_FPEMU/PR_SET_FPEMU
+//
+
+const PR_GET_FPEMU: c_int = 9;
+
+bitflags! {
+ /// `PR_FPEMU_*` flags for use with [`floating_point_emulation_control`]
+ /// and [`set_floating_point_emulation_control`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FloatingPointEmulationControl: u32 {
+ /// Silently emulate floating point operations accesses.
+ #[doc(alias = "PR_UNALIGN_NOPRINT")]
+ const NO_PRINT = 1;
+ /// Don't emulate floating point operations, send a [`Signal::Fpe`]
+ /// signal instead.
+ #[doc(alias = "PR_UNALIGN_SIGFPE")]
+ const SIGFPE = 2;
+ }
+}
+
+/// Get floating point emulation control bits.
+///
+/// # References
+/// - [`prctl(PR_GET_FPEMU,...)`]
+///
+/// [`prctl(PR_GET_FPEMU,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_FPEMU")]
+pub fn floating_point_emulation_control() -> io::Result<FloatingPointEmulationControl> {
+ let r = unsafe { prctl_get_at_arg2_optional::<c_uint>(PR_GET_FPEMU)? };
+ FloatingPointEmulationControl::from_bits(r).ok_or(io::Errno::RANGE)
+}
+
+const PR_SET_FPEMU: c_int = 10;
+
+/// Set floating point emulation control bits.
+///
+/// # References
+/// - [`prctl(PR_SET_FPEMU,...)`]
+///
+/// [`prctl(PR_SET_FPEMU,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_FPEMU")]
+pub fn set_floating_point_emulation_control(
+ config: FloatingPointEmulationControl,
+) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_FPEMU, config.bits() as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_FPEXC/PR_SET_FPEXC
+//
+
+const PR_GET_FPEXC: c_int = 11;
+
+bitflags! {
+ /// Zero means floating point exceptions are disabled.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct FloatingPointExceptionMode: u32 {
+ /// Async non-recoverable exception mode.
+ const NONRECOV = 1;
+ /// Async recoverable exception mode.
+ const ASYNC = 2;
+ /// Precise exception mode.
+ const PRECISE = 3;
+
+ /// Use FPEXC for floating point exception enables.
+ const SW_ENABLE = 0x80;
+ /// Floating point divide by zero.
+ const DIV = 0x01_0000;
+ /// Floating point overflow.
+ const OVF = 0x02_0000;
+ /// Floating point underflow.
+ const UND = 0x04_0000;
+ /// Floating point inexact result.
+ const RES = 0x08_0000;
+ /// Floating point invalid operation.
+ const INV = 0x10_0000;
+ }
+}
+
+/// Get floating point exception mode.
+///
+/// # References
+/// - [`prctl(PR_GET_FPEXC,...)`]
+///
+/// [`prctl(PR_GET_FPEXC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_FPEXEC")]
+pub fn floating_point_exception_mode() -> io::Result<Option<FloatingPointExceptionMode>> {
+ unsafe { prctl_get_at_arg2_optional::<c_uint>(PR_GET_FPEXC) }
+ .map(FloatingPointExceptionMode::from_bits)
+}
+
+const PR_SET_FPEXC: c_int = 12;
+
+/// Set floating point exception mode.
+///
+/// # References
+/// - [`prctl(PR_SET_FPEXC,...)`]
+///
+/// [`prctl(PR_SET_FPEXC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_FPEXEC")]
+pub fn set_floating_point_exception_mode(
+ config: Option<FloatingPointExceptionMode>,
+) -> io::Result<()> {
+ let config = config.as_ref().map_or(0, FloatingPointExceptionMode::bits);
+ unsafe { prctl_2args(PR_SET_FPEXC, config as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_TIMING/PR_SET_TIMING
+//
+
+const PR_GET_TIMING: c_int = 13;
+
+const PR_TIMING_STATISTICAL: i32 = 0;
+const PR_TIMING_TIMESTAMP: i32 = 1;
+
+/// `PR_TIMING_*` values for use with [`timing_method`] and
+/// [`set_timing_method`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum TimingMethod {
+ /// Normal, traditional, statistical process timing.
+ Statistical = PR_TIMING_STATISTICAL,
+ /// Accurate timestamp based process timing.
+ TimeStamp = PR_TIMING_TIMESTAMP,
+}
+
+impl TryFrom<i32> for TimingMethod {
+ type Error = io::Errno;
+
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
+ match value {
+ PR_TIMING_STATISTICAL => Ok(Self::Statistical),
+ PR_TIMING_TIMESTAMP => Ok(Self::TimeStamp),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get which process timing method is currently in use.
+///
+/// # References
+/// - [`prctl(PR_GET_TIMING,...)`]
+///
+/// [`prctl(PR_GET_TIMING,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_TIMING")]
+pub fn timing_method() -> io::Result<TimingMethod> {
+ unsafe { prctl_1arg(PR_GET_TIMING) }.and_then(TryInto::try_into)
+}
+
+const PR_SET_TIMING: c_int = 14;
+
+/// Set whether to use (normal, traditional) statistical process timing or
+/// accurate timestamp-based process timing.
+///
+/// # References
+/// - [`prctl(PR_SET_TIMING,...)`]
+///
+/// [`prctl(PR_SET_TIMING,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_TIMING")]
+pub fn set_timing_method(method: TimingMethod) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_TIMING, method as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_ENDIAN/PR_SET_ENDIAN
+//
+
+const PR_GET_ENDIAN: c_int = 19;
+
+const PR_ENDIAN_BIG: u32 = 0;
+const PR_ENDIAN_LITTLE: u32 = 1;
+const PR_ENDIAN_PPC_LITTLE: u32 = 2;
+
+/// `PR_ENDIAN_*` values for use with [`endian_mode`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum EndianMode {
+ /// Big endian mode.
+ Big = PR_ENDIAN_BIG,
+ /// True little endian mode.
+ Little = PR_ENDIAN_LITTLE,
+ /// `PowerPC` pseudo little endian.
+ PowerPCLittle = PR_ENDIAN_PPC_LITTLE,
+}
+
+impl TryFrom<u32> for EndianMode {
+ type Error = io::Errno;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ PR_ENDIAN_BIG => Ok(Self::Big),
+ PR_ENDIAN_LITTLE => Ok(Self::Little),
+ PR_ENDIAN_PPC_LITTLE => Ok(Self::PowerPCLittle),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get the endianness of the calling process.
+///
+/// # References
+/// - [`prctl(PR_GET_ENDIAN,...)`]
+///
+/// [`prctl(PR_GET_ENDIAN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_ENDIAN")]
+pub fn endian_mode() -> io::Result<EndianMode> {
+ unsafe { prctl_get_at_arg2::<c_uint, _>(PR_GET_ENDIAN) }
+}
+
+const PR_SET_ENDIAN: c_int = 20;
+
+/// Set the endianness of the calling process.
+///
+/// # References
+/// - [`prctl(PR_SET_ENDIAN,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_ENDIAN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_ENDIAN")]
+pub unsafe fn set_endian_mode(mode: EndianMode) -> io::Result<()> {
+ prctl_2args(PR_SET_ENDIAN, mode as usize as *mut _).map(|_r| ())
+}
+
+//
+// PR_GET_TSC/PR_SET_TSC
+//
+
+const PR_GET_TSC: c_int = 25;
+
+const PR_TSC_ENABLE: u32 = 1;
+const PR_TSC_SIGSEGV: u32 = 2;
+
+/// `PR_TSC_*` values for use with [`time_stamp_counter_readability`] and
+/// [`set_time_stamp_counter_readability`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum TimeStampCounterReadability {
+ /// Allow the use of the timestamp counter.
+ Readable = PR_TSC_ENABLE,
+ /// Throw a [`Signal::Segv`] signal instead of reading the TSC.
+ RaiseSIGSEGV = PR_TSC_SIGSEGV,
+}
+
+impl TryFrom<u32> for TimeStampCounterReadability {
+ type Error = io::Errno;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ PR_TSC_ENABLE => Ok(Self::Readable),
+ PR_TSC_SIGSEGV => Ok(Self::RaiseSIGSEGV),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get the state of the flag determining if the timestamp counter can be read.
+///
+/// # References
+/// - [`prctl(PR_GET_TSC,...)`]
+///
+/// [`prctl(PR_GET_TSC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_TSC")]
+pub fn time_stamp_counter_readability() -> io::Result<TimeStampCounterReadability> {
+ unsafe { prctl_get_at_arg2::<c_uint, _>(PR_GET_TSC) }
+}
+
+const PR_SET_TSC: c_int = 26;
+
+/// Set the state of the flag determining if the timestamp counter can be read
+/// by the process.
+///
+/// # References
+/// - [`prctl(PR_SET_TSC,...)`]
+///
+/// [`prctl(PR_SET_TSC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_TSC")]
+pub fn set_time_stamp_counter_readability(
+ readability: TimeStampCounterReadability,
+) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_TSC, readability as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_TASK_PERF_EVENTS_DISABLE/PR_TASK_PERF_EVENTS_ENABLE
+//
+
+const PR_TASK_PERF_EVENTS_DISABLE: c_int = 31;
+const PR_TASK_PERF_EVENTS_ENABLE: c_int = 32;
+
+/// Enable or disable all performance counters attached to the calling process.
+///
+/// # References
+/// - [`prctl(PR_TASK_PERF_EVENTS_ENABLE,...)`]
+/// - [`prctl(PR_TASK_PERF_EVENTS_DISABLE,...)`]
+///
+/// [`prctl(PR_TASK_PERF_EVENTS_ENABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [`prctl(PR_TASK_PERF_EVENTS_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_TASK_PERF_EVENTS_ENABLE")]
+#[doc(alias = "PR_TASK_PERF_EVENTS_DISABLE")]
+pub fn configure_performance_counters(enable: bool) -> io::Result<()> {
+ let option = if enable {
+ PR_TASK_PERF_EVENTS_ENABLE
+ } else {
+ PR_TASK_PERF_EVENTS_DISABLE
+ };
+
+ unsafe { prctl_1arg(option) }.map(|_r| ())
+}
+
+//
+// PR_MCE_KILL_GET/PR_MCE_KILL
+//
+
+const PR_MCE_KILL_GET: c_int = 34;
+
+const PR_MCE_KILL_LATE: u32 = 0;
+const PR_MCE_KILL_EARLY: u32 = 1;
+const PR_MCE_KILL_DEFAULT: u32 = 2;
+
+/// `PR_MCE_KILL_*` values for use with
+/// [`machine_check_memory_corruption_kill_policy`] and
+/// [`set_machine_check_memory_corruption_kill_policy`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum MachineCheckMemoryCorruptionKillPolicy {
+ /// Late kill policy.
+ #[doc(alias = "PR_MCE_KILL_LATE")]
+ Late = PR_MCE_KILL_LATE,
+ /// Early kill policy.
+ #[doc(alias = "PR_MCE_KILL_EARLY")]
+ Early = PR_MCE_KILL_EARLY,
+ /// System-wide default policy.
+ #[doc(alias = "PR_MCE_KILL_DEFAULT")]
+ Default = PR_MCE_KILL_DEFAULT,
+}
+
+impl TryFrom<u32> for MachineCheckMemoryCorruptionKillPolicy {
+ type Error = io::Errno;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ PR_MCE_KILL_LATE => Ok(Self::Late),
+ PR_MCE_KILL_EARLY => Ok(Self::Early),
+ PR_MCE_KILL_DEFAULT => Ok(Self::Default),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get the current per-process machine check kill policy.
+///
+/// # References
+/// - [`prctl(PR_MCE_KILL_GET,...)`]
+///
+/// [`prctl(PR_MCE_KILL_GET,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_MCE_KILL_GET")]
+pub fn machine_check_memory_corruption_kill_policy(
+) -> io::Result<MachineCheckMemoryCorruptionKillPolicy> {
+ let r = unsafe { prctl_1arg(PR_MCE_KILL_GET)? } as c_uint;
+ MachineCheckMemoryCorruptionKillPolicy::try_from(r)
+}
+
+const PR_MCE_KILL: c_int = 33;
+
+const PR_MCE_KILL_CLEAR: usize = 0;
+const PR_MCE_KILL_SET: usize = 1;
+
+/// Set the machine check memory corruption kill policy for the calling thread.
+///
+/// # References
+/// - [`prctl(PR_MCE_KILL,...)`]
+///
+/// [`prctl(PR_MCE_KILL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_MCE_KILL")]
+pub fn set_machine_check_memory_corruption_kill_policy(
+ policy: Option<MachineCheckMemoryCorruptionKillPolicy>,
+) -> io::Result<()> {
+ let (sub_operation, policy) = if let Some(policy) = policy {
+ (PR_MCE_KILL_SET, policy as usize as *mut _)
+ } else {
+ (PR_MCE_KILL_CLEAR, null_mut())
+ };
+
+ unsafe { prctl_3args(PR_MCE_KILL, sub_operation as *mut _, policy) }.map(|_r| ())
+}
+
+//
+// PR_SET_MM
+//
+
+const PR_SET_MM: c_int = 35;
+
+const PR_SET_MM_START_CODE: u32 = 1;
+const PR_SET_MM_END_CODE: u32 = 2;
+const PR_SET_MM_START_DATA: u32 = 3;
+const PR_SET_MM_END_DATA: u32 = 4;
+const PR_SET_MM_START_STACK: u32 = 5;
+const PR_SET_MM_START_BRK: u32 = 6;
+const PR_SET_MM_BRK: u32 = 7;
+const PR_SET_MM_ARG_START: u32 = 8;
+const PR_SET_MM_ARG_END: u32 = 9;
+const PR_SET_MM_ENV_START: u32 = 10;
+const PR_SET_MM_ENV_END: u32 = 11;
+const PR_SET_MM_AUXV: usize = 12;
+const PR_SET_MM_EXE_FILE: usize = 13;
+const PR_SET_MM_MAP: usize = 14;
+const PR_SET_MM_MAP_SIZE: usize = 15;
+
+/// `PR_SET_MM_*` values for use with [`set_virtual_memory_map_address`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum VirtualMemoryMapAddress {
+ /// Set the address above which the program text can run.
+ CodeStart = PR_SET_MM_START_CODE,
+ /// Set the address below which the program text can run.
+ CodeEnd = PR_SET_MM_END_CODE,
+ /// Set the address above which initialized and uninitialized (bss) data
+ /// are placed.
+ DataStart = PR_SET_MM_START_DATA,
+ /// Set the address below which initialized and uninitialized (bss) data
+ /// are placed.
+ DataEnd = PR_SET_MM_END_DATA,
+ /// Set the start address of the stack.
+ StackStart = PR_SET_MM_START_STACK,
+ /// Set the address above which the program heap can be expanded with `brk`
+ /// call.
+ BrkStart = PR_SET_MM_START_BRK,
+ /// Set the current `brk` value.
+ BrkCurrent = PR_SET_MM_BRK,
+ /// Set the address above which the program command line is placed.
+ ArgStart = PR_SET_MM_ARG_START,
+ /// Set the address below which the program command line is placed.
+ ArgEnd = PR_SET_MM_ARG_END,
+ /// Set the address above which the program environment is placed.
+ EnvironmentStart = PR_SET_MM_ENV_START,
+ /// Set the address below which the program environment is placed.
+ EnvironmentEnd = PR_SET_MM_ENV_END,
+}
+
+/// Modify certain kernel memory map descriptor addresses of the calling
+/// process.
+///
+/// # References
+/// - [`prctl(PR_SET_MM,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_MM,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_MM")]
+pub unsafe fn set_virtual_memory_map_address(
+ option: VirtualMemoryMapAddress,
+ address: Option<NonNull<c_void>>,
+) -> io::Result<()> {
+ let address = address.map_or_else(null_mut, NonNull::as_ptr);
+ prctl_3args(PR_SET_MM, option as usize as *mut _, address).map(|_r| ())
+}
+
+/// Supersede the `/proc/pid/exe` symbolic link with a new one pointing to a
+/// new executable file.
+///
+/// # References
+/// - [`prctl(PR_SET_MM,PR_SET_MM_EXE_FILE,...)`]
+///
+/// [`prctl(PR_SET_MM,PR_SET_MM_EXE_FILE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_MM")]
+#[doc(alias = "PR_SET_MM_EXE_FILE")]
+pub fn set_executable_file(fd: BorrowedFd<'_>) -> io::Result<()> {
+ let fd = usize::try_from(fd.as_raw_fd()).map_err(|_r| io::Errno::RANGE)?;
+ unsafe { prctl_3args(PR_SET_MM, PR_SET_MM_EXE_FILE as *mut _, fd as *mut _) }.map(|_r| ())
+}
+
+/// Set a new auxiliary vector.
+///
+/// # References
+/// - [`prctl(PR_SET_MM,PR_SET_MM_AUXV,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_MM,PR_SET_MM_AUXV,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_MM")]
+#[doc(alias = "PR_SET_MM_AUXV")]
+pub unsafe fn set_auxiliary_vector(auxv: &[*const c_void]) -> io::Result<()> {
+ syscalls::prctl(
+ PR_SET_MM,
+ PR_SET_MM_AUXV as *mut _,
+ auxv.as_ptr() as *mut _,
+ auxv.len() as *mut _,
+ null_mut(),
+ )
+ .map(|_r| ())
+}
+
+/// Get the size of the [`PrctlMmMap`] the kernel expects.
+///
+/// # References
+/// - [`prctl(PR_SET_MM,PR_SET_MM_MAP_SIZE,...)`]
+///
+/// [`prctl(PR_SET_MM,PR_SET_MM_MAP_SIZE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_MM")]
+#[doc(alias = "PR_SET_MM_MAP_SIZE")]
+pub fn virtual_memory_map_config_struct_size() -> io::Result<usize> {
+ let mut value: c_uint = 0;
+ let value_ptr = as_mut_ptr(&mut value);
+ unsafe { prctl_3args(PR_SET_MM, PR_SET_MM_MAP_SIZE as *mut _, value_ptr.cast())? };
+ Ok(value as usize)
+}
+
+/// This structure provides new memory descriptor map which mostly modifies
+/// `/proc/pid/stat[m]` output for a task.
+/// This mostly done in a sake of checkpoint/restore functionality.
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct PrctlMmMap {
+ /// Code section start address.
+ pub start_code: u64,
+ /// Code section end address.
+ pub end_code: u64,
+ /// Data section start address.
+ pub start_data: u64,
+ /// Data section end address.
+ pub end_data: u64,
+ /// `brk` start address.
+ pub start_brk: u64,
+ /// `brk` current address.
+ pub brk: u64,
+ /// Stack start address.
+ pub start_stack: u64,
+ /// Program command line start address.
+ pub arg_start: u64,
+ /// Program command line end address.
+ pub arg_end: u64,
+ /// Program environment start address.
+ pub env_start: u64,
+ /// Program environment end address.
+ pub env_end: u64,
+ /// Auxiliary vector start address.
+ pub auxv: *mut u64,
+ /// Auxiliary vector size.
+ pub auxv_size: u32,
+ /// File descriptor of executable file that was used to create this
+ /// process.
+ pub exe_fd: u32,
+}
+
+/// Provides one-shot access to all the addresses by passing in a
+/// [`PrctlMmMap`].
+///
+/// # References
+/// - [`prctl(PR_SET_MM,PR_SET_MM_MAP,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_MM,PR_SET_MM_MAP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_MM")]
+#[doc(alias = "PR_SET_MM_MAP")]
+pub unsafe fn configure_virtual_memory_map(config: &PrctlMmMap) -> io::Result<()> {
+ syscalls::prctl(
+ PR_SET_MM,
+ PR_SET_MM_MAP as *mut _,
+ as_ptr(config) as *mut _,
+ size_of::<PrctlMmMap>() as *mut _,
+ null_mut(),
+ )
+ .map(|_r| ())
+}
+
+//
+// PR_SET_PTRACER
+//
+
+const PR_SET_PTRACER: c_int = 0x59_61_6d_61;
+
+const PR_SET_PTRACER_ANY: usize = usize::MAX;
+
+/// Process ptracer.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum PTracer {
+ /// None.
+ None,
+ /// Disable `ptrace` restrictions for the calling process.
+ Any,
+ /// Specific process.
+ ProcessID(Pid),
+}
+
+/// Declare that the ptracer process can `ptrace` the calling process as if it
+/// were a direct process ancestor.
+///
+/// # References
+/// - [`prctl(PR_SET_PTRACER,...)`]
+///
+/// [`prctl(PR_SET_PTRACER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_PTRACER")]
+pub fn set_ptracer(tracer: PTracer) -> io::Result<()> {
+ let pid = match tracer {
+ PTracer::None => null_mut(),
+ PTracer::Any => PR_SET_PTRACER_ANY as *mut _,
+ PTracer::ProcessID(pid) => pid.as_raw_nonzero().get() as usize as *mut _,
+ };
+
+ unsafe { prctl_2args(PR_SET_PTRACER, pid) }.map(|_r| ())
+}
+
+//
+// PR_GET_CHILD_SUBREAPER/PR_SET_CHILD_SUBREAPER
+//
+
+const PR_GET_CHILD_SUBREAPER: c_int = 37;
+
+/// Get the `child subreaper` setting of the calling process.
+///
+/// # References
+/// - [`prctl(PR_GET_CHILD_SUBREAPER,...)`]
+///
+/// [`prctl(PR_GET_CHILD_SUBREAPER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_CHILD_SUBREAPER")]
+pub fn child_subreaper() -> io::Result<Option<Pid>> {
+ unsafe {
+ let r = prctl_get_at_arg2_optional::<c_uint>(PR_GET_CHILD_SUBREAPER)?;
+ Ok(Pid::from_raw(r as RawPid))
+ }
+}
+
+const PR_SET_CHILD_SUBREAPER: c_int = 36;
+
+/// Set the `child subreaper` attribute of the calling process.
+///
+/// # References
+/// - [`prctl(PR_SET_CHILD_SUBREAPER,...)`]
+///
+/// [`prctl(PR_SET_CHILD_SUBREAPER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_CHILD_SUBREAPER")]
+pub fn set_child_subreaper(pid: Option<Pid>) -> io::Result<()> {
+ let pid = pid.map_or(0_usize, |pid| pid.as_raw_nonzero().get() as usize);
+ unsafe { prctl_2args(PR_SET_CHILD_SUBREAPER, pid as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_FP_MODE/PR_SET_FP_MODE
+//
+
+const PR_GET_FP_MODE: c_int = 46;
+
+const PR_FP_MODE_FR: u32 = 1_u32 << 0;
+const PR_FP_MODE_FRE: u32 = 1_u32 << 1;
+
+/// `PR_FP_MODE_*` values for use with [`floating_point_mode`] and
+/// [`set_floating_point_mode`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum FloatingPointMode {
+ /// 64-bit floating point registers.
+ FloatingPointRegisters = PR_FP_MODE_FR,
+ /// Enable emulation of 32-bit floating-point mode.
+ FloatingPointEmulation = PR_FP_MODE_FRE,
+}
+
+impl TryFrom<u32> for FloatingPointMode {
+ type Error = io::Errno;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ PR_FP_MODE_FR => Ok(Self::FloatingPointRegisters),
+ PR_FP_MODE_FRE => Ok(Self::FloatingPointEmulation),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get the current floating point mode.
+///
+/// # References
+/// - [`prctl(PR_GET_FP_MODE,...)`]
+///
+/// [`prctl(PR_GET_FP_MODE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_FP_MODE")]
+pub fn floating_point_mode() -> io::Result<FloatingPointMode> {
+ let r = unsafe { prctl_1arg(PR_GET_FP_MODE)? } as c_uint;
+ FloatingPointMode::try_from(r)
+}
+
+const PR_SET_FP_MODE: c_int = 45;
+
+/// Allow control of the floating point mode from user space.
+///
+/// # References
+/// - [`prctl(PR_SET_FP_MODE,...)`]
+///
+/// [`prctl(PR_SET_FP_MODE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_FP_MODE")]
+pub fn set_floating_point_mode(mode: FloatingPointMode) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_FP_MODE, mode as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_SPECULATION_CTRL/PR_SET_SPECULATION_CTRL
+//
+
+const PR_GET_SPECULATION_CTRL: c_int = 52;
+
+const PR_SPEC_STORE_BYPASS: u32 = 0;
+const PR_SPEC_INDIRECT_BRANCH: u32 = 1;
+const PR_SPEC_L1D_FLUSH: u32 = 2;
+
+/// `PR_SPEC_*` values for use with [`speculative_feature_state`] and
+/// [`control_speculative_feature`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum SpeculationFeature {
+ /// Set the state of the speculative store bypass misfeature.
+ SpeculativeStoreBypass = PR_SPEC_STORE_BYPASS,
+ /// Set the state of the indirect branch speculation misfeature.
+ IndirectBranchSpeculation = PR_SPEC_INDIRECT_BRANCH,
+ /// Flush L1D Cache on context switch out of the task.
+ FlushL1DCacheOnContextSwitchOutOfTask = PR_SPEC_L1D_FLUSH,
+}
+
+impl TryFrom<u32> for SpeculationFeature {
+ type Error = io::Errno;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ PR_SPEC_STORE_BYPASS => Ok(Self::SpeculativeStoreBypass),
+ PR_SPEC_INDIRECT_BRANCH => Ok(Self::IndirectBranchSpeculation),
+ PR_SPEC_L1D_FLUSH => Ok(Self::FlushL1DCacheOnContextSwitchOutOfTask),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+bitflags! {
+ /// `PR_SPEC_*` flags for use with [`control_speculative_feature`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SpeculationFeatureControl: u32 {
+ /// The speculation feature is enabled, mitigation is disabled.
+ const ENABLE = 1_u32 << 1;
+ /// The speculation feature is disabled, mitigation is enabled.
+ const DISABLE = 1_u32 << 2;
+ /// The speculation feature is disabled, mitigation is enabled, and it
+ /// cannot be undone.
+ const FORCE_DISABLE = 1_u32 << 3;
+ /// The speculation feature is disabled, mitigation is enabled, and the
+ /// state will be cleared on `execve`.
+ const DISABLE_NOEXEC = 1_u32 << 4;
+ }
+}
+
+bitflags! {
+ /// Zero means the processors are not vulnerable.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct SpeculationFeatureState: u32 {
+ /// Mitigation can be controlled per thread by
+ /// `PR_SET_SPECULATION_CTRL`.
+ const PRCTL = 1_u32 << 0;
+ /// The speculation feature is enabled, mitigation is disabled.
+ const ENABLE = 1_u32 << 1;
+ /// The speculation feature is disabled, mitigation is enabled.
+ const DISABLE = 1_u32 << 2;
+ /// The speculation feature is disabled, mitigation is enabled, and it
+ /// cannot be undone.
+ const FORCE_DISABLE = 1_u32 << 3;
+ /// The speculation feature is disabled, mitigation is enabled, and the
+ /// state will be cleared on `execve`.
+ const DISABLE_NOEXEC = 1_u32 << 4;
+ }
+}
+
+/// Get the state of the speculation misfeature.
+///
+/// # References
+/// - [`prctl(PR_GET_SPECULATION_CTRL,...)`]
+///
+/// [`prctl(PR_GET_SPECULATION_CTRL,...)`]: https://www.kernel.org/doc/html/v5.18/userspace-api/spec_ctrl.html
+#[inline]
+#[doc(alias = "PR_GET_SPECULATION_CTRL")]
+pub fn speculative_feature_state(
+ feature: SpeculationFeature,
+) -> io::Result<Option<SpeculationFeatureState>> {
+ let r = unsafe { prctl_2args(PR_GET_SPECULATION_CTRL, feature as usize as *mut _)? } as c_uint;
+ Ok(SpeculationFeatureState::from_bits(r))
+}
+
+const PR_SET_SPECULATION_CTRL: c_int = 53;
+
+/// Sets the state of the speculation misfeature.
+///
+/// # References
+/// - [`prctl(PR_SET_SPECULATION_CTRL,...)`]
+///
+/// [`prctl(PR_SET_SPECULATION_CTRL,...)`]: https://www.kernel.org/doc/html/v5.18/userspace-api/spec_ctrl.html
+#[inline]
+#[doc(alias = "PR_SET_SPECULATION_CTRL")]
+pub fn control_speculative_feature(
+ feature: SpeculationFeature,
+ config: SpeculationFeatureControl,
+) -> io::Result<()> {
+ let feature = feature as usize as *mut _;
+ let config = config.bits() as usize as *mut _;
+ unsafe { prctl_3args(PR_SET_SPECULATION_CTRL, feature, config) }.map(|_r| ())
+}
+
+//
+// PR_GET_IO_FLUSHER/PR_SET_IO_FLUSHER
+//
+
+const PR_GET_IO_FLUSHER: c_int = 58;
+
+/// Get the `IO_FLUSHER` state of the caller.
+///
+/// # References
+/// - [`prctl(PR_GET_IO_FLUSHER,...)`]
+///
+/// [`prctl(PR_GET_IO_FLUSHER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_GET_IO_FLUSHER")]
+pub fn is_io_flusher() -> io::Result<bool> {
+ unsafe { prctl_1arg(PR_GET_IO_FLUSHER) }.map(|r| r != 0)
+}
+
+const PR_SET_IO_FLUSHER: c_int = 57;
+
+/// Put the process in the `IO_FLUSHER` state, allowing it to make progress
+/// when allocating memory.
+///
+/// # References
+/// - [`prctl(PR_SET_IO_FLUSHER,...)`]
+///
+/// [`prctl(PR_SET_IO_FLUSHER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[doc(alias = "PR_SET_IO_FLUSHER")]
+pub fn configure_io_flusher_behavior(enable: bool) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_IO_FLUSHER, usize::from(enable) as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_PAC_GET_ENABLED_KEYS/PR_PAC_SET_ENABLED_KEYS
+//
+
+const PR_PAC_GET_ENABLED_KEYS: c_int = 61;
+
+/// Get enabled pointer authentication keys.
+///
+/// # References
+/// - [`prctl(PR_PAC_GET_ENABLED_KEYS,...)`]
+///
+/// [`prctl(PR_PAC_GET_ENABLED_KEYS,...)`]: https://www.kernel.org/doc/html/v5.18/arm64/pointer-authentication.html
+#[inline]
+#[doc(alias = "PR_PAC_GET_ENABLED_KEYS")]
+pub fn enabled_pointer_authentication_keys() -> io::Result<PointerAuthenticationKeys> {
+ let r = unsafe { prctl_1arg(PR_PAC_GET_ENABLED_KEYS)? } as c_uint;
+ PointerAuthenticationKeys::from_bits(r).ok_or(io::Errno::RANGE)
+}
+
+const PR_PAC_SET_ENABLED_KEYS: c_int = 60;
+
+/// Set enabled pointer authentication keys.
+///
+/// # References
+/// - [`prctl(PR_PAC_SET_ENABLED_KEYS,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_PAC_SET_ENABLED_KEYS,...)`]: https://www.kernel.org/doc/html/v5.18/arm64/pointer-authentication.html
+#[inline]
+#[doc(alias = "PR_PAC_SET_ENABLED_KEYS")]
+pub unsafe fn configure_pointer_authentication_keys(
+ config: impl Iterator<Item = (PointerAuthenticationKeys, bool)>,
+) -> io::Result<()> {
+ let mut affected_keys: u32 = 0;
+ let mut enabled_keys: u32 = 0;
+
+ for (key, enable) in config {
+ let key = key.bits();
+ affected_keys |= key;
+
+ if enable {
+ enabled_keys |= key;
+ } else {
+ enabled_keys &= !key;
+ }
+ }
+
+ if affected_keys == 0 {
+ return Ok(()); // Nothing to do.
+ }
+
+ prctl_3args(
+ PR_PAC_SET_ENABLED_KEYS,
+ affected_keys as usize as *mut _,
+ enabled_keys as usize as *mut _,
+ )
+ .map(|_r| ())
+}
+
+//
+// PR_SET_VMA
+//
+
+const PR_SET_VMA: c_int = 0x53_56_4d_41;
+
+const PR_SET_VMA_ANON_NAME: usize = 0;
+
+/// Set the name for a virtual memory region.
+///
+/// # References
+/// - [`prctl(PR_SET_VMA,PR_SET_VMA_ANON_NAME,...)`]
+///
+/// [`prctl(PR_SET_VMA,PR_SET_VMA_ANON_NAME,...)`]: https://lwn.net/Articles/867818/
+#[inline]
+#[doc(alias = "PR_SET_VMA")]
+#[doc(alias = "PR_SET_VMA_ANON_NAME")]
+pub fn set_virtual_memory_region_name(region: &[u8], name: Option<&CStr>) -> io::Result<()> {
+ unsafe {
+ syscalls::prctl(
+ PR_SET_VMA,
+ PR_SET_VMA_ANON_NAME as *mut _,
+ region.as_ptr() as *mut _,
+ region.len() as *mut _,
+ name.map_or_else(null, CStr::as_ptr) as *mut _,
+ )
+ .map(|_r| ())
+ }
+}
diff --git a/src/process/priority.rs b/src/process/priority.rs
new file mode 100644
index 0000000..7c29284
--- /dev/null
+++ b/src/process/priority.rs
@@ -0,0 +1,132 @@
+#[cfg(not(target_os = "espidf"))]
+use crate::process::{Pid, Uid};
+use crate::{backend, io};
+
+/// `nice(inc)`—Adjust the scheduling priority of the current process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/nice.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/nice.2.html
+#[inline]
+pub fn nice(inc: i32) -> io::Result<i32> {
+ backend::process::syscalls::nice(inc)
+}
+
+/// `getpriority(PRIO_USER, uid)`—Get the scheduling priority of the given
+/// user.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpriority.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setpriority.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "getpriority")]
+pub fn getpriority_user(uid: Uid) -> io::Result<i32> {
+ backend::process::syscalls::getpriority_user(uid)
+}
+
+/// `getpriority(PRIO_PGRP, gid)`—Get the scheduling priority of the given
+/// process group.
+///
+/// A `pgid` of `None` means the process group of the calling process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpriority.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setpriority.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "getpriority")]
+pub fn getpriority_pgrp(pgid: Option<Pid>) -> io::Result<i32> {
+ backend::process::syscalls::getpriority_pgrp(pgid)
+}
+
+/// `getpriority(PRIO_PROCESS, pid)`—Get the scheduling priority of the given
+/// process.
+///
+/// A `pid` of `None` means the calling process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getpriority.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setpriority.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "getpriority")]
+pub fn getpriority_process(pid: Option<Pid>) -> io::Result<i32> {
+ backend::process::syscalls::getpriority_process(pid)
+}
+
+/// `setpriority(PRIO_USER, uid)`—Get the scheduling priority of the given
+/// user.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpriority.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setpriority.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setpriority.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "setpriority")]
+pub fn setpriority_user(uid: Uid, priority: i32) -> io::Result<()> {
+ backend::process::syscalls::setpriority_user(uid, priority)
+}
+
+/// `setpriority(PRIO_PGRP, pgid)`—Get the scheduling priority of the given
+/// process group.
+///
+/// A `pgid` of `None` means the process group of the calling process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpriority.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setpriority.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setpriority.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "setpriority")]
+pub fn setpriority_pgrp(pgid: Option<Pid>, priority: i32) -> io::Result<()> {
+ backend::process::syscalls::setpriority_pgrp(pgid, priority)
+}
+
+/// `setpriority(PRIO_PROCESS, pid)`—Get the scheduling priority of the given
+/// process.
+///
+/// A `pid` of `None` means the calling process.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpriority.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setpriority.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setpriority.2.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "setpriority")]
+pub fn setpriority_process(pid: Option<Pid>, priority: i32) -> io::Result<()> {
+ backend::process::syscalls::setpriority_process(pid, priority)
+}
diff --git a/src/process/procctl.rs b/src/process/procctl.rs
new file mode 100644
index 0000000..bb63722
--- /dev/null
+++ b/src/process/procctl.rs
@@ -0,0 +1,530 @@
+//! Bindings for the FreeBSD `procctl` system call.
+//!
+//! There are similarities (but also differences) with Linux's `prctl` system
+//! call, whose interface is located in the `prctl.rs` file.
+
+#![allow(unsafe_code)]
+
+#[cfg(feature = "alloc")]
+use alloc::{vec, vec::Vec};
+use core::mem::MaybeUninit;
+use core::ptr;
+
+use bitflags::bitflags;
+
+use crate::backend::c::{c_int, c_uint, c_void};
+use crate::backend::process::syscalls;
+use crate::backend::process::types::RawId;
+use crate::io;
+use crate::process::{Pid, RawPid};
+use crate::signal::Signal;
+use crate::utils::{as_mut_ptr, as_ptr};
+
+//
+// Helper functions.
+//
+
+/// Subset of `idtype_t` C enum, with only the values allowed by `procctl`.
+#[repr(i32)]
+pub enum IdType {
+ /// Process id.
+ Pid = 0,
+ /// Process group id.
+ Pgid = 2,
+}
+
+/// A process selector for use with the `procctl` interface.
+///
+/// `None` represents the current process. `Some((IdType::Pid, pid))`
+/// represents the process with pid `pid`. `Some((IdType::Pgid, pgid))`
+/// represents the control processes belonging to the process group with id
+/// `pgid`.
+pub type ProcSelector = Option<(IdType, Pid)>;
+fn proc_selector_to_raw(selector: ProcSelector) -> (IdType, RawPid) {
+ match selector {
+ Some((idtype, id)) => (idtype, id.as_raw_nonzero().get()),
+ None => (IdType::Pid, 0),
+ }
+}
+
+#[inline]
+pub(crate) unsafe fn procctl(
+ option: c_int,
+ process: ProcSelector,
+ data: *mut c_void,
+) -> io::Result<()> {
+ let (idtype, id) = proc_selector_to_raw(process);
+ syscalls::procctl(idtype as c_uint, id as RawId, option, data)
+}
+
+#[inline]
+pub(crate) unsafe fn procctl_set<P>(
+ option: c_int,
+ process: ProcSelector,
+ data: &P,
+) -> io::Result<()> {
+ procctl(option, process, (as_ptr(data) as *mut P).cast())
+}
+
+#[inline]
+pub(crate) unsafe fn procctl_get_optional<P>(
+ option: c_int,
+ process: ProcSelector,
+) -> io::Result<P> {
+ let mut value: MaybeUninit<P> = MaybeUninit::uninit();
+ procctl(option, process, value.as_mut_ptr().cast())?;
+ Ok(value.assume_init())
+}
+
+//
+// PROC_PDEATHSIG_STATUS/PROC_PDEATHSIG_CTL
+//
+
+const PROC_PDEATHSIG_STATUS: c_int = 12;
+
+/// Get the current value of the parent process death signal.
+///
+/// # References
+/// - [Linux: `prctl(PR_GET_PDEATHSIG,...)`]
+/// - [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]
+///
+/// [Linux: `prctl(PR_GET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [FreeBSD: `procctl(PROC_PDEATHSIG_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn parent_process_death_signal() -> io::Result<Option<Signal>> {
+ unsafe { procctl_get_optional::<c_int>(PROC_PDEATHSIG_STATUS, None) }.map(Signal::from_raw)
+}
+
+const PROC_PDEATHSIG_CTL: c_int = 11;
+
+/// Set the parent-death signal of the calling process.
+///
+/// # References
+/// - [Linux: `prctl(PR_SET_PDEATHSIG,...)`]
+/// - [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]
+///
+/// [Linux: `prctl(PR_SET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [FreeBSD: `procctl(PROC_PDEATHSIG_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn set_parent_process_death_signal(signal: Option<Signal>) -> io::Result<()> {
+ let signal = signal.map_or(0, |signal| signal as c_int);
+ unsafe { procctl_set::<c_int>(PROC_PDEATHSIG_CTL, None, &signal) }
+}
+
+//
+// PROC_TRACE_CTL
+//
+
+const PROC_TRACE_CTL: c_int = 7;
+
+const PROC_TRACE_CTL_ENABLE: i32 = 1;
+const PROC_TRACE_CTL_DISABLE: i32 = 2;
+const PROC_TRACE_CTL_DISABLE_EXEC: i32 = 3;
+
+/// `PROC_TRACE_CTL_*`.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum DumpableBehavior {
+ /// Not dumpable.
+ NotDumpable = PROC_TRACE_CTL_DISABLE,
+ /// Dumpable.
+ Dumpable = PROC_TRACE_CTL_ENABLE,
+ /// Not dumpable, and this behaviour is preserved across `execve` calls.
+ NotDumpableExecPreserved = PROC_TRACE_CTL_DISABLE_EXEC,
+}
+
+/// Set the state of the `dumpable` attribute for the process indicated by
+/// `idtype` and `id`. This determines whether the process can be traced and
+/// whether core dumps are produced for the process upon delivery of a signal
+/// whose default behavior is to produce a core dump.
+///
+/// This is similar to `set_dumpable_behavior` on Linux, with the exception
+/// that on FreeBSD there is an extra argument `process`. When `process` is set
+/// to `None`, the operation is performed for the current process, like on
+/// Linux.
+///
+/// # References
+/// - [FreeBSD `procctl(PROC_TRACE_CTL,...)`]
+///
+/// [FreeBSD `procctl(PROC_TRACE_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn set_dumpable_behavior(process: ProcSelector, config: DumpableBehavior) -> io::Result<()> {
+ unsafe { procctl(PROC_TRACE_CTL, process, config as usize as *mut _) }
+}
+
+//
+// PROC_TRACE_STATUS
+//
+
+const PROC_TRACE_STATUS: c_int = 8;
+
+/// Tracing status as returned by [`trace_status`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum TracingStatus {
+ /// Tracing is disabled for the process.
+ NotTraceble,
+ /// Tracing is not disabled for the process, but not debugger/tracer is
+ /// attached.
+ Tracable,
+ /// The process is being traced by the process whose pid is stored in the
+ /// first component of this variant.
+ BeingTraced(Pid),
+}
+
+/// Get the tracing status of the process indicated by `idtype` and `id`.
+///
+/// # References
+/// - [FreeBSD `procctl(PROC_TRACE_STATUS,...)`]
+///
+/// [FreeBSD `procctl(PROC_TRACE_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn trace_status(process: ProcSelector) -> io::Result<TracingStatus> {
+ let val = unsafe { procctl_get_optional::<c_int>(PROC_TRACE_STATUS, process) }?;
+ match val {
+ -1 => Ok(TracingStatus::NotTraceble),
+ 0 => Ok(TracingStatus::Tracable),
+ pid => {
+ let pid = Pid::from_raw(pid as RawPid).ok_or(io::Errno::RANGE)?;
+ Ok(TracingStatus::BeingTraced(pid))
+ }
+ }
+}
+
+//
+// PROC_REAP_*
+//
+
+const PROC_REAP_ACQUIRE: c_int = 2;
+const PROC_REAP_RELEASE: c_int = 3;
+
+/// Acquire or release the reaper status of the calling process.
+///
+/// # References
+/// - [FreeBSD: `procctl(PROC_REAP_ACQUIRE/RELEASE,...)`]
+///
+/// [FreeBSD: `procctl(PROC_REAP_ACQUIRE/RELEASE,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn set_reaper_status(reaper: bool) -> io::Result<()> {
+ unsafe {
+ procctl(
+ if reaper {
+ PROC_REAP_ACQUIRE
+ } else {
+ PROC_REAP_RELEASE
+ },
+ None,
+ ptr::null_mut(),
+ )
+ }
+}
+
+const PROC_REAP_STATUS: c_int = 4;
+
+bitflags! {
+ /// `REAPER_STATUS_*`.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ReaperStatusFlags: c_uint {
+ /// The process has acquired reaper status.
+ const OWNED = 1;
+ /// The process is the root of the reaper tree (pid 1).
+ const REALINIT = 2;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[repr(C)]
+struct procctl_reaper_status {
+ rs_flags: c_uint,
+ rs_children: c_uint,
+ rs_descendants: c_uint,
+ rs_reaper: RawPid,
+ rs_pid: RawPid,
+ rs_pad0: [c_uint; 15],
+}
+
+/// Reaper status as returned by [`get_reaper_status`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct ReaperStatus {
+ /// The flags.
+ pub flags: ReaperStatusFlags,
+ /// The number of children of the reaper among the descendants.
+ pub children: usize,
+ /// The total number of descendants of the reaper(s), not counting
+ /// descendants of the reaper in the subtree.
+ pub descendants: usize,
+ /// The pid of the reaper for the specified process id.
+ pub reaper: Pid,
+ /// The pid of one reaper child if there are any descendants.
+ pub pid: Option<Pid>,
+}
+
+/// Get information about the reaper of the specified process (or the process
+/// itself if it is a reaper).
+///
+/// # References
+/// - [FreeBSD: `procctl(PROC_REAP_STATUS,...)`]
+///
+/// [FreeBSD: `procctl(PROC_REAP_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn get_reaper_status(process: ProcSelector) -> io::Result<ReaperStatus> {
+ let raw = unsafe { procctl_get_optional::<procctl_reaper_status>(PROC_REAP_STATUS, process) }?;
+ Ok(ReaperStatus {
+ flags: ReaperStatusFlags::from_bits_retain(raw.rs_flags),
+ children: raw.rs_children as _,
+ descendants: raw.rs_descendants as _,
+ reaper: Pid::from_raw(raw.rs_reaper).ok_or(io::Errno::RANGE)?,
+ pid: if raw.rs_pid == -1 {
+ None
+ } else {
+ Some(Pid::from_raw(raw.rs_pid).ok_or(io::Errno::RANGE)?)
+ },
+ })
+}
+
+const PROC_REAP_GETPIDS: c_int = 5;
+
+bitflags! {
+ /// `REAPER_PIDINFO_*`.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct PidInfoFlags: c_uint {
+ /// This structure was filled by the kernel.
+ const VALID = 1;
+ /// The pid field identifies a direct child of the reaper.
+ const CHILD = 2;
+ /// The reported process is itself a reaper. Descendants of a
+ /// subordinate reaper are not reported.
+ const REAPER = 4;
+ /// The reported process is in the zombie state.
+ const ZOMBIE = 8;
+ /// The reported process is stopped by
+ /// [`Signal::Stop`]/[`Signal::Tstp`].
+ const STOPPED = 16;
+ /// The reported process is in the process of exiting.
+ const EXITING = 32;
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Clone)]
+struct procctl_reaper_pidinfo {
+ pi_pid: RawPid,
+ pi_subtree: RawPid,
+ pi_flags: c_uint,
+ pi_pad0: [c_uint; 15],
+}
+
+#[repr(C)]
+struct procctl_reaper_pids {
+ rp_count: c_uint,
+ rp_pad0: [c_uint; 15],
+ rp_pids: *mut procctl_reaper_pidinfo,
+}
+
+/// A child process of a reaper.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct PidInfo {
+ /// The flags of the process.
+ pub flags: PidInfoFlags,
+ /// The pid of the process.
+ pub pid: Pid,
+ /// The pid of the child of the reaper which is the (grand-..)parent of the
+ /// process.
+ pub subtree: Pid,
+}
+
+/// Get the list of descendants of the specified reaper process.
+///
+/// # References
+/// - [FreeBSD: `procctl(PROC_REAP_GETPIDS,...)`]
+///
+/// [FreeBSD: `procctl(PROC_REAP_GETPIDS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[cfg(feature = "alloc")]
+pub fn get_reaper_pids(process: ProcSelector) -> io::Result<Vec<PidInfo>> {
+ // Sadly no better way to guarantee that we get all the results than to
+ // allocate ~8MB of memory..
+ const PID_MAX: usize = 99999;
+ let mut pids: Vec<procctl_reaper_pidinfo> = vec![Default::default(); PID_MAX];
+ let mut pinfo = procctl_reaper_pids {
+ rp_count: PID_MAX as _,
+ rp_pad0: [0; 15],
+ rp_pids: pids.as_mut_slice().as_mut_ptr(),
+ };
+ unsafe { procctl(PROC_REAP_GETPIDS, process, as_mut_ptr(&mut pinfo).cast())? };
+ let mut result = Vec::new();
+ for raw in pids.into_iter() {
+ let flags = PidInfoFlags::from_bits_retain(raw.pi_flags);
+ if !flags.contains(PidInfoFlags::VALID) {
+ break;
+ }
+ result.push(PidInfo {
+ flags,
+ subtree: Pid::from_raw(raw.pi_subtree).ok_or(io::Errno::RANGE)?,
+ pid: Pid::from_raw(raw.pi_pid).ok_or(io::Errno::RANGE)?,
+ });
+ }
+ Ok(result)
+}
+
+const PROC_REAP_KILL: c_int = 6;
+
+bitflags! {
+ /// `REAPER_KILL_*`.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ struct KillFlags: c_uint {
+ const CHILDREN = 1;
+ const SUBTREE = 2;
+ }
+}
+
+#[repr(C)]
+struct procctl_reaper_kill {
+ rk_sig: c_int,
+ rk_flags: c_uint,
+ rk_subtree: RawPid,
+ rk_killed: c_uint,
+ rk_fpid: RawPid,
+ rk_pad0: [c_uint; 15],
+}
+
+/// Reaper status as returned by [`get_reaper_status`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct KillResult {
+ /// The number of processes that were signalled.
+ pub killed: usize,
+ /// The pid of the first process that wasn't successfully signalled.
+ pub first_failed: Option<Pid>,
+}
+
+/// Deliver a signal to some subset of
+///
+/// # References
+/// - [FreeBSD: `procctl(PROC_REAP_KILL,...)`]
+///
+/// [FreeBSD: `procctl(PROC_REAP_KILL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+pub fn reaper_kill(
+ process: ProcSelector,
+ signal: Signal,
+ direct_children: bool,
+ subtree: Option<Pid>,
+) -> io::Result<KillResult> {
+ let mut flags = KillFlags::empty();
+ flags.set(KillFlags::CHILDREN, direct_children);
+ flags.set(KillFlags::SUBTREE, subtree.is_some());
+ let mut req = procctl_reaper_kill {
+ rk_sig: signal as c_int,
+ rk_flags: flags.bits(),
+ rk_subtree: subtree.map(|p| p.as_raw_nonzero().into()).unwrap_or(0),
+ rk_killed: 0,
+ rk_fpid: 0,
+ rk_pad0: [0; 15],
+ };
+ unsafe { procctl(PROC_REAP_KILL, process, as_mut_ptr(&mut req).cast())? };
+ Ok(KillResult {
+ killed: req.rk_killed as _,
+ first_failed: Pid::from_raw(req.rk_fpid),
+ })
+}
+
+//
+// PROC_TRAPCAP_STATUS/PROC_TRAPCAP_CTL
+//
+
+const PROC_TRAPCAP_CTL: c_int = 9;
+
+const PROC_TRAPCAP_CTL_ENABLE: i32 = 1;
+const PROC_TRAPCAP_CTL_DISABLE: i32 = 2;
+
+/// `PROC_TRAPCAP_CTL_*`.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum TrapCapBehavior {
+ /// Disable the [`Signal::Trap`] signal delivery on capability mode access
+ /// violations.
+ Disable = PROC_TRAPCAP_CTL_DISABLE,
+ /// Enable the [`Signal::Trap`] signal delivery on capability mode access
+ /// violations.
+ Enable = PROC_TRAPCAP_CTL_ENABLE,
+}
+
+/// Set the current value of the capability mode violation trapping behavior.
+/// If this behavior is enabled, the kernel would deliver a [`Signal::Trap`]
+/// signal on any return from a system call that would result in a
+/// [`io::Errno::NOTCAPABLE`]` or [`io::Errno::CAPMODE`] error.
+///
+/// This behavior is inherited by the children of the process and is kept
+/// across `execve` calls.
+///
+/// # References
+/// - [FreeBSD: `procctl(PROC_TRAPCAP_CTL,...)`]
+///
+/// [FreeBSD: `procctl(PROC_TRAPCAP_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn set_trap_cap_behavior(process: ProcSelector, config: TrapCapBehavior) -> io::Result<()> {
+ let config = config as c_int;
+ unsafe { procctl_set::<c_int>(PROC_TRAPCAP_CTL, process, &config) }
+}
+
+const PROC_TRAPCAP_STATUS: c_int = 10;
+
+/// Get the current value of the capability mode violation trapping behavior.
+///
+/// # References
+/// - [FreeBSD: `procctl(PROC_TRAPCAP_STATUS,...)`]
+///
+/// [FreeBSD: `procctl(PROC_TRAPCAP_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn trap_cap_behavior(process: ProcSelector) -> io::Result<TrapCapBehavior> {
+ let val = unsafe { procctl_get_optional::<c_int>(PROC_TRAPCAP_STATUS, process) }?;
+ match val {
+ PROC_TRAPCAP_CTL_DISABLE => Ok(TrapCapBehavior::Disable),
+ PROC_TRAPCAP_CTL_ENABLE => Ok(TrapCapBehavior::Enable),
+ _ => Err(io::Errno::RANGE),
+ }
+}
+
+//
+// PROC_NO_NEW_PRIVS_STATUS/PROC_NO_NEW_PRIVS_CTL
+//
+
+const PROC_NO_NEW_PRIVS_CTL: c_int = 19;
+
+const PROC_NO_NEW_PRIVS_ENABLE: c_int = 1;
+
+/// Enable the `no_new_privs` mode that ignores SUID and SGID bits on `execve`
+/// in the specified process and its future descendants.
+///
+/// This is similar to `set_no_new_privs` on Linux, with the exception that on
+/// FreeBSD there is no argument `no_new_privs` argument as it's only possible
+/// to enable this mode and there's no going back.
+///
+/// # References
+/// - [Linux: `prctl(PR_SET_NO_NEW_PRIVS,...)`]
+/// - [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_CTL,...)`]
+///
+/// [Linux: `prctl(PR_SET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_CTL,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn set_no_new_privs(process: ProcSelector) -> io::Result<()> {
+ unsafe { procctl_set::<c_int>(PROC_NO_NEW_PRIVS_CTL, process, &PROC_NO_NEW_PRIVS_ENABLE) }
+}
+
+const PROC_NO_NEW_PRIVS_STATUS: c_int = 20;
+
+/// Check the `no_new_privs` mode of the specified process.
+///
+/// # References
+/// - [Linux: `prctl(PR_GET_NO_NEW_PRIVS,...)`]
+/// - [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_STATUS,...)`]
+///
+/// [Linux: `prctl(PR_GET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [FreeBSD: `procctl(PROC_NO_NEW_PRIVS_STATUS,...)`]: https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2
+#[inline]
+pub fn no_new_privs(process: ProcSelector) -> io::Result<bool> {
+ unsafe { procctl_get_optional::<c_int>(PROC_NO_NEW_PRIVS_STATUS, process) }
+ .map(|x| x == PROC_NO_NEW_PRIVS_ENABLE)
+}
diff --git a/src/process/rlimit.rs b/src/process/rlimit.rs
new file mode 100644
index 0000000..ea760c2
--- /dev/null
+++ b/src/process/rlimit.rs
@@ -0,0 +1,53 @@
+#[cfg(linux_kernel)]
+use crate::process::Pid;
+use crate::{backend, io};
+
+pub use backend::process::types::Resource;
+
+/// `struct rlimit`—Current and maximum values used in [`getrlimit`],
+/// [`setrlimit`], and [`prlimit`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Rlimit {
+ /// Current effective, “soft”, limit.
+ pub current: Option<u64>,
+ /// Maximum, “hard”, value that `current` may be dynamically increased to.
+ pub maximum: Option<u64>,
+}
+
+/// `getrlimit(resource)`—Get a process resource limit value.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/getrlimit.2.html
+#[inline]
+pub fn getrlimit(resource: Resource) -> Rlimit {
+ backend::process::syscalls::getrlimit(resource)
+}
+
+/// `setrlimit(resource, new)`—Set a process resource limit value.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setrlimit.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setrlimit.2.html
+#[inline]
+pub fn setrlimit(resource: Resource, new: Rlimit) -> io::Result<()> {
+ backend::process::syscalls::setrlimit(resource, new)
+}
+
+/// `prlimit(pid, resource, new)`—Get and set a process resource limit value.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/prlimit.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn prlimit(pid: Option<Pid>, resource: Resource, new: Rlimit) -> io::Result<Rlimit> {
+ backend::process::syscalls::prlimit(pid, resource, new)
+}
diff --git a/src/process/sched.rs b/src/process/sched.rs
new file mode 100644
index 0000000..d6a303a
--- /dev/null
+++ b/src/process/sched.rs
@@ -0,0 +1,125 @@
+use crate::process::Pid;
+use crate::{backend, io};
+
+/// `CpuSet` represents a bit-mask of CPUs.
+///
+/// `CpuSet`s are used by [`sched_setaffinity`] and [`sched_getaffinity`], for
+/// example.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/CPU_SET.3.html
+/// [`sched_setaffinity`]: crate::process::sched_setaffinity
+/// [`sched_getaffinity`]: crate::process::sched_getaffinity
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub struct CpuSet {
+ cpu_set: backend::process::types::RawCpuSet,
+}
+
+impl CpuSet {
+ /// The maximum number of CPU in `CpuSet`.
+ pub const MAX_CPU: usize = backend::process::types::CPU_SETSIZE;
+
+ /// Create a new and empty `CpuSet`.
+ #[inline]
+ pub fn new() -> Self {
+ Self {
+ cpu_set: backend::process::types::raw_cpu_set_new(),
+ }
+ }
+
+ /// Test to see if a CPU is in the `CpuSet`.
+ ///
+ /// `field` is the CPU id to test.
+ #[inline]
+ pub fn is_set(&self, field: usize) -> bool {
+ backend::process::cpu_set::CPU_ISSET(field, &self.cpu_set)
+ }
+
+ /// Add a CPU to `CpuSet`.
+ ///
+ /// `field` is the CPU id to add.
+ #[inline]
+ pub fn set(&mut self, field: usize) {
+ backend::process::cpu_set::CPU_SET(field, &mut self.cpu_set)
+ }
+
+ /// Remove a CPU from `CpuSet`.
+ ///
+ /// `field` is the CPU id to remove.
+ #[inline]
+ pub fn unset(&mut self, field: usize) {
+ backend::process::cpu_set::CPU_CLR(field, &mut self.cpu_set)
+ }
+
+ /// Count the number of CPUs set in the `CpuSet`.
+ #[cfg(linux_kernel)]
+ #[inline]
+ pub fn count(&self) -> u32 {
+ backend::process::cpu_set::CPU_COUNT(&self.cpu_set)
+ }
+
+ /// Zeroes the `CpuSet`.
+ #[inline]
+ pub fn clear(&mut self) {
+ backend::process::cpu_set::CPU_ZERO(&mut self.cpu_set)
+ }
+}
+
+impl Default for CpuSet {
+ #[inline]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// `sched_setaffinity(pid, cpuset)`—Set a thread's CPU affinity mask.
+///
+/// `pid` is the thread ID to update. If pid is `None`, then the current thread
+/// is updated.
+///
+/// The `CpuSet` argument specifies the set of CPUs on which the thread will be
+/// eligible to run.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html
+#[inline]
+pub fn sched_setaffinity(pid: Option<Pid>, cpuset: &CpuSet) -> io::Result<()> {
+ backend::process::syscalls::sched_setaffinity(pid, &cpuset.cpu_set)
+}
+
+/// `sched_getaffinity(pid)`—Get a thread's CPU affinity mask.
+///
+/// `pid` is the thread ID to check. If pid is `None`, then the current thread
+/// is checked.
+///
+/// Returns the set of CPUs on which the thread is eligible to run.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sched_getaffinity.2.html
+#[inline]
+pub fn sched_getaffinity(pid: Option<Pid>) -> io::Result<CpuSet> {
+ let mut cpuset = CpuSet::new();
+ backend::process::syscalls::sched_getaffinity(pid, &mut cpuset.cpu_set).and(Ok(cpuset))
+}
+
+/// `sched_getcpu()`—Get the CPU that the current thread is currently on.
+///
+/// # References
+/// - [Linux]
+/// - [DragonFly BSD]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sched_getcpu.2.html
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=sched_getcpu&section=2
+// FreeBSD added `sched_getcpu` in 13.0.
+#[cfg(any(linux_kernel, target_os = "dragonfly"))]
+#[inline]
+pub fn sched_getcpu() -> usize {
+ backend::process::syscalls::sched_getcpu()
+}
diff --git a/src/process/sched_yield.rs b/src/process/sched_yield.rs
new file mode 100644
index 0000000..0324f67
--- /dev/null
+++ b/src/process/sched_yield.rs
@@ -0,0 +1,16 @@
+use crate::backend;
+
+/// `sched_yield()`—Hints to the OS that other processes should run.
+///
+/// This function always succeeds.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sched_yield.2.html
+#[inline]
+pub fn sched_yield() {
+ backend::process::syscalls::sched_yield()
+}
diff --git a/src/process/umask.rs b/src/process/umask.rs
new file mode 100644
index 0000000..01779d7
--- /dev/null
+++ b/src/process/umask.rs
@@ -0,0 +1,21 @@
+//! Umask support.
+
+#[cfg(feature = "fs")]
+use crate::backend;
+#[cfg(feature = "fs")]
+use crate::fs::Mode;
+
+/// `umask(mask)`—Set the process file creation mask.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/umask.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/umask.2.html
+#[cfg(feature = "fs")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+#[inline]
+pub fn umask(mask: Mode) -> Mode {
+ backend::process::syscalls::umask(mask)
+}
diff --git a/src/process/wait.rs b/src/process/wait.rs
new file mode 100644
index 0000000..4d0e6e2
--- /dev/null
+++ b/src/process/wait.rs
@@ -0,0 +1,365 @@
+use crate::process::Pid;
+use crate::{backend, io};
+use bitflags::bitflags;
+
+#[cfg(target_os = "linux")]
+use crate::fd::BorrowedFd;
+
+#[cfg(linux_raw)]
+use crate::backend::process::wait::SiginfoExt;
+
+bitflags! {
+ /// Options for modifying the behavior of [`wait`]/[`waitpid`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct WaitOptions: u32 {
+ /// Return immediately if no child has exited.
+ const NOHANG = bitcast!(backend::process::wait::WNOHANG);
+ /// Return if a child has stopped (but not traced via [`ptrace`])
+ ///
+ /// [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html
+ const UNTRACED = bitcast!(backend::process::wait::WUNTRACED);
+ /// Return if a stopped child has been resumed by delivery of
+ /// [`Signal::Cont`].
+ const CONTINUED = bitcast!(backend::process::wait::WCONTINUED);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
+bitflags! {
+ /// Options for modifying the behavior of [`waitid`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct WaitidOptions: u32 {
+ /// Return immediately if no child has exited.
+ const NOHANG = bitcast!(backend::process::wait::WNOHANG);
+ /// Return if a stopped child has been resumed by delivery of
+ /// [`Signal::Cont`].
+ const CONTINUED = bitcast!(backend::process::wait::WCONTINUED);
+ /// Wait for processed that have exited.
+ const EXITED = bitcast!(backend::process::wait::WEXITED);
+ /// Keep processed in a waitable state.
+ const NOWAIT = bitcast!(backend::process::wait::WNOWAIT);
+ /// Wait for processes that have been stopped.
+ const STOPPED = bitcast!(backend::process::wait::WSTOPPED);
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// The status of a child process after calling [`wait`]/[`waitpid`].
+#[derive(Debug, Clone, Copy)]
+#[repr(transparent)]
+pub struct WaitStatus(u32);
+
+impl WaitStatus {
+ /// Creates a `WaitStatus` out of an integer.
+ #[inline]
+ pub(crate) fn new(status: u32) -> Self {
+ Self(status)
+ }
+
+ /// Converts a `WaitStatus` into its raw representation as an integer.
+ #[inline]
+ pub const fn as_raw(self) -> u32 {
+ self.0
+ }
+
+ /// Returns whether the process is currently stopped.
+ #[inline]
+ pub fn stopped(self) -> bool {
+ backend::process::wait::WIFSTOPPED(self.0 as _)
+ }
+
+ /// Returns whether the process has exited normally.
+ #[inline]
+ pub fn exited(self) -> bool {
+ backend::process::wait::WIFEXITED(self.0 as _)
+ }
+
+ /// Returns whether the process was terminated by a signal.
+ #[inline]
+ pub fn signaled(self) -> bool {
+ backend::process::wait::WIFSIGNALED(self.0 as _)
+ }
+
+ /// Returns whether the process has continued from a job control stop.
+ #[inline]
+ pub fn continued(self) -> bool {
+ backend::process::wait::WIFCONTINUED(self.0 as _)
+ }
+
+ /// Returns the number of the signal that stopped the process, if the
+ /// process was stopped by a signal.
+ #[inline]
+ pub fn stopping_signal(self) -> Option<u32> {
+ if self.stopped() {
+ Some(backend::process::wait::WSTOPSIG(self.0 as _) as _)
+ } else {
+ None
+ }
+ }
+
+ /// Returns the exit status number returned by the process, if it exited
+ /// normally.
+ #[inline]
+ pub fn exit_status(self) -> Option<u32> {
+ if self.exited() {
+ Some(backend::process::wait::WEXITSTATUS(self.0 as _) as _)
+ } else {
+ None
+ }
+ }
+
+ /// Returns the number of the signal that terminated the process, if the
+ /// process was terminated by a signal.
+ #[inline]
+ pub fn terminating_signal(self) -> Option<u32> {
+ if self.signaled() {
+ Some(backend::process::wait::WTERMSIG(self.0 as _) as _)
+ } else {
+ None
+ }
+ }
+}
+
+/// The status of a process after calling [`waitid`].
+#[derive(Clone, Copy)]
+#[repr(transparent)]
+#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
+pub struct WaitidStatus(pub(crate) backend::c::siginfo_t);
+
+#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
+impl WaitidStatus {
+ /// Returns whether the process is currently stopped.
+ #[inline]
+ pub fn stopped(&self) -> bool {
+ self.si_code() == backend::c::CLD_STOPPED
+ }
+
+ /// Returns whether the process is currently trapped.
+ #[inline]
+ pub fn trapped(&self) -> bool {
+ self.si_code() == backend::c::CLD_TRAPPED
+ }
+
+ /// Returns whether the process has exited normally.
+ #[inline]
+ pub fn exited(&self) -> bool {
+ self.si_code() == backend::c::CLD_EXITED
+ }
+
+ /// Returns whether the process was terminated by a signal and did not
+ /// create a core file.
+ #[inline]
+ pub fn killed(&self) -> bool {
+ self.si_code() == backend::c::CLD_KILLED
+ }
+
+ /// Returns whether the process was terminated by a signal and did create a
+ /// core file.
+ #[inline]
+ pub fn dumped(&self) -> bool {
+ self.si_code() == backend::c::CLD_DUMPED
+ }
+
+ /// Returns whether the process has continued from a job control stop.
+ #[inline]
+ pub fn continued(&self) -> bool {
+ self.si_code() == backend::c::CLD_CONTINUED
+ }
+
+ /// Returns the number of the signal that stopped the process, if the
+ /// process was stopped by a signal.
+ #[inline]
+ #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
+ pub fn stopping_signal(&self) -> Option<u32> {
+ if self.stopped() {
+ Some(self.si_status() as _)
+ } else {
+ None
+ }
+ }
+
+ /// Returns the number of the signal that trapped the process, if the
+ /// process was trapped by a signal.
+ #[inline]
+ #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
+ pub fn trapping_signal(&self) -> Option<u32> {
+ if self.trapped() {
+ Some(self.si_status() as _)
+ } else {
+ None
+ }
+ }
+
+ /// Returns the exit status number returned by the process, if it exited
+ /// normally.
+ #[inline]
+ #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
+ pub fn exit_status(&self) -> Option<u32> {
+ if self.exited() {
+ Some(self.si_status() as _)
+ } else {
+ None
+ }
+ }
+
+ /// Returns the number of the signal that terminated the process, if the
+ /// process was terminated by a signal.
+ #[inline]
+ #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
+ pub fn terminating_signal(&self) -> Option<u32> {
+ if self.killed() || self.dumped() {
+ Some(self.si_status() as _)
+ } else {
+ None
+ }
+ }
+
+ /// Returns a reference to the raw platform-specific `siginfo_t` struct.
+ #[inline]
+ pub const fn as_raw(&self) -> &backend::c::siginfo_t {
+ &self.0
+ }
+
+ #[cfg(linux_raw)]
+ fn si_code(&self) -> u32 {
+ self.0.si_code() as u32 // CLD_ consts are unsigned
+ }
+
+ #[cfg(not(linux_raw))]
+ fn si_code(&self) -> backend::c::c_int {
+ self.0.si_code
+ }
+
+ #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))]
+ #[allow(unsafe_code)]
+ fn si_status(&self) -> backend::c::c_int {
+ // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a
+ // `waitid` call always has a valid `si_status` value.
+ //
+ // [specifies]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html
+ unsafe { self.0.si_status() }
+ }
+}
+
+/// The identifier to wait on in a call to [`waitid`].
+#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum WaitId<'a> {
+ /// Wait on all processes.
+ #[doc(alias = "P_ALL")]
+ All,
+
+ /// Wait for a specific process ID.
+ #[doc(alias = "P_PID")]
+ Pid(Pid),
+
+ /// Wait for a specific process group ID, or the calling process' group ID.
+ #[doc(alias = "P_PGID")]
+ Pgid(Option<Pid>),
+
+ /// Wait for a specific process file descriptor.
+ #[cfg(target_os = "linux")]
+ #[doc(alias = "P_PIDFD")]
+ PidFd(BorrowedFd<'a>),
+
+ /// Eat the lifetime for non-Linux platforms.
+ #[doc(hidden)]
+ #[cfg(not(target_os = "linux"))]
+ __EatLifetime(core::marker::PhantomData<&'a ()>),
+}
+
+/// `waitpid(pid, waitopts)`—Wait for a specific process to change state.
+///
+/// If the pid is `None`, the call will wait for any child process whose
+/// process group id matches that of the calling process.
+///
+/// Otherwise, the call will wait for the child process with the given pid.
+///
+/// On Success, returns the status of the selected process.
+///
+/// If `NOHANG` was specified in the options, and the selected child process
+/// didn't change state, returns `None`.
+///
+/// # Bugs
+///
+/// This function does not currently support waiting for given process group
+/// (the < 0 case of `waitpid`); to do that, currently the [`waitpgid`] or
+/// [`waitid`] function must be used.
+///
+/// This function does not currently support waiting for any process (the
+/// `-1` case of `waitpid`); to do that, currently the [`wait`] function must
+/// be used.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> {
+ Ok(backend::process::syscalls::waitpid(pid, waitopts)?.map(|(_, status)| status))
+}
+
+/// `waitpid(-pgid, waitopts)`—Wait for a process in a specific process group
+/// to change state.
+///
+/// The call will wait for any child process with the given pgid.
+///
+/// On Success, returns the status of the selected process.
+///
+/// If `NOHANG` was specified in the options, and no selected child process
+/// changed state, returns `None`.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> {
+ Ok(backend::process::syscalls::waitpgid(pgid, waitopts)?.map(|(_, status)| status))
+}
+
+/// `wait(waitopts)`—Wait for any of the children of calling process to
+/// change state.
+///
+/// On success, returns the pid of the child process whose state changed, and
+/// the status of said process.
+///
+/// If `NOHANG` was specified in the options, and the selected child process
+/// didn't change state, returns `None`.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> {
+ backend::process::syscalls::wait(waitopts)
+}
+
+/// `waitid(_, _, _, opts)`—Wait for the specified child process to change
+/// state.
+#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))]
+#[inline]
+pub fn waitid<'a>(
+ id: impl Into<WaitId<'a>>,
+ options: WaitidOptions,
+) -> io::Result<Option<WaitidStatus>> {
+ backend::process::syscalls::waitid(id.into(), options)
+}
diff --git a/src/procfs.rs b/src/procfs.rs
new file mode 100644
index 0000000..60a4b6c
--- /dev/null
+++ b/src/procfs.rs
@@ -0,0 +1,520 @@
+//! Utilities for working with `/proc`, where Linux's `procfs` is typically
+//! mounted.
+//!
+//! `/proc` serves as an adjunct to Linux's main syscall surface area,
+//! providing additional features with an awkward interface.
+//!
+//! This module does a considerable amount of work to determine whether `/proc`
+//! is mounted, with actual `procfs`, and without any additional mount points
+//! on top of the paths we open.
+//!
+//! Why all the effort to detect bind mount points? People are doing all kinds
+//! of things with Linux containers these days, with many different privilege
+//! schemes, and we want to avoid making any unnecessary assumptions. Rustix
+//! and its users will sometimes use procfs *implicitly* (when Linux gives them
+//! no better options), in ways that aren't obvious from their public APIs.
+//! These filesystem accesses might not be visible to someone auditing the main
+//! code of an application for places which may be influenced by the filesystem
+//! namespace. So with the checking here, they may fail, but they won't be able
+//! to succeed with bogus results.
+
+use crate::fd::{AsFd, BorrowedFd, OwnedFd};
+use crate::ffi::CStr;
+use crate::fs::{
+ fstat, fstatfs, major, openat, renameat, seek, FileType, FsWord, Mode, OFlags, RawDir,
+ SeekFrom, Stat, CWD, PROC_SUPER_MAGIC,
+};
+use crate::io;
+use crate::path::DecInt;
+#[cfg(feature = "rustc-dep-of-std")]
+use core::lazy::OnceCell;
+use core::mem::MaybeUninit;
+#[cfg(not(feature = "rustc-dep-of-std"))]
+use once_cell::sync::OnceCell;
+
+/// Linux's procfs always uses inode 1 for its root directory.
+const PROC_ROOT_INO: u64 = 1;
+
+// Identify an entry within "/proc", to determine which anomalies to check for.
+#[derive(Copy, Clone, Debug)]
+enum Kind {
+ Proc,
+ Pid,
+ Fd,
+ File,
+ Symlink,
+}
+
+/// Check a subdirectory of "/proc" for anomalies.
+fn check_proc_entry(
+ kind: Kind,
+ entry: BorrowedFd<'_>,
+ proc_stat: Option<&Stat>,
+) -> io::Result<Stat> {
+ let entry_stat = fstat(entry)?;
+ check_proc_entry_with_stat(kind, entry, entry_stat, proc_stat)
+}
+
+/// Check a subdirectory of "/proc" for anomalies, using the provided `Stat`.
+fn check_proc_entry_with_stat(
+ kind: Kind,
+ entry: BorrowedFd<'_>,
+ entry_stat: Stat,
+ proc_stat: Option<&Stat>,
+) -> io::Result<Stat> {
+ // Check the filesystem magic.
+ check_procfs(entry)?;
+
+ match kind {
+ Kind::Proc => check_proc_root(entry, &entry_stat)?,
+ Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?,
+ Kind::File => check_proc_file(&entry_stat, proc_stat)?,
+ Kind::Symlink => check_proc_symlink(&entry_stat, proc_stat)?,
+ }
+
+ // "/proc" directories are typically mounted r-xr-xr-x.
+ // "/proc/self/fd" is r-x------. Allow them to have fewer permissions, but
+ // not more.
+ match kind {
+ Kind::Symlink => {
+ // On Linux, symlinks don't have their own permissions.
+ }
+ _ => {
+ let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 };
+ if entry_stat.st_mode & 0o777 & !expected_mode != 0 {
+ return Err(io::Errno::NOTSUP);
+ }
+ }
+ }
+
+ match kind {
+ Kind::Fd => {
+ // Check that the "/proc/self/fd" directory doesn't have any
+ // extraneous links into it (which might include unexpected
+ // subdirectories).
+ if entry_stat.st_nlink != 2 {
+ return Err(io::Errno::NOTSUP);
+ }
+ }
+ Kind::Pid | Kind::Proc => {
+ // Check that the "/proc" and "/proc/self" directories aren't
+ // empty.
+ if entry_stat.st_nlink <= 2 {
+ return Err(io::Errno::NOTSUP);
+ }
+ }
+ Kind::File => {
+ // Check that files in procfs don't have extraneous hard links to
+ // them (which might indicate hard links to other things).
+ if entry_stat.st_nlink != 1 {
+ return Err(io::Errno::NOTSUP);
+ }
+ }
+ Kind::Symlink => {
+ // Check that symlinks in procfs don't have extraneous hard links
+ // to them (which might indicate hard links to other things).
+ if entry_stat.st_nlink != 1 {
+ return Err(io::Errno::NOTSUP);
+ }
+ }
+ }
+
+ Ok(entry_stat)
+}
+
+fn check_proc_root(entry: BorrowedFd<'_>, stat: &Stat) -> io::Result<()> {
+ // We use `O_DIRECTORY` for proc directories, so open should fail if we
+ // don't get a directory when we expect one.
+ assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
+
+ // Check the root inode number.
+ if stat.st_ino != PROC_ROOT_INO {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ // Proc is a non-device filesystem, so check for major number 0.
+ // <https://www.kernel.org/doc/Documentation/admin-guide/devices.txt>
+ if major(stat.st_dev) != 0 {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ // Check that "/proc" is a mountpoint.
+ if !is_mountpoint(entry) {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ Ok(())
+}
+
+fn check_proc_subdir(
+ entry: BorrowedFd<'_>,
+ stat: &Stat,
+ proc_stat: Option<&Stat>,
+) -> io::Result<()> {
+ // We use `O_DIRECTORY` for proc directories, so open should fail if we
+ // don't get a directory when we expect one.
+ assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
+
+ check_proc_nonroot(stat, proc_stat)?;
+
+ // Check that subdirectories of "/proc" are not mount points.
+ if is_mountpoint(entry) {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ Ok(())
+}
+
+fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
+ // Check that we have a regular file.
+ if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ check_proc_nonroot(stat, proc_stat)?;
+
+ Ok(())
+}
+
+fn check_proc_symlink(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
+ // Check that we have a symbolic link.
+ if FileType::from_raw_mode(stat.st_mode) != FileType::Symlink {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ check_proc_nonroot(stat, proc_stat)?;
+
+ Ok(())
+}
+
+fn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
+ // Check that we haven't been linked back to the root of "/proc".
+ if stat.st_ino == PROC_ROOT_INO {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ // Check that we're still in procfs.
+ if stat.st_dev != proc_stat.unwrap().st_dev {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ Ok(())
+}
+
+/// Check that `file` is opened on a `procfs` filesystem.
+fn check_procfs(file: BorrowedFd<'_>) -> io::Result<()> {
+ let statfs = fstatfs(file)?;
+ let f_type = statfs.f_type;
+ if f_type != FsWord::from(PROC_SUPER_MAGIC) {
+ return Err(io::Errno::NOTSUP);
+ }
+
+ Ok(())
+}
+
+/// Check whether the given directory handle is a mount point.
+fn is_mountpoint(file: BorrowedFd<'_>) -> bool {
+ // We use a `renameat` call that would otherwise fail, but which fails with
+ // `XDEV` first if it would cross a mount point.
+ let err = renameat(file, cstr!("../."), file, cstr!(".")).unwrap_err();
+ match err {
+ io::Errno::XDEV => true, // the rename failed due to crossing a mount point
+ io::Errno::BUSY => false, // the rename failed normally
+ _ => panic!("Unexpected error from `renameat`: {:?}", err),
+ }
+}
+
+/// Open a directory in `/proc`, mapping all errors to `io::Errno::NOTSUP`.
+fn proc_opendirat<P: crate::path::Arg, Fd: AsFd>(dirfd: Fd, path: P) -> io::Result<OwnedFd> {
+ // We don't add `PATH` here because that disables `DIRECTORY`. And we don't
+ // add `NOATIME` for the same reason as the comment in `open_and_check_file`.
+ let oflags = OFlags::NOFOLLOW | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOCTTY;
+ openat(dirfd, path, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)
+}
+
+/// Returns a handle to Linux's `/proc` directory.
+///
+/// This ensures that `/proc` is procfs, that nothing is mounted on top of it,
+/// and that it looks normal. It also returns the `Stat` of `/proc`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+fn proc() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
+ static PROC: StaticFd = StaticFd::new();
+
+ // `OnceBox` is "racey" in that the initialization function may run
+ // multiple times. We're ok with that, since the initialization function
+ // has no side effects.
+ PROC.get_or_try_init(|| {
+ // Open "/proc".
+ let proc = proc_opendirat(CWD, cstr!("/proc"))?;
+ let proc_stat =
+ check_proc_entry(Kind::Proc, proc.as_fd(), None).map_err(|_err| io::Errno::NOTSUP)?;
+
+ Ok(new_static_fd(proc, proc_stat))
+ })
+ .map(|(fd, stat)| (fd.as_fd(), stat))
+}
+
+/// Returns a handle to Linux's `/proc/self` directory.
+///
+/// This ensures that `/proc/self` is procfs, that nothing is mounted on top of
+/// it, and that it looks normal. It also returns the `Stat` of `/proc/self`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+#[allow(unsafe_code)]
+fn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
+ static PROC_SELF: StaticFd = StaticFd::new();
+
+ // The init function here may run multiple times; see above.
+ PROC_SELF
+ .get_or_try_init(|| {
+ let (proc, proc_stat) = proc()?;
+
+ // `getpid` would return our pid in our own pid namespace, so
+ // instead use `readlink` on the `self` symlink to learn our pid in
+ // the procfs namespace.
+ let self_symlink = open_and_check_file(proc, proc_stat, cstr!("self"), Kind::Symlink)?;
+ let mut buf = [MaybeUninit::<u8>::uninit(); 20];
+ let len = crate::backend::fs::syscalls::readlinkat(
+ self_symlink.as_fd(),
+ cstr!(""),
+ &mut buf,
+ )?;
+ let pid: &[u8] = unsafe { core::mem::transmute(&buf[..len]) };
+
+ // Open "/proc/self". Use our pid to compute the name rather than
+ // literally using "self", as "self" is a symlink.
+ let proc_self = proc_opendirat(proc, pid)?;
+ let proc_self_stat = check_proc_entry(Kind::Pid, proc_self.as_fd(), Some(proc_stat))
+ .map_err(|_err| io::Errno::NOTSUP)?;
+
+ Ok(new_static_fd(proc_self, proc_self_stat))
+ })
+ .map(|(owned, stat)| (owned.as_fd(), stat))
+}
+
+/// Returns a handle to Linux's `/proc/self/fd` directory.
+///
+/// This ensures that `/proc/self/fd` is `procfs`, that nothing is mounted on
+/// top of it, and that it looks normal.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+pub fn proc_self_fd() -> io::Result<BorrowedFd<'static>> {
+ static PROC_SELF_FD: StaticFd = StaticFd::new();
+
+ // The init function here may run multiple times; see above.
+ PROC_SELF_FD
+ .get_or_try_init(|| {
+ let (_, proc_stat) = proc()?;
+
+ let (proc_self, _proc_self_stat) = proc_self()?;
+
+ // Open "/proc/self/fd".
+ let proc_self_fd = proc_opendirat(proc_self, cstr!("fd"))?;
+ let proc_self_fd_stat =
+ check_proc_entry(Kind::Fd, proc_self_fd.as_fd(), Some(proc_stat))
+ .map_err(|_err| io::Errno::NOTSUP)?;
+
+ Ok(new_static_fd(proc_self_fd, proc_self_fd_stat))
+ })
+ .map(|(owned, _stat)| owned.as_fd())
+}
+
+type StaticFd = OnceCell<(OwnedFd, Stat)>;
+
+#[inline]
+fn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) {
+ (fd, stat)
+}
+
+/// Returns a handle to Linux's `/proc/self/fdinfo` directory.
+///
+/// This ensures that `/proc/self/fdinfo` is `procfs`, that nothing is mounted
+/// on top of it, and that it looks normal. It also returns the `Stat` of
+/// `/proc/self/fd`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
+ static PROC_SELF_FDINFO: StaticFd = StaticFd::new();
+
+ PROC_SELF_FDINFO
+ .get_or_try_init(|| {
+ let (_, proc_stat) = proc()?;
+
+ let (proc_self, _proc_self_stat) = proc_self()?;
+
+ // Open "/proc/self/fdinfo".
+ let proc_self_fdinfo = proc_opendirat(proc_self, cstr!("fdinfo"))?;
+ let proc_self_fdinfo_stat =
+ check_proc_entry(Kind::Fd, proc_self_fdinfo.as_fd(), Some(proc_stat))
+ .map_err(|_err| io::Errno::NOTSUP)?;
+
+ Ok((proc_self_fdinfo, proc_self_fdinfo_stat))
+ })
+ .map(|(owned, stat)| (owned.as_fd(), stat))
+}
+
+/// Returns a handle to a Linux `/proc/self/fdinfo/<fd>` file.
+///
+/// This ensures that `/proc/self/fdinfo/<fd>` is `procfs`, that nothing is
+/// mounted on top of it, and that it looks normal.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+#[inline]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+pub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
+ _proc_self_fdinfo(fd.as_fd())
+}
+
+fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
+ let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?;
+ let fd_str = DecInt::from_fd(fd);
+ open_and_check_file(
+ proc_self_fdinfo,
+ proc_self_fdinfo_stat,
+ fd_str.as_c_str(),
+ Kind::File,
+ )
+}
+
+/// Returns a handle to a Linux `/proc/self/pagemap` file.
+///
+/// This ensures that `/proc/self/pagemap` is `procfs`, that nothing is
+/// mounted on top of it, and that it looks normal.
+///
+/// # References
+/// - [Linux]
+/// - [Linux pagemap]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+/// [Linux pagemap]: https://www.kernel.org/doc/Documentation/vm/pagemap.txt
+#[inline]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+pub fn proc_self_pagemap() -> io::Result<OwnedFd> {
+ proc_self_file(cstr!("pagemap"))
+}
+
+/// Returns a handle to a Linux `/proc/self/maps` file.
+///
+/// This ensures that `/proc/self/maps` is `procfs`, that nothing is
+/// mounted on top of it, and that it looks normal.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+#[inline]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+pub fn proc_self_maps() -> io::Result<OwnedFd> {
+ proc_self_file(cstr!("maps"))
+}
+
+/// Returns a handle to a Linux `/proc/self/status` file.
+///
+/// This ensures that `/proc/self/status` is `procfs`, that nothing is
+/// mounted on top of it, and that it looks normal.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
+#[inline]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+pub fn proc_self_status() -> io::Result<OwnedFd> {
+ proc_self_file(cstr!("status"))
+}
+
+/// Open a file under `/proc/self`.
+fn proc_self_file(name: &CStr) -> io::Result<OwnedFd> {
+ let (proc_self, proc_self_stat) = proc_self()?;
+ open_and_check_file(proc_self, proc_self_stat, name, Kind::File)
+}
+
+/// Open a procfs file within in `dir` and check it for bind mounts.
+fn open_and_check_file(
+ dir: BorrowedFd<'_>,
+ dir_stat: &Stat,
+ name: &CStr,
+ kind: Kind,
+) -> io::Result<OwnedFd> {
+ let (_, proc_stat) = proc()?;
+
+ // Don't use `NOATIME`, because it [requires us to own the file], and when
+ // a process sets itself non-dumpable Linux changes the user:group of its
+ // `/proc/<pid>` files [to root:root].
+ //
+ // [requires us to own the file]: https://man7.org/linux/man-pages/man2/openat.2.html
+ // [to root:root]: https://man7.org/linux/man-pages/man5/proc.5.html
+ let mut oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY;
+ if let Kind::Symlink = kind {
+ // Open symlinks with `O_PATH`.
+ oflags |= OFlags::PATH;
+ }
+ let file = openat(dir, name, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?;
+ let file_stat = fstat(&file)?;
+
+ // `is_mountpoint` only works on directory mount points, not file mount
+ // points. To detect file mount points, scan the parent directory to see if
+ // we can find a regular file with an inode and name that matches the file
+ // we just opened. If we can't find it, there could be a file bind mount on
+ // top of the file we want.
+ //
+ // TODO: With Linux 5.8 we might be able to use `statx` and
+ // `STATX_ATTR_MOUNT_ROOT` to detect mountpoints directly instead of doing
+ // this scanning.
+
+ let expected_type = match kind {
+ Kind::File => FileType::RegularFile,
+ Kind::Symlink => FileType::Symlink,
+ _ => unreachable!(),
+ };
+
+ let mut found_file = false;
+ let mut found_dot = false;
+
+ // Position the directory iteration at the start.
+ seek(dir, SeekFrom::Start(0))?;
+
+ let mut buf = [MaybeUninit::uninit(); 2048];
+ let mut iter = RawDir::new(dir, &mut buf);
+ while let Some(entry) = iter.next() {
+ let entry = entry.map_err(|_err| io::Errno::NOTSUP)?;
+ if entry.ino() == file_stat.st_ino
+ && entry.file_type() == expected_type
+ && entry.file_name() == name
+ {
+ // We found the file. Proceed to check the file handle.
+ let _ = check_proc_entry_with_stat(kind, file.as_fd(), file_stat, Some(proc_stat))?;
+
+ found_file = true;
+ } else if entry.ino() == dir_stat.st_ino
+ && entry.file_type() == FileType::Directory
+ && entry.file_name() == cstr!(".")
+ {
+ // We found ".", and it's the right ".".
+ found_dot = true;
+ }
+ }
+
+ if found_file && found_dot {
+ Ok(file)
+ } else {
+ Err(io::Errno::NOTSUP)
+ }
+}
diff --git a/src/pty.rs b/src/pty.rs
new file mode 100644
index 0000000..926ebf7
--- /dev/null
+++ b/src/pty.rs
@@ -0,0 +1,207 @@
+//! Pseudoterminal operations.
+//!
+//! For the `openpty` and `login_tty` functions, see the
+//! [rustix-openpty crate].
+//!
+//! [rustix-openpty crate]: https://crates.io/crates/rustix-openpty
+
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+use crate::fd::{AsFd, OwnedFd};
+use crate::fs::OFlags;
+use crate::{backend, io};
+#[cfg(all(
+ feature = "alloc",
+ any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia")
+))]
+use {crate::ffi::CString, alloc::vec::Vec};
+
+#[cfg(target_os = "linux")]
+use crate::{fd::FromRawFd, ioctl};
+
+bitflags::bitflags! {
+ /// `O_*` flags for use with [`openpt`] and [`ioctl_tiocgptpeer`].
+ ///
+ /// [`ioctl_tiocgptpeer`]: https://docs.rs/rustix/*/x86_64-unknown-linux-gnu/rustix/pty/fn.ioctl_tiocgptpeer.html
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct OpenptFlags: u32 {
+ /// `O_RDWR`
+ const RDWR = c::O_RDWR as c::c_uint;
+
+ /// `O_NOCTTY`
+ #[cfg(not(any(target_os = "espidf", target_os = "l4re", target_os = "redox", target_os = "vita")))]
+ const NOCTTY = c::O_NOCTTY as c::c_uint;
+
+ /// `O_CLOEXEC`
+ ///
+ /// The standard `posix_openpt` function doesn't support `CLOEXEC`, but
+ /// rustix supports it on Linux, and FreeBSD and NetBSD support it.
+ #[cfg(any(linux_kernel, target_os = "freebsd", target_os = "netbsd"))]
+ const CLOEXEC = c::O_CLOEXEC as c::c_uint;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+impl From<OpenptFlags> for OFlags {
+ #[inline]
+ fn from(flags: OpenptFlags) -> Self {
+ // `OpenptFlags` is a subset of `OFlags`.
+ Self::from_bits_retain(flags.bits() as _)
+ }
+}
+
+/// `posix_openpt(flags)`—Open a pseudoterminal device.
+///
+/// On Linux, an additional `CLOEXEC` flag value may be passed to request the
+/// close-on-exec flag be set.
+///
+/// On Linux, if the system has no free pseudoterminals available, the
+/// underlying system call fails with [`io::Errno::NOSPC`], however this rustix
+/// function translates that to [`io::Errno::AGAIN`], so that the linux_raw and
+/// libc backends have the same behavior.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [FreeBSD]
+/// - [DragonFly BSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/posix_openpt.3.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/posix_openpt.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=posix_openpt&sektion=2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=posix_openpt&section=3
+/// [NetBSD]: https://man.netbsd.org/posix_openpt.3
+/// [OpenBSD]: https://man.openbsd.org/posix_openpt
+/// [illumos]: https://illumos.org/man/3C/posix_openpt
+#[inline]
+#[doc(alias = "posix_openpt")]
+pub fn openpt(flags: OpenptFlags) -> io::Result<OwnedFd> {
+ // On Linux, open the device ourselves so that we can support `CLOEXEC`.
+ #[cfg(linux_kernel)]
+ {
+ use crate::fs::{open, Mode};
+ match open(cstr!("/dev/ptmx"), flags.into(), Mode::empty()) {
+ // Match libc `openat` behavior with `ENOSPC`.
+ Err(io::Errno::NOSPC) => Err(io::Errno::AGAIN),
+ otherwise => otherwise,
+ }
+ }
+
+ // On all other platforms, use `openpt`.
+ #[cfg(not(linux_kernel))]
+ {
+ backend::pty::syscalls::openpt(flags)
+ }
+}
+
+/// `ptsname(fd)`—Return the name of a pseudoterminal.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/ptsname.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/ptsname.3.html
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Allocation.html#index-ptsname
+#[cfg(all(
+ feature = "alloc",
+ any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia")
+))]
+#[inline]
+#[doc(alias = "ptsname_r")]
+pub fn ptsname<Fd: AsFd, B: Into<Vec<u8>>>(fd: Fd, reuse: B) -> io::Result<CString> {
+ backend::pty::syscalls::ptsname(fd.as_fd(), reuse.into())
+}
+
+/// `unlockpt(fd)`—Unlock a pseudoterminal.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/unlockpt.3.html
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Allocation.html#index-unlockpt
+#[inline]
+pub fn unlockpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::pty::syscalls::unlockpt(fd.as_fd())
+}
+
+/// `grantpt(fd)`—Grant access to the user side of a pseudoterminal.
+///
+/// On Linux, calling this function has no effect, as the kernel is expected to
+/// grant the appropriate access. On all other platorms, this function has
+/// unspecified behavior if the calling process has a [`Signal::Child`] signal
+/// handler installed.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/grantpt.3.html
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Allocation.html#index-grantpt
+/// [`Signal::Child`]: crate::process::Signal::Child
+#[inline]
+pub fn grantpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ #[cfg(not(linux_kernel))]
+ {
+ backend::pty::syscalls::grantpt(fd.as_fd())
+ }
+
+ // On Linux, we assume the kernel has already granted the needed
+ // permissions to the user side of the pseudoterminal.
+ #[cfg(linux_kernel)]
+ {
+ let _ = fd;
+ Ok(())
+ }
+}
+
+/// `ioctl(fd, TIOCGPTPEER)`—Open the user side of a pseduoterminal.
+///
+/// This function is currently only implemented on Linux.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/ioctl_tty.2.html
+#[cfg(target_os = "linux")]
+#[inline]
+pub fn ioctl_tiocgptpeer<Fd: AsFd>(fd: Fd, flags: OpenptFlags) -> io::Result<OwnedFd> {
+ unsafe { ioctl::ioctl(fd, Tiocgptpeer(flags)) }
+}
+
+#[cfg(target_os = "linux")]
+struct Tiocgptpeer(OpenptFlags);
+
+#[cfg(target_os = "linux")]
+unsafe impl ioctl::Ioctl for Tiocgptpeer {
+ type Output = OwnedFd;
+
+ const IS_MUTATING: bool = false;
+ const OPCODE: ioctl::Opcode = ioctl::Opcode::old(c::TIOCGPTPEER as ioctl::RawOpcode);
+
+ fn as_ptr(&mut self) -> *mut c::c_void {
+ self.0.bits() as *mut c::c_void
+ }
+
+ unsafe fn output_from_ptr(
+ ret: ioctl::IoctlOutput,
+ _arg: *mut c::c_void,
+ ) -> io::Result<Self::Output> {
+ Ok(OwnedFd::from_raw_fd(ret))
+ }
+}
diff --git a/src/rand/getrandom.rs b/src/rand/getrandom.rs
new file mode 100644
index 0000000..c7f117a
--- /dev/null
+++ b/src/rand/getrandom.rs
@@ -0,0 +1,43 @@
+#![allow(unsafe_code)]
+
+use crate::buffer::split_init;
+use crate::{backend, io};
+use core::mem::MaybeUninit;
+
+pub use backend::rand::types::GetRandomFlags;
+
+/// `getrandom(buf, flags)`—Reads a sequence of random bytes.
+///
+/// This is a very low-level API which may be difficult to use correctly. Most
+/// users should prefer to use [`getrandom`] or [`rand`] APIs instead.
+///
+/// [`getrandom`]: https://crates.io/crates/getrandom
+/// [`rand`]: https://crates.io/crates/rand
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/getrandom.2.html
+#[inline]
+pub fn getrandom(buf: &mut [u8], flags: GetRandomFlags) -> io::Result<usize> {
+ unsafe { backend::rand::syscalls::getrandom(buf.as_mut_ptr(), buf.len(), flags) }
+}
+
+/// `getrandom(buf, flags)`—Reads a sequence of random bytes.
+///
+/// This is identical to [`getrandom`], except that it can read into
+/// uninitialized memory. It returns the slice that was initialized by this
+/// function and the slice that remains uninitialized.
+#[inline]
+pub fn getrandom_uninit(
+ buf: &mut [MaybeUninit<u8>],
+ flags: GetRandomFlags,
+) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
+ // Get number of initialized bytes.
+ let length = unsafe {
+ backend::rand::syscalls::getrandom(buf.as_mut_ptr() as *mut u8, buf.len(), flags)
+ };
+
+ // Split into the initialized and uninitialized portions.
+ Ok(unsafe { split_init(buf, length?) })
+}
diff --git a/src/rand/mod.rs b/src/rand/mod.rs
new file mode 100644
index 0000000..ec4c112
--- /dev/null
+++ b/src/rand/mod.rs
@@ -0,0 +1,7 @@
+//! Random-related operations.
+
+#[cfg(linux_kernel)]
+mod getrandom;
+
+#[cfg(linux_kernel)]
+pub use getrandom::{getrandom, getrandom_uninit, GetRandomFlags};
diff --git a/src/runtime.rs b/src/runtime.rs
new file mode 100644
index 0000000..bd3eed2
--- /dev/null
+++ b/src/runtime.rs
@@ -0,0 +1,581 @@
+//! Experimental low-level implementation details for libc-like runtime
+//! libraries such as [Origin].
+//!
+//! Do not use the functions in this module unless you've read all of their
+//! code. They don't always behave the same way as functions with similar names
+//! in `libc`. Sometimes information about the differences is included in the
+//! Linux documentation under “C library/kernel differences” sections. And, if
+//! there is a libc in the process, these functions may have surprising
+//! interactions with it.
+//!
+//! These functions are for implementing thread-local storage (TLS), managing
+//! threads, loaded libraries, and other process-wide resources. Most of
+//! `rustix` doesn't care about what other libraries are linked into the
+//! program or what they're doing, but the features in this module generally
+//! can only be used by one entity within a process.
+//!
+//! The API for these functions is not stable, and this module is
+//! `doc(hidden)`.
+//!
+//! [Origin]: https://github.com/sunfishcode/origin#readme
+//!
+//! # Safety
+//!
+//! This module is intended to be used for implementing a runtime library such
+//! as libc. Use of these features for any other purpose is likely to create
+//! serious problems.
+#![allow(unsafe_code)]
+
+use crate::backend;
+#[cfg(linux_raw)]
+use crate::ffi::CStr;
+#[cfg(linux_raw)]
+#[cfg(feature = "fs")]
+use crate::fs::AtFlags;
+#[cfg(linux_raw)]
+use crate::io;
+#[cfg(linux_raw)]
+use crate::pid::Pid;
+#[cfg(linux_raw)]
+#[cfg(feature = "fs")]
+use backend::fd::AsFd;
+#[cfg(linux_raw)]
+use core::ffi::c_void;
+
+#[cfg(linux_raw)]
+pub use crate::signal::Signal;
+
+/// `sigaction`
+#[cfg(linux_raw)]
+pub type Sigaction = linux_raw_sys::general::kernel_sigaction;
+
+/// `stack_t`
+#[cfg(linux_raw)]
+pub type Stack = linux_raw_sys::general::stack_t;
+
+/// `sigset_t`
+#[cfg(linux_raw)]
+pub type Sigset = linux_raw_sys::general::kernel_sigset_t;
+
+/// `siginfo_t`
+#[cfg(linux_raw)]
+pub type Siginfo = linux_raw_sys::general::siginfo_t;
+
+pub use crate::timespec::{Nsecs, Secs, Timespec};
+
+/// `SIG_*` constants for use with [`sigprocmask`].
+#[cfg(linux_raw)]
+#[repr(u32)]
+pub enum How {
+ /// `SIG_BLOCK`
+ BLOCK = linux_raw_sys::general::SIG_BLOCK,
+
+ /// `SIG_UNBLOCK`
+ UNBLOCK = linux_raw_sys::general::SIG_UNBLOCK,
+
+ /// `SIG_SETMASK`
+ SETMASK = linux_raw_sys::general::SIG_SETMASK,
+}
+
+#[cfg(target_arch = "x86")]
+#[inline]
+pub unsafe fn set_thread_area(u_info: &mut UserDesc) -> io::Result<()> {
+ backend::runtime::syscalls::tls::set_thread_area(u_info)
+}
+
+#[cfg(target_arch = "arm")]
+#[inline]
+pub unsafe fn arm_set_tls(data: *mut c_void) -> io::Result<()> {
+ backend::runtime::syscalls::tls::arm_set_tls(data)
+}
+
+/// `prctl(PR_SET_FS, data)`—Set the x86-64 `fs` register.
+///
+/// # Safety
+///
+/// This is a very low-level feature for implementing threading libraries.
+/// See the references links above.
+#[cfg(target_arch = "x86_64")]
+#[inline]
+pub unsafe fn set_fs(data: *mut c_void) {
+ backend::runtime::syscalls::tls::set_fs(data)
+}
+
+/// Set the x86-64 thread ID address.
+///
+/// # Safety
+///
+/// This is a very low-level feature for implementing threading libraries.
+/// See the references links above.
+#[inline]
+pub unsafe fn set_tid_address(data: *mut c_void) -> Pid {
+ backend::runtime::syscalls::tls::set_tid_address(data)
+}
+
+#[cfg(linux_raw)]
+#[cfg(target_arch = "x86")]
+pub use backend::runtime::tls::UserDesc;
+
+/// `syscall(SYS_exit, status)`—Exit the current thread.
+///
+/// # Safety
+///
+/// This is a very low-level feature for implementing threading libraries.
+#[inline]
+pub unsafe fn exit_thread(status: i32) -> ! {
+ backend::runtime::syscalls::tls::exit_thread(status)
+}
+
+/// Exit all the threads in the current process' thread group.
+///
+/// This is equivalent to `_exit` and `_Exit` in libc.
+///
+/// This does not call any `__cxa_atexit`, `atexit`, or any other destructors.
+/// Most programs should use [`std::process::exit`] instead of calling this
+/// directly.
+///
+/// # References
+/// - [POSIX `_Exit`]
+/// - [Linux `exit_group`]
+/// - [Linux `_Exit`]
+///
+/// [POSIX `_Exit`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/_Exit.html
+/// [Linux `exit_group`]: https://man7.org/linux/man-pages/man2/exit_group.2.html
+/// [Linux `_Exit`]: https://man7.org/linux/man-pages/man2/_Exit.2.html
+#[doc(alias = "_exit")]
+#[doc(alias = "_Exit")]
+#[inline]
+pub fn exit_group(status: i32) -> ! {
+ backend::runtime::syscalls::exit_group(status)
+}
+
+/// `EXIT_SUCCESS` for use with [`exit_group`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdlib.h.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/exit.3.html
+pub const EXIT_SUCCESS: i32 = backend::c::EXIT_SUCCESS;
+
+/// `EXIT_FAILURE` for use with [`exit_group`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdlib.h.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/exit.3.html
+pub const EXIT_FAILURE: i32 = backend::c::EXIT_FAILURE;
+
+/// Return fields from the main executable segment headers ("phdrs") relevant
+/// to initializing TLS provided to the program at startup.
+///
+/// `addr` will always be non-null, even when the TLS data is absent, so that
+/// the `addr` and `file_size` parameters are suitable for creating a slice
+/// with `slice::from_raw_parts`.
+#[inline]
+pub fn startup_tls_info() -> StartupTlsInfo {
+ backend::runtime::tls::startup_tls_info()
+}
+
+/// `(getauxval(AT_PHDR), getauxval(AT_PHENT), getauxval(AT_PHNUM))`—Returns
+/// the address, ELF segment header size, and number of ELF segment headers for
+/// the main executable.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/getauxval.3.html
+#[inline]
+pub fn exe_phdrs() -> (*const c_void, usize, usize) {
+ backend::param::auxv::exe_phdrs()
+}
+
+/// `getauxval(AT_ENTRY)`—Returns the address of the program entrypoint.
+///
+/// Most code interested in the program entrypoint address should instead use a
+/// symbol reference to `_start`. That will be properly PC-relative or
+/// relocated if needed, and will come with appropriate pointer type and
+/// pointer provenance.
+///
+/// This function is intended only for use in code that implements those
+/// relocations, to compute the ASLR offset. It has type `usize`, so it doesn't
+/// carry any provenance, and it shouldn't be used to dereference memory.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/getauxval.3.html
+#[inline]
+pub fn entry() -> usize {
+ backend::param::auxv::entry()
+}
+
+/// `getauxval(AT_RANDOM)`—Returns the address of 16 pseudorandom bytes.
+///
+/// These bytes are for use by libc. For anything else, use the `rand` crate.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/getauxval.3.html
+#[inline]
+pub fn random() -> *const [u8; 16] {
+ backend::param::auxv::random()
+}
+
+#[cfg(linux_raw)]
+pub use backend::runtime::tls::StartupTlsInfo;
+
+/// `fork()`—Creates a new process by duplicating the calling process.
+///
+/// On success, the pid of the child process is returned in the parent, and
+/// `None` is returned in the child.
+///
+/// Unlike its POSIX and libc counterparts, this `fork` does not invoke any
+/// handlers (such as those registered with `pthread_atfork`).
+///
+/// The program environment in the child after a `fork` and before an `execve`
+/// is very special. All code that executes in this environment must avoid:
+///
+/// - Acquiring any other locks that are held in other threads on the parent
+/// at the time of the `fork`, as the child only contains one thread, and
+/// attempting to acquire such locks will deadlock (though this is [not
+/// considered unsafe]).
+///
+/// - Performing any dynamic allocation using the global allocator, since
+/// global allocators may use locks to ensure thread safety, and their locks
+/// may not be released in the child process, so attempts to allocate may
+/// deadlock (as described in the previous point).
+///
+/// - Accessing any external state which the parent assumes it has exclusive
+/// access to, such as a file protected by a file lock, as this could
+/// corrupt the external state.
+///
+/// - Accessing any random-number-generator state inherited from the parent,
+/// as the parent may have the same state and generate the same random
+/// numbers, which may violate security invariants.
+///
+/// - Accessing any thread runtime state, since this function does not update
+/// the thread id in the thread runtime, so thread runtime functions could
+/// cause undefined behavior.
+///
+/// - Accessing any memory shared with the parent, such as a [`MAP_SHARED`]
+/// mapping, even with anonymous or [`memfd_create`] mappings, as this could
+/// cause undefined behavior.
+///
+/// - Calling any C function which isn't known to be [async-signal-safe], as
+/// that could cause undefined behavior. The extent to which this also
+/// applies to Rust functions is unclear at this time.
+///
+/// - And more.
+///
+/// # Safety
+///
+/// The child must avoid accessing any memory shared with the parent in a
+/// way that invokes undefined behavior. It must avoid accessing any threading
+/// runtime functions in a way that invokes undefined behavior. And it must
+/// avoid invoking any undefined behavior through any function that is not
+/// guaranteed to be async-signal-safe. But, what does async-signal-safe even
+/// mean in a Rust program? This documentation does not have all the answers.
+///
+/// So you're on your own. And on top of all the troubles with `fork` in
+/// general, this wrapper implementation is highly experimental.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// # Literary interlude
+///
+/// > Do not jump on ancient uncles.
+/// > Do not yell at average mice.
+/// > Do not wear a broom to breakfast.
+/// > Do not ask a snake’s advice.
+/// > Do not bathe in chocolate pudding.
+/// > Do not talk to bearded bears.
+/// > Do not smoke cigars on sofas.
+/// > Do not dance on velvet chairs.
+/// > Do not take a whale to visit
+/// > Russell’s mother’s cousin’s yacht.
+/// > And whatever else you do do
+/// > It is better you
+/// > Do not.
+///
+/// - “Rules”, by Karla Kuskin
+///
+/// [`MAP_SHARED`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html
+/// [not considered unsafe]: https://doc.rust-lang.org/reference/behavior-not-considered-unsafe.html#deadlocks
+/// [`memfd_create`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/fork.2.html
+/// [async-signal-safe]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+pub unsafe fn fork() -> io::Result<Fork> {
+ backend::runtime::syscalls::fork()
+}
+
+/// Regular Unix `fork` doesn't tell the child its own PID because it assumes
+/// the child can just do `getpid`. That's true, but it's more fun if it
+/// doesn't have to.
+pub enum Fork {
+ Child(Pid),
+ Parent(Pid),
+}
+
+/// `execveat(dirfd, path.as_c_str(), argv, envp, flags)`—Execute a new
+/// command using the current process.
+///
+/// # Safety
+///
+/// The `argv` and `envp` pointers must point to NUL-terminated arrays, and
+/// their contents must be pointers to NUL-terminated byte arrays.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/execveat.2.html
+#[inline]
+#[cfg(feature = "fs")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "fs")))]
+pub unsafe fn execveat<Fd: AsFd>(
+ dirfd: Fd,
+ path: &CStr,
+ argv: *const *const u8,
+ envp: *const *const u8,
+ flags: AtFlags,
+) -> io::Errno {
+ backend::runtime::syscalls::execveat(dirfd.as_fd(), path, argv, envp, flags)
+}
+
+/// `execve(path.as_c_str(), argv, envp)`—Execute a new command using the
+/// current process.
+///
+/// # Safety
+///
+/// The `argv` and `envp` pointers must point to NUL-terminated arrays, and
+/// their contents must be pointers to NUL-terminated byte arrays.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/execve.2.html
+#[inline]
+pub unsafe fn execve(path: &CStr, argv: *const *const u8, envp: *const *const u8) -> io::Errno {
+ backend::runtime::syscalls::execve(path, argv, envp)
+}
+
+/// `sigaction(signal, &new, &old)`—Modify or query a signal handler.
+///
+/// # Safety
+///
+/// You're on your own. And on top of all the troubles with signal handlers,
+/// this implementation is highly experimental. Even further, it differs from
+/// the libc `sigaction` in several non-obvious and unsafe ways.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaction.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sigaction.2.html
+#[inline]
+pub unsafe fn sigaction(signal: Signal, new: Option<Sigaction>) -> io::Result<Sigaction> {
+ backend::runtime::syscalls::sigaction(signal, new)
+}
+
+/// `sigaltstack(new, old)`—Modify or query a signal stack.
+///
+/// # Safety
+///
+/// You're on your own. And on top of all the troubles with signal handlers,
+/// this implementation is highly experimental.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaltstack.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/sigaltstack.2.html
+#[inline]
+pub unsafe fn sigaltstack(new: Option<Stack>) -> io::Result<Stack> {
+ backend::runtime::syscalls::sigaltstack(new)
+}
+
+/// `tkill(tid, sig)`—Send a signal to a thread.
+///
+/// # Safety
+///
+/// You're on your own. And on top of all the troubles with signal handlers,
+/// this implementation is highly experimental. The warning about the hazard
+/// of recycled thread ID's applies.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/tkill.2.html
+#[inline]
+pub unsafe fn tkill(tid: Pid, sig: Signal) -> io::Result<()> {
+ backend::runtime::syscalls::tkill(tid, sig)
+}
+
+/// `sigprocmask(how, set, oldset)`—Adjust the process signal mask.
+///
+/// # Safety
+///
+/// You're on your own. And on top of all the troubles with signal handlers,
+/// this implementation is highly experimental. Even further, it differs from
+/// the libc `sigprocmask` in several non-obvious and unsafe ways.
+///
+/// # References
+/// - [Linux `sigprocmask`]
+/// - [Linux `pthread_sigmask`]
+///
+/// [Linux `sigprocmask`]: https://man7.org/linux/man-pages/man2/sigprocmask.2.html
+/// [Linux `pthread_sigmask`]: https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html
+#[inline]
+#[doc(alias = "pthread_sigmask")]
+pub unsafe fn sigprocmask(how: How, set: Option<&Sigset>) -> io::Result<Sigset> {
+ backend::runtime::syscalls::sigprocmask(how, set)
+}
+
+/// `sigpending()`—Query the pending signals.
+///
+/// # References
+/// - [Linux `sigpending`]
+///
+/// [Linux `sigpending`]: https://man7.org/linux/man-pages/man2/sigpending.2.html
+#[inline]
+pub fn sigpending() -> Sigset {
+ backend::runtime::syscalls::sigpending()
+}
+
+/// `sigsuspend(set)`—Suspend the calling thread and wait for signals.
+///
+/// # References
+/// - [Linux `sigsuspend`]
+///
+/// [Linux `sigsuspend`]: https://man7.org/linux/man-pages/man2/sigsuspend.2.html
+#[inline]
+pub fn sigsuspend(set: &Sigset) -> io::Result<()> {
+ backend::runtime::syscalls::sigsuspend(set)
+}
+
+/// `sigwait(set)`—Wait for signals.
+///
+/// # Safety
+///
+/// If code elsewhere in the process is depending on delivery of a signal to
+/// prevent it from executing some code, this could cause it to miss that
+/// signal and execute that code.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/sigwait.3.html
+#[inline]
+pub unsafe fn sigwait(set: &Sigset) -> io::Result<Signal> {
+ backend::runtime::syscalls::sigwait(set)
+}
+
+/// `sigwaitinfo(set)`—Wait for signals, returning a [`Siginfo`].
+///
+/// # Safety
+///
+/// If code elsewhere in the process is depending on delivery of a signal to
+/// prevent it from executing some code, this could cause it to miss that
+/// signal and execute that code.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sigwaitinfo.2.html
+#[inline]
+pub unsafe fn sigwaitinfo(set: &Sigset) -> io::Result<Siginfo> {
+ backend::runtime::syscalls::sigwaitinfo(set)
+}
+
+/// `sigtimedwait(set)`—Wait for signals, optionally with a timeout.
+///
+/// # Safety
+///
+/// If code elsewhere in the process is depending on delivery of a signal to
+/// prevent it from executing some code, this could cause it to miss that
+/// signal and execute that code.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sigtimedwait.2.html
+#[inline]
+pub unsafe fn sigtimedwait(set: &Sigset, timeout: Option<Timespec>) -> io::Result<Siginfo> {
+ backend::runtime::syscalls::sigtimedwait(set, timeout)
+}
+
+/// `getauxval(AT_SECURE)`—Returns the Linux “secure execution” mode.
+///
+/// Return a boolean value indicating whether “secure execution” mode was
+/// requested, due to the process having elevated privileges. This includes
+/// whether the `AT_SECURE` AUX value is set, and whether the initial real UID
+/// and GID differ from the initial effective UID and GID.
+///
+/// The meaning of “secure execution” mode is beyond the scope of this
+/// comment.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man3/getauxval.3.html
+#[cfg(any(
+ linux_raw,
+ any(
+ all(target_os = "android", target_pointer_width = "64"),
+ target_os = "linux",
+ )
+))]
+#[inline]
+pub fn linux_secure() -> bool {
+ backend::param::auxv::linux_secure()
+}
+
+/// `brk(addr)`—Change the location of the “program break”.
+///
+/// # Safety
+///
+/// This is not identical to `brk` in libc. libc `brk` may have bookkeeping
+/// that needs to be kept up to date that this doesn't keep up to date, so
+/// don't use it unless you are implementing libc.
+#[cfg(linux_raw)]
+#[inline]
+pub unsafe fn brk(addr: *mut c_void) -> io::Result<*mut c_void> {
+ backend::runtime::syscalls::brk(addr)
+}
+
+/// `__SIGRTMIN`—The start of the realtime signal range.
+///
+/// This is the raw `SIGRTMIN` value from the OS, which is not the same as the
+/// `SIGRTMIN` macro provided by libc. Don't use this unless you are
+/// implementing libc.
+#[cfg(linux_raw)]
+pub const SIGRTMIN: u32 = linux_raw_sys::general::SIGRTMIN;
+
+/// `__SIGRTMAX`—The last of the realtime signal range.
+///
+/// This is the raw `SIGRTMAX` value from the OS, which is not the same as the
+/// `SIGRTMAX` macro provided by libc. Don't use this unless you are
+/// implementing libc.
+#[cfg(linux_raw)]
+pub const SIGRTMAX: u32 = {
+ // Use the actual `SIGRTMAX` value on platforms which define it.
+ #[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
+ {
+ linux_raw_sys::general::SIGRTMAX
+ }
+
+ // On platfoms that don't, derive it from `_NSIG`.
+ #[cfg(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64"))]
+ {
+ linux_raw_sys::general::_NSIG - 1
+ }
+};
diff --git a/src/shm.rs b/src/shm.rs
new file mode 100644
index 0000000..450b6fc
--- /dev/null
+++ b/src/shm.rs
@@ -0,0 +1,40 @@
+//! POSIX shared memory
+
+use crate::fd::OwnedFd;
+use crate::{backend, io, path};
+
+pub use crate::backend::fs::types::Mode;
+pub use crate::backend::shm::types::ShmOFlags;
+
+/// `shm_open(name, oflags, mode)`—Opens a shared memory object.
+///
+/// For portability, `name` should begin with a slash, contain no other
+/// slashes, and be no longer than an implementation-defined limit (255 on
+/// Linux).
+///
+/// Exactly one of [`ShmOFlags::RDONLY`] and [`ShmOFlags::RDWR`] should be
+/// passed. The file descriptor will be opened with `FD_CLOEXEC` set.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/shm_open.3.html
+#[inline]
+pub fn shm_open<P: path::Arg>(name: P, flags: ShmOFlags, mode: Mode) -> io::Result<OwnedFd> {
+ name.into_with_c_str(|name| backend::shm::syscalls::shm_open(name, flags, mode))
+}
+
+/// `shm_unlink(name)`—Unlinks a shared memory object.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/shm_unlink.3.html
+#[inline]
+pub fn shm_unlink<P: path::Arg>(name: P) -> io::Result<()> {
+ name.into_with_c_str(backend::shm::syscalls::shm_unlink)
+}
diff --git a/src/signal.rs b/src/signal.rs
new file mode 100644
index 0000000..e55126f
--- /dev/null
+++ b/src/signal.rs
@@ -0,0 +1,250 @@
+use crate::backend::c;
+
+/// A signal number for use with [`kill_process`], [`kill_process_group`],
+/// and [`kill_current_process_group`].
+///
+/// [`kill_process`]: crate::process::kill_process
+/// [`kill_process_group`]: crate::process::kill_process_group
+/// [`kill_current_process_group`]: crate::process::kill_current_process_group
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum Signal {
+ /// `SIGHUP`
+ Hup = c::SIGHUP,
+ /// `SIGINT`
+ Int = c::SIGINT,
+ /// `SIGQUIT`
+ Quit = c::SIGQUIT,
+ /// `SIGILL`
+ Ill = c::SIGILL,
+ /// `SIGTRAP`
+ Trap = c::SIGTRAP,
+ /// `SIGABRT`, aka `SIGIOT`
+ #[doc(alias = "Iot")]
+ #[doc(alias = "Abrt")]
+ Abort = c::SIGABRT,
+ /// `SIGBUS`
+ Bus = c::SIGBUS,
+ /// `SIGFPE`
+ Fpe = c::SIGFPE,
+ /// `SIGKILL`
+ Kill = c::SIGKILL,
+ /// `SIGUSR1`
+ #[cfg(not(target_os = "vita"))]
+ Usr1 = c::SIGUSR1,
+ /// `SIGSEGV`
+ Segv = c::SIGSEGV,
+ /// `SIGUSR2`
+ #[cfg(not(target_os = "vita"))]
+ Usr2 = c::SIGUSR2,
+ /// `SIGPIPE`
+ Pipe = c::SIGPIPE,
+ /// `SIGALRM`
+ #[doc(alias = "Alrm")]
+ Alarm = c::SIGALRM,
+ /// `SIGTERM`
+ Term = c::SIGTERM,
+ /// `SIGSTKFLT`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ all(
+ linux_kernel,
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ ),
+ )
+ )))]
+ Stkflt = c::SIGSTKFLT,
+ /// `SIGCHLD`
+ #[cfg(not(target_os = "vita"))]
+ #[doc(alias = "Chld")]
+ Child = c::SIGCHLD,
+ /// `SIGCONT`
+ #[cfg(not(target_os = "vita"))]
+ Cont = c::SIGCONT,
+ /// `SIGSTOP`
+ #[cfg(not(target_os = "vita"))]
+ Stop = c::SIGSTOP,
+ /// `SIGTSTP`
+ #[cfg(not(target_os = "vita"))]
+ Tstp = c::SIGTSTP,
+ /// `SIGTTIN`
+ #[cfg(not(target_os = "vita"))]
+ Ttin = c::SIGTTIN,
+ /// `SIGTTOU`
+ #[cfg(not(target_os = "vita"))]
+ Ttou = c::SIGTTOU,
+ /// `SIGURG`
+ #[cfg(not(target_os = "vita"))]
+ Urg = c::SIGURG,
+ /// `SIGXCPU`
+ #[cfg(not(target_os = "vita"))]
+ Xcpu = c::SIGXCPU,
+ /// `SIGXFSZ`
+ #[cfg(not(target_os = "vita"))]
+ Xfsz = c::SIGXFSZ,
+ /// `SIGVTALRM`
+ #[cfg(not(target_os = "vita"))]
+ #[doc(alias = "Vtalrm")]
+ Vtalarm = c::SIGVTALRM,
+ /// `SIGPROF`
+ #[cfg(not(target_os = "vita"))]
+ Prof = c::SIGPROF,
+ /// `SIGWINCH`
+ #[cfg(not(target_os = "vita"))]
+ Winch = c::SIGWINCH,
+ /// `SIGIO`, aka `SIGPOLL`
+ #[doc(alias = "Poll")]
+ #[cfg(not(any(target_os = "haiku", target_os = "vita")))]
+ Io = c::SIGIO,
+ /// `SIGPWR`
+ #[cfg(not(any(bsd, target_os = "haiku", target_os = "vita")))]
+ #[doc(alias = "Pwr")]
+ Power = c::SIGPWR,
+ /// `SIGSYS`, aka `SIGUNUSED`
+ #[doc(alias = "Unused")]
+ Sys = c::SIGSYS,
+ /// `SIGEMT`
+ #[cfg(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "hermit",
+ all(
+ linux_kernel,
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ )
+ )
+ ))]
+ Emt = c::SIGEMT,
+ /// `SIGINFO`
+ #[cfg(bsd)]
+ Info = c::SIGINFO,
+ /// `SIGTHR`
+ #[cfg(target_os = "freebsd")]
+ #[doc(alias = "Lwp")]
+ Thr = c::SIGTHR,
+ /// `SIGLIBRT`
+ #[cfg(target_os = "freebsd")]
+ Librt = c::SIGLIBRT,
+}
+
+impl Signal {
+ /// Convert a raw signal number into a `Signal`, if possible.
+ pub fn from_raw(sig: c::c_int) -> Option<Self> {
+ match sig {
+ c::SIGHUP => Some(Self::Hup),
+ c::SIGINT => Some(Self::Int),
+ c::SIGQUIT => Some(Self::Quit),
+ c::SIGILL => Some(Self::Ill),
+ c::SIGTRAP => Some(Self::Trap),
+ c::SIGABRT => Some(Self::Abort),
+ c::SIGBUS => Some(Self::Bus),
+ c::SIGFPE => Some(Self::Fpe),
+ c::SIGKILL => Some(Self::Kill),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGUSR1 => Some(Self::Usr1),
+ c::SIGSEGV => Some(Self::Segv),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGUSR2 => Some(Self::Usr2),
+ c::SIGPIPE => Some(Self::Pipe),
+ c::SIGALRM => Some(Self::Alarm),
+ c::SIGTERM => Some(Self::Term),
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita",
+ all(
+ linux_kernel,
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ ),
+ )
+ )))]
+ c::SIGSTKFLT => Some(Self::Stkflt),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGCHLD => Some(Self::Child),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGCONT => Some(Self::Cont),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGSTOP => Some(Self::Stop),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGTSTP => Some(Self::Tstp),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGTTIN => Some(Self::Ttin),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGTTOU => Some(Self::Ttou),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGURG => Some(Self::Urg),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGXCPU => Some(Self::Xcpu),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGXFSZ => Some(Self::Xfsz),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGVTALRM => Some(Self::Vtalarm),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGPROF => Some(Self::Prof),
+ #[cfg(not(target_os = "vita"))]
+ c::SIGWINCH => Some(Self::Winch),
+ #[cfg(not(any(target_os = "haiku", target_os = "vita")))]
+ c::SIGIO => Some(Self::Io),
+ #[cfg(not(any(bsd, target_os = "haiku", target_os = "vita")))]
+ c::SIGPWR => Some(Self::Power),
+ c::SIGSYS => Some(Self::Sys),
+ #[cfg(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "hermit",
+ all(
+ linux_kernel,
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ )
+ )
+ ))]
+ c::SIGEMT => Some(Self::Emt),
+ #[cfg(bsd)]
+ c::SIGINFO => Some(Self::Info),
+ #[cfg(target_os = "freebsd")]
+ c::SIGTHR => Some(Self::Thr),
+ #[cfg(target_os = "freebsd")]
+ c::SIGLIBRT => Some(Self::Librt),
+ _ => None,
+ }
+ }
+}
+
+#[test]
+fn test_sizes() {
+ assert_eq_size!(Signal, c::c_int);
+}
diff --git a/src/static_assertions.rs b/src/static_assertions.rs
new file mode 100644
index 0000000..3d07118
--- /dev/null
+++ b/src/static_assertions.rs
@@ -0,0 +1,36 @@
+//! Workarounds for Rust 1.63 where some things in the `static_assertions`
+//! crate do things that don't work in const contexts. We want to call them in
+//! const contexts in Rust versions where that's supported so that problems are
+//! caught at compile time, and fall back to dynamic asserts in Rust 1.63.
+
+#![allow(unused_macros)]
+
+macro_rules! assert_eq_size {
+ ($x:ty, $y:ty) => {
+ assert_eq!(core::mem::size_of::<$x>(), core::mem::size_of::<$y>());
+ };
+}
+
+macro_rules! assert_eq_align {
+ ($x:ty, $y:ty) => {
+ assert_eq!(core::mem::align_of::<$x>(), core::mem::align_of::<$y>());
+ };
+}
+
+macro_rules! const_assert_eq {
+ ($x:expr, $y:expr) => {
+ assert_eq!($x, $y);
+ };
+}
+
+macro_rules! const_assert_ne {
+ ($x:expr, $y:expr) => {
+ assert_ne!($x, $y);
+ };
+}
+
+macro_rules! const_assert {
+ ($x:expr) => {
+ assert!($x);
+ };
+}
diff --git a/src/stdio.rs b/src/stdio.rs
new file mode 100644
index 0000000..99e118b
--- /dev/null
+++ b/src/stdio.rs
@@ -0,0 +1,511 @@
+//! Functions returning the stdio file descriptors.
+//!
+//! # Safety
+//!
+//! These access the file descriptors by absolute index value, and nothing
+//! prevents them from being closed and reused. They should only be used in
+//! `main` or other situations where one is in control of the process'
+//! stdio streams.
+#![allow(unsafe_code)]
+
+use crate::backend;
+use crate::fd::OwnedFd;
+use backend::c;
+use backend::fd::{BorrowedFd, FromRawFd, RawFd};
+
+#[cfg(not(any(windows, target_os = "wasi")))]
+use {crate::io, backend::fd::AsFd, core::mem::forget};
+
+/// `STDIN_FILENO`—Standard input, borrowed.
+///
+/// In `std`-using configurations, this is a safe function, because the
+/// standard library already assumes that the stdin file descriptor is always
+/// valid. In `no_std` configurations, it is `unsafe`.
+///
+/// # Warning
+///
+/// This function allows reading directly from stdin without coordinating
+/// with the buffering performed by [`std::io::Stdin`], so it could cause
+/// corrupted input.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdin.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdin&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdin.4
+/// [OpenBSD]: https://man.openbsd.org/stdin.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdin&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdin
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdin
+#[cfg(feature = "std")]
+#[doc(alias = "STDIN_FILENO")]
+#[inline]
+pub const fn stdin() -> BorrowedFd<'static> {
+ // SAFETY: When "std" is enabled, the standard library assumes that the
+ // stdio file descriptors are all valid.
+ unsafe { BorrowedFd::borrow_raw(c::STDIN_FILENO as RawFd) }
+}
+
+/// `STDIN_FILENO`—Standard input, borrowed.
+///
+/// In `std`-using configurations, this is a safe function, because the
+/// standard library already assumes that the stdin file descriptor is always
+/// valid. In `no_std` configurations, it is `unsafe`.
+///
+/// # Safety
+///
+/// In `no_std` configurations, the stdin file descriptor can be closed,
+/// potentially on other threads, in which case the file descriptor index
+/// value could be dynamically reused for other purposes, potentially on
+/// different threads.
+///
+/// # Warning
+///
+/// This function allows reading directly from stdin without coordinating
+/// with the buffering performed by [`std::io::Stdin`], so it could cause
+/// corrupted input.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdin.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdin&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdin.4
+/// [OpenBSD]: https://man.openbsd.org/stdin.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdin&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdin
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdin
+#[cfg(not(feature = "std"))]
+#[doc(alias = "STDIN_FILENO")]
+#[inline]
+pub const unsafe fn stdin() -> BorrowedFd<'static> {
+ BorrowedFd::borrow_raw(c::STDIN_FILENO as RawFd)
+}
+
+/// `STDIN_FILENO`—Standard input, owned.
+///
+/// This is similar to [`stdin`], however it returns an `OwnedFd` which closes
+/// standard input when it is dropped.
+///
+/// # Safety
+///
+/// Safe `std`-using Rust code is permitted to assume that the stdin file
+/// descriptor is always valid. This function returns an `OwnedFd` which will
+/// close the stdin file descriptor when dropped.
+///
+/// # Warning
+///
+/// This has the same hazards as [`stdin`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdin.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdin&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdin.4
+/// [OpenBSD]: https://man.openbsd.org/stdin.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdin&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdin
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdin
+#[doc(alias = "STDIN_FILENO")]
+#[inline]
+pub unsafe fn take_stdin() -> OwnedFd {
+ backend::fd::OwnedFd::from_raw_fd(c::STDIN_FILENO as RawFd)
+}
+
+/// `STDOUT_FILENO`—Standard output, borrowed.
+///
+/// In `std`-using configurations, this is a safe function, because the
+/// standard library already assumes that the stdout file descriptor is always
+/// valid. In `no_std` configurations, it is `unsafe`.
+///
+/// # Warning
+///
+/// This function allows writing directly to stdout without coordinating
+/// with the buffering performed by [`std::io::Stdout`], so it could cause
+/// corrupted output.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdout.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdout.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdout&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdout.4
+/// [OpenBSD]: https://man.openbsd.org/stdout.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdout&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdout
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdout
+#[cfg(feature = "std")]
+#[doc(alias = "STDOUT_FILENO")]
+#[inline]
+pub const fn stdout() -> BorrowedFd<'static> {
+ // SAFETY: When "std" is enabled, the standard library assumes that the
+ // stdio file descriptors are all valid.
+ unsafe { BorrowedFd::borrow_raw(c::STDOUT_FILENO as RawFd) }
+}
+
+/// `STDOUT_FILENO`—Standard output, borrowed.
+///
+/// In `std`-using configurations, this is a safe function, because the
+/// standard library already assumes that the stdout file descriptor is always
+/// valid. In `no_std` configurations, it is `unsafe`.
+///
+/// # Safety
+///
+/// In `no_std` configurations, the stdout file descriptor can be closed,
+/// potentially on other threads, in which case the file descriptor index
+/// value could be dynamically reused for other purposes, potentially on
+/// different threads.
+///
+/// # Warning
+///
+/// This function allows writing directly to stdout without coordinating
+/// with the buffering performed by [`std::io::Stdout`], so it could cause
+/// corrupted output.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdout.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdout.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdout&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdout.4
+/// [OpenBSD]: https://man.openbsd.org/stdout.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdout&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdout
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdout
+#[cfg(not(feature = "std"))]
+#[doc(alias = "STDOUT_FILENO")]
+#[inline]
+pub const unsafe fn stdout() -> BorrowedFd<'static> {
+ BorrowedFd::borrow_raw(c::STDOUT_FILENO as RawFd)
+}
+
+/// `STDOUT_FILENO`—Standard output, owned.
+///
+/// This is similar to [`stdout`], however it returns an `OwnedFd` which closes
+/// standard output when it is dropped.
+///
+/// # Safety
+///
+/// Safe `std`-using Rust code is permitted to assume that the stdout file
+/// descriptor is always valid. This function returns an `OwnedFd` which will
+/// close the stdout file descriptor when dropped.
+///
+/// # Warning
+///
+/// This has the same hazards as [`stdout`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdout.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdout.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdout&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdout.4
+/// [OpenBSD]: https://man.openbsd.org/stdout.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdout&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdout
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdout
+#[doc(alias = "STDOUT_FILENO")]
+#[inline]
+pub unsafe fn take_stdout() -> OwnedFd {
+ backend::fd::OwnedFd::from_raw_fd(c::STDOUT_FILENO as RawFd)
+}
+
+/// `STDERR_FILENO`—Standard error, borrowed.
+///
+/// In `std`-using configurations, this is a safe function, because the
+/// standard library already assumes that the stderr file descriptor is always
+/// valid. In `no_std` configurations, it is `unsafe`.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stderr.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stderr.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stderr&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stderr.4
+/// [OpenBSD]: https://man.openbsd.org/stderr.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stderr&section=4
+/// [illumos]: https://illumos.org/man/4FS/stderr
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stderr
+#[cfg(feature = "std")]
+#[doc(alias = "STDERR_FILENO")]
+#[inline]
+pub const fn stderr() -> BorrowedFd<'static> {
+ // SAFETY: When "std" is enabled, the standard library assumes that the
+ // stdio file descriptors are all valid.
+ unsafe { BorrowedFd::borrow_raw(c::STDERR_FILENO as RawFd) }
+}
+
+/// `STDERR_FILENO`—Standard error, borrowed.
+///
+/// In `std`-using configurations, this is a safe function, because the
+/// standard library already assumes that the stderr file descriptor is always
+/// valid. In `no_std` configurations, it is `unsafe`.
+///
+/// # Safety
+///
+/// In `no_std` configurations, the stderr file descriptor can be closed,
+/// potentially on other threads, in which case the file descriptor index
+/// value could be dynamically reused for other purposes, potentially on
+/// different threads.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stderr.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stderr.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stderr&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stderr.4
+/// [OpenBSD]: https://man.openbsd.org/stderr.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stderr&section=4
+/// [illumos]: https://illumos.org/man/4FS/stderr
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stderr
+#[cfg(not(feature = "std"))]
+#[doc(alias = "STDERR_FILENO")]
+#[inline]
+pub const unsafe fn stderr() -> BorrowedFd<'static> {
+ BorrowedFd::borrow_raw(c::STDERR_FILENO as RawFd)
+}
+
+/// `STDERR_FILENO`—Standard error, owned.
+///
+/// This is similar to [`stderr`], however it returns an `OwnedFd` which closes
+/// standard output when it is dropped.
+///
+/// # Safety
+///
+/// Safe std-using Rust code is permitted to assume that the stderr file
+/// descriptor is always valid. This function returns an `OwnedFd` which will
+/// close the stderr file descriptor when dropped.
+///
+/// # Other hazards
+///
+/// This has the same hazards as [`stderr`].
+///
+/// And, when the `OwnedFd` is dropped, subsequent newly created file
+/// descriptors may unknowingly reuse the stderr file descriptor number, which
+/// may break common assumptions, so it should typically only be dropped at the
+/// end of a program when no more file descriptors will be created.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stderr.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stderr.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stderr&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stderr.4
+/// [OpenBSD]: https://man.openbsd.org/stderr.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stderr&section=4
+/// [illumos]: https://illumos.org/man/4FS/stderr
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stderr
+#[doc(alias = "STDERR_FILENO")]
+#[inline]
+pub unsafe fn take_stderr() -> OwnedFd {
+ backend::fd::OwnedFd::from_raw_fd(c::STDERR_FILENO as RawFd)
+}
+
+/// `STDIN_FILENO`—Standard input, raw.
+///
+/// This is similar to [`stdin`], however it returns a `RawFd`.
+///
+/// # Other hazards
+///
+/// This has the same hazards as [`stdin`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdin.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdin.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdin&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdin.4
+/// [OpenBSD]: https://man.openbsd.org/stdin.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdin&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdin
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdin
+#[doc(alias = "STDIN_FILENO")]
+#[inline]
+pub const fn raw_stdin() -> RawFd {
+ c::STDIN_FILENO as RawFd
+}
+
+/// `STDOUT_FILENO`—Standard output, raw.
+///
+/// This is similar to [`stdout`], however it returns a `RawFd`.
+///
+/// # Other hazards
+///
+/// This has the same hazards as [`stdout`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stdout.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stdout.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stdout&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stdout.4
+/// [OpenBSD]: https://man.openbsd.org/stdout.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stdout&section=4
+/// [illumos]: https://illumos.org/man/4FS/stdout
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stdout
+#[doc(alias = "STDOUT_FILENO")]
+#[inline]
+pub const fn raw_stdout() -> RawFd {
+ c::STDOUT_FILENO as RawFd
+}
+
+/// `STDERR_FILENO`—Standard error, raw.
+///
+/// This is similar to [`stderr`], however it returns a `RawFd`.
+///
+/// # Other hazards
+///
+/// This has the same hazards as [`stderr`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/stderr.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/stderr.3.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=stderr&sektion=4
+/// [NetBSD]: https://man.netbsd.org/stderr.4
+/// [OpenBSD]: https://man.openbsd.org/stderr.4
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=stderr&section=4
+/// [illumos]: https://illumos.org/man/4FS/stderr
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Standard-Streams.html#index-stderr
+#[doc(alias = "STDERR_FILENO")]
+#[inline]
+pub const fn raw_stderr() -> RawFd {
+ c::STDERR_FILENO as RawFd
+}
+
+/// Utility function to safely `dup2` over stdin (fd 0).
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[allow(clippy::mem_forget)]
+#[inline]
+pub fn dup2_stdin<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ // SAFETY: We pass the returned `OwnedFd` to `forget` so that it isn't
+ // dropped.
+ let mut target = unsafe { take_stdin() };
+ backend::io::syscalls::dup2(fd.as_fd(), &mut target)?;
+ forget(target);
+ Ok(())
+}
+
+/// Utility function to safely `dup2` over stdout (fd 1).
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[allow(clippy::mem_forget)]
+#[inline]
+pub fn dup2_stdout<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ // SAFETY: We pass the returned `OwnedFd` to `forget` so that it isn't
+ // dropped.
+ let mut target = unsafe { take_stdout() };
+ backend::io::syscalls::dup2(fd.as_fd(), &mut target)?;
+ forget(target);
+ Ok(())
+}
+
+/// Utility function to safely `dup2` over stderr (fd 2).
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[allow(clippy::mem_forget)]
+#[inline]
+pub fn dup2_stderr<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ // SAFETY: We pass the returned `OwnedFd` to `forget` so that it isn't
+ // dropped.
+ let mut target = unsafe { take_stderr() };
+ backend::io::syscalls::dup2(fd.as_fd(), &mut target)?;
+ forget(target);
+ Ok(())
+}
diff --git a/src/system.rs b/src/system.rs
new file mode 100644
index 0000000..cebf29e
--- /dev/null
+++ b/src/system.rs
@@ -0,0 +1,220 @@
+//! Uname and other system-level functions.
+//!
+//! # Safety
+//!
+//! This function converts from `struct utsname` fields provided from the
+//! kernel into `&str` references, which assumes that they're NUL-terminated.
+#![allow(unsafe_code)]
+
+use crate::backend;
+#[cfg(target_os = "linux")]
+use crate::backend::c;
+use crate::ffi::CStr;
+#[cfg(not(any(target_os = "espidf", target_os = "emscripten", target_os = "vita")))]
+use crate::io;
+use core::fmt;
+
+#[cfg(linux_kernel)]
+pub use backend::system::types::Sysinfo;
+
+/// `uname()`—Returns high-level information about the runtime OS and
+/// hardware.
+///
+/// For `gethostname()`, use [`Uname::nodename`] on the result.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [Apple]
+/// - [NetBSD]
+/// - [FreeBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+/// - [glibc]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/uname.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/uname.2.html
+/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/uname.3.html
+/// [NetBSD]: https://man.netbsd.org/uname.3
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=uname&sektion=3
+/// [OpenBSD]: https://man.openbsd.org/uname.3
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=uname&section=3
+/// [illumos]: https://illumos.org/man/2/uname
+/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Platform-Type.html
+#[doc(alias = "gethostname")]
+#[inline]
+pub fn uname() -> Uname {
+ Uname(backend::system::syscalls::uname())
+}
+
+/// `struct utsname`—Return type for [`uname`].
+#[doc(alias = "utsname")]
+pub struct Uname(backend::system::types::RawUname);
+
+impl Uname {
+ /// `sysname`—Operating system release name
+ #[inline]
+ pub fn sysname(&self) -> &CStr {
+ Self::to_cstr(self.0.sysname.as_ptr().cast())
+ }
+
+ /// `nodename`—Name with vague meaning
+ ///
+ /// This is intended to be a network name, however it's unable to convey
+ /// information about hosts that have multiple names, or any information
+ /// about where the names are visible.
+ ///
+ /// This corresponds to the `gethostname` value.
+ #[inline]
+ pub fn nodename(&self) -> &CStr {
+ Self::to_cstr(self.0.nodename.as_ptr().cast())
+ }
+
+ /// `release`—Operating system release version string
+ #[inline]
+ pub fn release(&self) -> &CStr {
+ Self::to_cstr(self.0.release.as_ptr().cast())
+ }
+
+ /// `version`—Operating system build identifiers
+ #[inline]
+ pub fn version(&self) -> &CStr {
+ Self::to_cstr(self.0.version.as_ptr().cast())
+ }
+
+ /// `machine`—Hardware architecture identifier
+ #[inline]
+ pub fn machine(&self) -> &CStr {
+ Self::to_cstr(self.0.machine.as_ptr().cast())
+ }
+
+ /// `domainname`—NIS or YP domain identifier
+ #[cfg(linux_kernel)]
+ #[inline]
+ pub fn domainname(&self) -> &CStr {
+ Self::to_cstr(self.0.domainname.as_ptr().cast())
+ }
+
+ #[inline]
+ fn to_cstr<'a>(ptr: *const u8) -> &'a CStr {
+ // SAFETY: Strings returned from the kernel are always NUL-terminated.
+ unsafe { CStr::from_ptr(ptr.cast()) }
+ }
+}
+
+impl fmt::Debug for Uname {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ #[cfg(not(linux_kernel))]
+ {
+ write!(
+ fmt,
+ "{:?} {:?} {:?} {:?} {:?}",
+ self.sysname(),
+ self.nodename(),
+ self.release(),
+ self.version(),
+ self.machine(),
+ )
+ }
+ #[cfg(linux_kernel)]
+ {
+ write!(
+ fmt,
+ "{:?} {:?} {:?} {:?} {:?} {:?}",
+ self.sysname(),
+ self.nodename(),
+ self.release(),
+ self.version(),
+ self.machine(),
+ self.domainname(),
+ )
+ }
+ }
+}
+
+/// `sysinfo()`—Returns status information about the runtime OS.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/uname.2.html
+#[cfg(linux_kernel)]
+#[inline]
+pub fn sysinfo() -> Sysinfo {
+ backend::system::syscalls::sysinfo()
+}
+
+/// `sethostname(name)`—Sets the system host name.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sethostname.2.html
+#[cfg(not(any(
+ target_os = "emscripten",
+ target_os = "espidf",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+#[inline]
+pub fn sethostname(name: &[u8]) -> io::Result<()> {
+ backend::system::syscalls::sethostname(name)
+}
+
+/// Reboot command for use with [`reboot`].
+#[cfg(target_os = "linux")]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+#[non_exhaustive]
+pub enum RebootCommand {
+ /// Disables the Ctrl-Alt-Del keystroke.
+ ///
+ /// When disabled, the keystroke will send a [`Signal::Int`] to pid 1.
+ ///
+ /// [`Signal::Int`]: crate::process::Signal::Int
+ CadOff = c::LINUX_REBOOT_CMD_CAD_OFF,
+ /// Enables the Ctrl-Alt-Del keystroke.
+ ///
+ /// When enabled, the keystroke will trigger a [`Restart`].
+ ///
+ /// [`Restart`]: Self::Restart
+ CadOn = c::LINUX_REBOOT_CMD_CAD_ON,
+ /// Prints the message "System halted" and halts the system
+ Halt = c::LINUX_REBOOT_CMD_HALT,
+ /// Execute a kernel that has been loaded earlier with [`kexec_load`].
+ ///
+ /// [`kexec_load`]: https://man7.org/linux/man-pages/man2/kexec_load.2.html
+ Kexec = c::LINUX_REBOOT_CMD_KEXEC,
+ /// Prints the message "Power down.", stops the system, and tries to remove
+ /// all power
+ PowerOff = c::LINUX_REBOOT_CMD_POWER_OFF,
+ /// Prints the message "Restarting system." and triggers a restart
+ Restart = c::LINUX_REBOOT_CMD_RESTART,
+ /// Hibernate the system by suspending to disk
+ SwSuspend = c::LINUX_REBOOT_CMD_SW_SUSPEND,
+}
+
+/// `reboot`—Reboot the system or enable/disable Ctrl-Alt-Del
+///
+/// The reboot syscall, despite the name, can actually do much more than
+/// reboot.
+///
+/// Among other things, it can:
+/// - Restart, Halt, Power Off, and Suspend the system
+/// - Enable and disable the Ctrl-Alt-Del keystroke
+/// - Execute other kernels
+/// - Terminate init inside PID namespaces
+///
+/// It is highly recommended to carefully read the kernel documentation before
+/// calling this function.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/reboot.2.html
+#[cfg(target_os = "linux")]
+pub fn reboot(cmd: RebootCommand) -> io::Result<()> {
+ backend::system::syscalls::reboot(cmd)
+}
diff --git a/src/termios/ioctl.rs b/src/termios/ioctl.rs
new file mode 100644
index 0000000..620ae4c
--- /dev/null
+++ b/src/termios/ioctl.rs
@@ -0,0 +1,53 @@
+//! Terminal-related `ioctl` functions.
+
+#![allow(unsafe_code)]
+
+use crate::fd::AsFd;
+use crate::{backend, io, ioctl};
+use backend::c;
+
+/// `ioctl(fd, TIOCEXCL)`—Enables exclusive mode on a terminal.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=tty&sektion=4
+/// [NetBSD]: https://man.netbsd.org/tty.4
+/// [OpenBSD]: https://man.openbsd.org/tty.4
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TIOCEXCL")]
+pub fn ioctl_tiocexcl<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ // SAFETY: TIOCEXCL is a no-argument setter opcode.
+ unsafe {
+ let ctl = ioctl::NoArg::<ioctl::BadOpcode<{ c::TIOCEXCL as _ }>>::new();
+ ioctl::ioctl(fd, ctl)
+ }
+}
+
+/// `ioctl(fd, TIOCNXCL)`—Disables exclusive mode on a terminal.
+///
+/// # References
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=tty&sektion=4
+/// [NetBSD]: https://man.netbsd.org/tty.4
+/// [OpenBSD]: https://man.openbsd.org/tty.4
+#[cfg(not(any(windows, target_os = "redox", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TIOCNXCL")]
+pub fn ioctl_tiocnxcl<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ // SAFETY: TIOCNXCL is a no-argument setter opcode.
+ unsafe {
+ let ctl = ioctl::NoArg::<ioctl::BadOpcode<{ c::TIOCNXCL as _ }>>::new();
+ ioctl::ioctl(fd, ctl)
+ }
+}
diff --git a/src/termios/mod.rs b/src/termios/mod.rs
new file mode 100644
index 0000000..c61d3f1
--- /dev/null
+++ b/src/termios/mod.rs
@@ -0,0 +1,27 @@
+//! Terminal I/O stream operations.
+//!
+//! This API automatically supports setting arbitrary I/O speeds, on any
+//! platform that supports them, including Linux and the BSDs.
+//!
+//! The [`speed`] module contains various predefined speed constants which
+//! are more likely to be portable, however any `u32` value can be passed to
+//! [`Termios::set_input_speed`], and it will simply fail if the speed is not
+//! supported by the platform.
+
+#[cfg(not(any(target_os = "espidf", target_os = "haiku", target_os = "wasi")))]
+mod ioctl;
+#[cfg(not(target_os = "wasi"))]
+mod tc;
+#[cfg(not(windows))]
+mod tty;
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+mod types;
+
+#[cfg(not(any(target_os = "espidf", target_os = "haiku", target_os = "wasi")))]
+pub use ioctl::*;
+#[cfg(not(target_os = "wasi"))]
+pub use tc::*;
+#[cfg(not(windows))]
+pub use tty::*;
+#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
+pub use types::*;
diff --git a/src/termios/tc.rs b/src/termios/tc.rs
new file mode 100644
index 0000000..58ba2c2
--- /dev/null
+++ b/src/termios/tc.rs
@@ -0,0 +1,209 @@
+use crate::fd::AsFd;
+#[cfg(not(target_os = "espidf"))]
+use crate::termios::{Action, OptionalActions, QueueSelector, Termios, Winsize};
+use crate::{backend, io};
+
+pub use crate::pid::Pid;
+
+/// `tcgetattr(fd)`—Get terminal attributes.
+///
+/// Also known as the `TCGETS` (or `TCGETS2` on Linux) operation with `ioctl`.
+///
+/// # References
+/// - [POSIX `tcgetattr`]
+/// - [Linux `ioctl_tty`]
+/// - [Linux `termios`]
+///
+/// [POSIX `tcgetattr`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html
+/// [Linux `ioctl_tty`]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [Linux `termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
+#[cfg(not(any(windows, target_os = "espidf", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TCGETS")]
+#[doc(alias = "TCGETS2")]
+#[doc(alias = "tcgetattr2")]
+pub fn tcgetattr<Fd: AsFd>(fd: Fd) -> io::Result<Termios> {
+ backend::termios::syscalls::tcgetattr(fd.as_fd())
+}
+
+/// `tcgetwinsize(fd)`—Get the current terminal window size.
+///
+/// Also known as the `TIOCGWINSZ` operation with `ioctl`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+#[cfg(not(any(windows, target_os = "espidf", target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TIOCGWINSZ")]
+pub fn tcgetwinsize<Fd: AsFd>(fd: Fd) -> io::Result<Winsize> {
+ backend::termios::syscalls::tcgetwinsize(fd.as_fd())
+}
+
+/// `tcgetpgrp(fd)`—Get the terminal foreground process group.
+///
+/// Also known as the `TIOCGPGRP` operation with `ioctl`.
+///
+/// On Linux, if `fd` is a pseudo-terminal, the underlying system call here can
+/// return a pid of 0, which rustix's `Pid` type doesn't support. So rustix
+/// instead handles this case by failing with [`io::Errno::OPNOTSUPP`] if the
+/// pid is 0.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetpgrp.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/tcgetpgrp.3.html
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TIOCGPGRP")]
+pub fn tcgetpgrp<Fd: AsFd>(fd: Fd) -> io::Result<Pid> {
+ backend::termios::syscalls::tcgetpgrp(fd.as_fd())
+}
+
+/// `tcsetpgrp(fd, pid)`—Set the terminal foreground process group.
+///
+/// Also known as the `TIOCSPGRP` operation with `ioctl`.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/tcsetpgrp.3.html
+#[cfg(not(any(windows, target_os = "wasi")))]
+#[inline]
+#[doc(alias = "TIOCSPGRP")]
+pub fn tcsetpgrp<Fd: AsFd>(fd: Fd, pid: Pid) -> io::Result<()> {
+ backend::termios::syscalls::tcsetpgrp(fd.as_fd(), pid)
+}
+
+/// `tcsetattr(fd)`—Set terminal attributes.
+///
+/// Also known as the `TCSETS` (or `TCSETS2 on Linux) operation with `ioctl`.
+///
+/// # References
+/// - [POSIX `tcsetattr`]
+/// - [Linux `ioctl_tty`]
+/// - [Linux `termios`]
+///
+/// [POSIX `tcsetattr`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html
+/// [Linux `ioctl_tty`]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [Linux `termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "TCSETS")]
+#[doc(alias = "TCSETS2")]
+#[doc(alias = "tcsetattr2")]
+pub fn tcsetattr<Fd: AsFd>(
+ fd: Fd,
+ optional_actions: OptionalActions,
+ termios: &Termios,
+) -> io::Result<()> {
+ backend::termios::syscalls::tcsetattr(fd.as_fd(), optional_actions, termios)
+}
+
+/// `tcsendbreak(fd, 0)`—Transmit zero-valued bits.
+///
+/// Also known as the `TCSBRK` operation with `ioctl`, with a duration of 0.
+///
+/// This function always uses an effective duration parameter of zero. For the
+/// equivalent of a `tcsendbreak` with a non-zero duration parameter, use
+/// `tcdrain`.
+///
+/// # References
+/// - [POSIX `tcsendbreak`]
+/// - [Linux `ioctl_tty`]
+/// - [Linux `termios`]
+///
+/// [POSIX `tcsendbreak`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html
+/// [Linux `ioctl_tty`]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [Linux `termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
+#[inline]
+#[doc(alias = "TCSBRK")]
+pub fn tcsendbreak<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::termios::syscalls::tcsendbreak(fd.as_fd())
+}
+
+/// `tcdrain(fd, duration)`—Wait until all pending output has been written.
+///
+/// # References
+/// - [POSIX `tcdrain`]
+/// - [Linux `ioctl_tty`]
+/// - [Linux `termios`]
+///
+/// [POSIX `tcsetattr`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html
+/// [Linux `ioctl_tty`]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [Linux `termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+pub fn tcdrain<Fd: AsFd>(fd: Fd) -> io::Result<()> {
+ backend::termios::syscalls::tcdrain(fd.as_fd())
+}
+
+/// `tcflush(fd, queue_selector)`—Wait until all pending output has been
+/// written.
+///
+/// # References
+/// - [POSIX `tcflush`]
+/// - [Linux `ioctl_tty`]
+/// - [Linux `termios`]
+///
+/// [POSIX `tcflush`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html
+/// [Linux `ioctl_tty`]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [Linux `termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "TCFLSH")]
+pub fn tcflush<Fd: AsFd>(fd: Fd, queue_selector: QueueSelector) -> io::Result<()> {
+ backend::termios::syscalls::tcflush(fd.as_fd(), queue_selector)
+}
+
+/// `tcflow(fd, action)`—Suspend or resume transmission or reception.
+///
+/// # References
+/// - [POSIX `tcflow`]
+/// - [Linux `ioctl_tty`]
+/// - [Linux `termios`]
+///
+/// [POSIX `tcflow`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html
+/// [Linux `ioctl_tty`]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+/// [Linux `termios`]: https://man7.org/linux/man-pages/man3/termios.3.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "TCXONC")]
+pub fn tcflow<Fd: AsFd>(fd: Fd, action: Action) -> io::Result<()> {
+ backend::termios::syscalls::tcflow(fd.as_fd(), action)
+}
+
+/// `tcgetsid(fd)`—Return the session ID of the current session with `fd` as
+/// its controlling terminal.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/tcgetsid.3.html
+#[inline]
+#[doc(alias = "TIOCGSID")]
+pub fn tcgetsid<Fd: AsFd>(fd: Fd) -> io::Result<Pid> {
+ backend::termios::syscalls::tcgetsid(fd.as_fd())
+}
+
+/// `tcsetwinsize(fd)`—Set the current terminal window size.
+///
+/// Also known as the `TIOCSWINSZ` operation with `ioctl`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
+#[cfg(not(target_os = "espidf"))]
+#[inline]
+#[doc(alias = "TIOCSWINSZ")]
+pub fn tcsetwinsize<Fd: AsFd>(fd: Fd, winsize: Winsize) -> io::Result<()> {
+ backend::termios::syscalls::tcsetwinsize(fd.as_fd(), winsize)
+}
diff --git a/src/termios/tty.rs b/src/termios/tty.rs
new file mode 100644
index 0000000..b14e602
--- /dev/null
+++ b/src/termios/tty.rs
@@ -0,0 +1,84 @@
+//! Functions which operate on file descriptors which might be terminals.
+
+use crate::backend;
+use backend::fd::AsFd;
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+use {
+ crate::ffi::CString, crate::io, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec,
+ backend::fd::BorrowedFd,
+};
+
+/// `isatty(fd)`—Tests whether a file descriptor refers to a terminal.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/isatty.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/isatty.3.html
+#[inline]
+pub fn isatty<Fd: AsFd>(fd: Fd) -> bool {
+ backend::termios::syscalls::isatty(fd.as_fd())
+}
+
+/// `ttyname_r(fd)`
+///
+/// If `reuse` already has available capacity, reuse it if possible.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html
+/// [Linux]: https://man7.org/linux/man-pages/man3/ttyname.3.html
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
+#[doc(alias = "ttyname_r")]
+#[inline]
+pub fn ttyname<Fd: AsFd, B: Into<Vec<u8>>>(dirfd: Fd, reuse: B) -> io::Result<CString> {
+ _ttyname(dirfd.as_fd(), reuse.into())
+}
+
+#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
+#[cfg(all(feature = "alloc", feature = "procfs"))]
+#[allow(unsafe_code)]
+fn _ttyname(dirfd: BorrowedFd<'_>, mut buffer: Vec<u8>) -> io::Result<CString> {
+ buffer.clear();
+ buffer.reserve(SMALL_PATH_BUFFER_SIZE);
+
+ loop {
+ match backend::termios::syscalls::ttyname(dirfd, buffer.spare_capacity_mut()) {
+ Err(io::Errno::RANGE) => {
+ // Use `Vec` reallocation strategy to grow capacity
+ // exponentially.
+ buffer.reserve(buffer.capacity() + 1);
+ }
+ Ok(len) => {
+ // SAFETY: Assume the backend returns the length of the string
+ // excluding the NUL.
+ unsafe {
+ buffer.set_len(len + 1);
+ }
+
+ // SAFETY:
+ // - “ttyname_r stores this pathname in the buffer buf”
+ // - [POSIX definition 3.271: Pathname]: “A string that is used
+ // to identify a file.”
+ // - [POSIX definition 3.375: String]: “A contiguous sequence
+ // of bytes terminated by and including the first null byte.”
+ //
+ // Thus, there will be a single NUL byte at the end of the
+ // string.
+ //
+ // [POSIX definition 3.271: Pathname]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271
+ // [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_375
+ unsafe {
+ return Ok(CString::from_vec_with_nul_unchecked(buffer));
+ }
+ }
+ Err(errno) => return Err(errno),
+ }
+ }
+}
diff --git a/src/termios/types.rs b/src/termios/types.rs
new file mode 100644
index 0000000..57e9a5d
--- /dev/null
+++ b/src/termios/types.rs
@@ -0,0 +1,1450 @@
+use crate::backend::c;
+use crate::{backend, io};
+use bitflags::bitflags;
+
+/// `struct termios` for use with [`tcgetattr`] and [`tcsetattr`].
+///
+/// [`tcgetattr`]: crate::termios::tcgetattr
+/// [`tcsetattr`]: crate::termios::tcsetattr
+#[repr(C)]
+#[derive(Clone)]
+pub struct Termios {
+ /// How is input interpreted?
+ #[doc(alias = "c_iflag")]
+ pub input_modes: InputModes,
+
+ /// How is output translated?
+ #[doc(alias = "c_oflag")]
+ pub output_modes: OutputModes,
+
+ /// Low-level configuration flags.
+ #[doc(alias = "c_cflag")]
+ pub control_modes: ControlModes,
+
+ /// High-level configuration flags.
+ #[doc(alias = "c_lflag")]
+ pub local_modes: LocalModes,
+
+ /// Line discipline.
+ #[doc(alias = "c_line")]
+ #[cfg(not(all(linux_raw, any(target_arch = "powerpc", target_arch = "powerpc64"))))]
+ #[cfg(any(
+ linux_like,
+ target_env = "newlib",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "redox"
+ ))]
+ pub line_discipline: c::cc_t,
+
+ /// How are various special control codes handled?
+ #[doc(alias = "c_cc")]
+ #[cfg(not(target_os = "haiku"))]
+ pub special_codes: SpecialCodes,
+
+ #[cfg(target_os = "nto")]
+ pub(crate) __reserved: [c::c_uint; 3],
+
+ /// Line discipline.
+ // On PowerPC, this field comes after `c_cc`.
+ #[doc(alias = "c_line")]
+ #[cfg(all(linux_raw, any(target_arch = "powerpc", target_arch = "powerpc64")))]
+ pub line_discipline: c::cc_t,
+
+ /// See the `input_speed` and `set_input_seed` functions.
+ ///
+ /// On Linux and BSDs, this is the arbitrary integer speed value. On all
+ /// other platforms, this is the encoded speed value.
+ #[cfg(not(any(solarish, all(libc, target_env = "newlib"), target_os = "aix")))]
+ pub(crate) input_speed: c::speed_t,
+
+ /// See the `output_speed` and `set_output_seed` functions.
+ ///
+ /// On Linux and BSDs, this is the integer speed value. On all other
+ /// platforms, this is the encoded speed value.
+ #[cfg(not(any(solarish, all(libc, target_env = "newlib"), target_os = "aix")))]
+ pub(crate) output_speed: c::speed_t,
+
+ /// How are various special control codes handled?
+ #[doc(alias = "c_cc")]
+ #[cfg(target_os = "haiku")]
+ pub special_codes: SpecialCodes,
+}
+
+impl Termios {
+ /// `cfmakeraw(self)`—Set a `Termios` value to the settings for “raw” mode.
+ ///
+ /// In raw mode, input is available a byte at a time, echoing is disabled,
+ /// and special terminal input and output codes are disabled.
+ #[cfg(not(target_os = "nto"))]
+ #[doc(alias = "cfmakeraw")]
+ #[inline]
+ pub fn make_raw(&mut self) {
+ backend::termios::syscalls::cfmakeraw(self)
+ }
+
+ /// Return the input communication speed.
+ ///
+ /// Unlike the `c_ispeed` field in glibc and others, this returns the
+ /// integer value of the speed, rather than the `B*` encoded constant
+ /// value.
+ #[doc(alias = "c_ispeed")]
+ #[doc(alias = "cfgetispeed")]
+ #[doc(alias = "cfgetspeed")]
+ #[inline]
+ pub fn input_speed(&self) -> u32 {
+ // On Linux and BSDs, `input_speed` is the arbitrary integer speed.
+ #[cfg(any(linux_kernel, bsd))]
+ {
+ debug_assert!(u32::try_from(self.input_speed).is_ok());
+ self.input_speed as u32
+ }
+
+ // On illumos, `input_speed` is not present.
+ #[cfg(any(solarish, all(libc, target_env = "newlib"), target_os = "aix"))]
+ unsafe {
+ speed::decode(c::cfgetispeed(crate::utils::as_ptr(self).cast())).unwrap()
+ }
+
+ // On other platforms, it's the encoded speed.
+ #[cfg(not(any(
+ linux_kernel,
+ bsd,
+ solarish,
+ all(libc, target_env = "newlib"),
+ target_os = "aix"
+ )))]
+ {
+ speed::decode(self.input_speed).unwrap()
+ }
+ }
+
+ /// Return the output communication speed.
+ ///
+ /// Unlike the `c_ospeed` field in glibc and others, this returns the
+ /// arbitrary integer value of the speed, rather than the `B*` encoded
+ /// constant value.
+ #[inline]
+ pub fn output_speed(&self) -> u32 {
+ // On Linux and BSDs, `input_speed` is the arbitrary integer speed.
+ #[cfg(any(linux_kernel, bsd))]
+ {
+ debug_assert!(u32::try_from(self.output_speed).is_ok());
+ self.output_speed as u32
+ }
+
+ // On illumos, `output_speed` is not present.
+ #[cfg(any(solarish, all(libc, target_env = "newlib"), target_os = "aix"))]
+ unsafe {
+ speed::decode(c::cfgetospeed(crate::utils::as_ptr(self).cast())).unwrap()
+ }
+
+ // On other platforms, it's the encoded speed.
+ #[cfg(not(any(
+ linux_kernel,
+ bsd,
+ solarish,
+ all(libc, target_env = "newlib"),
+ target_os = "aix"
+ )))]
+ {
+ speed::decode(self.output_speed).unwrap()
+ }
+ }
+
+ /// Set the input and output communication speeds.
+ ///
+ /// Unlike the `c_ispeed` and `c_ospeed` fields in glibc and others, this
+ /// takes the arbitrary integer value of the speed, rather than the `B*`
+ /// encoded constant value. Not all implementations support all integer
+ /// values; use the constants in the [`speed`] module for likely-supported
+ /// speeds.
+ #[cfg(not(target_os = "nto"))]
+ #[doc(alias = "cfsetspeed")]
+ #[doc(alias = "CBAUD")]
+ #[doc(alias = "CBAUDEX")]
+ #[doc(alias = "CIBAUD")]
+ #[doc(alias = "CIBAUDEX")]
+ #[inline]
+ pub fn set_speed(&mut self, new_speed: u32) -> io::Result<()> {
+ backend::termios::syscalls::set_speed(self, new_speed)
+ }
+
+ /// Set the input communication speed.
+ ///
+ /// Unlike the `c_ispeed` field in glibc and others, this takes the
+ /// arbitrary integer value of the speed, rather than the `B*` encoded
+ /// constant value. Not all implementations support all integer values; use
+ /// the constants in the [`speed`] module for known-supported speeds.
+ ///
+ /// On some platforms, changing the input speed changes the output speed
+ /// to the same speed.
+ #[doc(alias = "c_ispeed")]
+ #[doc(alias = "cfsetispeed")]
+ #[doc(alias = "CIBAUD")]
+ #[doc(alias = "CIBAUDEX")]
+ #[inline]
+ pub fn set_input_speed(&mut self, new_speed: u32) -> io::Result<()> {
+ backend::termios::syscalls::set_input_speed(self, new_speed)
+ }
+
+ /// Set the output communication speed.
+ ///
+ /// Unlike the `c_ospeed` field in glibc and others, this takes the
+ /// arbitrary integer value of the speed, rather than the `B*` encoded
+ /// constant value. Not all implementations support all integer values; use
+ /// the constants in the [`speed`] module for known-supported speeds.
+ ///
+ /// On some platforms, changing the output speed changes the input speed
+ /// to the same speed.
+ #[doc(alias = "c_ospeed")]
+ #[doc(alias = "cfsetospeed")]
+ #[doc(alias = "CBAUD")]
+ #[doc(alias = "CBAUDEX")]
+ #[inline]
+ pub fn set_output_speed(&mut self, new_speed: u32) -> io::Result<()> {
+ backend::termios::syscalls::set_output_speed(self, new_speed)
+ }
+}
+
+impl core::fmt::Debug for Termios {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ let mut d = f.debug_struct("Termios");
+ d.field("input_modes", &self.input_modes);
+ d.field("output_modes", &self.output_modes);
+ d.field("control_modes", &self.control_modes);
+ d.field("local_modes", &self.local_modes);
+ #[cfg(any(
+ linux_like,
+ target_env = "newlib",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "redox"
+ ))]
+ {
+ d.field("line_discipline", &self.line_discipline);
+ }
+ d.field("special_codes", &self.special_codes);
+ d.field("input_speed", &self.input_speed());
+ d.field("output_speed", &self.output_speed());
+ d.finish()
+ }
+}
+
+bitflags! {
+ /// Flags controlling terminal input.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct InputModes: c::tcflag_t {
+ /// `IGNBRK`
+ const IGNBRK = c::IGNBRK;
+
+ /// `BRKINT`
+ const BRKINT = c::BRKINT;
+
+ /// `IGNPAR`
+ const IGNPAR = c::IGNPAR;
+
+ /// `PARMRK`
+ const PARMRK = c::PARMRK;
+
+ /// `INPCK`
+ const INPCK = c::INPCK;
+
+ /// `ISTRIP`
+ const ISTRIP = c::ISTRIP;
+
+ /// `INLCR`
+ const INLCR = c::INLCR;
+
+ /// `IGNCR`
+ const IGNCR = c::IGNCR;
+
+ /// `ICRNL`
+ const ICRNL = c::ICRNL;
+
+ /// `IUCLC`
+ #[cfg(any(linux_kernel, solarish, target_os = "aix", target_os = "haiku", target_os = "nto"))]
+ const IUCLC = c::IUCLC;
+
+ /// `IXON`
+ const IXON = c::IXON;
+
+ /// `IXANY`
+ #[cfg(not(target_os = "redox"))]
+ const IXANY = c::IXANY;
+
+ /// `IXOFF`
+ const IXOFF = c::IXOFF;
+
+ /// `IMAXBEL`
+ #[cfg(not(any(target_os = "haiku", target_os = "redox")))]
+ const IMAXBEL = c::IMAXBEL;
+
+ /// `IUTF8`
+ #[cfg(not(any(
+ freebsdlike,
+ netbsdlike,
+ solarish,
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "redox",
+ )))]
+ const IUTF8 = c::IUTF8;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// Flags controlling terminal output.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct OutputModes: c::tcflag_t {
+ /// `OPOST`
+ const OPOST = c::OPOST;
+
+ /// `OLCUC`
+ #[cfg(not(any(
+ apple,
+ freebsdlike,
+ target_os = "aix",
+ target_os = "netbsd",
+ target_os = "redox",
+ )))]
+ const OLCUC = c::OLCUC;
+
+ /// `ONLCR`
+ const ONLCR = c::ONLCR;
+
+ /// `OCRNL`
+ const OCRNL = c::OCRNL;
+
+ /// `ONOCR`
+ const ONOCR = c::ONOCR;
+
+ /// `ONLRET`
+ const ONLRET = c::ONLRET;
+
+ /// `OFILL`
+ #[cfg(not(bsd))]
+ const OFILL = c::OFILL;
+
+ /// `OFDEL`
+ #[cfg(not(bsd))]
+ const OFDEL = c::OFDEL;
+
+ /// `NLDLY`
+ #[cfg(not(any(bsd, solarish, target_os = "redox")))]
+ const NLDLY = c::NLDLY;
+
+ /// `NL0`
+ #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
+ const NL0 = c::NL0;
+
+ /// `NL1`
+ #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
+ const NL1 = c::NL1;
+
+ /// `CRDLY`
+ #[cfg(not(any(bsd, solarish, target_os = "redox")))]
+ const CRDLY = c::CRDLY;
+
+ /// `CR0`
+ #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
+ const CR0 = c::CR0;
+
+ /// `CR1`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const CR1 = c::CR1;
+
+ /// `CR2`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const CR2 = c::CR2;
+
+ /// `CR3`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const CR3 = c::CR3;
+
+ /// `TABDLY`
+ #[cfg(not(any(
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "redox",
+ )))]
+ const TABDLY = c::TABDLY;
+
+ /// `TAB0`
+ #[cfg(not(any(
+ netbsdlike,
+ solarish,
+ target_os = "dragonfly",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const TAB0 = c::TAB0;
+
+ /// `TAB1`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const TAB1 = c::TAB1;
+
+ /// `TAB2`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const TAB2 = c::TAB2;
+
+ /// `TAB3`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const TAB3 = c::TAB3;
+
+ /// `XTABS`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "redox",
+ )))]
+ const XTABS = c::XTABS;
+
+ /// `BSDLY`
+ #[cfg(not(any(bsd, solarish, target_os = "redox")))]
+ const BSDLY = c::BSDLY;
+
+ /// `BS0`
+ #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
+ const BS0 = c::BS0;
+
+ /// `BS1`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const BS1 = c::BS1;
+
+ /// `FFDLY`
+ #[cfg(not(any(bsd, solarish, target_os = "redox")))]
+ const FFDLY = c::FFDLY;
+
+ /// `FF0`
+ #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
+ const FF0 = c::FF0;
+
+ /// `FF1`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const FF1 = c::FF1;
+
+ /// `VTDLY`
+ #[cfg(not(any(bsd, solarish, target_os = "redox")))]
+ const VTDLY = c::VTDLY;
+
+ /// `VT0`
+ #[cfg(not(any(bsd, solarish, target_os = "fuchsia", target_os = "redox")))]
+ const VT0 = c::VT0;
+
+ /// `VT1`
+ #[cfg(not(any(
+ target_env = "musl",
+ bsd,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia",
+ target_os = "redox",
+ )))]
+ const VT1 = c::VT1;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// Flags controlling special terminal modes.
+ ///
+ /// `CBAUD`, `CBAUDEX`, `CIBAUD`, and `CIBAUDEX` are not defined here,
+ /// because they're handled automatically by [`Termios::set_speed`] and
+ /// related functions.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ControlModes: c::tcflag_t {
+ /// `CSIZE`
+ const CSIZE = c::CSIZE;
+
+ /// `CS5`
+ const CS5 = c::CS5;
+
+ /// `CS6`
+ const CS6 = c::CS6;
+
+ /// `CS7`
+ const CS7 = c::CS7;
+
+ /// `CS8`
+ const CS8 = c::CS8;
+
+ /// `CSTOPB`
+ const CSTOPB = c::CSTOPB;
+
+ /// `CREAD`
+ const CREAD = c::CREAD;
+
+ /// `PARENB`
+ const PARENB = c::PARENB;
+
+ /// `PARODD`
+ const PARODD = c::PARODD;
+
+ /// `HUPCL`
+ const HUPCL = c::HUPCL;
+
+ /// `CLOCAL`
+ const CLOCAL = c::CLOCAL;
+
+ /// `CRTSCTS`
+ #[cfg(not(any(target_os = "aix", target_os = "nto", target_os = "redox")))]
+ const CRTSCTS = c::CRTSCTS;
+
+ /// `CMSPAR`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "emscripten",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto",
+ target_os = "redox",
+ )))]
+ const CMSPAR = c::CMSPAR;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+bitflags! {
+ /// Flags controlling “local” terminal modes.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct LocalModes: c::tcflag_t {
+ /// `XCASE`
+ #[cfg(any(linux_kernel, target_arch = "s390x", target_os = "haiku"))]
+ const XCASE = c::XCASE;
+
+ /// `ECHOCTL`
+ #[cfg(not(target_os = "redox"))]
+ const ECHOCTL = c::ECHOCTL;
+
+ /// `ECHOPRT`
+ #[cfg(not(any(target_os = "nto", target_os = "redox")))]
+ const ECHOPRT = c::ECHOPRT;
+
+ /// `ECHOKE`
+ #[cfg(not(target_os = "redox"))]
+ const ECHOKE = c::ECHOKE;
+
+ /// `FLUSHO`
+ #[cfg(not(any(target_os = "nto", target_os = "redox")))]
+ const FLUSHO = c::FLUSHO;
+
+ /// `PENDIN`
+ #[cfg(not(any(target_os = "nto", target_os = "redox")))]
+ const PENDIN = c::PENDIN;
+
+ /// `EXTPROC`
+ #[cfg(not(any(target_os = "aix", target_os = "haiku", target_os = "nto", target_os = "redox")))]
+ const EXTPROC = c::EXTPROC;
+
+ /// `ISIG`
+ const ISIG = c::ISIG;
+
+ /// `ICANON`—A flag for the `c_lflag` field of [`Termios`] indicating
+ /// canonical mode.
+ const ICANON = c::ICANON;
+
+ /// `ECHO`
+ const ECHO = c::ECHO;
+
+ /// `ECHOE`
+ const ECHOE = c::ECHOE;
+
+ /// `ECHOK`
+ const ECHOK = c::ECHOK;
+
+ /// `ECHONL`
+ const ECHONL = c::ECHONL;
+
+ /// `NOFLSH`
+ const NOFLSH = c::NOFLSH;
+
+ /// `TOSTOP`
+ const TOSTOP = c::TOSTOP;
+
+ /// `IEXTEN`
+ const IEXTEN = c::IEXTEN;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// Speeds for use with [`Termios::set_input_speed`] and
+/// [`Termios::set_output_speed`].
+///
+/// Unlike in some platforms' libc APIs, these always have the same numerical
+/// value as their names; for example, `B50` has the value `50`, and so on.
+/// Consequently, it's not necessary to use them. They are provided here
+/// because they help identify speeds which are likely to be supported, on
+/// platforms which don't support arbitrary speeds.
+pub mod speed {
+ #[cfg(not(bsd))]
+ use crate::backend::c;
+
+ /// `B0`
+ pub const B0: u32 = 0;
+
+ /// `B50`
+ pub const B50: u32 = 50;
+
+ /// `B75`
+ pub const B75: u32 = 75;
+
+ /// `B110`
+ pub const B110: u32 = 110;
+
+ /// `B134`
+ pub const B134: u32 = 134;
+
+ /// `B150`
+ pub const B150: u32 = 150;
+
+ /// `B200`
+ pub const B200: u32 = 200;
+
+ /// `B300`
+ pub const B300: u32 = 300;
+
+ /// `B600`
+ pub const B600: u32 = 600;
+
+ /// `B1200`
+ pub const B1200: u32 = 1200;
+
+ /// `B1800`
+ pub const B1800: u32 = 1800;
+
+ /// `B2400`
+ pub const B2400: u32 = 2400;
+
+ /// `B4800`
+ pub const B4800: u32 = 4800;
+
+ /// `B9600`
+ pub const B9600: u32 = 9600;
+
+ /// `B19200`
+ #[doc(alias = "EXTA")]
+ pub const B19200: u32 = 19200;
+
+ /// `B38400`
+ #[doc(alias = "EXTB")]
+ pub const B38400: u32 = 38400;
+
+ /// `B57600`
+ #[cfg(not(target_os = "aix"))]
+ pub const B57600: u32 = 57600;
+
+ /// `B115200`
+ #[cfg(not(target_os = "aix"))]
+ pub const B115200: u32 = 115_200;
+
+ /// `B230400`
+ #[cfg(not(target_os = "aix"))]
+ pub const B230400: u32 = 230_400;
+
+ /// `B460800`
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "openbsd"
+ )))]
+ pub const B460800: u32 = 460_800;
+
+ /// `B500000`
+ #[cfg(not(any(bsd, solarish, target_os = "aix", target_os = "haiku")))]
+ pub const B500000: u32 = 500_000;
+
+ /// `B576000`
+ #[cfg(not(any(bsd, solarish, target_os = "aix", target_os = "haiku")))]
+ pub const B576000: u32 = 576_000;
+
+ /// `B921600`
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "openbsd"
+ )))]
+ pub const B921600: u32 = 921_600;
+
+ /// `B1000000`
+ #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
+ pub const B1000000: u32 = 1_000_000;
+
+ /// `B1152000`
+ #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
+ pub const B1152000: u32 = 1_152_000;
+
+ /// `B1500000`
+ #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
+ pub const B1500000: u32 = 1_500_000;
+
+ /// `B2000000`
+ #[cfg(not(any(bsd, target_os = "aix", target_os = "haiku", target_os = "solaris")))]
+ pub const B2000000: u32 = 2_000_000;
+
+ /// `B2500000`
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "solaris",
+ )))]
+ pub const B2500000: u32 = 2_500_000;
+
+ /// `B3000000`
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "solaris",
+ )))]
+ pub const B3000000: u32 = 3_000_000;
+
+ /// `B3500000`
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "solaris",
+ )))]
+ pub const B3500000: u32 = 3_500_000;
+
+ /// `B4000000`
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "solaris",
+ )))]
+ pub const B4000000: u32 = 4_000_000;
+
+ /// Translate from a `c::speed_t` code to an arbitrary integer speed value
+ /// `u32`.
+ ///
+ /// On BSD platforms, integer speed values are already the same as their
+ /// encoded values, and on Linux platforms, we use `TCGETS2`/`TCSETS2`
+ /// and the `c_ispeed`/`c_ospeed`` fields, except that on Linux on
+ /// PowerPC on QEMU, `TCGETS2`/`TCSETS2` don't set `c_ispeed`/`c_ospeed`.
+ #[cfg(not(any(
+ bsd,
+ all(
+ linux_kernel,
+ not(any(target_arch = "powerpc", target_arch = "powerpc64"))
+ )
+ )))]
+ pub(crate) const fn decode(encoded_speed: c::speed_t) -> Option<u32> {
+ match encoded_speed {
+ c::B0 => Some(0),
+ c::B50 => Some(50),
+ c::B75 => Some(75),
+ c::B110 => Some(110),
+ c::B134 => Some(134),
+ c::B150 => Some(150),
+ c::B200 => Some(200),
+ c::B300 => Some(300),
+ c::B600 => Some(600),
+ c::B1200 => Some(1200),
+ c::B1800 => Some(1800),
+ c::B2400 => Some(2400),
+ c::B4800 => Some(4800),
+ c::B9600 => Some(9600),
+ c::B19200 => Some(19200),
+ c::B38400 => Some(38400),
+ #[cfg(not(target_os = "aix"))]
+ c::B57600 => Some(57600),
+ #[cfg(not(target_os = "aix"))]
+ c::B115200 => Some(115_200),
+ #[cfg(not(any(target_os = "aix", target_os = "nto")))]
+ c::B230400 => Some(230_400),
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "openbsd"
+ )))]
+ c::B460800 => Some(460_800),
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto"
+ )))]
+ c::B500000 => Some(500_000),
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto"
+ )))]
+ c::B576000 => Some(576_000),
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "openbsd"
+ )))]
+ c::B921600 => Some(921_600),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ c::B1000000 => Some(1_000_000),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ c::B1152000 => Some(1_152_000),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ c::B1500000 => Some(1_500_000),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ c::B2000000 => Some(2_000_000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ c::B2500000 => Some(2_500_000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ c::B3000000 => Some(3_000_000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ c::B3500000 => Some(3_500_000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ c::B4000000 => Some(4_000_000),
+ _ => None,
+ }
+ }
+
+ /// Translate from an arbitrary `u32` arbitrary integer speed value to a
+ /// `c::speed_t` code.
+ #[cfg(not(bsd))]
+ pub(crate) const fn encode(speed: u32) -> Option<c::speed_t> {
+ match speed {
+ 0 => Some(c::B0),
+ 50 => Some(c::B50),
+ 75 => Some(c::B75),
+ 110 => Some(c::B110),
+ 134 => Some(c::B134),
+ 150 => Some(c::B150),
+ 200 => Some(c::B200),
+ 300 => Some(c::B300),
+ 600 => Some(c::B600),
+ 1200 => Some(c::B1200),
+ 1800 => Some(c::B1800),
+ 2400 => Some(c::B2400),
+ 4800 => Some(c::B4800),
+ 9600 => Some(c::B9600),
+ 19200 => Some(c::B19200),
+ 38400 => Some(c::B38400),
+ #[cfg(not(target_os = "aix"))]
+ 57600 => Some(c::B57600),
+ #[cfg(not(target_os = "aix"))]
+ 115_200 => Some(c::B115200),
+ #[cfg(not(any(target_os = "aix", target_os = "nto")))]
+ 230_400 => Some(c::B230400),
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "openbsd",
+ )))]
+ 460_800 => Some(c::B460800),
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto"
+ )))]
+ 500_000 => Some(c::B500000),
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto"
+ )))]
+ 576_000 => Some(c::B576000),
+ #[cfg(not(any(
+ apple,
+ target_os = "aix",
+ target_os = "dragonfly",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "openbsd"
+ )))]
+ 921_600 => Some(c::B921600),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ 1_000_000 => Some(c::B1000000),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ 1_152_000 => Some(c::B1152000),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ 1_500_000 => Some(c::B1500000),
+ #[cfg(not(any(
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris"
+ )))]
+ 2_000_000 => Some(c::B2000000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ 2_500_000 => Some(c::B2500000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ 3_000_000 => Some(c::B3000000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ 3_500_000 => Some(c::B3500000),
+ #[cfg(not(any(
+ target_arch = "sparc",
+ target_arch = "sparc64",
+ bsd,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "solaris",
+ )))]
+ 4_000_000 => Some(c::B4000000),
+ _ => None,
+ }
+ }
+}
+
+/// An array indexed by [`SpecialCodeIndex`] indicating the current values
+/// of various special control codes.
+#[repr(transparent)]
+#[derive(Clone, Debug)]
+pub struct SpecialCodes(pub(crate) [c::cc_t; c::NCCS as usize]);
+
+impl core::ops::Index<SpecialCodeIndex> for SpecialCodes {
+ type Output = c::cc_t;
+
+ fn index(&self, index: SpecialCodeIndex) -> &Self::Output {
+ &self.0[index.0]
+ }
+}
+
+impl core::ops::IndexMut<SpecialCodeIndex> for SpecialCodes {
+ fn index_mut(&mut self, index: SpecialCodeIndex) -> &mut Self::Output {
+ &mut self.0[index.0]
+ }
+}
+
+/// Indices for use with [`Termios::special_codes`].
+pub struct SpecialCodeIndex(usize);
+
+#[rustfmt::skip]
+impl SpecialCodeIndex {
+ /// `VINTR`
+ pub const VINTR: Self = Self(c::VINTR as usize);
+
+ /// `VQUIT`
+ pub const VQUIT: Self = Self(c::VQUIT as usize);
+
+ /// `VERASE`
+ pub const VERASE: Self = Self(c::VERASE as usize);
+
+ /// `VKILL`
+ pub const VKILL: Self = Self(c::VKILL as usize);
+
+ /// `VEOF`
+ pub const VEOF: Self = Self(c::VEOF as usize);
+
+ /// `VTIME`
+ pub const VTIME: Self = Self(c::VTIME as usize);
+
+ /// `VMIN`
+ pub const VMIN: Self = Self(c::VMIN as usize);
+
+ /// `VSWTC`
+ #[cfg(not(any(
+ bsd,
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "hurd",
+ target_os = "nto",
+ )))]
+ pub const VSWTC: Self = Self(c::VSWTC as usize);
+
+ /// `VSTART`
+ pub const VSTART: Self = Self(c::VSTART as usize);
+
+ /// `VSTOP`
+ pub const VSTOP: Self = Self(c::VSTOP as usize);
+
+ /// `VSUSP`
+ pub const VSUSP: Self = Self(c::VSUSP as usize);
+
+ /// `VEOL`
+ pub const VEOL: Self = Self(c::VEOL as usize);
+
+ /// `VREPRINT`
+ #[cfg(not(target_os = "haiku"))]
+ pub const VREPRINT: Self = Self(c::VREPRINT as usize);
+
+ /// `VDISCARD`
+ #[cfg(not(any(target_os = "aix", target_os = "haiku")))]
+ pub const VDISCARD: Self = Self(c::VDISCARD as usize);
+
+ /// `VWERASE`
+ #[cfg(not(any(target_os = "aix", target_os = "haiku")))]
+ pub const VWERASE: Self = Self(c::VWERASE as usize);
+
+ /// `VLNEXT`
+ #[cfg(not(target_os = "haiku"))]
+ pub const VLNEXT: Self = Self(c::VLNEXT as usize);
+
+ /// `VEOL2`
+ pub const VEOL2: Self = Self(c::VEOL2 as usize);
+}
+
+/// `TCSA*` values for use with [`tcsetattr`].
+///
+/// [`tcsetattr`]: crate::termios::tcsetattr
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u32)]
+pub enum OptionalActions {
+ /// `TCSANOW`—Make the change immediately.
+ #[doc(alias = "TCSANOW")]
+ Now = c::TCSANOW as u32,
+
+ /// `TCSADRAIN`—Make the change after all output has been transmitted.
+ #[doc(alias = "TCSADRAIN")]
+ Drain = c::TCSADRAIN as u32,
+
+ /// `TCSAFLUSH`—Discard any pending input and then make the change
+ /// after all output has been transmitted.
+ #[doc(alias = "TCSAFLUSH")]
+ Flush = c::TCSAFLUSH as u32,
+}
+
+/// `TC*` values for use with [`tcflush`].
+///
+/// [`tcflush`]: crate::termios::tcflush
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u32)]
+pub enum QueueSelector {
+ /// `TCIFLUSH`—Flush data received but not read.
+ #[doc(alias = "TCIFLUSH")]
+ IFlush = c::TCIFLUSH as u32,
+
+ /// `TCOFLUSH`—Flush data written but not transmitted.
+ #[doc(alias = "TCOFLUSH")]
+ OFlush = c::TCOFLUSH as u32,
+
+ /// `TCIOFLUSH`—`IFlush` and `OFlush` combined.
+ #[doc(alias = "TCIOFLUSH")]
+ IOFlush = c::TCIOFLUSH as u32,
+}
+
+/// `TC*` values for use with [`tcflow`].
+///
+/// [`tcflow`]: crate::termios::tcflow
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u32)]
+pub enum Action {
+ /// `TCOOFF`—Suspend output.
+ #[doc(alias = "TCOOFF")]
+ OOff = c::TCOOFF as u32,
+
+ /// `TCOON`—Restart suspended output.
+ #[doc(alias = "TCOON")]
+ OOn = c::TCOON as u32,
+
+ /// `TCIOFF`—Transmits a STOP byte.
+ #[doc(alias = "TCIOFF")]
+ IOff = c::TCIOFF as u32,
+
+ /// `TCION`—Transmits a START byte.
+ #[doc(alias = "TCION")]
+ IOn = c::TCION as u32,
+}
+
+/// `struct winsize` for use with [`tcgetwinsize`].
+///
+/// [`tcgetwinsize`]: crate::termios::tcgetwinsize
+#[doc(alias = "winsize")]
+pub type Winsize = c::winsize;
+
+#[test]
+fn termios_layouts() {
+ check_renamed_type!(InputModes, tcflag_t);
+ check_renamed_type!(OutputModes, tcflag_t);
+ check_renamed_type!(ControlModes, tcflag_t);
+ check_renamed_type!(LocalModes, tcflag_t);
+
+ // On platforms with a termios/termios2 split, check `termios`.
+ #[cfg(linux_raw)]
+ {
+ check_renamed_type!(Termios, termios2);
+ check_renamed_struct_renamed_field!(Termios, termios2, input_modes, c_iflag);
+ check_renamed_struct_renamed_field!(Termios, termios2, output_modes, c_oflag);
+ check_renamed_struct_renamed_field!(Termios, termios2, control_modes, c_cflag);
+ check_renamed_struct_renamed_field!(Termios, termios2, local_modes, c_lflag);
+ check_renamed_struct_renamed_field!(Termios, termios2, line_discipline, c_line);
+ check_renamed_struct_renamed_field!(Termios, termios2, special_codes, c_cc);
+ check_renamed_struct_renamed_field!(Termios, termios2, input_speed, c_ispeed);
+ check_renamed_struct_renamed_field!(Termios, termios2, output_speed, c_ospeed);
+
+ // We assume that `termios` has the same layout as `termios2` minus the
+ // `c_ispeed` and `c_ospeed` fields.
+ check_renamed_struct_renamed_field!(Termios, termios, input_modes, c_iflag);
+ check_renamed_struct_renamed_field!(Termios, termios, output_modes, c_oflag);
+ check_renamed_struct_renamed_field!(Termios, termios, control_modes, c_cflag);
+ check_renamed_struct_renamed_field!(Termios, termios, local_modes, c_lflag);
+ check_renamed_struct_renamed_field!(Termios, termios, special_codes, c_cc);
+
+ // On everything except PowerPC, `termios` matches `termios2` except
+ // for the addition of `c_ispeed` and `c_ospeed`.
+ #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
+ const_assert_eq!(
+ memoffset::offset_of!(Termios, input_speed),
+ core::mem::size_of::<c::termios>()
+ );
+
+ // On PowerPC, `termios2` is `termios`.
+ #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
+ assert_eq_size!(c::termios2, c::termios);
+ }
+
+ #[cfg(not(linux_raw))]
+ {
+ // On Mips, Sparc, and Android, the libc lacks the ospeed and ispeed
+ // fields.
+ #[cfg(all(
+ not(all(
+ target_env = "gnu",
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ )
+ )),
+ not(all(libc, target_os = "android"))
+ ))]
+ check_renamed_type!(Termios, termios);
+ #[cfg(not(all(
+ not(all(
+ target_env = "gnu",
+ any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6",
+ target_arch = "sparc",
+ target_arch = "sparc64"
+ )
+ )),
+ not(all(libc, target_os = "android"))
+ )))]
+ const_assert!(core::mem::size_of::<Termios>() >= core::mem::size_of::<c::termios>());
+
+ check_renamed_struct_renamed_field!(Termios, termios, input_modes, c_iflag);
+ check_renamed_struct_renamed_field!(Termios, termios, output_modes, c_oflag);
+ check_renamed_struct_renamed_field!(Termios, termios, control_modes, c_cflag);
+ check_renamed_struct_renamed_field!(Termios, termios, local_modes, c_lflag);
+ #[cfg(any(
+ linux_like,
+ target_env = "newlib",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "redox"
+ ))]
+ check_renamed_struct_renamed_field!(Termios, termios, line_discipline, c_line);
+ check_renamed_struct_renamed_field!(Termios, termios, special_codes, c_cc);
+ #[cfg(not(any(
+ linux_kernel,
+ solarish,
+ target_os = "emscripten",
+ target_os = "fuchsia"
+ )))]
+ {
+ check_renamed_struct_renamed_field!(Termios, termios, input_speed, c_ispeed);
+ check_renamed_struct_renamed_field!(Termios, termios, output_speed, c_ospeed);
+ }
+ #[cfg(any(target_env = "musl", target_os = "fuchsia"))]
+ {
+ check_renamed_struct_renamed_field!(Termios, termios, input_speed, __c_ispeed);
+ check_renamed_struct_renamed_field!(Termios, termios, output_speed, __c_ospeed);
+ }
+ }
+
+ check_renamed_type!(OptionalActions, c_int);
+ check_renamed_type!(QueueSelector, c_int);
+ check_renamed_type!(Action, c_int);
+}
+
+#[test]
+#[cfg(not(any(
+ solarish,
+ target_os = "emscripten",
+ target_os = "haiku",
+ target_os = "redox"
+)))]
+fn termios_legacy() {
+ // Check that our doc aliases above are correct.
+ const_assert_eq!(c::EXTA, c::B19200);
+ const_assert_eq!(c::EXTB, c::B38400);
+}
+
+#[cfg(bsd)]
+#[test]
+fn termios_bsd() {
+ // On BSD platforms we can assume that the `B*` constants have their
+ // arbitrary integer speed value. Confirm this.
+ const_assert_eq!(c::B0, 0);
+ const_assert_eq!(c::B50, 50);
+ const_assert_eq!(c::B19200, 19200);
+ const_assert_eq!(c::B38400, 38400);
+}
+
+#[test]
+#[cfg(not(bsd))]
+fn termios_speed_encoding() {
+ assert_eq!(speed::encode(0), Some(c::B0));
+ assert_eq!(speed::encode(50), Some(c::B50));
+ assert_eq!(speed::encode(19200), Some(c::B19200));
+ assert_eq!(speed::encode(38400), Some(c::B38400));
+ assert_eq!(speed::encode(1), None);
+ assert_eq!(speed::encode(!0), None);
+
+ #[cfg(not(linux_kernel))]
+ {
+ assert_eq!(speed::decode(c::B0), Some(0));
+ assert_eq!(speed::decode(c::B50), Some(50));
+ assert_eq!(speed::decode(c::B19200), Some(19200));
+ assert_eq!(speed::decode(c::B38400), Some(38400));
+ }
+}
+
+#[cfg(linux_kernel)]
+#[test]
+fn termios_ioctl_contiguity() {
+ // When using `termios2`, we assume that we can add the optional actions
+ // value to the ioctl request code. Test this assumption.
+
+ const_assert_eq!(c::TCSETS2, c::TCSETS2 + 0);
+ const_assert_eq!(c::TCSETSW2, c::TCSETS2 + 1);
+ const_assert_eq!(c::TCSETSF2, c::TCSETS2 + 2);
+
+ const_assert_eq!(c::TCSANOW - c::TCSANOW, 0);
+ const_assert_eq!(c::TCSADRAIN - c::TCSANOW, 1);
+ const_assert_eq!(c::TCSAFLUSH - c::TCSANOW, 2);
+
+ // MIPS is different here.
+ #[cfg(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ ))]
+ {
+ assert_eq!(i128::from(c::TCSANOW) - i128::from(c::TCSETS), 0);
+ assert_eq!(i128::from(c::TCSADRAIN) - i128::from(c::TCSETS), 1);
+ assert_eq!(i128::from(c::TCSAFLUSH) - i128::from(c::TCSETS), 2);
+ }
+ #[cfg(not(any(
+ target_arch = "mips",
+ target_arch = "mips32r6",
+ target_arch = "mips64",
+ target_arch = "mips64r6"
+ )))]
+ {
+ const_assert_eq!(c::TCSANOW, 0);
+ const_assert_eq!(c::TCSADRAIN, 1);
+ const_assert_eq!(c::TCSAFLUSH, 2);
+ }
+}
+
+#[cfg(linux_kernel)]
+#[test]
+fn termios_cibaud() {
+ // Test an assumption.
+ const_assert_eq!(c::CIBAUD, c::CBAUD << c::IBSHIFT);
+}
diff --git a/src/thread/clock.rs b/src/thread/clock.rs
new file mode 100644
index 0000000..986e1f9
--- /dev/null
+++ b/src/thread/clock.rs
@@ -0,0 +1,100 @@
+use crate::{backend, io};
+
+pub use crate::timespec::Timespec;
+
+#[cfg(not(any(
+ apple,
+ target_os = "dragonfly",
+ target_os = "espidf",
+ target_os = "freebsd", // FreeBSD 12 has clock_nanosleep, but libc targets FreeBSD 11.
+ target_os = "openbsd",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+pub use crate::clockid::ClockId;
+
+/// `clock_nanosleep(id, 0, request, remain)`—Sleeps for a duration on a
+/// given clock.
+///
+/// This is `clock_nanosleep` specialized for the case of a relative sleep
+/// interval. See [`clock_nanosleep_absolute`] for absolute intervals.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_nanosleep.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/clock_nanosleep.2.html
+#[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 fn clock_nanosleep_relative(id: ClockId, request: &Timespec) -> NanosleepRelativeResult {
+ backend::thread::syscalls::clock_nanosleep_relative(id, request)
+}
+
+/// `clock_nanosleep(id, TIMER_ABSTIME, request, NULL)`—Sleeps until an
+/// absolute time on a given clock.
+///
+/// This is `clock_nanosleep` specialized for the case of an absolute sleep
+/// interval. See [`clock_nanosleep_relative`] for relative intervals.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_nanosleep.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/clock_nanosleep.2.html
+#[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 fn clock_nanosleep_absolute(id: ClockId, request: &Timespec) -> io::Result<()> {
+ backend::thread::syscalls::clock_nanosleep_absolute(id, request)
+}
+
+/// `nanosleep(request, remain)`—Sleeps for a duration.
+///
+/// This effectively uses the system monotonic clock.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/nanosleep.2.html
+#[inline]
+pub fn nanosleep(request: &Timespec) -> NanosleepRelativeResult {
+ backend::thread::syscalls::nanosleep(request)
+}
+
+/// A return type for `nanosleep` and `clock_nanosleep_relative`.
+#[derive(Debug, Clone)]
+#[must_use]
+pub enum NanosleepRelativeResult {
+ /// The sleep completed normally.
+ Ok,
+ /// The sleep was interrupted, the remaining time is returned.
+ Interrupted(Timespec),
+ /// An invalid time value was provided.
+ Err(io::Errno),
+}
diff --git a/src/thread/futex.rs b/src/thread/futex.rs
new file mode 100644
index 0000000..47947c8
--- /dev/null
+++ b/src/thread/futex.rs
@@ -0,0 +1,38 @@
+//! Linux `futex`.
+//!
+//! # Safety
+//!
+//! Futex is a very low-level mechanism for implementing concurrency
+//! primitives.
+#![allow(unsafe_code)]
+
+use crate::thread::Timespec;
+use crate::{backend, io};
+
+pub use backend::thread::futex::{FutexFlags, FutexOperation};
+
+/// `futex(uaddr, op, val, utime, uaddr2, val3)`
+///
+/// # References
+/// - [Linux `futex` system call]
+/// - [Linux `futex` feature]
+///
+/// # Safety
+///
+/// This is a very low-level feature for implementing synchronization
+/// primitives. See the references links above.
+///
+/// [Linux `futex` system call]: https://man7.org/linux/man-pages/man2/futex.2.html
+/// [Linux `futex` feature]: https://man7.org/linux/man-pages/man7/futex.7.html
+#[inline]
+pub unsafe fn futex(
+ uaddr: *mut u32,
+ op: FutexOperation,
+ flags: FutexFlags,
+ val: u32,
+ utime: *const Timespec,
+ uaddr2: *mut u32,
+ val3: u32,
+) -> io::Result<usize> {
+ backend::thread::syscalls::futex(uaddr, op, flags, val, utime, uaddr2, val3)
+}
diff --git a/src/thread/id.rs b/src/thread/id.rs
new file mode 100644
index 0000000..11193c9
--- /dev/null
+++ b/src/thread/id.rs
@@ -0,0 +1,115 @@
+use crate::{backend, io};
+
+pub use crate::pid::{Pid, RawPid};
+pub use crate::ugid::{Gid, RawGid, RawUid, Uid};
+
+/// `gettid()`—Returns the thread ID.
+///
+/// This returns the OS thread ID, which is not necessarily the same as the
+/// `rust::thread::Thread::id` or the pthread ID.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/gettid.2.html
+#[inline]
+#[must_use]
+pub fn gettid() -> Pid {
+ backend::thread::syscalls::gettid()
+}
+
+/// `setuid(uid)`
+///
+/// # Warning
+///
+/// This is not the setxid you are looking for… POSIX requires xids to be
+/// process granular, but on Linux they are per-thread. Thus, this call only
+/// changes the xid for the current *thread*, not the entire process even
+/// though that is in violation of the POSIX standard.
+///
+/// For details on this distinction, see the C library vs. kernel differences
+/// in the [manual page][linux_notes]. This call implements the kernel
+/// behavior.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setuid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setuid.2.html
+/// [linux_notes]: https://man7.org/linux/man-pages/man2/setuid.2.html#NOTES
+#[inline]
+pub fn set_thread_uid(uid: Uid) -> io::Result<()> {
+ backend::thread::syscalls::setuid_thread(uid)
+}
+
+/// `setresuid(ruid, euid, suid)`
+///
+/// # Warning
+///
+/// This is not the setresxid you are looking for… POSIX requires xids to be
+/// process granular, but on Linux they are per-thread. Thus, this call only
+/// changes the xid for the current *thread*, not the entire process even
+/// though that is in violation of the POSIX standard.
+///
+/// For details on this distinction, see the C library vs. kernel differences
+/// in the [manual page][linux_notes] and the notes in [`set_thread_uid`]. This
+/// call implements the kernel behavior.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/setresuid.2.html
+/// [linux_notes]: https://man7.org/linux/man-pages/man2/setresuid.2.html#NOTES
+#[inline]
+pub fn set_thread_res_uid(ruid: Uid, euid: Uid, suid: Uid) -> io::Result<()> {
+ backend::thread::syscalls::setresuid_thread(ruid, euid, suid)
+}
+
+/// `setgid(gid)`
+///
+/// # Warning
+///
+/// This is not the setxid you are looking for… POSIX requires xids to be
+/// process granular, but on Linux they are per-thread. Thus, this call only
+/// changes the xid for the current *thread*, not the entire process even
+/// though that is in violation of the POSIX standard.
+///
+/// For details on this distinction, see the C library vs. kernel differences
+/// in the [manual page][linux_notes]. This call implements the kernel
+/// behavior.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setgid.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/setgid.2.html
+/// [linux_notes]: https://man7.org/linux/man-pages/man2/setgid.2.html#NOTES
+#[inline]
+pub fn set_thread_gid(gid: Gid) -> io::Result<()> {
+ backend::thread::syscalls::setgid_thread(gid)
+}
+
+/// `setresgid(rgid, egid, sgid)`
+///
+/// # Warning
+///
+/// This is not the setresxid you are looking for… POSIX requires xids to be
+/// process granular, but on Linux they are per-thread. Thus, this call only
+/// changes the xid for the current *thread*, not the entire process even
+/// though that is in violation of the POSIX standard.
+///
+/// For details on this distinction, see the C library vs. kernel differences
+/// in the [manual page][linux_notes] and the notes in [`set_thread_gid`]. This
+/// call implements the kernel behavior.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/setresgid.2.html
+/// [linux_notes]: https://man7.org/linux/man-pages/man2/setresgid.2.html#NOTES
+#[inline]
+pub fn set_thread_res_gid(rgid: Gid, egid: Gid, sgid: Gid) -> io::Result<()> {
+ backend::thread::syscalls::setresgid_thread(rgid, egid, sgid)
+}
diff --git a/src/thread/libcap.rs b/src/thread/libcap.rs
new file mode 100644
index 0000000..0a0fbb4
--- /dev/null
+++ b/src/thread/libcap.rs
@@ -0,0 +1,185 @@
+use bitflags::bitflags;
+use core::mem::MaybeUninit;
+
+use crate::pid::Pid;
+use crate::{backend, io};
+
+/// `__user_cap_data_struct`
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct CapabilitySets {
+ /// `__user_cap_data_struct.effective`
+ pub effective: CapabilityFlags,
+ /// `__user_cap_data_struct.permitted`
+ pub permitted: CapabilityFlags,
+ /// `__user_cap_data_struct.inheritable`
+ pub inheritable: CapabilityFlags,
+}
+
+bitflags! {
+ /// `CAP_*` constants.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CapabilityFlags: u64 {
+ /// `CAP_CHOWN`
+ const CHOWN = 1 << linux_raw_sys::general::CAP_CHOWN;
+ /// `CAP_DAC_OVERRIDE`
+ const DAC_OVERRIDE = 1 << linux_raw_sys::general::CAP_DAC_OVERRIDE;
+ /// `CAP_DAC_READ_SEARCH`
+ const DAC_READ_SEARCH = 1 << linux_raw_sys::general::CAP_DAC_READ_SEARCH;
+ /// `CAP_FOWNER`
+ const FOWNER = 1 << linux_raw_sys::general::CAP_FOWNER;
+ /// `CAP_FSETID`
+ const FSETID = 1 << linux_raw_sys::general::CAP_FSETID;
+ /// `CAP_KILL`
+ const KILL = 1 << linux_raw_sys::general::CAP_KILL;
+ /// `CAP_SETGID`
+ const SETGID = 1 << linux_raw_sys::general::CAP_SETGID;
+ /// `CAP_SETUID`
+ const SETUID = 1 << linux_raw_sys::general::CAP_SETUID;
+ /// `CAP_SETPCAP`
+ const SETPCAP = 1 << linux_raw_sys::general::CAP_SETPCAP;
+ /// `CAP_LINUX_IMMUTABLE`
+ const LINUX_IMMUTABLE = 1 << linux_raw_sys::general::CAP_LINUX_IMMUTABLE;
+ /// `CAP_NET_BIND_SERVICE`
+ const NET_BIND_SERVICE = 1 << linux_raw_sys::general::CAP_NET_BIND_SERVICE;
+ /// `CAP_NET_BROADCAST`
+ const NET_BROADCAST = 1 << linux_raw_sys::general::CAP_NET_BROADCAST;
+ /// `CAP_NET_ADMIN`
+ const NET_ADMIN = 1 << linux_raw_sys::general::CAP_NET_ADMIN;
+ /// `CAP_NET_RAW`
+ const NET_RAW = 1 << linux_raw_sys::general::CAP_NET_RAW;
+ /// `CAP_IPC_LOCK`
+ const IPC_LOCK = 1 << linux_raw_sys::general::CAP_IPC_LOCK;
+ /// `CAP_IPC_OWNER`
+ const IPC_OWNER = 1 << linux_raw_sys::general::CAP_IPC_OWNER;
+ /// `CAP_SYS_MODULE`
+ const SYS_MODULE = 1 << linux_raw_sys::general::CAP_SYS_MODULE;
+ /// `CAP_SYS_RAWIO`
+ const SYS_RAWIO = 1 << linux_raw_sys::general::CAP_SYS_RAWIO;
+ /// `CAP_SYS_CHROOT`
+ const SYS_CHROOT = 1 << linux_raw_sys::general::CAP_SYS_CHROOT;
+ /// `CAP_SYS_PTRACE`
+ const SYS_PTRACE = 1 << linux_raw_sys::general::CAP_SYS_PTRACE;
+ /// `CAP_SYS_PACCT`
+ const SYS_PACCT = 1 << linux_raw_sys::general::CAP_SYS_PACCT;
+ /// `CAP_SYS_ADMIN`
+ const SYS_ADMIN = 1 << linux_raw_sys::general::CAP_SYS_ADMIN;
+ /// `CAP_SYS_BOOT`
+ const SYS_BOOT = 1 << linux_raw_sys::general::CAP_SYS_BOOT;
+ /// `CAP_SYS_NICE`
+ const SYS_NICE = 1 << linux_raw_sys::general::CAP_SYS_NICE;
+ /// `CAP_SYS_RESOURCE`
+ const SYS_RESOURCE = 1 << linux_raw_sys::general::CAP_SYS_RESOURCE;
+ /// `CAP_SYS_TIME`
+ const SYS_TIME = 1 << linux_raw_sys::general::CAP_SYS_TIME;
+ /// `CAP_SYS_TTY_CONFIG`
+ const SYS_TTY_CONFIG = 1 << linux_raw_sys::general::CAP_SYS_TTY_CONFIG;
+ /// `CAP_MKNOD`
+ const MKNOD = 1 << linux_raw_sys::general::CAP_MKNOD;
+ /// `CAP_LEASE`
+ const LEASE = 1 << linux_raw_sys::general::CAP_LEASE;
+ /// `CAP_AUDIT_WRITE`
+ const AUDIT_WRITE = 1 << linux_raw_sys::general::CAP_AUDIT_WRITE;
+ /// `CAP_AUDIT_CONTROL`
+ const AUDIT_CONTROL = 1 << linux_raw_sys::general::CAP_AUDIT_CONTROL;
+ /// `CAP_SETFCAP`
+ const SETFCAP = 1 << linux_raw_sys::general::CAP_SETFCAP;
+ /// `CAP_MAC_OVERRIDE`
+ const MAC_OVERRIDE = 1 << linux_raw_sys::general::CAP_MAC_OVERRIDE;
+ /// `CAP_MAC_ADMIN`
+ const MAC_ADMIN = 1 << linux_raw_sys::general::CAP_MAC_ADMIN;
+ /// `CAP_SYSLOG`
+ const SYSLOG = 1 << linux_raw_sys::general::CAP_SYSLOG;
+ /// `CAP_WAKE_ALARM`
+ const WAKE_ALARM = 1 << linux_raw_sys::general::CAP_WAKE_ALARM;
+ /// `CAP_BLOCK_SUSPEND`
+ const BLOCK_SUSPEND = 1 << linux_raw_sys::general::CAP_BLOCK_SUSPEND;
+ /// `CAP_AUDIT_READ`
+ const AUDIT_READ = 1 << linux_raw_sys::general::CAP_AUDIT_READ;
+ /// `CAP_PERFMON`
+ const PERFMON = 1 << linux_raw_sys::general::CAP_PERFMON;
+ /// `CAP_BPF`
+ const BPF = 1 << linux_raw_sys::general::CAP_BPF;
+ /// `CAP_CHECKPOINT_RESTORE`
+ const CHECKPOINT_RESTORE = 1 << linux_raw_sys::general::CAP_CHECKPOINT_RESTORE;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// `capget(_LINUX_CAPABILITY_VERSION_3, pid)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/capget.2.html
+#[inline]
+#[doc(alias = "capget")]
+pub fn capabilities(pid: Option<Pid>) -> io::Result<CapabilitySets> {
+ capget(pid)
+}
+
+/// `capset(_LINUX_CAPABILITY_VERSION_3, pid, effective, permitted,
+/// inheritable)`
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/capget.2.html
+#[inline]
+#[doc(alias = "capset")]
+pub fn set_capabilities(pid: Option<Pid>, sets: CapabilitySets) -> io::Result<()> {
+ capset(pid, sets)
+}
+
+#[inline]
+#[allow(unsafe_code)]
+fn capget(pid: Option<Pid>) -> io::Result<CapabilitySets> {
+ let mut data = [MaybeUninit::<linux_raw_sys::general::__user_cap_data_struct>::uninit(); 2];
+
+ let data = {
+ let mut header = linux_raw_sys::general::__user_cap_header_struct {
+ version: linux_raw_sys::general::_LINUX_CAPABILITY_VERSION_3,
+ pid: Pid::as_raw(pid) as backend::c::c_int,
+ };
+
+ backend::thread::syscalls::capget(&mut header, &mut data)?;
+ // SAFETY: v3 is a 64-bit implementation, so the kernel filled in both
+ // data structs.
+ unsafe { (data[0].assume_init(), data[1].assume_init()) }
+ };
+
+ let effective = u64::from(data.0.effective) | (u64::from(data.1.effective) << u32::BITS);
+ let permitted = u64::from(data.0.permitted) | (u64::from(data.1.permitted) << u32::BITS);
+ let inheritable = u64::from(data.0.inheritable) | (u64::from(data.1.inheritable) << u32::BITS);
+
+ // The kernel returns a partitioned bitset that we just combined above.
+ Ok(CapabilitySets {
+ effective: CapabilityFlags::from_bits_retain(effective),
+ permitted: CapabilityFlags::from_bits_retain(permitted),
+ inheritable: CapabilityFlags::from_bits_retain(inheritable),
+ })
+}
+
+#[inline]
+fn capset(pid: Option<Pid>, sets: CapabilitySets) -> io::Result<()> {
+ let mut header = linux_raw_sys::general::__user_cap_header_struct {
+ version: linux_raw_sys::general::_LINUX_CAPABILITY_VERSION_3,
+ pid: Pid::as_raw(pid) as backend::c::c_int,
+ };
+ let data = [
+ linux_raw_sys::general::__user_cap_data_struct {
+ effective: sets.effective.bits() as u32,
+ permitted: sets.permitted.bits() as u32,
+ inheritable: sets.inheritable.bits() as u32,
+ },
+ linux_raw_sys::general::__user_cap_data_struct {
+ effective: (sets.effective.bits() >> u32::BITS) as u32,
+ permitted: (sets.permitted.bits() >> u32::BITS) as u32,
+ inheritable: (sets.inheritable.bits() >> u32::BITS) as u32,
+ },
+ ];
+
+ backend::thread::syscalls::capset(&mut header, &data)
+}
diff --git a/src/thread/mod.rs b/src/thread/mod.rs
new file mode 100644
index 0000000..6f19635
--- /dev/null
+++ b/src/thread/mod.rs
@@ -0,0 +1,30 @@
+//! Thread-associated operations.
+
+#[cfg(not(target_os = "redox"))]
+mod clock;
+#[cfg(linux_kernel)]
+mod futex;
+#[cfg(linux_kernel)]
+mod id;
+#[cfg(linux_kernel)]
+mod libcap;
+#[cfg(linux_kernel)]
+mod prctl;
+#[cfg(linux_kernel)]
+mod setns;
+
+#[cfg(not(target_os = "redox"))]
+pub use clock::*;
+#[cfg(linux_kernel)]
+pub use futex::{futex, FutexFlags, FutexOperation};
+#[cfg(linux_kernel)]
+pub use id::{
+ gettid, set_thread_gid, set_thread_res_gid, set_thread_res_uid, set_thread_uid, Gid, Pid,
+ RawGid, RawPid, RawUid, Uid,
+};
+#[cfg(linux_kernel)]
+pub use libcap::{capabilities, set_capabilities, CapabilityFlags, CapabilitySets};
+#[cfg(linux_kernel)]
+pub use prctl::*;
+#[cfg(linux_kernel)]
+pub use setns::*;
diff --git a/src/thread/prctl.rs b/src/thread/prctl.rs
new file mode 100644
index 0000000..9d9e6b2
--- /dev/null
+++ b/src/thread/prctl.rs
@@ -0,0 +1,1007 @@
+//! Linux `prctl` wrappers.
+//!
+//! Rustix wraps variadic/dynamic-dispatch functions like `prctl` in
+//! type-safe wrappers.
+//!
+//! # Safety
+//!
+//! The inner `prctl` calls are dynamically typed and must be called
+//! correctly.
+#![allow(unsafe_code)]
+
+use core::mem::MaybeUninit;
+use core::num::NonZeroU64;
+use core::ptr;
+use core::ptr::NonNull;
+use core::sync::atomic::AtomicU8;
+
+use bitflags::bitflags;
+
+use crate::backend::c::{c_int, c_uint, c_void};
+use crate::backend::prctl::syscalls;
+use crate::ffi::CStr;
+#[cfg(feature = "alloc")]
+use crate::ffi::CString;
+use crate::io;
+use crate::pid::Pid;
+use crate::prctl::{
+ prctl_1arg, prctl_2args, prctl_3args, prctl_get_at_arg2_optional, PointerAuthenticationKeys,
+};
+use crate::utils::as_ptr;
+
+//
+// PR_GET_KEEPCAPS/PR_SET_KEEPCAPS
+//
+
+const PR_GET_KEEPCAPS: c_int = 7;
+
+/// Get the current state of the calling thread's `keep capabilities` flag.
+///
+/// # References
+/// - [`prctl(PR_GET_KEEPCAPS,...)`]
+///
+/// [`prctl(PR_GET_KEEPCAPS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn get_keep_capabilities() -> io::Result<bool> {
+ unsafe { prctl_1arg(PR_GET_KEEPCAPS) }.map(|r| r != 0)
+}
+
+const PR_SET_KEEPCAPS: c_int = 8;
+
+/// Set the state of the calling thread's `keep capabilities` flag.
+///
+/// # References
+/// - [`prctl(PR_SET_KEEPCAPS,...)`]
+///
+/// [`prctl(PR_SET_KEEPCAPS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn set_keep_capabilities(enable: bool) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_KEEPCAPS, usize::from(enable) as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_NAME/PR_SET_NAME
+//
+
+#[cfg(feature = "alloc")]
+const PR_GET_NAME: c_int = 16;
+
+/// Get the name of the calling thread.
+///
+/// # References
+/// - [`prctl(PR_GET_NAME,...)`]
+///
+/// [`prctl(PR_GET_NAME,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+#[cfg(feature = "alloc")]
+pub fn name() -> io::Result<CString> {
+ let mut buffer = [0_u8; 16];
+ unsafe { prctl_2args(PR_GET_NAME, buffer.as_mut_ptr().cast())? };
+
+ let len = buffer.iter().position(|&x| x == 0_u8).unwrap_or(0);
+ CString::new(&buffer[..len]).map_err(|_r| io::Errno::ILSEQ)
+}
+
+const PR_SET_NAME: c_int = 15;
+
+/// Set the name of the calling thread.
+///
+/// Unlike `pthread_setname_np`, this function silently truncates the name to
+/// 16 bytes, as the Linux syscall does.
+///
+/// # References
+/// - [`prctl(PR_SET_NAME,...)`]
+///
+/// [`prctl(PR_SET_NAME,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn set_name(name: &CStr) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_NAME, name.as_ptr() as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_SECCOMP/PR_SET_SECCOMP
+//
+
+//const PR_GET_SECCOMP: c_int = 21;
+
+const SECCOMP_MODE_DISABLED: i32 = 0;
+const SECCOMP_MODE_STRICT: i32 = 1;
+const SECCOMP_MODE_FILTER: i32 = 2;
+
+/// `SECCOMP_MODE_*`.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum SecureComputingMode {
+ /// Secure computing is not in use.
+ Disabled = SECCOMP_MODE_DISABLED,
+ /// Use hard-coded filter.
+ Strict = SECCOMP_MODE_STRICT,
+ /// Use user-supplied filter.
+ Filter = SECCOMP_MODE_FILTER,
+}
+
+impl TryFrom<i32> for SecureComputingMode {
+ type Error = io::Errno;
+
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
+ match value {
+ SECCOMP_MODE_DISABLED => Ok(Self::Disabled),
+ SECCOMP_MODE_STRICT => Ok(Self::Strict),
+ SECCOMP_MODE_FILTER => Ok(Self::Filter),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/*
+/// Get the secure computing mode of the calling thread.
+///
+/// If the caller is not in secure computing mode, this returns
+/// [`SecureComputingMode::Disabled`]. If the caller is in strict secure
+/// computing mode, then this call will cause a [`Signal::Kill`] signal to be
+/// sent to the process. If the caller is in filter mode, and this system call
+/// is allowed by the seccomp filters, it returns
+/// [`SecureComputingMode::Filter`]; otherwise, the process is killed with a
+/// [`Signal::Kill`] signal.
+///
+/// Since Linux 3.8, the Seccomp field of the `/proc/[pid]/status` file
+/// provides a method of obtaining the same information, without the risk that
+/// the process is killed; see [the `proc` manual page].
+///
+/// # References
+/// - [`prctl(PR_GET_SECCOMP,...)`]
+///
+/// [`prctl(PR_GET_SECCOMP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+/// [the `proc` manual page]: https://man7.org/linux/man-pages/man5/proc.5.html
+#[inline]
+pub fn secure_computing_mode() -> io::Result<SecureComputingMode> {
+ unsafe { prctl_1arg(PR_GET_SECCOMP) }.and_then(TryInto::try_into)
+}
+*/
+
+const PR_SET_SECCOMP: c_int = 22;
+
+/// Set the secure computing mode for the calling thread, to limit the
+/// available system calls.
+///
+/// # References
+/// - [`prctl(PR_SET_SECCOMP,...)`]
+///
+/// [`prctl(PR_SET_SECCOMP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn set_secure_computing_mode(mode: SecureComputingMode) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_SECCOMP, mode as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_CAPBSET_READ/PR_CAPBSET_DROP
+//
+
+const PR_CAPBSET_READ: c_int = 23;
+
+/// Linux per-thread capability.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum Capability {
+ /// In a system with the `_POSIX_CHOWN_RESTRICTED` option defined, this
+ /// overrides the restriction of changing file ownership and group
+ /// ownership.
+ ChangeOwnership = linux_raw_sys::general::CAP_CHOWN,
+ /// Override all DAC access, including ACL execute access if `_POSIX_ACL`
+ /// is defined. Excluding DAC access covered by
+ /// [`Capability::LinuxImmutable`].
+ DACOverride = linux_raw_sys::general::CAP_DAC_OVERRIDE,
+ /// Overrides all DAC restrictions regarding read and search on files and
+ /// directories, including ACL restrictions if `_POSIX_ACL` is defined.
+ /// Excluding DAC access covered by [`Capability::LinuxImmutable`].
+ DACReadSearch = linux_raw_sys::general::CAP_DAC_READ_SEARCH,
+ /// Overrides all restrictions about allowed operations on files, where
+ /// file owner ID must be equal to the user ID, except where
+ /// [`Capability::FileSetID`] is applicable. It doesn't override MAC
+ /// and DAC restrictions.
+ FileOwner = linux_raw_sys::general::CAP_FOWNER,
+ /// Overrides the following restrictions that the effective user ID shall
+ /// match the file owner ID when setting the `S_ISUID` and `S_ISGID`
+ /// bits on that file; that the effective group ID (or one of the
+ /// supplementary group IDs) shall match the file owner ID when setting the
+ /// `S_ISGID` bit on that file; that the `S_ISUID` and `S_ISGID` bits are
+ /// cleared on successful return from `chown` (not implemented).
+ FileSetID = linux_raw_sys::general::CAP_FSETID,
+ /// Overrides the restriction that the real or effective user ID of a
+ /// process sending a signal must match the real or effective user ID of
+ /// the process receiving the signal.
+ Kill = linux_raw_sys::general::CAP_KILL,
+ /// Allows `setgid` manipulation. Allows `setgroups`. Allows forged gids on
+ /// socket credentials passing.
+ SetGroupID = linux_raw_sys::general::CAP_SETGID,
+ /// Allows `set*uid` manipulation (including fsuid). Allows forged pids on
+ /// socket credentials passing.
+ SetUserID = linux_raw_sys::general::CAP_SETUID,
+ /// Without VFS support for capabilities:
+ /// - Transfer any capability in your permitted set to any pid.
+ /// - remove any capability in your permitted set from any pid. With VFS
+ /// support for capabilities (neither of above, but)
+ /// - Add any capability from current's capability bounding set to the
+ /// current process' inheritable set.
+ /// - Allow taking bits out of capability bounding set.
+ /// - Allow modification of the securebits for a process.
+ SetPermittedCapabilities = linux_raw_sys::general::CAP_SETPCAP,
+ /// Allow modification of `S_IMMUTABLE` and `S_APPEND` file attributes.
+ LinuxImmutable = linux_raw_sys::general::CAP_LINUX_IMMUTABLE,
+ /// Allows binding to TCP/UDP sockets below 1024. Allows binding to ATM
+ /// VCIs below 32.
+ NetBindService = linux_raw_sys::general::CAP_NET_BIND_SERVICE,
+ /// Allow broadcasting, listen to multicast.
+ NetBroadcast = linux_raw_sys::general::CAP_NET_BROADCAST,
+ /// Allow interface configuration. Allow administration of IP firewall,
+ /// masquerading and accounting. Allow setting debug option on sockets.
+ /// Allow modification of routing tables. Allow setting arbitrary
+ /// process / process group ownership on sockets. Allow binding to any
+ /// address for transparent proxying (also via [`Capability::NetRaw`]).
+ /// Allow setting TOS (type of service). Allow setting promiscuous
+ /// mode. Allow clearing driver statistics. Allow multicasting. Allow
+ /// read/write of device-specific registers. Allow activation of ATM
+ /// control sockets.
+ NetAdmin = linux_raw_sys::general::CAP_NET_ADMIN,
+ /// Allow use of `RAW` sockets. Allow use of `PACKET` sockets. Allow
+ /// binding to any address for transparent proxying (also via
+ /// [`Capability::NetAdmin`]).
+ NetRaw = linux_raw_sys::general::CAP_NET_RAW,
+ /// Allow locking of shared memory segments. Allow mlock and mlockall
+ /// (which doesn't really have anything to do with IPC).
+ IPCLock = linux_raw_sys::general::CAP_IPC_LOCK,
+ /// Override IPC ownership checks.
+ IPCOwner = linux_raw_sys::general::CAP_IPC_OWNER,
+ /// Insert and remove kernel modules - modify kernel without limit.
+ SystemModule = linux_raw_sys::general::CAP_SYS_MODULE,
+ /// Allow ioperm/iopl access. Allow sending USB messages to any device via
+ /// `/dev/bus/usb`.
+ SystemRawIO = linux_raw_sys::general::CAP_SYS_RAWIO,
+ /// Allow use of `chroot`.
+ SystemChangeRoot = linux_raw_sys::general::CAP_SYS_CHROOT,
+ /// Allow `ptrace` of any process.
+ SystemProcessTrace = linux_raw_sys::general::CAP_SYS_PTRACE,
+ /// Allow configuration of process accounting.
+ SystemProcessAccounting = linux_raw_sys::general::CAP_SYS_PACCT,
+ /// Allow configuration of the secure attention key. Allow administration
+ /// of the random device. Allow examination and configuration of disk
+ /// quotas. Allow setting the domainname. Allow setting the hostname.
+ /// Allow `mount` and `umount`, setting up new smb connection.
+ /// Allow some autofs root ioctls. Allow nfsservctl. Allow
+ /// `VM86_REQUEST_IRQ`. Allow to read/write pci config on alpha. Allow
+ /// `irix_prctl` on mips (setstacksize). Allow flushing all cache on
+ /// m68k (`sys_cacheflush`). Allow removing semaphores. Used instead of
+ /// [`Capability::ChangeOwnership`] to "chown" IPC message queues,
+ /// semaphores and shared memory. Allow locking/unlocking of shared
+ /// memory segment. Allow turning swap on/off. Allow forged pids on
+ /// socket credentials passing. Allow setting readahead and
+ /// flushing buffers on block devices. Allow setting geometry in floppy
+ /// driver. Allow turning DMA on/off in `xd` driver. Allow
+ /// administration of md devices (mostly the above, but some
+ /// extra ioctls). Allow tuning the ide driver. Allow access to the nvram
+ /// device. Allow administration of `apm_bios`, serial and bttv (TV)
+ /// device. Allow manufacturer commands in isdn CAPI support driver.
+ /// Allow reading non-standardized portions of pci configuration space.
+ /// Allow DDI debug ioctl on sbpcd driver. Allow setting up serial ports.
+ /// Allow sending raw qic-117 commands. Allow enabling/disabling tagged
+ /// queuing on SCSI controllers and sending arbitrary SCSI commands.
+ /// Allow setting encryption key on loopback filesystem. Allow setting
+ /// zone reclaim policy. Allow everything under
+ /// [`Capability::BerkeleyPacketFilters`] and
+ /// [`Capability::PerformanceMonitoring`] for backward compatibility.
+ SystemAdmin = linux_raw_sys::general::CAP_SYS_ADMIN,
+ /// Allow use of `reboot`.
+ SystemBoot = linux_raw_sys::general::CAP_SYS_BOOT,
+ /// Allow raising priority and setting priority on other (different UID)
+ /// processes. Allow use of FIFO and round-robin (realtime) scheduling
+ /// on own processes and setting the scheduling algorithm used by
+ /// another process. Allow setting cpu affinity on other processes.
+ /// Allow setting realtime ioprio class. Allow setting ioprio class on
+ /// other processes.
+ SystemNice = linux_raw_sys::general::CAP_SYS_NICE,
+ /// Override resource limits. Set resource limits. Override quota limits.
+ /// Override reserved space on ext2 filesystem. Modify data journaling
+ /// mode on ext3 filesystem (uses journaling resources). NOTE: ext2
+ /// honors fsuid when checking for resource overrides, so you can
+ /// override using fsuid too. Override size restrictions on IPC message
+ /// queues. Allow more than 64hz interrupts from the real-time clock.
+ /// Override max number of consoles on console allocation. Override max
+ /// number of keymaps. Control memory reclaim behavior.
+ SystemResource = linux_raw_sys::general::CAP_SYS_RESOURCE,
+ /// Allow manipulation of system clock. Allow `irix_stime` on mips. Allow
+ /// setting the real-time clock.
+ SystemTime = linux_raw_sys::general::CAP_SYS_TIME,
+ /// Allow configuration of tty devices. Allow `vhangup` of tty.
+ SystemTTYConfig = linux_raw_sys::general::CAP_SYS_TTY_CONFIG,
+ /// Allow the privileged aspects of `mknod`.
+ MakeNode = linux_raw_sys::general::CAP_MKNOD,
+ /// Allow taking of leases on files.
+ Lease = linux_raw_sys::general::CAP_LEASE,
+ /// Allow writing the audit log via unicast netlink socket.
+ AuditWrite = linux_raw_sys::general::CAP_AUDIT_WRITE,
+ /// Allow configuration of audit via unicast netlink socket.
+ AuditControl = linux_raw_sys::general::CAP_AUDIT_CONTROL,
+ /// Set or remove capabilities on files. Map `uid=0` into a child user
+ /// namespace.
+ SetFileCapabilities = linux_raw_sys::general::CAP_SETFCAP,
+ /// Override MAC access. The base kernel enforces no MAC policy. An LSM may
+ /// enforce a MAC policy, and if it does and it chooses to implement
+ /// capability based overrides of that policy, this is the capability
+ /// it should use to do so.
+ MACOverride = linux_raw_sys::general::CAP_MAC_OVERRIDE,
+ /// Allow MAC configuration or state changes. The base kernel requires no
+ /// MAC configuration. An LSM may enforce a MAC policy, and if it does
+ /// and it chooses to implement capability based
+ /// checks on modifications to that policy or the data required to maintain
+ /// it, this is the capability it should use to do so.
+ MACAdmin = linux_raw_sys::general::CAP_MAC_ADMIN,
+ /// Allow configuring the kernel's `syslog` (`printk` behaviour).
+ SystemLog = linux_raw_sys::general::CAP_SYSLOG,
+ /// Allow triggering something that will wake the system.
+ WakeAlarm = linux_raw_sys::general::CAP_WAKE_ALARM,
+ /// Allow preventing system suspends.
+ BlockSuspend = linux_raw_sys::general::CAP_BLOCK_SUSPEND,
+ /// Allow reading the audit log via multicast netlink socket.
+ AuditRead = linux_raw_sys::general::CAP_AUDIT_READ,
+ /// Allow system performance and observability privileged operations using
+ /// `perf_events`, `i915_perf` and other kernel subsystems.
+ PerformanceMonitoring = linux_raw_sys::general::CAP_PERFMON,
+ /// This capability allows the following BPF operations:
+ /// - Creating all types of BPF maps
+ /// - Advanced verifier features
+ /// - Indirect variable access
+ /// - Bounded loops
+ /// - BPF to BPF function calls
+ /// - Scalar precision tracking
+ /// - Larger complexity limits
+ /// - Dead code elimination
+ /// - And potentially other features
+ /// - Loading BPF Type Format (BTF) data
+ /// - Retrieve `xlated` and JITed code of BPF programs
+ /// - Use `bpf_spin_lock` helper
+ ///
+ /// [`Capability::PerformanceMonitoring`] relaxes the verifier checks
+ /// further:
+ /// - BPF progs can use of pointer-to-integer conversions
+ /// - speculation attack hardening measures are bypassed
+ /// - `bpf_probe_read` to read arbitrary kernel memory is allowed
+ /// - `bpf_trace_printk` to print kernel memory is allowed
+ ///
+ /// [`Capability::SystemAdmin`] is required to use bpf_probe_write_user.
+ ///
+ /// [`Capability::SystemAdmin`] is required to iterate system-wide loaded
+ /// programs, maps, links, and BTFs, and convert their IDs to file
+ /// descriptors.
+ ///
+ /// [`Capability::PerformanceMonitoring`] and
+ /// [`Capability::BerkeleyPacketFilters`] are required to load tracing
+ /// programs. [`Capability::NetAdmin`] and
+ /// [`Capability::BerkeleyPacketFilters`] are required to load
+ /// networking programs.
+ BerkeleyPacketFilters = linux_raw_sys::general::CAP_BPF,
+ /// Allow checkpoint/restore related operations. Allow PID selection during
+ /// `clone3`. Allow writing to `ns_last_pid`.
+ CheckpointRestore = linux_raw_sys::general::CAP_CHECKPOINT_RESTORE,
+}
+
+/// Check if the specified capability is in the calling thread's capability
+/// bounding set.
+///
+/// # References
+/// - [`prctl(PR_CAPBSET_READ,...)`]
+///
+/// [`prctl(PR_CAPBSET_READ,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn capability_is_in_bounding_set(capability: Capability) -> io::Result<bool> {
+ unsafe { prctl_2args(PR_CAPBSET_READ, capability as usize as *mut _) }.map(|r| r != 0)
+}
+
+const PR_CAPBSET_DROP: c_int = 24;
+
+/// If the calling thread has the [`Capability::SetPermittedCapabilities`]
+/// capability within its user namespace, then drop the specified capability
+/// from the thread's capability bounding set.
+///
+/// # References
+/// - [`prctl(PR_CAPBSET_DROP,...)`]
+///
+/// [`prctl(PR_CAPBSET_DROP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn remove_capability_from_bounding_set(capability: Capability) -> io::Result<()> {
+ unsafe { prctl_2args(PR_CAPBSET_DROP, capability as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_SECUREBITS/PR_SET_SECUREBITS
+//
+
+const PR_GET_SECUREBITS: c_int = 27;
+
+bitflags! {
+ /// `SECBIT_*`.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct CapabilitiesSecureBits: u32 {
+ /// If this bit is set, then the kernel does not grant capabilities
+ /// when a `set-user-ID-root` program is executed, or when a process
+ /// with an effective or real UID of 0 calls `execve`.
+ const NO_ROOT = 1_u32 << 0;
+ /// Set [`NO_ROOT`] irreversibly.
+ const NO_ROOT_LOCKED = 1_u32 << 1;
+ /// Setting this flag stops the kernel from adjusting the process'
+ /// permitted, effective, and ambient capability sets when the thread's
+ /// effective and filesystem UIDs are switched between zero and nonzero
+ /// values.
+ const NO_SETUID_FIXUP = 1_u32 << 2;
+ /// Set [`NO_SETUID_FIXUP`] irreversibly.
+ const NO_SETUID_FIXUP_LOCKED = 1_u32 << 3;
+ /// Setting this flag allows a thread that has one or more 0 UIDs to
+ /// retain capabilities in its permitted set when it switches all of
+ /// its UIDs to nonzero values.
+ const KEEP_CAPS = 1_u32 << 4;
+ /// Set [`KEEP_CAPS`] irreversibly.
+ const KEEP_CAPS_LOCKED = 1_u32 << 5;
+ /// Setting this flag disallows raising ambient capabilities via the
+ /// `prctl`'s `PR_CAP_AMBIENT_RAISE` operation.
+ const NO_CAP_AMBIENT_RAISE = 1_u32 << 6;
+ /// Set [`NO_CAP_AMBIENT_RAISE`] irreversibly.
+ const NO_CAP_AMBIENT_RAISE_LOCKED = 1_u32 << 7;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// Get the `securebits` flags of the calling thread.
+///
+/// # References
+/// - [`prctl(PR_GET_SECUREBITS,...)`]
+///
+/// [`prctl(PR_GET_SECUREBITS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn capabilities_secure_bits() -> io::Result<CapabilitiesSecureBits> {
+ let r = unsafe { prctl_1arg(PR_GET_SECUREBITS)? } as c_uint;
+ CapabilitiesSecureBits::from_bits(r).ok_or(io::Errno::RANGE)
+}
+
+const PR_SET_SECUREBITS: c_int = 28;
+
+/// Set the `securebits` flags of the calling thread.
+///
+/// # References
+/// - [`prctl(PR_SET_SECUREBITS,...)`]
+///
+/// [`prctl(PR_SET_SECUREBITS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn set_capabilities_secure_bits(bits: CapabilitiesSecureBits) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_SECUREBITS, bits.bits() as usize as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_TIMERSLACK/PR_SET_TIMERSLACK
+//
+
+const PR_GET_TIMERSLACK: c_int = 30;
+
+/// Get the `current` timer slack value of the calling thread.
+///
+/// # References
+/// - [`prctl(PR_GET_TIMERSLACK,...)`]
+///
+/// [`prctl(PR_GET_TIMERSLACK,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn current_timer_slack() -> io::Result<u64> {
+ unsafe { prctl_1arg(PR_GET_TIMERSLACK) }.map(|r| r as u64)
+}
+
+const PR_SET_TIMERSLACK: c_int = 29;
+
+/// Sets the `current` timer slack value for the calling thread.
+///
+/// # References
+/// - [`prctl(PR_SET_TIMERSLACK,...)`]
+///
+/// [`prctl(PR_SET_TIMERSLACK,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn set_current_timer_slack(value: Option<NonZeroU64>) -> io::Result<()> {
+ let value = usize::try_from(value.map_or(0, NonZeroU64::get)).map_err(|_r| io::Errno::RANGE)?;
+ unsafe { prctl_2args(PR_SET_TIMERSLACK, value as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_NO_NEW_PRIVS/PR_SET_NO_NEW_PRIVS
+//
+
+const PR_GET_NO_NEW_PRIVS: c_int = 39;
+
+/// Get the value of the `no_new_privs` attribute for the calling thread.
+///
+/// # References
+/// - [`prctl(PR_GET_NO_NEW_PRIVS,...)`]
+///
+/// [`prctl(PR_GET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn no_new_privs() -> io::Result<bool> {
+ unsafe { prctl_1arg(PR_GET_NO_NEW_PRIVS) }.map(|r| r != 0)
+}
+
+const PR_SET_NO_NEW_PRIVS: c_int = 38;
+
+/// Set the calling thread's `no_new_privs` attribute.
+///
+/// # References
+/// - [`prctl(PR_SET_NO_NEW_PRIVS,...)`]
+///
+/// [`prctl(PR_SET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn set_no_new_privs(no_new_privs: bool) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_NO_NEW_PRIVS, usize::from(no_new_privs) as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_GET_TID_ADDRESS
+//
+
+const PR_GET_TID_ADDRESS: c_int = 40;
+
+/// Get the `clear_child_tid` address set by `set_tid_address`
+/// and `clone`'s `CLONE_CHILD_CLEARTID` flag.
+///
+/// # References
+/// - [`prctl(PR_GET_TID_ADDRESS,...)`]
+///
+/// [`prctl(PR_GET_TID_ADDRESS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn get_clear_child_tid_address() -> io::Result<Option<NonNull<c_void>>> {
+ unsafe { prctl_get_at_arg2_optional::<*mut c_void>(PR_GET_TID_ADDRESS) }.map(NonNull::new)
+}
+
+//
+// PR_GET_THP_DISABLE/PR_SET_THP_DISABLE
+//
+
+const PR_GET_THP_DISABLE: c_int = 42;
+
+/// Get the current setting of the `THP disable` flag for the calling thread.
+///
+/// # References
+/// - [`prctl(PR_GET_THP_DISABLE,...)`]
+///
+/// [`prctl(PR_GET_THP_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn transparent_huge_pages_are_disabled() -> io::Result<bool> {
+ unsafe { prctl_1arg(PR_GET_THP_DISABLE) }.map(|r| r != 0)
+}
+
+const PR_SET_THP_DISABLE: c_int = 41;
+
+/// Set the state of the `THP disable` flag for the calling thread.
+///
+/// # References
+/// - [`prctl(PR_SET_THP_DISABLE,...)`]
+///
+/// [`prctl(PR_SET_THP_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn disable_transparent_huge_pages(thp_disable: bool) -> io::Result<()> {
+ unsafe { prctl_2args(PR_SET_THP_DISABLE, usize::from(thp_disable) as *mut _) }.map(|_r| ())
+}
+
+//
+// PR_CAP_AMBIENT
+//
+
+const PR_CAP_AMBIENT: c_int = 47;
+
+const PR_CAP_AMBIENT_IS_SET: usize = 1;
+
+/// Check if the specified capability is in the ambient set.
+///
+/// # References
+/// - [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_IS_SET,...)`]
+///
+/// [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_IS_SET,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn capability_is_in_ambient_set(capability: Capability) -> io::Result<bool> {
+ let cap = capability as usize as *mut _;
+ unsafe { prctl_3args(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET as *mut _, cap) }.map(|r| r != 0)
+}
+
+const PR_CAP_AMBIENT_CLEAR_ALL: usize = 4;
+
+/// Remove all capabilities from the ambient set.
+///
+/// # References
+/// - [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_CLEAR_ALL,...)`]
+///
+/// [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_CLEAR_ALL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn clear_ambient_capability_set() -> io::Result<()> {
+ unsafe { prctl_2args(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL as *mut _) }.map(|_r| ())
+}
+
+const PR_CAP_AMBIENT_RAISE: usize = 2;
+const PR_CAP_AMBIENT_LOWER: usize = 3;
+
+/// Add or remove the specified capability to the ambient set.
+///
+/// # References
+/// - [`prctl(PR_CAP_AMBIENT,...)`]
+///
+/// [`prctl(PR_CAP_AMBIENT,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn configure_capability_in_ambient_set(capability: Capability, enable: bool) -> io::Result<()> {
+ let sub_operation = if enable {
+ PR_CAP_AMBIENT_RAISE
+ } else {
+ PR_CAP_AMBIENT_LOWER
+ };
+ let cap = capability as usize as *mut _;
+
+ unsafe { prctl_3args(PR_CAP_AMBIENT, sub_operation as *mut _, cap) }.map(|_r| ())
+}
+
+//
+// PR_SVE_GET_VL/PR_SVE_SET_VL
+//
+
+const PR_SVE_GET_VL: c_int = 51;
+
+const PR_SVE_VL_LEN_MASK: u32 = 0xffff;
+const PR_SVE_VL_INHERIT: u32 = 1_u32 << 17;
+
+/// Scalable Vector Extension vector length configuration.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct SVEVectorLengthConfig {
+ /// Vector length in bytes.
+ pub vector_length_in_bytes: u32,
+ /// Vector length inherited across `execve`.
+ pub vector_length_inherited_across_execve: bool,
+}
+
+/// Get the thread's current SVE vector length configuration.
+///
+/// # References
+/// - [`prctl(PR_SVE_GET_VL,...)`]
+///
+/// [`prctl(PR_SVE_GET_VL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn sve_vector_length_configuration() -> io::Result<SVEVectorLengthConfig> {
+ let bits = unsafe { prctl_1arg(PR_SVE_GET_VL)? } as c_uint;
+ Ok(SVEVectorLengthConfig {
+ vector_length_in_bytes: bits & PR_SVE_VL_LEN_MASK,
+ vector_length_inherited_across_execve: (bits & PR_SVE_VL_INHERIT) != 0,
+ })
+}
+
+const PR_SVE_SET_VL: c_int = 50;
+
+const PR_SVE_SET_VL_ONEXEC: u32 = 1_u32 << 18;
+
+/// Configure the thread's vector length of Scalable Vector Extension.
+///
+/// # References
+/// - [`prctl(PR_SVE_SET_VL,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function,
+/// as detailed in the references above.
+///
+/// [`prctl(PR_SVE_SET_VL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub unsafe fn set_sve_vector_length_configuration(
+ vector_length_in_bytes: usize,
+ vector_length_inherited_across_execve: bool,
+ defer_change_to_next_execve: bool,
+) -> io::Result<()> {
+ let vector_length_in_bytes =
+ u32::try_from(vector_length_in_bytes).map_err(|_r| io::Errno::RANGE)?;
+
+ let mut bits = vector_length_in_bytes & PR_SVE_VL_LEN_MASK;
+
+ if vector_length_inherited_across_execve {
+ bits |= PR_SVE_VL_INHERIT;
+ }
+
+ if defer_change_to_next_execve {
+ bits |= PR_SVE_SET_VL_ONEXEC;
+ }
+
+ prctl_2args(PR_SVE_SET_VL, bits as usize as *mut _).map(|_r| ())
+}
+
+//
+// PR_PAC_RESET_KEYS
+//
+
+const PR_PAC_RESET_KEYS: c_int = 54;
+
+/// Securely reset the thread's pointer authentication keys to fresh random
+/// values generated by the kernel.
+///
+/// # References
+/// - [`prctl(PR_PAC_RESET_KEYS,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function,
+/// as detailed in the references above.
+///
+/// [`prctl(PR_PAC_RESET_KEYS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub unsafe fn reset_pointer_authentication_keys(
+ keys: Option<PointerAuthenticationKeys>,
+) -> io::Result<()> {
+ let keys = keys.as_ref().map_or(0_u32, PointerAuthenticationKeys::bits);
+ prctl_2args(PR_PAC_RESET_KEYS, keys as usize as *mut _).map(|_r| ())
+}
+
+//
+// PR_GET_TAGGED_ADDR_CTRL/PR_SET_TAGGED_ADDR_CTRL
+//
+
+const PR_GET_TAGGED_ADDR_CTRL: c_int = 56;
+
+const PR_MTE_TAG_SHIFT: u32 = 3;
+const PR_MTE_TAG_MASK: u32 = 0xffff_u32 << PR_MTE_TAG_SHIFT;
+
+bitflags! {
+ /// Zero means addresses that are passed for the purpose of being
+ /// dereferenced by the kernel must be untagged.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct TaggedAddressMode: u32 {
+ /// Addresses that are passed for the purpose of being dereferenced by
+ /// the kernel may be tagged.
+ const ENABLED = 1_u32 << 0;
+ /// Synchronous tag check fault mode.
+ const TCF_SYNC = 1_u32 << 1;
+ /// Asynchronous tag check fault mode.
+ const TCF_ASYNC = 1_u32 << 2;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// Get the current tagged address mode for the calling thread.
+///
+/// # References
+/// - [`prctl(PR_GET_TAGGED_ADDR_CTRL,...)`]
+///
+/// [`prctl(PR_GET_TAGGED_ADDR_CTRL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub fn current_tagged_address_mode() -> io::Result<(Option<TaggedAddressMode>, u32)> {
+ let r = unsafe { prctl_1arg(PR_GET_TAGGED_ADDR_CTRL)? } as c_uint;
+ let mode = r & 0b111_u32;
+ let mte_tag = (r & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT;
+ Ok((TaggedAddressMode::from_bits(mode), mte_tag))
+}
+
+const PR_SET_TAGGED_ADDR_CTRL: c_int = 55;
+
+/// Controls support for passing tagged user-space addresses to the kernel.
+///
+/// # References
+/// - [`prctl(PR_SET_TAGGED_ADDR_CTRL,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_TAGGED_ADDR_CTRL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub unsafe fn set_current_tagged_address_mode(
+ mode: Option<TaggedAddressMode>,
+ mte_tag: u32,
+) -> io::Result<()> {
+ let config = mode.as_ref().map_or(0_u32, TaggedAddressMode::bits)
+ | ((mte_tag << PR_MTE_TAG_SHIFT) & PR_MTE_TAG_MASK);
+ prctl_2args(PR_SET_TAGGED_ADDR_CTRL, config as usize as *mut _).map(|_r| ())
+}
+
+//
+// PR_SET_SYSCALL_USER_DISPATCH
+//
+
+const PR_SET_SYSCALL_USER_DISPATCH: c_int = 59;
+
+const PR_SYS_DISPATCH_OFF: usize = 0;
+
+/// Disable Syscall User Dispatch mechanism.
+///
+/// # References
+/// - [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_OFF,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_OFF,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub unsafe fn disable_syscall_user_dispatch() -> io::Result<()> {
+ prctl_2args(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_OFF as *mut _).map(|_r| ())
+}
+
+const PR_SYS_DISPATCH_ON: usize = 1;
+
+/// Allow system calls to be executed.
+const SYSCALL_DISPATCH_FILTER_ALLOW: u8 = 0;
+/// Block system calls from executing.
+const SYSCALL_DISPATCH_FILTER_BLOCK: u8 = 1;
+
+/// Value of the fast switch flag controlling system calls user dispatch
+/// mechanism without the need to issue a syscall.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u8)]
+pub enum SysCallUserDispatchFastSwitch {
+ /// System calls are allowed to execute.
+ Allow = SYSCALL_DISPATCH_FILTER_ALLOW,
+ /// System calls are blocked from executing.
+ Block = SYSCALL_DISPATCH_FILTER_BLOCK,
+}
+
+impl TryFrom<u8> for SysCallUserDispatchFastSwitch {
+ type Error = io::Errno;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ SYSCALL_DISPATCH_FILTER_ALLOW => Ok(Self::Allow),
+ SYSCALL_DISPATCH_FILTER_BLOCK => Ok(Self::Block),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Enable Syscall User Dispatch mechanism.
+///
+/// # References
+/// - [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_ON,...)`]
+///
+/// # Safety
+///
+/// Please ensure the conditions necessary to safely call this function, as
+/// detailed in the references above.
+///
+/// [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_ON,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html
+#[inline]
+pub unsafe fn enable_syscall_user_dispatch(
+ always_allowed_region: &[u8],
+ fast_switch_flag: &AtomicU8,
+) -> io::Result<()> {
+ syscalls::prctl(
+ PR_SET_SYSCALL_USER_DISPATCH,
+ PR_SYS_DISPATCH_ON as *mut _,
+ always_allowed_region.as_ptr() as *mut _,
+ always_allowed_region.len() as *mut _,
+ as_ptr(fast_switch_flag) as *mut _,
+ )
+ .map(|_r| ())
+}
+
+//
+// PR_SCHED_CORE
+//
+
+const PR_SCHED_CORE: c_int = 62;
+
+const PR_SCHED_CORE_GET: usize = 0;
+
+const PR_SCHED_CORE_SCOPE_THREAD: u32 = 0;
+const PR_SCHED_CORE_SCOPE_THREAD_GROUP: u32 = 1;
+const PR_SCHED_CORE_SCOPE_PROCESS_GROUP: u32 = 2;
+
+/// `PR_SCHED_CORE_SCOPE_*`.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum CoreSchedulingScope {
+ /// Operation will be performed for the thread.
+ Thread = PR_SCHED_CORE_SCOPE_THREAD,
+ /// Operation will be performed for all tasks in the task group of the
+ /// process.
+ ThreadGroup = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
+ /// Operation will be performed for all processes in the process group.
+ ProcessGroup = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
+}
+
+impl TryFrom<u32> for CoreSchedulingScope {
+ type Error = io::Errno;
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ PR_SCHED_CORE_SCOPE_THREAD => Ok(Self::Thread),
+ PR_SCHED_CORE_SCOPE_THREAD_GROUP => Ok(Self::ThreadGroup),
+ PR_SCHED_CORE_SCOPE_PROCESS_GROUP => Ok(Self::ProcessGroup),
+ _ => Err(io::Errno::RANGE),
+ }
+ }
+}
+
+/// Get core scheduling cookie of a process.
+///
+/// # References
+/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_GET,...)`]
+///
+/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_GET,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html
+#[inline]
+pub fn core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<u64> {
+ let mut value: MaybeUninit<u64> = MaybeUninit::uninit();
+ unsafe {
+ syscalls::prctl(
+ PR_SCHED_CORE,
+ PR_SCHED_CORE_GET as *mut _,
+ pid.as_raw_nonzero().get() as usize as *mut _,
+ scope as usize as *mut _,
+ value.as_mut_ptr().cast(),
+ )?;
+ Ok(value.assume_init())
+ }
+}
+
+const PR_SCHED_CORE_CREATE: usize = 1;
+
+/// Create unique core scheduling cookie.
+///
+/// # References
+/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_CREATE,...)`]
+///
+/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_CREATE,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html
+#[inline]
+pub fn create_core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<()> {
+ unsafe {
+ syscalls::prctl(
+ PR_SCHED_CORE,
+ PR_SCHED_CORE_CREATE as *mut _,
+ pid.as_raw_nonzero().get() as usize as *mut _,
+ scope as usize as *mut _,
+ ptr::null_mut(),
+ )
+ .map(|_r| ())
+ }
+}
+
+const PR_SCHED_CORE_SHARE_TO: usize = 2;
+
+/// Push core scheduling cookie to a process.
+///
+/// # References
+/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_TO,...)`]
+///
+/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_TO,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html
+#[inline]
+pub fn push_core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<()> {
+ unsafe {
+ syscalls::prctl(
+ PR_SCHED_CORE,
+ PR_SCHED_CORE_SHARE_TO as *mut _,
+ pid.as_raw_nonzero().get() as usize as *mut _,
+ scope as usize as *mut _,
+ ptr::null_mut(),
+ )
+ .map(|_r| ())
+ }
+}
+
+const PR_SCHED_CORE_SHARE_FROM: usize = 3;
+
+/// Pull core scheduling cookie from a process.
+///
+/// # References
+/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_FROM,...)`]
+///
+/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_FROM,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html
+#[inline]
+pub fn pull_core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<()> {
+ unsafe {
+ syscalls::prctl(
+ PR_SCHED_CORE,
+ PR_SCHED_CORE_SHARE_FROM as *mut _,
+ pid.as_raw_nonzero().get() as usize as *mut _,
+ scope as usize as *mut _,
+ ptr::null_mut(),
+ )
+ .map(|_r| ())
+ }
+}
diff --git a/src/thread/setns.rs b/src/thread/setns.rs
new file mode 100644
index 0000000..ef61d11
--- /dev/null
+++ b/src/thread/setns.rs
@@ -0,0 +1,137 @@
+use bitflags::bitflags;
+use linux_raw_sys::general::{
+ CLONE_FILES, CLONE_FS, CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID,
+ CLONE_NEWTIME, CLONE_NEWUSER, CLONE_NEWUTS, CLONE_SYSVSEM,
+};
+
+use crate::backend::c::c_int;
+use crate::backend::thread::syscalls;
+use crate::fd::BorrowedFd;
+use crate::io;
+
+bitflags! {
+ /// Thread name space type.
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct ThreadNameSpaceType: u32 {
+ /// Time name space.
+ const TIME = CLONE_NEWTIME;
+ /// Mount name space.
+ const MOUNT = CLONE_NEWNS;
+ /// Control group (CGroup) name space.
+ const CONTROL_GROUP = CLONE_NEWCGROUP;
+ /// `Host name` and `NIS domain name` (UTS) name space.
+ const HOST_NAME_AND_NIS_DOMAIN_NAME = CLONE_NEWUTS;
+ /// Inter-process communication (IPC) name space.
+ const INTER_PROCESS_COMMUNICATION = CLONE_NEWIPC;
+ /// User name space.
+ const USER = CLONE_NEWUSER;
+ /// Process ID name space.
+ const PROCESS_ID = CLONE_NEWPID;
+ /// Network name space.
+ const NETWORK = CLONE_NEWNET;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// Type of name space referred to by a link.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u32)]
+pub enum LinkNameSpaceType {
+ /// Time name space.
+ Time = CLONE_NEWTIME,
+ /// Mount name space.
+ Mount = CLONE_NEWNS,
+ /// Control group (CGroup) name space.
+ ControlGroup = CLONE_NEWCGROUP,
+ /// `Host name` and `NIS domain name` (UTS) name space.
+ HostNameAndNISDomainName = CLONE_NEWUTS,
+ /// Inter-process communication (IPC) name space.
+ InterProcessCommunication = CLONE_NEWIPC,
+ /// User name space.
+ User = CLONE_NEWUSER,
+ /// Process ID name space.
+ ProcessID = CLONE_NEWPID,
+ /// Network name space.
+ Network = CLONE_NEWNET,
+}
+
+bitflags! {
+ /// `CLONE_*` for use with [`unshare`].
+ #[repr(transparent)]
+ #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+ pub struct UnshareFlags: u32 {
+ /// `CLONE_FILES`.
+ const FILES = CLONE_FILES;
+ /// `CLONE_FS`.
+ const FS = CLONE_FS;
+ /// `CLONE_NEWCGROUP`.
+ const NEWCGROUP = CLONE_NEWCGROUP;
+ /// `CLONE_NEWIPC`.
+ const NEWIPC = CLONE_NEWIPC;
+ /// `CLONE_NEWNET`.
+ const NEWNET = CLONE_NEWNET;
+ /// `CLONE_NEWNS`.
+ const NEWNS = CLONE_NEWNS;
+ /// `CLONE_NEWPID`.
+ const NEWPID = CLONE_NEWPID;
+ /// `CLONE_NEWTIME`.
+ const NEWTIME = CLONE_NEWTIME;
+ /// `CLONE_NEWUSER`.
+ const NEWUSER = CLONE_NEWUSER;
+ /// `CLONE_NEWUTS`
+ const NEWUTS = CLONE_NEWUTS;
+ /// `CLONE_SYSVSEM`.
+ const SYSVSEM = CLONE_SYSVSEM;
+
+ /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+ const _ = !0;
+ }
+}
+
+/// Reassociate the calling thread with the namespace associated with link
+/// referred to by `fd`.
+///
+/// `fd` must refer to one of the magic links in a `/proc/[pid]/ns/` directory,
+/// or a bind mount to such a link.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/setns.2.html
+pub fn move_into_link_name_space(
+ fd: BorrowedFd<'_>,
+ allowed_type: Option<LinkNameSpaceType>,
+) -> io::Result<()> {
+ let allowed_type = allowed_type.map_or(0, |t| t as c_int);
+ syscalls::setns(fd, allowed_type).map(|_r| ())
+}
+
+/// Atomically move the calling thread into one or more of the same namespaces
+/// as the thread referred to by `fd`.
+///
+/// `fd` must refer to a thread ID. See: `pidfd_open` and `clone`.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/setns.2.html
+pub fn move_into_thread_name_spaces(
+ fd: BorrowedFd<'_>,
+ allowed_types: ThreadNameSpaceType,
+) -> io::Result<()> {
+ syscalls::setns(fd, allowed_types.bits() as c_int).map(|_r| ())
+}
+
+/// `unshare(flags)`—Disassociate parts of the current thread's execution
+/// context with other threads.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/unshare.2.html
+pub fn unshare(flags: UnshareFlags) -> io::Result<()> {
+ syscalls::unshare(flags)
+}
diff --git a/src/time/clock.rs b/src/time/clock.rs
new file mode 100644
index 0000000..db585d1
--- /dev/null
+++ b/src/time/clock.rs
@@ -0,0 +1,108 @@
+use crate::{backend, io};
+
+pub use crate::timespec::{Nsecs, Secs, Timespec};
+
+/// `clockid_t`
+#[cfg(not(target_os = "wasi"))]
+pub use crate::clockid::{ClockId, DynamicClockId};
+
+/// `clock_getres(id)`—Returns the resolution of a clock.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/clock_getres.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=clock_getres&sektion=2
+/// [NetBSD]: https://man.netbsd.org/clock_getres.2
+/// [OpenBSD]: https://man.openbsd.org/clock_getres.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=clock_getres&section=2
+/// [illumos]: https://illumos.org/man/3C/clock_getres
+#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
+#[inline]
+#[must_use]
+pub fn clock_getres(id: ClockId) -> Timespec {
+ backend::time::syscalls::clock_getres(id)
+}
+
+/// `clock_gettime(id)`—Returns the current value of a clock.
+///
+/// This function uses `ClockId` which only contains clocks which are known to
+/// always be supported at runtime, allowing this function to be infallible.
+/// For a greater set of clocks and dynamic clock support, see
+/// [`clock_gettime_dynamic`].
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/clock_gettime.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=clock_getres&sektion=2
+/// [NetBSD]: https://man.netbsd.org/clock_getres.2
+/// [OpenBSD]: https://man.openbsd.org/clock_getres.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=clock_getres&section=2
+/// [illumos]: https://illumos.org/man/3C/clock_gettime
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+#[must_use]
+pub fn clock_gettime(id: ClockId) -> Timespec {
+ backend::time::syscalls::clock_gettime(id)
+}
+
+/// Like [`clock_gettime`] but with support for dynamic clocks.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/clock_gettime.2.html
+#[cfg(not(target_os = "wasi"))]
+#[inline]
+pub fn clock_gettime_dynamic(id: DynamicClockId<'_>) -> io::Result<Timespec> {
+ backend::time::syscalls::clock_gettime_dynamic(id)
+}
+
+/// `clock_settime(id, timespec)`—Sets the current value of a settable clock.
+///
+/// This fails with [`io::Errno::INVAL`] if the clock is not settable, and
+/// [`io::Errno::ACCESS`] if the current process does not have permission to
+/// set it.
+///
+/// # References
+/// - [POSIX]
+/// - [Linux]
+/// - [FreeBSD]
+/// - [NetBSD]
+/// - [OpenBSD]
+/// - [DragonFly BSD]
+/// - [illumos]
+///
+/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_settime.html
+/// [Linux]: https://man7.org/linux/man-pages/man2/clock_settime.2.html
+/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=clock_settime&sektion=2
+/// [NetBSD]: https://man.netbsd.org/clock_settime.2
+/// [OpenBSD]: https://man.openbsd.org/clock_settime.2
+/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=clock_settime&section=2
+/// [illumos]: https://illumos.org/man/3C/clock_settime
+#[cfg(not(any(
+ target_os = "redox",
+ target_os = "wasi",
+ all(apple, not(target_os = "macos"))
+)))]
+#[inline]
+pub fn clock_settime(id: ClockId, timespec: Timespec) -> io::Result<()> {
+ backend::time::syscalls::clock_settime(id, timespec)
+}
diff --git a/src/time/mod.rs b/src/time/mod.rs
new file mode 100644
index 0000000..c633e76
--- /dev/null
+++ b/src/time/mod.rs
@@ -0,0 +1,11 @@
+//! Time-related operations.
+
+mod clock;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+mod timerfd;
+
+// TODO: Convert WASI'S clock APIs to use handles rather than ambient clock
+// identifiers, update `wasi-libc`, and then add support in `rustix`.
+pub use clock::*;
+#[cfg(any(linux_kernel, target_os = "fuchsia"))]
+pub use timerfd::*;
diff --git a/src/time/timerfd.rs b/src/time/timerfd.rs
new file mode 100644
index 0000000..7f661f7
--- /dev/null
+++ b/src/time/timerfd.rs
@@ -0,0 +1,41 @@
+use crate::fd::{AsFd, OwnedFd};
+use crate::{backend, io};
+
+pub use backend::time::types::{Itimerspec, TimerfdClockId, TimerfdFlags, TimerfdTimerFlags};
+
+/// `timerfd_create(clockid, flags)`—Create a timer.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/timerfd_create.2.html
+#[inline]
+pub fn timerfd_create(clockid: TimerfdClockId, flags: TimerfdFlags) -> io::Result<OwnedFd> {
+ backend::time::syscalls::timerfd_create(clockid, flags)
+}
+
+/// `timerfd_settime(clockid, flags, new_value)`—Set the time on a timer.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/timerfd_settime.2.html
+#[inline]
+pub fn timerfd_settime<Fd: AsFd>(
+ fd: Fd,
+ flags: TimerfdTimerFlags,
+ new_value: &Itimerspec,
+) -> io::Result<Itimerspec> {
+ backend::time::syscalls::timerfd_settime(fd.as_fd(), flags, new_value)
+}
+
+/// `timerfd_gettime(clockid, flags)`—Query a timer.
+///
+/// # References
+/// - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/timerfd_gettime.2.html
+#[inline]
+pub fn timerfd_gettime<Fd: AsFd>(fd: Fd) -> io::Result<Itimerspec> {
+ backend::time::syscalls::timerfd_gettime(fd.as_fd())
+}
diff --git a/src/timespec.rs b/src/timespec.rs
new file mode 100644
index 0000000..a2df0e7
--- /dev/null
+++ b/src/timespec.rs
@@ -0,0 +1,115 @@
+//! `Timespec` and related types, which are used by multiple public API
+//! modules.
+
+#[cfg(not(fix_y2038))]
+use crate::backend::c;
+
+/// `struct timespec`
+#[cfg(not(fix_y2038))]
+pub type Timespec = c::timespec;
+
+/// `struct timespec`
+#[cfg(fix_y2038)]
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct Timespec {
+ /// Seconds.
+ pub tv_sec: Secs,
+
+ /// Nanoseconds. Must be less than 1_000_000_000.
+ pub tv_nsec: Nsecs,
+}
+
+/// A type for the `tv_sec` field of [`Timespec`].
+#[cfg(not(fix_y2038))]
+#[allow(deprecated)]
+pub type Secs = c::time_t;
+
+/// A type for the `tv_sec` field of [`Timespec`].
+#[cfg(fix_y2038)]
+pub type Secs = i64;
+
+/// A type for the `tv_sec` field of [`Timespec`].
+#[cfg(any(
+ fix_y2038,
+ linux_raw,
+ all(libc, target_arch = "x86_64", target_pointer_width = "32")
+))]
+pub type Nsecs = i64;
+
+/// A type for the `tv_nsec` field of [`Timespec`].
+#[cfg(all(
+ not(fix_y2038),
+ libc,
+ not(all(target_arch = "x86_64", target_pointer_width = "32"))
+))]
+pub type Nsecs = c::c_long;
+
+/// On 32-bit glibc platforms, `timespec` has anonymous padding fields, which
+/// Rust doesn't support yet (see `unnamed_fields`), so we define our own
+/// struct with explicit padding, with bidirectional `From` impls.
+#[cfg(fix_y2038)]
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub(crate) struct LibcTimespec {
+ pub(crate) tv_sec: Secs,
+
+ #[cfg(target_endian = "big")]
+ padding: core::mem::MaybeUninit<u32>,
+
+ pub(crate) tv_nsec: i32,
+
+ #[cfg(target_endian = "little")]
+ padding: core::mem::MaybeUninit<u32>,
+}
+
+#[cfg(fix_y2038)]
+impl From<LibcTimespec> for Timespec {
+ #[inline]
+ fn from(t: LibcTimespec) -> Self {
+ Self {
+ tv_sec: t.tv_sec,
+ tv_nsec: t.tv_nsec as _,
+ }
+ }
+}
+
+#[cfg(fix_y2038)]
+impl From<Timespec> for LibcTimespec {
+ #[inline]
+ fn from(t: Timespec) -> Self {
+ Self {
+ tv_sec: t.tv_sec,
+ tv_nsec: t.tv_nsec as _,
+ padding: core::mem::MaybeUninit::uninit(),
+ }
+ }
+}
+
+#[test]
+fn test_sizes() {
+ assert_eq_size!(Secs, u64);
+ const_assert!(core::mem::size_of::<Timespec>() >= core::mem::size_of::<(u64, u32)>());
+ const_assert!(core::mem::size_of::<Nsecs>() >= 4);
+
+ let mut t = Timespec {
+ tv_sec: 0,
+ tv_nsec: 0,
+ };
+
+ // `tv_nsec` needs to be able to hold nanoseconds up to a second.
+ t.tv_nsec = 999_999_999_u32 as _;
+ assert_eq!(t.tv_nsec as u64, 999_999_999_u64);
+
+ // `tv_sec` needs to be able to hold more than 32-bits of seconds.
+ t.tv_sec = 0x1_0000_0000_u64 as _;
+ assert_eq!(t.tv_sec as u64, 0x1_0000_0000_u64);
+}
+
+// Test that our workarounds are needed.
+#[cfg(fix_y2038)]
+#[test]
+#[allow(deprecated)]
+fn test_fix_y2038() {
+ assert_eq_size!(libc::time_t, u32);
+}
diff --git a/src/ugid.rs b/src/ugid.rs
new file mode 100644
index 0000000..57cc666
--- /dev/null
+++ b/src/ugid.rs
@@ -0,0 +1,98 @@
+//! User and Group ID types.
+
+#![allow(unsafe_code)]
+
+use crate::backend::c;
+
+/// A group identifier as a raw integer.
+#[cfg(not(target_os = "wasi"))]
+pub type RawGid = c::gid_t;
+/// A user identifier as a raw integer.
+#[cfg(not(target_os = "wasi"))]
+pub type RawUid = c::uid_t;
+
+/// `uid_t`—A Unix user ID.
+#[repr(transparent)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
+pub struct Uid(RawUid);
+
+/// `gid_t`—A Unix group ID.
+#[repr(transparent)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
+pub struct Gid(RawGid);
+
+impl Uid {
+ /// A `Uid` corresponding to the root user (uid 0).
+ pub const ROOT: Self = Self(0);
+
+ /// Converts a `RawUid` into a `Uid`.
+ ///
+ /// # Safety
+ ///
+ /// `raw` must be the value of a valid Unix user ID.
+ #[inline]
+ pub const unsafe fn from_raw(raw: RawUid) -> Self {
+ Self(raw)
+ }
+
+ /// Converts a `Uid` into a `RawUid`.
+ #[inline]
+ pub const fn as_raw(self) -> RawUid {
+ self.0
+ }
+
+ /// Test whether this uid represents the root user (uid 0).
+ #[inline]
+ pub const fn is_root(self) -> bool {
+ self.0 == Self::ROOT.0
+ }
+}
+
+impl Gid {
+ /// A `Gid` corresponding to the root group (gid 0).
+ pub const ROOT: Self = Self(0);
+
+ /// Converts a `RawGid` into a `Gid`.
+ ///
+ /// # Safety
+ ///
+ /// `raw` must be the value of a valid Unix group ID.
+ #[inline]
+ pub const unsafe fn from_raw(raw: RawGid) -> Self {
+ Self(raw)
+ }
+
+ /// Converts a `Gid` into a `RawGid`.
+ #[inline]
+ pub const fn as_raw(self) -> RawGid {
+ self.0
+ }
+
+ /// Test whether this gid represents the root group (gid 0).
+ #[inline]
+ pub const fn is_root(self) -> bool {
+ self.0 == Self::ROOT.0
+ }
+}
+
+// Return the raw value of the IDs. In case of `None` it returns `!0` since it
+// has the same bit pattern as `-1` indicating no change to the owner/group ID.
+pub(crate) fn translate_fchown_args(owner: Option<Uid>, group: Option<Gid>) -> (RawUid, RawGid) {
+ let ow = match owner {
+ Some(o) => o.as_raw(),
+ None => !0,
+ };
+
+ let gr = match group {
+ Some(g) => g.as_raw(),
+ None => !0,
+ };
+
+ (ow, gr)
+}
+
+#[test]
+fn test_sizes() {
+ assert_eq_size!(RawUid, u32);
+ assert_eq_size!(RawGid, u32);
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..d21ed88
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,82 @@
+//! Miscellaneous minor utilities.
+
+#![allow(dead_code)]
+#![allow(unused_macros)]
+
+use core::ffi::c_void;
+use core::mem::{align_of, size_of};
+use core::ptr::{null, null_mut, NonNull};
+
+/// Convert a `&T` into a `*const T` without using an `as`.
+#[inline]
+pub(crate) const fn as_ptr<T>(t: &T) -> *const T {
+ t
+}
+
+/// Convert a `&mut T` into a `*mut T` without using an `as`.
+#[inline]
+pub(crate) fn as_mut_ptr<T>(t: &mut T) -> *mut T {
+ t
+}
+
+/// Convert an `Option<&T>` into a possibly-null `*const T`.
+#[inline]
+pub(crate) const fn option_as_ptr<T>(t: Option<&T>) -> *const T {
+ match t {
+ Some(t) => t,
+ None => null(),
+ }
+}
+
+/// Convert an `Option<&mut T>` into a possibly-null `*mut T`.
+#[inline]
+pub(crate) fn option_as_mut_ptr<T>(t: Option<&mut T>) -> *mut T {
+ match t {
+ Some(t) => t,
+ None => null_mut(),
+ }
+}
+
+/// Convert a `*mut c_void` to a `*mut T`, checking that it is not null,
+/// misaligned, or pointing to a region of memory that wraps around the address
+/// space.
+pub(crate) fn check_raw_pointer<T>(value: *mut c_void) -> Option<NonNull<T>> {
+ if (value as usize).checked_add(size_of::<T>()).is_none()
+ || (value as usize) % align_of::<T>() != 0
+ {
+ return None;
+ }
+
+ NonNull::new(value.cast())
+}
+
+/// Create an array value containing all default values, inferring the type.
+#[inline]
+pub(crate) fn default_array<T: Default + Copy, const N: usize>() -> [T; N] {
+ [T::default(); N]
+}
+
+/// Create a union value containing a default value in one of its arms.
+///
+/// The field names a union field which must have the same size as the union
+/// itself.
+macro_rules! default_union {
+ ($union:ident, $field:ident) => {{
+ let u = $union {
+ $field: Default::default(),
+ };
+
+ // Assert that the given field initializes the whole union.
+ #[cfg(test)]
+ unsafe {
+ let field_value = u.$field;
+ assert_eq!(
+ core::mem::size_of_val(&u),
+ core::mem::size_of_val(&field_value)
+ );
+ const_assert_eq!(memoffset::offset_of_union!($union, $field), 0);
+ }
+
+ u
+ }};
+}
diff --git a/src/weak.rs b/src/weak.rs
new file mode 100644
index 0000000..56be05c
--- /dev/null
+++ b/src/weak.rs
@@ -0,0 +1,288 @@
+// Implementation derived from `weak` in Rust's
+// library/std/src/sys/unix/weak.rs at revision
+// fd0cb0cdc21dd9c06025277d772108f8d42cb25f.
+//
+// Ideally we should update to a newer version which doesn't need `dlsym`,
+// however that depends on the `extern_weak` feature which is currently
+// unstable.
+
+#![cfg_attr(linux_raw, allow(unsafe_code))]
+
+//! Support for "weak linkage" to symbols on Unix
+//!
+//! Some I/O operations we do in libstd require newer versions of OSes but we
+//! need to maintain binary compatibility with older releases for now. In order
+//! to use the new functionality when available we use this module for
+//! detection.
+//!
+//! One option to use here is weak linkage, but that is unfortunately only
+//! really workable on Linux. Hence, use dlsym to get the symbol value at
+//! runtime. This is also done for compatibility with older versions of glibc,
+//! and to avoid creating dependencies on `GLIBC_PRIVATE` symbols. It assumes
+//! that we've been dynamically linked to the library the symbol comes from,
+//! but that is currently always the case for things like libpthread/libc.
+//!
+//! A long time ago this used weak linkage for the `__pthread_get_minstack`
+//! symbol, but that caused Debian to detect an unnecessarily strict versioned
+//! dependency on libc6 (#23628).
+
+// There are a variety of `#[cfg]`s controlling which targets are involved in
+// each instance of `weak!` and `syscall!`. Rather than trying to unify all of
+// that, we'll just allow that some unix targets don't use this module at all.
+#![allow(dead_code, unused_macros)]
+#![allow(clippy::doc_markdown)]
+
+use crate::ffi::CStr;
+use core::ffi::c_void;
+use core::ptr::null_mut;
+use core::sync::atomic::{self, AtomicPtr, Ordering};
+use core::{marker, mem};
+
+use errno as libc_errno;
+
+const NULL: *mut c_void = null_mut();
+const INVALID: *mut c_void = 1 as *mut c_void;
+
+macro_rules! weak {
+ ($vis:vis fn $name:ident($($t:ty),*) -> $ret:ty) => (
+ #[allow(non_upper_case_globals)]
+ $vis static $name: $crate::weak::Weak<unsafe extern fn($($t),*) -> $ret> =
+ $crate::weak::Weak::new(concat!(stringify!($name), '\0'));
+ )
+}
+
+pub(crate) struct Weak<F> {
+ name: &'static str,
+ addr: AtomicPtr<c_void>,
+ _marker: marker::PhantomData<F>,
+}
+
+impl<F> Weak<F> {
+ pub(crate) const fn new(name: &'static str) -> Self {
+ Self {
+ name,
+ addr: AtomicPtr::new(INVALID),
+ _marker: marker::PhantomData,
+ }
+ }
+
+ pub(crate) fn get(&self) -> Option<F> {
+ assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>());
+ unsafe {
+ // Relaxed is fine here because we fence before reading through the
+ // pointer (see the comment below).
+ match self.addr.load(Ordering::Relaxed) {
+ INVALID => self.initialize(),
+ NULL => None,
+ addr => {
+ let func = mem::transmute_copy::<*mut c_void, F>(&addr);
+ // The caller is presumably going to read through this value
+ // (by calling the function we've dlsymed). This means we'd
+ // need to have loaded it with at least C11's consume
+ // ordering in order to be guaranteed that the data we read
+ // from the pointer isn't from before the pointer was
+ // stored. Rust has no equivalent to memory_order_consume,
+ // so we use an acquire fence (sorry, ARM).
+ //
+ // Now, in practice this likely isn't needed even on CPUs
+ // where relaxed and consume mean different things. The
+ // symbols we're loading are probably present (or not) at
+ // init, and even if they aren't the runtime dynamic loader
+ // is extremely likely have sufficient barriers internally
+ // (possibly implicitly, for example the ones provided by
+ // invoking `mprotect`).
+ //
+ // That said, none of that's *guaranteed*, and so we fence.
+ atomic::fence(Ordering::Acquire);
+ Some(func)
+ }
+ }
+ }
+ }
+
+ // Cold because it should only happen during first-time initialization.
+ #[cold]
+ unsafe fn initialize(&self) -> Option<F> {
+ let val = fetch(self.name);
+ // This synchronizes with the acquire fence in `get`.
+ self.addr.store(val, Ordering::Release);
+
+ match val {
+ NULL => None,
+ addr => Some(mem::transmute_copy::<*mut c_void, F>(&addr)),
+ }
+ }
+}
+
+// To avoid having the `linux_raw` backend depend on the libc crate, just
+// declare the few things we need in a module called `libc` so that `fetch`
+// uses it.
+#[cfg(linux_raw)]
+mod libc {
+ use core::ptr;
+ use linux_raw_sys::ctypes::{c_char, c_void};
+
+ #[cfg(all(target_os = "android", target_pointer_width = "32"))]
+ pub(super) const RTLD_DEFAULT: *mut c_void = -1isize as *mut c_void;
+ #[cfg(not(all(target_os = "android", target_pointer_width = "32")))]
+ pub(super) const RTLD_DEFAULT: *mut c_void = ptr::null_mut();
+
+ extern "C" {
+ pub(super) fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
+ }
+
+ #[test]
+ fn test_abi() {
+ assert_eq!(self::RTLD_DEFAULT, ::libc::RTLD_DEFAULT);
+ }
+}
+
+unsafe fn fetch(name: &str) -> *mut c_void {
+ let name = match CStr::from_bytes_with_nul(name.as_bytes()) {
+ Ok(c_str) => c_str,
+ Err(..) => return null_mut(),
+ };
+ libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr().cast())
+}
+
+#[cfg(not(linux_kernel))]
+macro_rules! syscall {
+ (fn $name:ident($($arg_name:ident: $t:ty),*) via $_sys_name:ident -> $ret:ty) => (
+ unsafe fn $name($($arg_name: $t),*) -> $ret {
+ weak! { fn $name($($t),*) -> $ret }
+
+ if let Some(fun) = $name.get() {
+ fun($($arg_name),*)
+ } else {
+ libc_errno::set_errno(libc_errno::Errno(libc::ENOSYS));
+ -1
+ }
+ }
+ )
+}
+
+#[cfg(linux_kernel)]
+macro_rules! syscall {
+ (fn $name:ident($($arg_name:ident: $t:ty),*) via $sys_name:ident -> $ret:ty) => (
+ unsafe fn $name($($arg_name:$t),*) -> $ret {
+ // This looks like a hack, but `concat_idents` only accepts idents
+ // (not paths).
+ use libc::*;
+
+ trait AsSyscallArg {
+ type SyscallArgType;
+ fn into_syscall_arg(self) -> Self::SyscallArgType;
+ }
+
+ // Pass pointer types as pointers, to preserve provenance.
+ impl<T> AsSyscallArg for *mut T {
+ type SyscallArgType = *mut T;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self }
+ }
+ impl<T> AsSyscallArg for *const T {
+ type SyscallArgType = *const T;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self }
+ }
+
+ // Pass `BorrowedFd` values as the integer value.
+ impl AsSyscallArg for $crate::fd::BorrowedFd<'_> {
+ type SyscallArgType = ::libc::c_int;
+ fn into_syscall_arg(self) -> Self::SyscallArgType {
+ $crate::fd::AsRawFd::as_raw_fd(&self) as _
+ }
+ }
+
+ // Coerce integer values into `c_long`.
+ impl AsSyscallArg for i8 {
+ type SyscallArgType = ::libc::c_int;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self.into() }
+ }
+ impl AsSyscallArg for u8 {
+ type SyscallArgType = ::libc::c_int;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self.into() }
+ }
+ impl AsSyscallArg for i16 {
+ type SyscallArgType = ::libc::c_int;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self.into() }
+ }
+ impl AsSyscallArg for u16 {
+ type SyscallArgType = ::libc::c_int;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self.into() }
+ }
+ impl AsSyscallArg for i32 {
+ type SyscallArgType = ::libc::c_int;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self }
+ }
+ impl AsSyscallArg for u32 {
+ type SyscallArgType = ::libc::c_uint;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self }
+ }
+ impl AsSyscallArg for usize {
+ type SyscallArgType = ::libc::c_ulong;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self as _ }
+ }
+
+ // On 64-bit platforms, also coerce `i64` and `u64` since `c_long`
+ // is 64-bit and can hold those values.
+ #[cfg(target_pointer_width = "64")]
+ impl AsSyscallArg for i64 {
+ type SyscallArgType = ::libc::c_long;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self }
+ }
+ #[cfg(target_pointer_width = "64")]
+ impl AsSyscallArg for u64 {
+ type SyscallArgType = ::libc::c_ulong;
+ fn into_syscall_arg(self) -> Self::SyscallArgType { self }
+ }
+
+ // `concat_idents` is [unstable], so we take an extra `sys_name`
+ // parameter and have our users do the concat for us for now.
+ //
+ // [unstable]: https://github.com/rust-lang/rust/issues/29599
+ /*
+ syscall(
+ concat_idents!(SYS_, $name),
+ $($arg_name.into_syscall_arg()),*
+ ) as $ret
+ */
+
+ syscall($sys_name, $($arg_name.into_syscall_arg()),*) as $ret
+ }
+ )
+}
+
+macro_rules! weakcall {
+ ($vis:vis fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => (
+ $vis unsafe fn $name($($arg_name: $t),*) -> $ret {
+ weak! { fn $name($($t),*) -> $ret }
+
+ // Use a weak symbol from libc when possible, allowing `LD_PRELOAD`
+ // interposition, but if it's not found just fail.
+ if let Some(fun) = $name.get() {
+ fun($($arg_name),*)
+ } else {
+ libc_errno::set_errno(libc_errno::Errno(libc::ENOSYS));
+ -1
+ }
+ }
+ )
+}
+
+/// A combination of `weakcall` and `syscall`. Use the libc function if it's
+/// available, and fall back to `libc::syscall` otherwise.
+macro_rules! weak_or_syscall {
+ ($vis:vis fn $name:ident($($arg_name:ident: $t:ty),*) via $sys_name:ident -> $ret:ty) => (
+ $vis unsafe fn $name($($arg_name: $t),*) -> $ret {
+ weak! { fn $name($($t),*) -> $ret }
+
+ // Use a weak symbol from libc when possible, allowing `LD_PRELOAD`
+ // interposition, but if it's not found just fail.
+ if let Some(fun) = $name.get() {
+ fun($($arg_name),*)
+ } else {
+ syscall! { fn $name($($arg_name: $t),*) via $sys_name -> $ret }
+ $name($($arg_name),*)
+ }
+ }
+ )
+}