From c72cc85ebc00d0dcff2197a372ab17eb997f3db4 Mon Sep 17 00:00:00 2001 From: Roy7Kim Date: Tue, 21 Mar 2023 15:28:42 +0900 Subject: [PATCH 1/1] Import nix 0.26.2 --- .cargo_vcs_info.json | 6 + CHANGELOG.md | 1585 +++++++++++++++++ Cargo.toml | 185 ++ Cargo.toml.orig | 114 ++ LICENSE | 21 + README.md | 105 ++ src/dir.rs | 276 +++ src/env.rs | 64 + src/errno.rs | 3133 ++++++++++++++++++++++++++++++++++ src/fcntl.rs | 882 ++++++++++ src/features.rs | 126 ++ src/ifaddrs.rs | 213 +++ src/kmod.rs | 128 ++ src/lib.rs | 333 ++++ src/macros.rs | 328 ++++ src/mount/bsd.rs | 453 +++++ src/mount/linux.rs | 115 ++ src/mount/mod.rs | 26 + src/mqueue.rs | 276 +++ src/net/if_.rs | 469 +++++ src/net/mod.rs | 4 + src/poll.rs | 197 +++ src/pty.rs | 371 ++++ src/sched.rs | 324 ++++ src/sys/aio.rs | 1241 ++++++++++++++ src/sys/epoll.rs | 128 ++ src/sys/event.rs | 374 ++++ src/sys/eventfd.rs | 17 + src/sys/inotify.rs | 248 +++ src/sys/ioctl/bsd.rs | 129 ++ src/sys/ioctl/linux.rs | 172 ++ src/sys/ioctl/mod.rs | 786 +++++++++ src/sys/memfd.rs | 64 + src/sys/mman.rs | 599 +++++++ src/sys/mod.rs | 228 +++ src/sys/personality.rs | 93 + src/sys/pthread.rs | 43 + src/sys/ptrace/bsd.rs | 195 +++ src/sys/ptrace/linux.rs | 558 ++++++ src/sys/ptrace/mod.rs | 25 + src/sys/quota.rs | 338 ++++ src/sys/reboot.rs | 48 + src/sys/resource.rs | 443 +++++ src/sys/select.rs | 455 +++++ src/sys/sendfile.rs | 277 +++ src/sys/signal.rs | 1348 +++++++++++++++ src/sys/signalfd.rs | 175 ++ src/sys/socket/addr.rs | 3247 +++++++++++++++++++++++++++++++++++ src/sys/socket/mod.rs | 2487 +++++++++++++++++++++++++++ src/sys/socket/sockopt.rs | 1422 ++++++++++++++++ src/sys/stat.rs | 480 ++++++ src/sys/statfs.rs | 853 ++++++++++ src/sys/statvfs.rs | 173 ++ src/sys/sysinfo.rs | 83 + src/sys/termios.rs | 1227 ++++++++++++++ src/sys/time.rs | 811 +++++++++ src/sys/timer.rs | 187 ++ src/sys/timerfd.rs | 214 +++ src/sys/uio.rs | 291 ++++ src/sys/utsname.rs | 85 + src/sys/wait.rs | 388 +++++ src/time.rs | 283 ++++ src/ucontext.rs | 47 + src/unistd.rs | 3383 +++++++++++++++++++++++++++++++++++++ test/common/mod.rs | 149 ++ test/sys/mod.rs | 60 + test/sys/test_aio.rs | 626 +++++++ test/sys/test_aio_drop.rs | 35 + test/sys/test_epoll.rs | 24 + test/sys/test_inotify.rs | 65 + test/sys/test_ioctl.rs | 376 +++++ test/sys/test_mman.rs | 122 ++ test/sys/test_pthread.rs | 22 + test/sys/test_ptrace.rs | 275 +++ test/sys/test_select.rs | 81 + test/sys/test_signal.rs | 147 ++ test/sys/test_signalfd.rs | 27 + test/sys/test_socket.rs | 2628 ++++++++++++++++++++++++++++ test/sys/test_sockopt.rs | 431 +++++ test/sys/test_stat.rs | 29 + test/sys/test_sysinfo.rs | 20 + test/sys/test_termios.rs | 136 ++ test/sys/test_timerfd.rs | 69 + test/sys/test_uio.rs | 270 +++ test/sys/test_wait.rs | 257 +++ test/test.rs | 124 ++ test/test_clearenv.rs | 9 + test/test_dir.rs | 65 + test/test_fcntl.rs | 565 +++++++ test/test_kmod/hello_mod/Makefile | 7 + test/test_kmod/hello_mod/hello.c | 26 + test/test_kmod/mod.rs | 188 +++ test/test_mount.rs | 271 +++ test/test_mq.rs | 190 +++ test/test_net.rs | 19 + test/test_nix_path.rs | 1 + test/test_nmount.rs | 49 + test/test_poll.rs | 84 + test/test_pty.rs | 313 ++++ test/test_ptymaster_drop.rs | 20 + test/test_resource.rs | 34 + test/test_sched.rs | 39 + test/test_sendfile.rs | 208 +++ test/test_stat.rs | 421 +++++ test/test_time.rs | 59 + test/test_timer.rs | 102 ++ test/test_unistd.rs | 1407 +++++++++++++++ 107 files changed, 43429 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/dir.rs create mode 100644 src/env.rs create mode 100644 src/errno.rs create mode 100644 src/fcntl.rs create mode 100644 src/features.rs create mode 100644 src/ifaddrs.rs create mode 100644 src/kmod.rs create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/mount/bsd.rs create mode 100644 src/mount/linux.rs create mode 100644 src/mount/mod.rs create mode 100644 src/mqueue.rs create mode 100644 src/net/if_.rs create mode 100644 src/net/mod.rs create mode 100644 src/poll.rs create mode 100644 src/pty.rs create mode 100644 src/sched.rs create mode 100644 src/sys/aio.rs create mode 100644 src/sys/epoll.rs create mode 100644 src/sys/event.rs create mode 100644 src/sys/eventfd.rs create mode 100644 src/sys/inotify.rs create mode 100644 src/sys/ioctl/bsd.rs create mode 100644 src/sys/ioctl/linux.rs create mode 100644 src/sys/ioctl/mod.rs create mode 100644 src/sys/memfd.rs create mode 100644 src/sys/mman.rs create mode 100644 src/sys/mod.rs create mode 100644 src/sys/personality.rs create mode 100644 src/sys/pthread.rs create mode 100644 src/sys/ptrace/bsd.rs create mode 100644 src/sys/ptrace/linux.rs create mode 100644 src/sys/ptrace/mod.rs create mode 100644 src/sys/quota.rs create mode 100644 src/sys/reboot.rs create mode 100644 src/sys/resource.rs create mode 100644 src/sys/select.rs create mode 100644 src/sys/sendfile.rs create mode 100644 src/sys/signal.rs create mode 100644 src/sys/signalfd.rs create mode 100644 src/sys/socket/addr.rs create mode 100644 src/sys/socket/mod.rs create mode 100644 src/sys/socket/sockopt.rs create mode 100644 src/sys/stat.rs create mode 100644 src/sys/statfs.rs create mode 100644 src/sys/statvfs.rs create mode 100644 src/sys/sysinfo.rs create mode 100644 src/sys/termios.rs create mode 100644 src/sys/time.rs create mode 100644 src/sys/timer.rs create mode 100644 src/sys/timerfd.rs create mode 100644 src/sys/uio.rs create mode 100644 src/sys/utsname.rs create mode 100644 src/sys/wait.rs create mode 100644 src/time.rs create mode 100644 src/ucontext.rs create mode 100644 src/unistd.rs create mode 100644 test/common/mod.rs create mode 100644 test/sys/mod.rs create mode 100644 test/sys/test_aio.rs create mode 100644 test/sys/test_aio_drop.rs create mode 100644 test/sys/test_epoll.rs create mode 100644 test/sys/test_inotify.rs create mode 100644 test/sys/test_ioctl.rs create mode 100644 test/sys/test_mman.rs create mode 100644 test/sys/test_pthread.rs create mode 100644 test/sys/test_ptrace.rs create mode 100644 test/sys/test_select.rs create mode 100644 test/sys/test_signal.rs create mode 100644 test/sys/test_signalfd.rs create mode 100644 test/sys/test_socket.rs create mode 100644 test/sys/test_sockopt.rs create mode 100644 test/sys/test_stat.rs create mode 100644 test/sys/test_sysinfo.rs create mode 100644 test/sys/test_termios.rs create mode 100644 test/sys/test_timerfd.rs create mode 100644 test/sys/test_uio.rs create mode 100644 test/sys/test_wait.rs create mode 100644 test/test.rs create mode 100644 test/test_clearenv.rs create mode 100644 test/test_dir.rs create mode 100644 test/test_fcntl.rs create mode 100644 test/test_kmod/hello_mod/Makefile create mode 100644 test/test_kmod/hello_mod/hello.c create mode 100644 test/test_kmod/mod.rs create mode 100644 test/test_mount.rs create mode 100644 test/test_mq.rs create mode 100644 test/test_net.rs create mode 100644 test/test_nix_path.rs create mode 100644 test/test_nmount.rs create mode 100644 test/test_poll.rs create mode 100644 test/test_pty.rs create mode 100644 test/test_ptymaster_drop.rs create mode 100644 test/test_resource.rs create mode 100644 test/test_sched.rs create mode 100644 test/test_sendfile.rs create mode 100644 test/test_stat.rs create mode 100644 test/test_time.rs create mode 100644 test/test_timer.rs create mode 100644 test/test_unistd.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..b9cb2ab --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "1e3f062fd842b7ce130ea6c792a8eab7f78f82e3" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..283cb86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1585 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](https://semver.org/). + +## [0.26.2] - 2023-01-18 +### Fixed +- Fix `SockaddrIn6` bug that was swapping flowinfo and scope_id byte ordering. + ([#1964](https://github.com/nix-rust/nix/pull/1964)) + +## [0.26.1] - 2022-11-29 +### Fixed +- Fix UB with `sys::socket::sockopt::SockType` using `SOCK_PACKET`. + ([#1821](https://github.com/nix-rust/nix/pull/1821)) + +## [0.26.0] - 2022-11-29 +### Added + +- Added `SockaddrStorage::{as_unix_addr, as_unix_addr_mut}` + ([#1871](https://github.com/nix-rust/nix/pull/1871)) +- Added `MntFlags` and `unmount` on all of the BSDs. +- Added `any()` and `all()` to `poll::PollFd`. + ([#1877](https://github.com/nix-rust/nix/pull/1877)) +- Add `MntFlags` and `unmount` on all of the BSDs. + ([#1849](https://github.com/nix-rust/nix/pull/1849)) +- Added a `Statfs::flags` method. + ([#1849](https://github.com/nix-rust/nix/pull/1849)) +- Added `NSFS_MAGIC` FsType on Linux and Android. + ([#1829](https://github.com/nix-rust/nix/pull/1829)) +- Added `sched_getcpu` on platforms that support it. + ([#1825](https://github.com/nix-rust/nix/pull/1825)) +- Added `sched_getaffinity` and `sched_setaffinity` on FreeBSD. + ([#1804](https://github.com/nix-rust/nix/pull/1804)) +- Added `line_discipline` field to `Termios` on Linux, Android and Haiku + ([#1805](https://github.com/nix-rust/nix/pull/1805)) +- Expose the memfd module on FreeBSD (memfd was added in FreeBSD 13) + ([#1808](https://github.com/nix-rust/nix/pull/1808)) +- Added `domainname` field of `UtsName` on Android and Linux + ([#1817](https://github.com/nix-rust/nix/pull/1817)) +- Re-export `RLIM_INFINITY` from `libc` + ([#1831](https://github.com/nix-rust/nix/pull/1831)) +- Added `syncfs(2)` on Linux + ([#1833](https://github.com/nix-rust/nix/pull/1833)) +- Added `faccessat(2)` on illumos + ([#1841](https://github.com/nix-rust/nix/pull/1841)) +- Added `eaccess()` on FreeBSD, DragonFly and Linux (glibc and musl). + ([#1842](https://github.com/nix-rust/nix/pull/1842)) +- Added `IP_TOS` `SO_PRIORITY` and `IPV6_TCLASS` sockopts for Linux + ([#1853](https://github.com/nix-rust/nix/pull/1853)) +- Added `new_unnamed` and `is_unnamed` for `UnixAddr` on Linux and Android. + ([#1857](https://github.com/nix-rust/nix/pull/1857)) +- Added `SockProtocol::Raw` for raw sockets + ([#1848](https://github.com/nix-rust/nix/pull/1848)) +- added `IP_MTU` (`IpMtu`) `IPPROTO_IP` sockopt on Linux and Android. + ([#1865](https://github.com/nix-rust/nix/pull/1865)) + +### Changed + +- The MSRV is now 1.56.1 + ([#1792](https://github.com/nix-rust/nix/pull/1792)) +- The `addr` argument of `sys::mman::mmap` is now of type `Option`. + ([#1870](https://github.com/nix-rust/nix/pull/1870)) +- The `length` argument of `sys::mman::mmap` is now of type `NonZeroUsize`. + ([#1873](https://github.com/nix-rust/nix/pull/1873)) + +### Fixed + +- Fixed using `SockaddrStorage` to store a Unix-domain socket address on Linux. + ([#1871](https://github.com/nix-rust/nix/pull/1871)) +- Fix microsecond calculation for `TimeSpec`. + ([#1801](https://github.com/nix-rust/nix/pull/1801)) +- Fix `User::from_name` and `Group::from_name` panicking + when given a name containing a nul. + ([#1815](https://github.com/nix-rust/nix/pull/1815)) +- Fix `User::from_uid` and `User::from_name` crash on Android platform. + ([#1824](https://github.com/nix-rust/nix/pull/1824)) +- Workaround XNU bug causing netmasks returned by `getifaddrs` to misbehave. + ([#1788](https://github.com/nix-rust/nix/pull/1788)) + +### Removed + +- Removed deprecated error constants and conversions. + ([#1860](https://github.com/nix-rust/nix/pull/1860)) + +## [0.25.0] - 2022-08-13 +### Added + +- Added `faccessat` + ([#1780](https://github.com/nix-rust/nix/pull/1780)) +- Added `memfd` on Android. + (#[1773](https://github.com/nix-rust/nix/pull/1773)) +- Added `ETH_P_ALL` to `SockProtocol` enum + (#[1768](https://github.com/nix-rust/nix/pull/1768)) +- Added four non-standard Linux `SysconfVar` variants + (#[1761](https://github.com/nix-rust/nix/pull/1761)) +- Added const constructors for `TimeSpec` and `TimeVal` + (#[1760](https://github.com/nix-rust/nix/pull/1760)) +- Added `chflags`. + (#[1758](https://github.com/nix-rust/nix/pull/1758)) +- Added `aio_writev` and `aio_readv`. + (#[1713](https://github.com/nix-rust/nix/pull/1713)) +- impl `From` for `Uid` and `From` for `Gid` + (#[1727](https://github.com/nix-rust/nix/pull/1727)) +- impl `From` for `std::net::SocketAddrV4` and + impl `From` for `std::net::SocketAddrV6`. + (#[1711](https://github.com/nix-rust/nix/pull/1711)) +- Added support for the `x86_64-unknown-haiku` target. + (#[1703](https://github.com/nix-rust/nix/pull/1703)) +- Added `ptrace::read_user` and `ptrace::write_user` for Linux. + (#[1697](https://github.com/nix-rust/nix/pull/1697)) +- Added `getrusage` and helper types `UsageWho` and `Usage` + (#[1747](https://github.com/nix-rust/nix/pull/1747)) +- Added the `DontRoute` SockOpt + (#[1752](https://github.com/nix-rust/nix/pull/1752)) +- Added `signal::SigSet::from_sigset_t_unchecked()`. + (#[1741](https://github.com/nix-rust/nix/pull/1741)) +- Added the `Ipv4OrigDstAddr` sockopt and control message. + (#[1772](https://github.com/nix-rust/nix/pull/1772)) +- Added the `Ipv6OrigDstAddr` sockopt and control message. + (#[1772](https://github.com/nix-rust/nix/pull/1772)) +- Added the `Ipv4SendSrcAddr` control message. + (#[1776](https://github.com/nix-rust/nix/pull/1776)) + +### Changed + +- Reimplemented sendmmsg/recvmmsg to avoid allocations and with better API + (#[1744](https://github.com/nix-rust/nix/pull/1744)) + +- Rewrote the aio module. The new module: + * Does more type checking at compile time rather than runtime. + * Gives the caller control over whether and when to `Box` an aio operation. + * Changes the type of the `priority` arguments to `i32`. + * Changes the return type of `aio_return` to `usize`. + (#[1713](https://github.com/nix-rust/nix/pull/1713)) +- `nix::poll::ppoll`: `sigmask` parameter is now optional. + (#[1739](https://github.com/nix-rust/nix/pull/1739)) +- Changed `gethostname` to return an owned `OsString`. + (#[1745](https://github.com/nix-rust/nix/pull/1745)) +- `signal:SigSet` is now marked as `repr(transparent)`. + (#[1741](https://github.com/nix-rust/nix/pull/1741)) + +### Removed + +- Removed support for resubmitting partially complete `lio_listio` operations. + It was too complicated, and didn't fit Nix's theme of zero-cost abstractions. + Instead, it can be reimplemented downstream. + (#[1713](https://github.com/nix-rust/nix/pull/1713)) + +## [0.24.2] - 2022-07-17 +### Fixed + +- Fixed buffer overflow in `nix::sys::socket::recvfrom`. + (#[1763](https://github.com/nix-rust/nix/pull/1763)) +- Enabled `SockaddrStorage::{as_link_addr, as_link_addr_mut}` for Linux-like + operating systems. + (#[1729](https://github.com/nix-rust/nix/pull/1729)) +- Fixed `SockaddrLike::from_raw` implementations for `VsockAddr` and + `SysControlAddr`. + (#[1736](https://github.com/nix-rust/nix/pull/1736)) + +## [0.24.1] - 2022-04-22 +### Fixed + +- Fixed `UnixAddr::size` on Linux-based OSes. + (#[1702](https://github.com/nix-rust/nix/pull/1702)) + +## [0.24.0] - 2022-04-21 +### Added + +- Added fine-grained features flags. Most Nix functionality can now be + conditionally enabled. By default, all features are enabled. + (#[1611](https://github.com/nix-rust/nix/pull/1611)) +- Added statfs FS type magic constants for `target_os = "android"` + and synced constants with libc v0.2.121. + (#[1690](https://github.com/nix-rust/nix/pull/1690)) +- Added `fexecve` on DragonFly. + (#[1577](https://github.com/nix-rust/nix/pull/1577)) +- `sys::uio::IoVec` is now `Send` and `Sync` + (#[1582](https://github.com/nix-rust/nix/pull/1582)) +- Added `EPOLLEXCLUSIVE` on Android. + (#[1567](https://github.com/nix-rust/nix/pull/1567)) +- Added `fdatasync` for FreeBSD, Fuchsia, NetBSD, and OpenBSD. + (#[1581](https://github.com/nix-rust/nix/pull/1581)) +- Added `sched_setaffinity` and `sched_getaffinity` on DragonFly. + (#[1537](https://github.com/nix-rust/nix/pull/1537)) +- Added `posix_fallocate` on DragonFly. + (#[1621](https://github.com/nix-rust/nix/pull/1621)) +- Added `SO_TIMESTAMPING` support + (#[1547](https://github.com/nix-rust/nix/pull/1547)) +- Added getter methods to `MqAttr` struct + (#[1619](https://github.com/nix-rust/nix/pull/1619)) +- Added the `TxTime` sockopt and control message. + (#[1564](https://github.com/nix-rust/nix/pull/1564)) +- Added POSIX per-process timer support + (#[1622](https://github.com/nix-rust/nix/pull/1622)) +- Added `sendfile` on DragonFly. + (#[1615](https://github.com/nix-rust/nix/pull/1615)) +- Added `UMOUNT_NOFOLLOW`, `FUSE_SUPER_MAGIC` on Linux. + (#[1634](https://github.com/nix-rust/nix/pull/1634)) +- Added `getresuid`, `setresuid`, `getresgid`, and `setresgid` on DragonFly, FreeBSD, and OpenBSD. + (#[1628](https://github.com/nix-rust/nix/pull/1628)) +- Added `MAP_FIXED_NOREPLACE` on Linux. + (#[1636](https://github.com/nix-rust/nix/pull/1636)) +- Added `fspacectl` on FreeBSD + (#[1640](https://github.com/nix-rust/nix/pull/1640)) +- Added `accept4` on DragonFly, Emscripten, Fuchsia, Illumos, and NetBSD. + (#[1654](https://github.com/nix-rust/nix/pull/1654)) +- Added `AsRawFd` implementation on `OwningIter`. + (#[1563](https://github.com/nix-rust/nix/pull/1563)) +- Added `process_vm_readv` and `process_vm_writev` on Android. + (#[1557](https://github.com/nix-rust/nix/pull/1557)) +- Added `nix::uncontext` module on s390x. + (#[1662](https://github.com/nix-rust/nix/pull/1662)) +- Implemented `Extend`, `FromIterator`, and `IntoIterator` for `SigSet` and + added `SigSet::iter` and `SigSetIter`. + (#[1553](https://github.com/nix-rust/nix/pull/1553)) +- Added `ENOTRECOVERABLE` and `EOWNERDEAD` error codes on DragonFly. + (#[1665](https://github.com/nix-rust/nix/pull/1665)) +- Implemented `Read` and `Write` for `&PtyMaster` + (#[1664](https://github.com/nix-rust/nix/pull/1664)) +- Added `MSG_NOSIGNAL` for Android, Dragonfly, FreeBSD, Fuchsia, Haiku, Illumos, Linux, NetBSD, OpenBSD and Solaris. + (#[1670](https://github.com/nix-rust/nix/pull/1670)) +- Added `waitid`. + (#[1584](https://github.com/nix-rust/nix/pull/1584)) +- Added `Ipv6DontFrag` for android, iOS, linux and macOS. +- Added `IpDontFrag` for iOS, macOS. + (#[1692](https://github.com/nix-rust/nix/pull/1692)) + +### Changed + +- `mqueue` functions now operate on a distinct type, `nix::mqueue::MqdT`. + Accessors take this type by reference, not by value. + (#[1639](https://github.com/nix-rust/nix/pull/1639)) +- Removed `SigSet::extend` in favor of `>::extend`. + Because of this change, you now need `use std::iter::Extend` to call `extend` + on a `SigSet`. + (#[1553](https://github.com/nix-rust/nix/pull/1553)) +- Removed the the `PATH_MAX` restriction from APIs accepting paths. Paths + will now be allocated on the heap if they are too long. In addition, large + instruction count improvements (~30x) were made to path handling. + (#[1656](https://github.com/nix-rust/nix/pull/1656)) +- Changed `getrlimit` and `setrlimit` to use `rlim_t` directly + instead of `Option`. + (#[1668](https://github.com/nix-rust/nix/pull/1668)) +- Deprecated `InetAddr` and `SockAddr` in favor of `SockaddrIn`, `SockaddrIn6`, + and `SockaddrStorage`. + (#[1684](https://github.com/nix-rust/nix/pull/1684)) +- Deprecated `IpAddr`, `Ipv4Addr`, and `Ipv6Addr` in favor of their equivalents + from the standard library. + (#[1685](https://github.com/nix-rust/nix/pull/1685)) +- `uname` now returns a `Result` instead of just a `UtsName` and + ignoring failures from libc. And getters on the `UtsName` struct now return + an `&OsStr` instead of `&str`. + (#[1672](https://github.com/nix-rust/nix/pull/1672)) +- Replaced `IoVec` with `IoSlice` and `IoSliceMut`, and replaced `IoVec::from_slice` with + `IoSlice::new`. (#[1643](https://github.com/nix-rust/nix/pull/1643)) + +### Fixed + +- `InetAddr::from_std` now sets the `sin_len`/`sin6_len` fields on the BSDs. + (#[1642](https://github.com/nix-rust/nix/pull/1642)) +- Fixed a panic in `LinkAddr::addr`. That function now returns an `Option`. + (#[1675](https://github.com/nix-rust/nix/pull/1675)) + (#[1677](https://github.com/nix-rust/nix/pull/1677)) + +### Removed + +- Removed public access to the inner fields of `NetlinkAddr`, `AlgAddr`, + `SysControlAddr`, `LinkAddr`, and `VsockAddr`. + (#[1614](https://github.com/nix-rust/nix/pull/1614)) +- Removed `EventFlag::EV_SYSFLAG`. + (#[1635](https://github.com/nix-rust/nix/pull/1635)) + +## [0.23.1] - 2021-12-16 + +### Changed + +- Relaxed the bitflags requirement from 1.3.1 to 1.1. This partially reverts + #1492. From now on, the MSRV is not guaranteed to work with all versions of + all dependencies, just with some version of all dependencies. + (#[1607](https://github.com/nix-rust/nix/pull/1607)) + +### Fixed + +- Fixed soundness issues in `FdSet::insert`, `FdSet::remove`, and + `FdSet::contains` involving file descriptors outside of the range + `0..FD_SETSIZE`. + (#[1575](https://github.com/nix-rust/nix/pull/1575)) + +## [0.23.0] - 2021-09-28 +### Added + +- Added the `LocalPeerCred` sockopt. + (#[1482](https://github.com/nix-rust/nix/pull/1482)) +- Added `TimeSpec::from_duration` and `TimeSpec::from_timespec` + (#[1465](https://github.com/nix-rust/nix/pull/1465)) +- Added `IPV6_V6ONLY` sockopt. + (#[1470](https://github.com/nix-rust/nix/pull/1470)) +- Added `impl From for libc::passwd` trait implementation to convert a `User` + into a `libc::passwd`. Consumes the `User` struct to give ownership over + the member pointers. + (#[1471](https://github.com/nix-rust/nix/pull/1471)) +- Added `pthread_kill`. + (#[1472](https://github.com/nix-rust/nix/pull/1472)) +- Added `mknodat`. + (#[1473](https://github.com/nix-rust/nix/pull/1473)) +- Added `setrlimit` and `getrlimit`. + (#[1302](https://github.com/nix-rust/nix/pull/1302)) +- Added `ptrace::interrupt` method for platforms that support `PTRACE_INTERRUPT` + (#[1422](https://github.com/nix-rust/nix/pull/1422)) +- Added `IP6T_SO_ORIGINAL_DST` sockopt. + (#[1490](https://github.com/nix-rust/nix/pull/1490)) +- Added the `PTRACE_EVENT_STOP` variant to the `sys::ptrace::Event` enum + (#[1335](https://github.com/nix-rust/nix/pull/1335)) +- Exposed `SockAddr::from_raw_sockaddr` + (#[1447](https://github.com/nix-rust/nix/pull/1447)) +- Added `TcpRepair` + (#[1503](https://github.com/nix-rust/nix/pull/1503)) +- Enabled `pwritev` and `preadv` for more operating systems. + (#[1511](https://github.com/nix-rust/nix/pull/1511)) +- Added support for `TCP_MAXSEG` TCP Maximum Segment Size socket options + (#[1292](https://github.com/nix-rust/nix/pull/1292)) +- Added `Ipv4RecvErr` and `Ipv6RecvErr` sockopts and associated control messages. + (#[1514](https://github.com/nix-rust/nix/pull/1514)) +- Added `AsRawFd` implementation on `PollFd`. + (#[1516](https://github.com/nix-rust/nix/pull/1516)) +- Added `Ipv4Ttl` and `Ipv6Ttl` sockopts. + (#[1515](https://github.com/nix-rust/nix/pull/1515)) +- Added `MAP_EXCL`, `MAP_ALIGNED_SUPER`, and `MAP_CONCEAL` mmap flags, and + exposed `MAP_ANONYMOUS` for all operating systems. + (#[1522](https://github.com/nix-rust/nix/pull/1522)) + (#[1525](https://github.com/nix-rust/nix/pull/1525)) + (#[1531](https://github.com/nix-rust/nix/pull/1531)) + (#[1534](https://github.com/nix-rust/nix/pull/1534)) +- Added read/write accessors for 'events' on `PollFd`. + (#[1517](https://github.com/nix-rust/nix/pull/1517)) + +### Changed + +- `FdSet::{contains, highest, fds}` no longer require a mutable reference. + (#[1464](https://github.com/nix-rust/nix/pull/1464)) +- `User::gecos` and corresponding `libc::passwd::pw_gecos` are supported on + 64-bit Android, change conditional compilation to include the field in + 64-bit Android builds + (#[1471](https://github.com/nix-rust/nix/pull/1471)) +- `eventfd`s are supported on Android, change conditional compilation to + include `sys::eventfd::eventfd` and `sys::eventfd::EfdFlags`for Android + builds. + (#[1481](https://github.com/nix-rust/nix/pull/1481)) +- Most enums that come from C, for example `Errno`, are now marked as + `#[non_exhaustive]`. + (#[1474](https://github.com/nix-rust/nix/pull/1474)) +- Many more functions, mostly contructors, are now `const`. + (#[1476](https://github.com/nix-rust/nix/pull/1476)) + (#[1492](https://github.com/nix-rust/nix/pull/1492)) +- `sys::event::KEvent::filter` now returns a `Result` instead of being + infalliable. The only cases where it will now return an error are cases + where it previously would've had undefined behavior. + (#[1484](https://github.com/nix-rust/nix/pull/1484)) +- Minimum supported Rust version is now 1.46.0. + ([#1492](https://github.com/nix-rust/nix/pull/1492)) +- Rework `UnixAddr` to encapsulate internals better in order to fix soundness + issues. No longer allows creating a `UnixAddr` from a raw `sockaddr_un`. + ([#1496](https://github.com/nix-rust/nix/pull/1496)) +- Raised bitflags to 1.3.0 and the MSRV to 1.46.0. + ([#1492](https://github.com/nix-rust/nix/pull/1492)) + +### Fixed + +- `posix_fadvise` now returns errors in the conventional way, rather than as a + non-zero value in `Ok()`. + (#[1538](https://github.com/nix-rust/nix/pull/1538)) +- Added more errno definitions for better backwards compatibility with + Nix 0.21.0. + (#[1467](https://github.com/nix-rust/nix/pull/1467)) +- Fixed potential undefined behavior in `Signal::try_from` on some platforms. + (#[1484](https://github.com/nix-rust/nix/pull/1484)) +- Fixed buffer overflow in `unistd::getgrouplist`. + (#[1545](https://github.com/nix-rust/nix/pull/1545)) + + +### Removed + +- Removed a couple of termios constants on redox that were never actually + supported. + (#[1483](https://github.com/nix-rust/nix/pull/1483)) +- Removed `nix::sys::signal::NSIG`. It was of dubious utility, and not correct + for all platforms. + (#[1484](https://github.com/nix-rust/nix/pull/1484)) +- Removed support for 32-bit Apple targets, since they've been dropped by both + Rustc and Xcode. + (#[1492](https://github.com/nix-rust/nix/pull/1492)) +- Deprecated `SockAddr/InetAddr::to_str` in favor of `ToString::to_string` + (#[1495](https://github.com/nix-rust/nix/pull/1495)) +- Removed `SigevNotify` on OpenBSD and Redox. + (#[1511](https://github.com/nix-rust/nix/pull/1511)) + +## [0.22.3] - 22 January 2022 +### Changed +- Relaxed the bitflags requirement from 1.3.1 to 1.1. This partially reverts + #1492. From now on, the MSRV is not guaranteed to work with all versions of + all dependencies, just with some version of all dependencies. + (#[1607](https://github.com/nix-rust/nix/pull/1607)) + +## [0.22.2] - 28 September 2021 +### Fixed +- Fixed buffer overflow in `unistd::getgrouplist`. + (#[1545](https://github.com/nix-rust/nix/pull/1545)) +- Added more errno definitions for better backwards compatibility with + Nix 0.21.0. + (#[1467](https://github.com/nix-rust/nix/pull/1467)) + +## [0.22.1] - 13 August 2021 +### Fixed +- Locked bitflags to < 1.3.0 to fix the build with rust < 1.46.0. + +### Removed +- Removed a couple of termios constants on redox that were never actually + supported. + (#[1483](https://github.com/nix-rust/nix/pull/1483)) + +## [0.22.0] - 9 July 2021 +### Added +- Added `if_nameindex` (#[1445](https://github.com/nix-rust/nix/pull/1445)) +- Added `nmount` for FreeBSD. + (#[1453](https://github.com/nix-rust/nix/pull/1453)) +- Added `IpFreebind` socket option (sockopt) on Linux, Fuchsia and Android. + (#[1456](https://github.com/nix-rust/nix/pull/1456)) +- Added `TcpUserTimeout` socket option (sockopt) on Linux and Fuchsia. + (#[1457](https://github.com/nix-rust/nix/pull/1457)) +- Added `renameat2` for Linux + (#[1458](https://github.com/nix-rust/nix/pull/1458)) +- Added `RxqOvfl` support on Linux, Fuchsia and Android. + (#[1455](https://github.com/nix-rust/nix/pull/1455)) + +### Changed +- `ptsname_r` now returns a lossily-converted string in the event of bad UTF, + just like `ptsname`. + ([#1446](https://github.com/nix-rust/nix/pull/1446)) +- Nix's error type is now a simple wrapper around the platform's Errno. This + means it is now `Into`. It's also `Clone`, `Copy`, `Eq`, and + has a small fixed size. It also requires less typing. For example, the old + enum variant `nix::Error::Sys(nix::errno::Errno::EINVAL)` is now simply + `nix::Error::EINVAL`. + ([#1446](https://github.com/nix-rust/nix/pull/1446)) + +## [0.21.2] - 29 September 2021 +### Fixed +- Fixed buffer overflow in `unistd::getgrouplist`. + (#[1545](https://github.com/nix-rust/nix/pull/1545)) + +## [0.21.1] - 13 August 2021 +### Fixed +- Locked bitflags to < 1.3.0 to fix the build with rust < 1.46.0. + +### Removed +- Removed a couple of termios constants on redox that were never actually + supported. + (#[1483](https://github.com/nix-rust/nix/pull/1483)) + +## [0.21.0] - 31 May 2021 +### Added +- Added `getresuid` and `getresgid` + (#[1430](https://github.com/nix-rust/nix/pull/1430)) +- Added TIMESTAMPNS support for linux + (#[1402](https://github.com/nix-rust/nix/pull/1402)) +- Added `sendfile64` (#[1439](https://github.com/nix-rust/nix/pull/1439)) +- Added `MS_LAZYTIME` to `MsFlags` + (#[1437](https://github.com/nix-rust/nix/pull/1437)) + +### Changed +- Made `forkpty` unsafe, like `fork` + (#[1390](https://github.com/nix-rust/nix/pull/1390)) +- Made `Uid`, `Gid` and `Pid` methods `from_raw` and `as_raw` a `const fn` + (#[1429](https://github.com/nix-rust/nix/pull/1429)) +- Made `Uid::is_root` a `const fn` + (#[1429](https://github.com/nix-rust/nix/pull/1429)) +- `AioCb` is now always pinned. Once a `libc::aiocb` gets sent to the kernel, + its address in memory must not change. Nix now enforces that by using + `std::pin`. Most users won't need to change anything, except when using + `aio_suspend`. See that method's documentation for the new usage. + (#[1440](https://github.com/nix-rust/nix/pull/1440)) +- `LioCb` is now constructed using a distinct `LioCbBuilder` struct. This + avoids a soundness issue with the old `LioCb`. Usage is similar but + construction now uses the builder pattern. See the documentation for + details. + (#[1440](https://github.com/nix-rust/nix/pull/1440)) +- Minimum supported Rust version is now 1.41.0. + ([#1440](https://github.com/nix-rust/nix/pull/1440)) +- Errno aliases are now associated consts on `Errno`, instead of consts in the + `errno` module. + (#[1452](https://github.com/nix-rust/nix/pull/1452)) + +### Fixed +- Allow `sockaddr_ll` size, as reported by the Linux kernel, to be smaller then it's definition + (#[1395](https://github.com/nix-rust/nix/pull/1395)) +- Fix spurious errors using `sendmmsg` with multiple cmsgs + (#[1414](https://github.com/nix-rust/nix/pull/1414)) +- Added `Errno::EOPNOTSUPP` to FreeBSD, where it was missing. + (#[1452](https://github.com/nix-rust/nix/pull/1452)) + +### Removed + +- Removed `sys::socket::accept4` from Android arm because libc removed it in + version 0.2.87. + ([#1399](https://github.com/nix-rust/nix/pull/1399)) +- `AioCb::from_boxed_slice` and `AioCb::from_boxed_mut_slice` have been + removed. They were useful with earlier versions of Rust, but should no + longer be needed now that async/await are available. `AioCb`s now work + exclusively with borrowed buffers, not owned ones. + (#[1440](https://github.com/nix-rust/nix/pull/1440)) +- Removed some Errno values from platforms where they aren't actually defined. + (#[1452](https://github.com/nix-rust/nix/pull/1452)) + +## [0.20.2] - 28 September 2021 +### Fixed +- Fixed buffer overflow in `unistd::getgrouplist`. + (#[1545](https://github.com/nix-rust/nix/pull/1545)) + +## [0.20.1] - 13 August 2021 +### Fixed +- Locked bitflags to < 1.3.0 to fix the build with rust < 1.46.0. + +### Removed +- Removed a couple of termios constants on redox that were never actually + supported. + (#[1483](https://github.com/nix-rust/nix/pull/1483)) + +## [0.20.0] - 20 February 2021 +### Added + +- Added a `passwd` field to `Group` (#[1338](https://github.com/nix-rust/nix/pull/1338)) +- Added `mremap` (#[1306](https://github.com/nix-rust/nix/pull/1306)) +- Added `personality` (#[1331](https://github.com/nix-rust/nix/pull/1331)) +- Added limited Fuchsia support (#[1285](https://github.com/nix-rust/nix/pull/1285)) +- Added `getpeereid` (#[1342](https://github.com/nix-rust/nix/pull/1342)) +- Implemented `IntoIterator` for `Dir` + (#[1333](https://github.com/nix-rust/nix/pull/1333)). + +### Changed + +- Minimum supported Rust version is now 1.40.0. + ([#1356](https://github.com/nix-rust/nix/pull/1356)) +- i686-apple-darwin has been demoted to Tier 2 support, because it's deprecated + by Xcode. + (#[1350](https://github.com/nix-rust/nix/pull/1350)) +- Fixed calling `recvfrom` on an `AddrFamily::Packet` socket + (#[1344](https://github.com/nix-rust/nix/pull/1344)) + +### Fixed +- `TimerFd` now closes the underlying fd on drop. + ([#1381](https://github.com/nix-rust/nix/pull/1381)) +- Define `*_MAGIC` filesystem constants on Linux s390x + (#[1372](https://github.com/nix-rust/nix/pull/1372)) +- mqueue, sysinfo, timespec, statfs, test_ptrace_syscall() on x32 + (#[1366](https://github.com/nix-rust/nix/pull/1366)) + +### Removed + +- `Dir`, `SignalFd`, and `PtyMaster` are no longer `Clone`. + (#[1382](https://github.com/nix-rust/nix/pull/1382)) +- Removed `SockLevel`, which hasn't been used for a few years + (#[1362](https://github.com/nix-rust/nix/pull/1362)) +- Removed both `Copy` and `Clone` from `TimerFd`. + ([#1381](https://github.com/nix-rust/nix/pull/1381)) + +## [0.19.1] - 28 November 2020 +### Fixed +- Fixed bugs in `recvmmsg`. + (#[1341](https://github.com/nix-rust/nix/pull/1341)) + +## [0.19.0] - 6 October 2020 +### Added +- Added Netlink protocol families to the `SockProtocol` enum + (#[1289](https://github.com/nix-rust/nix/pull/1289)) +- Added `clock_gettime`, `clock_settime`, `clock_getres`, + `clock_getcpuclockid` functions and `ClockId` struct. + (#[1281](https://github.com/nix-rust/nix/pull/1281)) +- Added wrapper functions for `PTRACE_SYSEMU` and `PTRACE_SYSEMU_SINGLESTEP`. + (#[1300](https://github.com/nix-rust/nix/pull/1300)) +- Add support for Vsock on Android rather than just Linux. + (#[1301](https://github.com/nix-rust/nix/pull/1301)) +- Added `TCP_KEEPCNT` and `TCP_KEEPINTVL` TCP keepalive options. + (#[1283](https://github.com/nix-rust/nix/pull/1283)) +### Changed +- Expose `SeekData` and `SeekHole` on all Linux targets + (#[1284](https://github.com/nix-rust/nix/pull/1284)) +- Changed unistd::{execv,execve,execvp,execvpe,fexecve,execveat} to take both `&[&CStr]` and `&[CString]` as its list argument(s). + (#[1278](https://github.com/nix-rust/nix/pull/1278)) +- Made `unistd::fork` an unsafe funtion, bringing it in line with [libstd's decision](https://github.com/rust-lang/rust/pull/58059). + (#[1293](https://github.com/nix-rust/nix/pull/1293)) + +## [0.18.0] - 26 July 2020 +### Added +- Added `fchown(2)` wrapper. + (#[1257](https://github.com/nix-rust/nix/pull/1257)) +- Added support on linux systems for `MAP_HUGE_`_`SIZE`_ family of flags. + (#[1211](https://github.com/nix-rust/nix/pull/1211)) +- Added support for `F_OFD_*` `fcntl` commands on Linux and Android. + (#[1195](https://github.com/nix-rust/nix/pull/1195)) +- Added `env::clearenv()`: calls `libc::clearenv` on platforms + where it's available, and clears the environment of all variables + via `std::env::vars` and `std::env::remove_var` on others. + (#[1185](https://github.com/nix-rust/nix/pull/1185)) +- `FsType` inner value made public. + (#[1187](https://github.com/nix-rust/nix/pull/1187)) +- Added `unistd::setfsuid` and `unistd::setfsgid` to set the user or group + identity for filesystem checks per-thread. + (#[1163](https://github.com/nix-rust/nix/pull/1163)) +- Derived `Ord`, `PartialOrd` for `unistd::Pid` (#[1189](https://github.com/nix-rust/nix/pull/1189)) +- Added `select::FdSet::fds` method to iterate over file descriptors in a set. + ([#1207](https://github.com/nix-rust/nix/pull/1207)) +- Added support for UDP generic segmentation offload (GSO) and generic + receive offload (GRO) ([#1209](https://github.com/nix-rust/nix/pull/1209)) +- Added support for `sendmmsg` and `recvmmsg` calls + (#[1208](https://github.com/nix-rust/nix/pull/1208)) +- Added support for `SCM_CREDS` messages (`UnixCredentials`) on FreeBSD/DragonFly + (#[1216](https://github.com/nix-rust/nix/pull/1216)) +- Added `BindToDevice` socket option (sockopt) on Linux + (#[1233](https://github.com/nix-rust/nix/pull/1233)) +- Added `EventFilter` bitflags for `EV_DISPATCH` and `EV_RECEIPT` on OpenBSD. + (#[1252](https://github.com/nix-rust/nix/pull/1252)) +- Added support for `Ipv4PacketInfo` and `Ipv6PacketInfo` to `ControlMessage`. + (#[1222](https://github.com/nix-rust/nix/pull/1222)) +- `CpuSet` and `UnixCredentials` now implement `Default`. + (#[1244](https://github.com/nix-rust/nix/pull/1244)) +- Added `unistd::ttyname` + (#[1259](https://github.com/nix-rust/nix/pull/1259)) +- Added support for `Ipv4PacketInfo` and `Ipv6PacketInfo` to `ControlMessage` for iOS and Android. + (#[1265](https://github.com/nix-rust/nix/pull/1265)) +- Added support for `TimerFd`. + (#[1261](https://github.com/nix-rust/nix/pull/1261)) + +### Changed +- Changed `fallocate` return type from `c_int` to `()` (#[1201](https://github.com/nix-rust/nix/pull/1201)) +- Enabled `sys::ptrace::setregs` and `sys::ptrace::getregs` on x86_64-unknown-linux-musl target + (#[1198](https://github.com/nix-rust/nix/pull/1198)) +- On Linux, `ptrace::write` is now an `unsafe` function. Caveat programmer. + (#[1245](https://github.com/nix-rust/nix/pull/1245)) +- `execv`, `execve`, `execvp` and `execveat` in `::nix::unistd` and `reboot` in + `::nix::sys::reboot` now return `Result` instead of `Result` (#[1239](https://github.com/nix-rust/nix/pull/1239)) +- `sys::socket::sockaddr_storage_to_addr` is no longer `unsafe`. So is + `offset_of!`. +- `sys::socket::sockaddr_storage_to_addr`, `offset_of!`, and `Errno::clear` are + no longer `unsafe`. +- `SockAddr::as_ffi_pair`,`sys::socket::sockaddr_storage_to_addr`, `offset_of!`, + and `Errno::clear` are no longer `unsafe`. + (#[1244](https://github.com/nix-rust/nix/pull/1244)) +- Several `Inotify` methods now take `self` by value instead of by reference + (#[1244](https://github.com/nix-rust/nix/pull/1244)) +- `nix::poll::ppoll`: `timeout` parameter is now optional, None is equivalent for infinite timeout. + +### Fixed + +- Fixed `getsockopt`. The old code produced UB which triggers a panic with + Rust 1.44.0. + (#[1214](https://github.com/nix-rust/nix/pull/1214)) + +- Fixed a bug in nix::unistd that would result in an infinite loop + when a group or user lookup required a buffer larger than + 16KB. (#[1198](https://github.com/nix-rust/nix/pull/1198)) +- Fixed unaligned casting of `cmsg_data` to `af_alg_iv` (#[1206](https://github.com/nix-rust/nix/pull/1206)) +- Fixed `readlink`/`readlinkat` when reading symlinks longer than `PATH_MAX` (#[1231](https://github.com/nix-rust/nix/pull/1231)) +- `PollFd`, `EpollEvent`, `IpMembershipRequest`, `Ipv6MembershipRequest`, + `TimeVal`, and `IoVec` are now `repr(transparent)`. This is required for + correctness's sake across all architectures and compilers, though now bugs + have been reported so far. + (#[1243](https://github.com/nix-rust/nix/pull/1243)) +- Fixed unaligned pointer read in `Inotify::read_events`. + (#[1244](https://github.com/nix-rust/nix/pull/1244)) + +### Removed + +- Removed `sys::socket::addr::from_libc_sockaddr` from the public API. + (#[1215](https://github.com/nix-rust/nix/pull/1215)) +- Removed `sys::termios::{get_libc_termios, get_libc_termios_mut, update_wrapper` + from the public API. These were previously hidden in the docs but still usable + by downstream. + (#[1235](https://github.com/nix-rust/nix/pull/1235)) + +- Nix no longer implements `NixPath` for `Option

where P: NixPath`. Most + Nix functions that accept `NixPath` arguments can't do anything useful with + `None`. The exceptions (`mount` and `quotactl_sync`) already take explicitly + optional arguments. + (#[1242](https://github.com/nix-rust/nix/pull/1242)) + +- Removed `unistd::daemon` and `unistd::pipe2` on OSX and ios + (#[1255](https://github.com/nix-rust/nix/pull/1255)) + +- Removed `sys::event::FilterFlag::NOTE_EXIT_REPARENTED` and + `sys::event::FilterFlag::NOTE_REAP` on OSX and ios. + (#[1255](https://github.com/nix-rust/nix/pull/1255)) + +- Removed `sys::ptrace::ptrace` on Android and Linux. + (#[1255](https://github.com/nix-rust/nix/pull/1255)) + +- Dropped support for powerpc64-unknown-linux-gnu + (#[1266](https://github.com/nix-rust/nix/pull/1268)) + +## [0.17.0] - 3 February 2020 +### Added +- Add `CLK_TCK` to `SysconfVar` + (#[1177](https://github.com/nix-rust/nix/pull/1177)) +### Removed +- Removed deprecated Error::description from error types + (#[1175](https://github.com/nix-rust/nix/pull/1175)) + +## [0.16.1] - 23 December 2019 +### Fixed + +- Fixed the build for OpenBSD + (#[1168](https://github.com/nix-rust/nix/pull/1168)) + +## [0.16.0] - 1 December 2019 +### Added +- Added `ptrace::seize()`: similar to `attach()` on Linux + but with better-defined semantics. + (#[1154](https://github.com/nix-rust/nix/pull/1154)) + +- Added `Signal::as_str()`: returns signal name as `&'static str` + (#[1138](https://github.com/nix-rust/nix/pull/1138)) + +- Added `posix_fallocate`. + ([#1105](https://github.com/nix-rust/nix/pull/1105)) + +- Implemented `Default` for `FdSet` + ([#1107](https://github.com/nix-rust/nix/pull/1107)) + +- Added `NixPath::is_empty`. + ([#1107](https://github.com/nix-rust/nix/pull/1107)) + +- Added `mkfifoat` + ([#1133](https://github.com/nix-rust/nix/pull/1133)) + +- Added `User::from_uid`, `User::from_name`, `User::from_gid` and + `Group::from_name`, + ([#1139](https://github.com/nix-rust/nix/pull/1139)) + +- Added `linkat` + ([#1101](https://github.com/nix-rust/nix/pull/1101)) + +- Added `sched_getaffinity`. + ([#1148](https://github.com/nix-rust/nix/pull/1148)) + +- Added optional `Signal` argument to `ptrace::{detach, syscall}` for signal + injection. ([#1083](https://github.com/nix-rust/nix/pull/1083)) + +### Changed +- `sys::termios::BaudRate` now implements `TryFrom` instead of + `From`. The old `From` implementation would panic on failure. + ([#1159](https://github.com/nix-rust/nix/pull/1159)) + +- `sys::socket::ControlMessage::ScmCredentials` and + `sys::socket::ControlMessageOwned::ScmCredentials` now wrap `UnixCredentials` + rather than `libc::ucred`. + ([#1160](https://github.com/nix-rust/nix/pull/1160)) + +- `sys::socket::recvmsg` now takes a plain `Vec` instead of a `CmsgBuffer` + implementor. If you were already using `cmsg_space!`, then you needn't worry. + ([#1156](https://github.com/nix-rust/nix/pull/1156)) + +- `sys::socket::recvfrom` now returns + `Result<(usize, Option)>` instead of `Result<(usize, SockAddr)>`. + ([#1145](https://github.com/nix-rust/nix/pull/1145)) + +- `Signal::from_c_int` has been replaced by `Signal::try_from` + ([#1113](https://github.com/nix-rust/nix/pull/1113)) + +- Changed `readlink` and `readlinkat` to return `OsString` + ([#1109](https://github.com/nix-rust/nix/pull/1109)) + + ```rust + # use nix::fcntl::{readlink, readlinkat}; + // the buffer argument of `readlink` and `readlinkat` has been removed, + // and the return value is now an owned type (`OsString`). + // Existing code can be updated by removing the buffer argument + // and removing any clone or similar operation on the output + + // old code `readlink(&path, &mut buf)` can be replaced with the following + let _: OsString = readlink(&path); + + // old code `readlinkat(dirfd, &path, &mut buf)` can be replaced with the following + let _: OsString = readlinkat(dirfd, &path); + ``` + +- Minimum supported Rust version is now 1.36.0. + ([#1108](https://github.com/nix-rust/nix/pull/1108)) + +- `Ipv4Addr::octets`, `Ipv4Addr::to_std`, `Error::as_errno`, + `ForkResult::is_child`, `ForkResult::is_parent`, `Gid::as_raw`, + `Uid::is_root`, `Uid::as_raw`, `Pid::as_raw`, and `PollFd::revents` now take + `self` by value. + ([#1107](https://github.com/nix-rust/nix/pull/1107)) + +- Type `&CString` for parameters of `exec(v|ve|vp|vpe|veat)` are changed to `&CStr`. + ([#1121](https://github.com/nix-rust/nix/pull/1121)) + +### Fixed +- Fix length of abstract socket addresses + ([#1120](https://github.com/nix-rust/nix/pull/1120)) + +- Fix initialization of msghdr in recvmsg/sendmsg when built with musl + ([#1136](https://github.com/nix-rust/nix/pull/1136)) + +### Removed +- Remove the deprecated `CmsgSpace`. + ([#1156](https://github.com/nix-rust/nix/pull/1156)) + +## [0.15.0] - 10 August 2019 +### Added +- Added `MSG_WAITALL` to `MsgFlags` in `sys::socket`. + ([#1079](https://github.com/nix-rust/nix/pull/1079)) +- Implemented `Clone`, `Copy`, `Debug`, `Eq`, `Hash`, and `PartialEq` for most + types that support them. ([#1035](https://github.com/nix-rust/nix/pull/1035)) +- Added `copy_file_range` wrapper + ([#1069](https://github.com/nix-rust/nix/pull/1069)) +- Add `mkdirat`. + ([#1084](https://github.com/nix-rust/nix/pull/1084)) +- Add `posix_fadvise`. + ([#1089](https://github.com/nix-rust/nix/pull/1089)) +- Added `AF_VSOCK` to `AddressFamily`. + ([#1091](https://github.com/nix-rust/nix/pull/1091)) +- Add `unlinkat` + ([#1058](https://github.com/nix-rust/nix/pull/1058)) +- Add `renameat`. + ([#1097](https://github.com/nix-rust/nix/pull/1097)) + +### Changed +- Support for `ifaddrs` now present when building for Android. + ([#1077](https://github.com/nix-rust/nix/pull/1077)) +- Minimum supported Rust version is now 1.31.0 + ([#1035](https://github.com/nix-rust/nix/pull/1035)) + ([#1095](https://github.com/nix-rust/nix/pull/1095)) +- Now functions `statfs()` and `fstatfs()` return result with `Statfs` wrapper + ([#928](https://github.com/nix-rust/nix/pull/928)) + +### Fixed +- Enabled `sched_yield` for all nix hosts. + ([#1090](https://github.com/nix-rust/nix/pull/1090)) + +## [0.14.1] - 2019-06-06 +### Added +- Macros exported by `nix` may now be imported via `use` on the Rust 2018 + edition without importing helper macros on Linux targets. + ([#1066](https://github.com/nix-rust/nix/pull/1066)) + + For example, in Rust 2018, the `ioctl_read_bad!` macro can now be imported + without importing the `convert_ioctl_res!` macro. + + ```rust + use nix::ioctl_read_bad; + + ioctl_read_bad!(tcgets, libc::TCGETS, libc::termios); + ``` + +### Changed +- Changed some public types from reexports of libc types like `uint32_t` to the + native equivalents like `u32.` + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) + +### Fixed +- Fix the build on Android and Linux/mips with recent versions of libc. + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) + +## [0.14.0] - 2019-05-21 +### Added +- Add IP_RECVIF & IP_RECVDSTADDR. Enable IP_PKTINFO and IP6_PKTINFO on netbsd/openbsd. + ([#1002](https://github.com/nix-rust/nix/pull/1002)) +- Added `inotify_init1`, `inotify_add_watch` and `inotify_rm_watch` wrappers for + Android and Linux. ([#1016](https://github.com/nix-rust/nix/pull/1016)) +- Add `ALG_SET_IV`, `ALG_SET_OP` and `ALG_SET_AEAD_ASSOCLEN` control messages and `AF_ALG` + socket types on Linux and Android ([#1031](https://github.com/nix-rust/nix/pull/1031)) +- Add killpg + ([#1034](https://github.com/nix-rust/nix/pull/1034)) +- Added ENOTSUP errno support for Linux and Android. + ([#969](https://github.com/nix-rust/nix/pull/969)) +- Add several errno constants from OpenBSD 6.2 + ([#1036](https://github.com/nix-rust/nix/pull/1036)) +- Added `from_std` and `to_std` methods for `sys::socket::IpAddr` + ([#1043](https://github.com/nix-rust/nix/pull/1043)) +- Added `nix::unistd:seteuid` and `nix::unistd::setegid` for those platforms that do + not support `setresuid` nor `setresgid` respectively. + ([#1044](https://github.com/nix-rust/nix/pull/1044)) +- Added a `access` wrapper + ([#1045](https://github.com/nix-rust/nix/pull/1045)) +- Add `forkpty` + ([#1042](https://github.com/nix-rust/nix/pull/1042)) +- Add `sched_yield` + ([#1050](https://github.com/nix-rust/nix/pull/1050)) + +### Changed +- `PollFd` event flags renamed to `PollFlags` ([#1024](https://github.com/nix-rust/nix/pull/1024/)) +- `recvmsg` now returns an Iterator over `ControlMessageOwned` objects rather + than `ControlMessage` objects. This is sadly not backwards-compatible. Fix + code like this: + ```rust + if let ControlMessage::ScmRights(&fds) = cmsg { + ``` + + By replacing it with code like this: + ```rust + if let ControlMessageOwned::ScmRights(fds) = cmsg { + ``` + ([#1020](https://github.com/nix-rust/nix/pull/1020)) +- Replaced `CmsgSpace` with the `cmsg_space` macro. + ([#1020](https://github.com/nix-rust/nix/pull/1020)) + +### Fixed +- Fixed multiple bugs when using `sendmsg` and `recvmsg` with ancillary control messages + ([#1020](https://github.com/nix-rust/nix/pull/1020)) +- Macros exported by `nix` may now be imported via `use` on the Rust 2018 + edition without importing helper macros for BSD targets. + ([#1041](https://github.com/nix-rust/nix/pull/1041)) + + For example, in Rust 2018, the `ioctl_read_bad!` macro can now be imported + without importing the `convert_ioctl_res!` macro. + + ```rust + use nix::ioctl_read_bad; + + ioctl_read_bad!(tcgets, libc::TCGETS, libc::termios); + ``` + +### Removed +- `Daemon`, `NOTE_REAP`, and `NOTE_EXIT_REPARENTED` are now deprecated on OSX + and iOS. + ([#1033](https://github.com/nix-rust/nix/pull/1033)) +- `PTRACE_GETREGS`, `PTRACE_SETREGS`, `PTRACE_GETFPREGS`, and + `PTRACE_SETFPREGS` have been removed from some platforms where they never + should've been defined in the first place. + ([#1055](https://github.com/nix-rust/nix/pull/1055)) + +## [0.13.1] - 2019-06-10 +### Changed +- Changed some public types from reexports of libc types like `uint32_t` to the + native equivalents like `u32.` + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) + +### Fixed +- Fix the build on Android and Linux/mips with recent versions of libc. + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) +- Fixed build on Linux/arm and Linux/s390x with the latest Rust libc + ([52102cb](https://github.com/nix-rust/nix/commit/52102cb76398c4dfb9ea141b98c5b01a2e050973)) + +### Removed +- `Daemon`, `NOTE_REAP`, and `NOTE_EXIT_REPARENTED` are now deprecated on OSX + and iOS. + ([#1033](https://github.com/nix-rust/nix/pull/1033)) + +## [0.13.0] - 2019-01-15 +### Added +- Added PKTINFO(V4) & V6PKTINFO cmsg support - Android/FreeBSD/iOS/Linux/MacOS. + ([#990](https://github.com/nix-rust/nix/pull/990)) +- Added support of CString type in `setsockopt`. + ([#972](https://github.com/nix-rust/nix/pull/972)) +- Added option `TCP_CONGESTION` in `setsockopt`. + ([#972](https://github.com/nix-rust/nix/pull/972)) +- Added `symlinkat` wrapper. + ([#997](https://github.com/nix-rust/nix/pull/997)) +- Added `ptrace::{getregs, setregs}`. + ([#1010](https://github.com/nix-rust/nix/pull/1010)) +- Added `nix::sys::signal::signal`. + ([#817](https://github.com/nix-rust/nix/pull/817)) +- Added an `mprotect` wrapper. + ([#991](https://github.com/nix-rust/nix/pull/991)) + +### Fixed +- `lutimes` never worked on OpenBSD as it is not implemented on OpenBSD. It has + been removed. ([#1000](https://github.com/nix-rust/nix/pull/1000)) +- `fexecve` never worked on NetBSD or on OpenBSD as it is not implemented on + either OS. It has been removed. ([#1000](https://github.com/nix-rust/nix/pull/1000)) + +## [0.12.1] 2019-06-08 +### Changed +- Changed some public types from reexports of libc types like `uint32_t` to the + native equivalents like `u32.` + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) + +### Fixed +- Fix the build on Android and Linux/mips with recent versions of libc. + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) +- Fixed build on Linux/arm and Linux/s390x with the latest Rust libc + ([52102cb](https://github.com/nix-rust/nix/commit/52102cb76398c4dfb9ea141b98c5b01a2e050973)) + +### Removed +- `fexecve` never worked on NetBSD or on OpenBSD as it is not implemented on + either OS. It has been removed. ([#1000](https://github.com/nix-rust/nix/pull/1000)) +- `Daemon`, `NOTE_REAP`, and `NOTE_EXIT_REPARENTED` are now deprecated on OSX + and iOS. + ([#1033](https://github.com/nix-rust/nix/pull/1033)) + +## [0.12.0] 2018-11-28 + +### Added +- Added `FromStr` and `Display` impls for `nix::sys::Signal` + ([#884](https://github.com/nix-rust/nix/pull/884)) +- Added a `sync` wrapper. + ([#961](https://github.com/nix-rust/nix/pull/961)) +- Added a `sysinfo` wrapper. + ([#922](https://github.com/nix-rust/nix/pull/922)) +- Support the `SO_PEERCRED` socket option and the `UnixCredentials` type on all Linux and Android targets. + ([#921](https://github.com/nix-rust/nix/pull/921)) +- Added support for `SCM_CREDENTIALS`, allowing to send process credentials over Unix sockets. + ([#923](https://github.com/nix-rust/nix/pull/923)) +- Added a `dir` module for reading directories (wraps `fdopendir`, `readdir`, and `rewinddir`). + ([#916](https://github.com/nix-rust/nix/pull/916)) +- Added `kmod` module that allows loading and unloading kernel modules on Linux. + ([#930](https://github.com/nix-rust/nix/pull/930)) +- Added `futimens` and `utimesat` wrappers ([#944](https://github.com/nix-rust/nix/pull/944)), + an `lutimes` wrapper ([#967](https://github.com/nix-rust/nix/pull/967)), + and a `utimes` wrapper ([#946](https://github.com/nix-rust/nix/pull/946)). +- Added `AF_UNSPEC` wrapper to `AddressFamily` ([#948](https://github.com/nix-rust/nix/pull/948)) +- Added the `mode_t` public alias within `sys::stat`. + ([#954](https://github.com/nix-rust/nix/pull/954)) +- Added a `truncate` wrapper. + ([#956](https://github.com/nix-rust/nix/pull/956)) +- Added a `fchownat` wrapper. + ([#955](https://github.com/nix-rust/nix/pull/955)) +- Added support for `ptrace` on BSD operating systems ([#949](https://github.com/nix-rust/nix/pull/949)) +- Added `ptrace` functions for reads and writes to tracee memory and ptrace kill + ([#949](https://github.com/nix-rust/nix/pull/949)) ([#958](https://github.com/nix-rust/nix/pull/958)) +- Added a `acct` wrapper module for enabling and disabling process accounting + ([#952](https://github.com/nix-rust/nix/pull/952)) +- Added the `time_t` and `suseconds_t` public aliases within `sys::time`. + ([#968](https://github.com/nix-rust/nix/pull/968)) +- Added `unistd::execvpe` for Haiku, Linux and OpenBSD + ([#975](https://github.com/nix-rust/nix/pull/975)) +- Added `Error::as_errno`. + ([#977](https://github.com/nix-rust/nix/pull/977)) + +### Changed +- Increased required Rust version to 1.24.1 + ([#900](https://github.com/nix-rust/nix/pull/900)) + ([#966](https://github.com/nix-rust/nix/pull/966)) + +### Fixed +- Made `preadv` take immutable slice of IoVec. + ([#914](https://github.com/nix-rust/nix/pull/914)) +- Fixed passing multiple file descriptors over Unix Sockets. + ([#918](https://github.com/nix-rust/nix/pull/918)) + +## [0.11.1] 2019-06-06 +### Changed +- Changed some public types from reexports of libc types like `uint32_t` to the + native equivalents like `u32.` + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) + +### Fixed +- Fix the build on Android and Linux/mips with recent versions of libc. + ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) +- Fixed build on Linux/arm and Linux/s390x with the latest Rust libc + ([52102cb](https://github.com/nix-rust/nix/commit/52102cb76398c4dfb9ea141b98c5b01a2e050973)) + +### Removed +- `fexecve` never worked on NetBSD or on OpenBSD as it is not implemented on + either OS. It has been removed. ([#1000](https://github.com/nix-rust/nix/pull/1000)) +- `Daemon`, `NOTE_REAP`, and `NOTE_EXIT_REPARENTED` are now deprecated on OSX + and iOS. + ([#1033](https://github.com/nix-rust/nix/pull/1033)) + +## [0.11.0] 2018-06-01 + +### Added +- Added `sendfile` on FreeBSD and Darwin. + ([#901](https://github.com/nix-rust/nix/pull/901)) +- Added `pselect` + ([#894](https://github.com/nix-rust/nix/pull/894)) +- Exposed `preadv` and `pwritev` on the BSDs. + ([#883](https://github.com/nix-rust/nix/pull/883)) +- Added `mlockall` and `munlockall` + ([#876](https://github.com/nix-rust/nix/pull/876)) +- Added `SO_MARK` on Linux. + ([#873](https://github.com/nix-rust/nix/pull/873)) +- Added safe support for nearly any buffer type in the `sys::aio` module. + ([#872](https://github.com/nix-rust/nix/pull/872)) +- Added `sys::aio::LioCb` as a wrapper for `libc::lio_listio`. + ([#872](https://github.com/nix-rust/nix/pull/872)) +- Added `unistd::getsid` + ([#850](https://github.com/nix-rust/nix/pull/850)) +- Added `alarm`. ([#830](https://github.com/nix-rust/nix/pull/830)) +- Added interface flags `IFF_NO_PI, IFF_TUN, IFF_TAP` on linux-like systems. + ([#853](https://github.com/nix-rust/nix/pull/853)) +- Added `statvfs` module to all MacOS and Linux architectures. + ([#832](https://github.com/nix-rust/nix/pull/832)) +- Added `EVFILT_EMPTY`, `EVFILT_PROCDESC`, and `EVFILT_SENDFILE` on FreeBSD. + ([#825](https://github.com/nix-rust/nix/pull/825)) +- Exposed `termios::cfmakesane` on FreeBSD. + ([#825](https://github.com/nix-rust/nix/pull/825)) +- Exposed `MSG_CMSG_CLOEXEC` on *BSD. + ([#825](https://github.com/nix-rust/nix/pull/825)) +- Added `fchmod`, `fchmodat`. + ([#857](https://github.com/nix-rust/nix/pull/857)) +- Added `request_code_write_int!` on FreeBSD/DragonFlyBSD + ([#833](https://github.com/nix-rust/nix/pull/833)) + +### Changed +- `Display` and `Debug` for `SysControlAddr` now includes all fields. + ([#837](https://github.com/nix-rust/nix/pull/837)) +- `ioctl!` has been replaced with a family of `ioctl_*!` macros. + ([#833](https://github.com/nix-rust/nix/pull/833)) +- `io!`, `ior!`, `iow!`, and `iorw!` has been renamed to `request_code_none!`, `request_code_read!`, + `request_code_write!`, and `request_code_readwrite!` respectively. These have also now been exposed + in the documentation. + ([#833](https://github.com/nix-rust/nix/pull/833)) +- Enabled more `ptrace::Request` definitions for uncommon Linux platforms + ([#892](https://github.com/nix-rust/nix/pull/892)) +- Emulation of `FD_CLOEXEC` and `O_NONBLOCK` was removed from `socket()`, `accept4()`, and + `socketpair()`. + ([#907](https://github.com/nix-rust/nix/pull/907)) + +### Fixed +- Fixed possible panics when using `SigAction::flags` on Linux + ([#869](https://github.com/nix-rust/nix/pull/869)) +- Properly exposed 460800 and 921600 baud rates on NetBSD + ([#837](https://github.com/nix-rust/nix/pull/837)) +- Fixed `ioctl_write_int!` on FreeBSD/DragonFlyBSD + ([#833](https://github.com/nix-rust/nix/pull/833)) +- `ioctl_write_int!` now properly supports passing a `c_ulong` as the parameter on Linux non-musl targets + ([#833](https://github.com/nix-rust/nix/pull/833)) + +### Removed +- Removed explicit support for the `bytes` crate from the `sys::aio` module. + See `sys::aio::AioCb::from_boxed_slice` examples for alternatives. + ([#872](https://github.com/nix-rust/nix/pull/872)) +- Removed `sys::aio::lio_listio`. Use `sys::aio::LioCb::listio` instead. + ([#872](https://github.com/nix-rust/nix/pull/872)) +- Removed emulated `accept4()` from macos, ios, and netbsd targets + ([#907](https://github.com/nix-rust/nix/pull/907)) +- Removed `IFF_NOTRAILERS` on OpenBSD, as it has been removed in OpenBSD 6.3 + ([#893](https://github.com/nix-rust/nix/pull/893)) + +## [0.10.0] 2018-01-26 + +### Added +- Added specialized wrapper: `sys::ptrace::step` + ([#852](https://github.com/nix-rust/nix/pull/852)) +- Added `AioCb::from_ptr` and `AioCb::from_mut_ptr` + ([#820](https://github.com/nix-rust/nix/pull/820)) +- Added specialized wrappers: `sys::ptrace::{traceme, syscall, cont, attach}`. Using the matching routines + with `sys::ptrace::ptrace` is now deprecated. +- Added `nix::poll` module for all platforms + ([#672](https://github.com/nix-rust/nix/pull/672)) +- Added `nix::ppoll` function for FreeBSD and DragonFly + ([#672](https://github.com/nix-rust/nix/pull/672)) +- Added protocol families in `AddressFamily` enum. + ([#647](https://github.com/nix-rust/nix/pull/647)) +- Added the `pid()` method to `WaitStatus` for extracting the PID. + ([#722](https://github.com/nix-rust/nix/pull/722)) +- Added `nix::unistd:fexecve`. + ([#727](https://github.com/nix-rust/nix/pull/727)) +- Expose `uname()` on all platforms. + ([#739](https://github.com/nix-rust/nix/pull/739)) +- Expose `signalfd` module on Android as well. + ([#739](https://github.com/nix-rust/nix/pull/739)) +- Added `nix::sys::ptrace::detach`. + ([#749](https://github.com/nix-rust/nix/pull/749)) +- Added timestamp socket control message variant: + `nix::sys::socket::ControlMessage::ScmTimestamp` + ([#663](https://github.com/nix-rust/nix/pull/663)) +- Added socket option variant that enables the timestamp socket + control message: `nix::sys::socket::sockopt::ReceiveTimestamp` + ([#663](https://github.com/nix-rust/nix/pull/663)) +- Added more accessor methods for `AioCb` + ([#773](https://github.com/nix-rust/nix/pull/773)) +- Add `nix::sys::fallocate` + ([#768](https:://github.com/nix-rust/nix/pull/768)) +- Added `nix::unistd::mkfifo`. + ([#602](https://github.com/nix-rust/nix/pull/774)) +- Added `ptrace::Options::PTRACE_O_EXITKILL` on Linux and Android. + ([#771](https://github.com/nix-rust/nix/pull/771)) +- Added `nix::sys::uio::{process_vm_readv, process_vm_writev}` on Linux + ([#568](https://github.com/nix-rust/nix/pull/568)) +- Added `nix::unistd::{getgroups, setgroups, getgrouplist, initgroups}`. ([#733](https://github.com/nix-rust/nix/pull/733)) +- Added `nix::sys::socket::UnixAddr::as_abstract` on Linux and Android. + ([#785](https://github.com/nix-rust/nix/pull/785)) +- Added `nix::unistd::execveat` on Linux and Android. + ([#800](https://github.com/nix-rust/nix/pull/800)) +- Added the `from_raw()` method to `WaitStatus` for converting raw status values + to `WaitStatus` independent of syscalls. + ([#741](https://github.com/nix-rust/nix/pull/741)) +- Added more standard trait implementations for various types. + ([#814](https://github.com/nix-rust/nix/pull/814)) +- Added `sigprocmask` to the signal module. + ([#826](https://github.com/nix-rust/nix/pull/826)) +- Added `nix::sys::socket::LinkAddr` on Linux and all bsdlike system. + ([#813](https://github.com/nix-rust/nix/pull/813)) +- Add socket options for `IP_TRANSPARENT` / `BIND_ANY`. + ([#835](https://github.com/nix-rust/nix/pull/835)) + +### Changed +- Exposed the `mqueue` module for all supported operating systems. + ([#834](https://github.com/nix-rust/nix/pull/834)) +- Use native `pipe2` on all BSD targets. Users should notice no difference. + ([#777](https://github.com/nix-rust/nix/pull/777)) +- Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692)) +- Marked `sys::ptrace::ptrace` as `unsafe`. +- Changed function signature of `socket()` and `socketpair()`. The `protocol` argument + has changed type from `c_int` to `SockProtocol`. + It accepts a `None` value for default protocol that was specified with zero using `c_int`. + ([#647](https://github.com/nix-rust/nix/pull/647)) +- Made `select` easier to use, adding the ability to automatically calculate the `nfds` parameter using the new + `FdSet::highest` ([#701](https://github.com/nix-rust/nix/pull/701)) +- Exposed `unistd::setresuid` and `unistd::setresgid` on FreeBSD and OpenBSD + ([#721](https://github.com/nix-rust/nix/pull/721)) +- Refactored the `statvfs` module removing extraneous API functions and the + `statvfs::vfs` module. Additionally `(f)statvfs()` now return the struct + directly. And the returned `Statvfs` struct now exposes its data through + accessor methods. ([#729](https://github.com/nix-rust/nix/pull/729)) +- The `addr` argument to `madvise` and `msync` is now `*mut` to better match the + libc API. ([#731](https://github.com/nix-rust/nix/pull/731)) +- `shm_open` and `shm_unlink` are no longer exposed on Android targets, where + they are not officially supported. ([#731](https://github.com/nix-rust/nix/pull/731)) +- `MapFlags`, `MmapAdvise`, and `MsFlags` expose some more variants and only + officially-supported variants are provided for each target. + ([#731](https://github.com/nix-rust/nix/pull/731)) +- Marked `pty::ptsname` function as `unsafe` + ([#744](https://github.com/nix-rust/nix/pull/744)) +- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly. + ([#749](https://github.com/nix-rust/nix/pull/749)) +- `AioCb::Drop` will now panic if the `AioCb` is still in-progress ([#715](https://github.com/nix-rust/nix/pull/715)) +- Restricted `nix::sys::socket::UnixAddr::new_abstract` to Linux and Android only. + ([#785](https://github.com/nix-rust/nix/pull/785)) +- The `ucred` struct has been removed in favor of a `UserCredentials` struct that + contains only getters for its fields. + ([#814](https://github.com/nix-rust/nix/pull/814)) +- Both `ip_mreq` and `ipv6_mreq` have been replaced with `IpMembershipRequest` and + `Ipv6MembershipRequest`. + ([#814](https://github.com/nix-rust/nix/pull/814)) +- Removed return type from `pause`. + ([#829](https://github.com/nix-rust/nix/pull/829)) +- Changed the termios APIs to allow for using a `u32` instead of the `BaudRate` + enum on BSD platforms to support arbitrary baud rates. See the module docs for + `nix::sys::termios` for more details. + ([#843](https://github.com/nix-rust/nix/pull/843)) + +### Fixed +- Fix compilation and tests for OpenBSD targets + ([#688](https://github.com/nix-rust/nix/pull/688)) +- Fixed error handling in `AioCb::fsync`, `AioCb::read`, and `AioCb::write`. + It is no longer an error to drop an `AioCb` that failed to enqueue in the OS. + ([#715](https://github.com/nix-rust/nix/pull/715)) +- Fix potential memory corruption on non-Linux platforms when using + `sendmsg`/`recvmsg`, caused by mismatched `msghdr` definition. + ([#648](https://github.com/nix-rust/nix/pull/648)) + +### Removed +- `AioCb::from_boxed_slice` has been removed. It was never actually safe. Use + `from_bytes` or `from_bytes_mut` instead. + ([#820](https://github.com/nix-rust/nix/pull/820)) +- The syscall module has been removed. This only exposed enough functionality for + `memfd_create()` and `pivot_root()`, which are still exposed as separate functions. + ([#747](https://github.com/nix-rust/nix/pull/747)) +- The `Errno` variants are no longer reexported from the `errno` module. `Errno` itself is no longer reexported from the + crate root and instead must be accessed using the `errno` module. ([#696](https://github.com/nix-rust/nix/pull/696)) +- Removed `MS_VERBOSE`, `MS_NOSEC`, and `MS_BORN` from `MsFlags`. These + are internal kernel flags and should never have been exposed. + ([#814](https://github.com/nix-rust/nix/pull/814)) + + +## [0.9.0] 2017-07-23 + +### Added +- Added `sysconf`, `pathconf`, and `fpathconf` + ([#630](https://github.com/nix-rust/nix/pull/630) +- Added `sys::signal::SigAction::{ flags, mask, handler}` + ([#611](https://github.com/nix-rust/nix/pull/609) +- Added `nix::sys::pthread::pthread_self` + ([#591](https://github.com/nix-rust/nix/pull/591) +- Added `AioCb::from_boxed_slice` + ([#582](https://github.com/nix-rust/nix/pull/582) +- Added `nix::unistd::{openat, fstatat, readlink, readlinkat}` + ([#551](https://github.com/nix-rust/nix/pull/551)) +- Added `nix::pty::{grantpt, posix_openpt, ptsname/ptsname_r, unlockpt}` + ([#556](https://github.com/nix-rust/nix/pull/556) +- Added `nix::ptr::openpty` + ([#456](https://github.com/nix-rust/nix/pull/456)) +- Added `nix::ptrace::{ptrace_get_data, ptrace_getsiginfo, ptrace_setsiginfo + and nix::Error::UnsupportedOperation}` + ([#614](https://github.com/nix-rust/nix/pull/614)) +- Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527)) +- Added "bad none", "bad write_ptr", "bad write_int", and "bad readwrite" variants to the `ioctl!` + macro. ([#670](https://github.com/nix-rust/nix/pull/670)) +- On Linux and Android, added support for receiving `PTRACE_O_TRACESYSGOOD` + events from `wait` and `waitpid` using `WaitStatus::PtraceSyscall` + ([#566](https://github.com/nix-rust/nix/pull/566)). + +### Changed +- The `ioctl!` macro and its variants now allow the generated functions to have + doccomments. ([#661](https://github.com/nix-rust/nix/pull/661)) +- Changed `ioctl!(write ...)` into `ioctl!(write_ptr ...)` and `ioctl!(write_int ..)` variants + to more clearly separate those use cases. ([#670](https://github.com/nix-rust/nix/pull/670)) +- Marked `sys::mman::{ mmap, munmap, madvise, munlock, msync }` as unsafe. + ([#559](https://github.com/nix-rust/nix/pull/559)) +- Minimum supported Rust version is now 1.13. +- Removed `revents` argument from `PollFd::new()` as it's an output argument and + will be overwritten regardless of value. + ([#542](https://github.com/nix-rust/nix/pull/542)) +- Changed type signature of `sys::select::FdSet::contains` to make `self` + immutable ([#564](https://github.com/nix-rust/nix/pull/564)) +- Introduced wrapper types for `gid_t`, `pid_t`, and `uid_t` as `Gid`, `Pid`, and `Uid` + respectively. Various functions have been changed to use these new types as + arguments. ([#629](https://github.com/nix-rust/nix/pull/629)) +- Fixed compilation on all Android and iOS targets ([#527](https://github.com/nix-rust/nix/pull/527)) + and promoted them to Tier 2 support. +- `nix::sys::statfs::{statfs,fstatfs}` uses statfs definition from `libc::statfs` instead of own linux specific type `nix::sys::Statfs`. + Also file system type constants like `nix::sys::statfs::ADFS_SUPER_MAGIC` were removed in favor of the libc equivalent. + ([#561](https://github.com/nix-rust/nix/pull/561)) +- Revised the termios API including additional tests and documentation and exposed it on iOS. ([#527](https://github.com/nix-rust/nix/pull/527)) +- `eventfd`, `signalfd`, and `pwritev`/`preadv` functionality is now included by default for all + supported platforms. ([#681](https://github.com/nix-rust/nix/pull/561)) +- The `ioctl!` macro's plain variants has been replaced with "bad read" to be consistent with + other variants. The generated functions also have more strict types for their arguments. The + "*_buf" variants also now calculate total array size and take slice references for improved type + safety. The documentation has also been dramatically improved. + ([#670](https://github.com/nix-rust/nix/pull/670)) + +### Removed +- Removed `io::Error` from `nix::Error` and the conversion from `nix::Error` to `Errno` + ([#614](https://github.com/nix-rust/nix/pull/614)) +- All feature flags have been removed in favor of conditional compilation on supported platforms. + `execvpe` is no longer supported, but this was already broken and will be added back in the next + release. ([#681](https://github.com/nix-rust/nix/pull/561)) +- Removed `ioc_*` functions and many helper constants and macros within the `ioctl` module. These + should always have been private and only the `ioctl!` should be used in public code. + ([#670](https://github.com/nix-rust/nix/pull/670)) + +### Fixed +- Fixed multiple issues compiling under different archetectures and OSes. + Now compiles on Linux/MIPS ([#538](https://github.com/nix-rust/nix/pull/538)), + `Linux/PPC` ([#553](https://github.com/nix-rust/nix/pull/553)), + `MacOS/x86_64,i686` ([#553](https://github.com/nix-rust/nix/pull/553)), + `NetBSD/x64_64` ([#538](https://github.com/nix-rust/nix/pull/538)), + `FreeBSD/x86_64,i686` ([#536](https://github.com/nix-rust/nix/pull/536)), and + `Android` ([#631](https://github.com/nix-rust/nix/pull/631)). +- `bind` and `errno_location` now work correctly on `Android` + ([#631](https://github.com/nix-rust/nix/pull/631)) +- Added `nix::ptrace` on all Linux-kernel-based platforms + [#624](https://github.com/nix-rust/nix/pull/624). Previously it was + only available on x86, x86-64, and ARM, and also not on Android. +- Fixed `sys::socket::sendmsg` with zero entry `cmsgs` parameter. + ([#623](https://github.com/nix-rust/nix/pull/623)) +- Multiple constants related to the termios API have now been properly defined for + all supported platforms. ([#527](https://github.com/nix-rust/nix/pull/527)) +- `ioctl!` macro now supports working with non-int datatypes and properly supports all platforms. + ([#670](https://github.com/nix-rust/nix/pull/670)) + +## [0.8.1] 2017-04-16 + +### Fixed +- Fixed build on FreeBSD. (Cherry-picked + [a859ee3c](https://github.com/nix-rust/nix/commit/a859ee3c9396dfdb118fcc2c8ecc697e2d303467)) + +## [0.8.0] 2017-03-02 + +### Added +- Added `::nix::sys::termios::BaudRate` enum to provide portable baudrate + values. ([#518](https://github.com/nix-rust/nix/pull/518)) +- Added a new `WaitStatus::PtraceEvent` to support ptrace events on Linux + and Android ([#438](https://github.com/nix-rust/nix/pull/438)) +- Added support for POSIX AIO + ([#483](https://github.com/nix-rust/nix/pull/483)) + ([#506](https://github.com/nix-rust/nix/pull/506)) +- Added support for XNU system control sockets + ([#478](https://github.com/nix-rust/nix/pull/478)) +- Added support for `ioctl` calls on BSD platforms + ([#478](https://github.com/nix-rust/nix/pull/478)) +- Added struct `TimeSpec` + ([#475](https://github.com/nix-rust/nix/pull/475)) + ([#483](https://github.com/nix-rust/nix/pull/483)) +- Added complete definitions for all kqueue-related constants on all supported + OSes + ([#415](https://github.com/nix-rust/nix/pull/415)) +- Added function `epoll_create1` and bitflags `EpollCreateFlags` in + `::nix::sys::epoll` in order to support `::libc::epoll_create1`. + ([#410](https://github.com/nix-rust/nix/pull/410)) +- Added `setresuid` and `setresgid` for Linux in `::nix::unistd` + ([#448](https://github.com/nix-rust/nix/pull/448)) +- Added `getpgid` in `::nix::unistd` + ([#433](https://github.com/nix-rust/nix/pull/433)) +- Added `tcgetpgrp` and `tcsetpgrp` in `::nix::unistd` + ([#451](https://github.com/nix-rust/nix/pull/451)) +- Added `CLONE_NEWCGROUP` in `::nix::sched` + ([#457](https://github.com/nix-rust/nix/pull/457)) +- Added `getpgrp` in `::nix::unistd` + ([#491](https://github.com/nix-rust/nix/pull/491)) +- Added `fchdir` in `::nix::unistd` + ([#497](https://github.com/nix-rust/nix/pull/497)) +- Added `major` and `minor` in `::nix::sys::stat` for decomposing `dev_t` + ([#508](https://github.com/nix-rust/nix/pull/508)) +- Fixed the style of many bitflags and use `libc` in more places. + ([#503](https://github.com/nix-rust/nix/pull/503)) +- Added `ppoll` in `::nix::poll` + ([#520](https://github.com/nix-rust/nix/pull/520)) +- Added support for getting and setting pipe size with fcntl(2) on Linux + ([#540](https://github.com/nix-rust/nix/pull/540)) + +### Changed +- `::nix::sys::termios::{cfgetispeed, cfsetispeed, cfgetospeed, cfsetospeed}` + switched to use `BaudRate` enum from `speed_t`. + ([#518](https://github.com/nix-rust/nix/pull/518)) +- `epoll_ctl` now could accept None as argument `event` + when op is `EpollOp::EpollCtlDel`. + ([#480](https://github.com/nix-rust/nix/pull/480)) +- Removed the `bad` keyword from the `ioctl!` macro + ([#478](https://github.com/nix-rust/nix/pull/478)) +- Changed `TimeVal` into an opaque Newtype + ([#475](https://github.com/nix-rust/nix/pull/475)) +- `kill`'s signature, defined in `::nix::sys::signal`, changed, so that the + signal parameter has type `T: Into>`. `None` as an argument + for that parameter will result in a 0 passed to libc's `kill`, while a + `Some`-argument will result in the previous behavior for the contained + `Signal`. + ([#445](https://github.com/nix-rust/nix/pull/445)) +- The minimum supported version of rustc is now 1.7.0. + ([#444](https://github.com/nix-rust/nix/pull/444)) +- Changed `KEvent` to an opaque structure that may only be modified by its + constructor and the `ev_set` method. + ([#415](https://github.com/nix-rust/nix/pull/415)) + ([#442](https://github.com/nix-rust/nix/pull/442)) + ([#463](https://github.com/nix-rust/nix/pull/463)) +- `pipe2` now calls `libc::pipe2` where available. Previously it was emulated + using `pipe`, which meant that setting `O_CLOEXEC` was not atomic. + ([#427](https://github.com/nix-rust/nix/pull/427)) +- Renamed `EpollEventKind` to `EpollFlags` in `::nix::sys::epoll` in order for + it to conform with our conventions. + ([#410](https://github.com/nix-rust/nix/pull/410)) +- `EpollEvent` in `::nix::sys::epoll` is now an opaque proxy for + `::libc::epoll_event`. The formerly public field `events` is now be read-only + accessible with the new method `events()` of `EpollEvent`. Instances of + `EpollEvent` can be constructed using the new method `new()` of EpollEvent. + ([#410](https://github.com/nix-rust/nix/pull/410)) +- `SigFlags` in `::nix::sys::signal` has be renamed to `SigmaskHow` and its type + has changed from `bitflags` to `enum` in order to conform to our conventions. + ([#460](https://github.com/nix-rust/nix/pull/460)) +- `sethostname` now takes a `&str` instead of a `&[u8]` as this provides an API + that makes more sense in normal, correct usage of the API. +- `gethostname` previously did not expose the actual length of the hostname + written from the underlying system call at all. This has been updated to + return a `&CStr` within the provided buffer that is always properly + NUL-terminated (this is not guaranteed by the call with all platforms/libc + implementations). +- Exposed all fcntl(2) operations at the module level, so they can be + imported direclty instead of via `FcntlArg` enum. + ([#541](https://github.com/nix-rust/nix/pull/541)) + +### Fixed +- Fixed multiple issues with Unix domain sockets on non-Linux OSes + ([#474](https://github.com/nix-rust/nix/pull/415)) +- Fixed using kqueue with `EVFILT_USER` on FreeBSD + ([#415](https://github.com/nix-rust/nix/pull/415)) +- Fixed the build on FreeBSD, and fixed the getsockopt, sendmsg, and recvmsg + functions on that same OS. + ([#397](https://github.com/nix-rust/nix/pull/397)) +- Fixed an off-by-one bug in `UnixAddr::new_abstract` in `::nix::sys::socket`. + ([#429](https://github.com/nix-rust/nix/pull/429)) +- Fixed clone passing a potentially unaligned stack. + ([#490](https://github.com/nix-rust/nix/pull/490)) +- Fixed mkdev not creating a `dev_t` the same way as libc. + ([#508](https://github.com/nix-rust/nix/pull/508)) + +## [0.7.0] 2016-09-09 + +### Added +- Added `lseek` and `lseek64` in `::nix::unistd` + ([#377](https://github.com/nix-rust/nix/pull/377)) +- Added `mkdir` and `getcwd` in `::nix::unistd` + ([#416](https://github.com/nix-rust/nix/pull/416)) +- Added accessors `sigmask_mut` and `sigmask` to `UContext` in + `::nix::ucontext`. + ([#370](https://github.com/nix-rust/nix/pull/370)) +- Added `WUNTRACED` to `WaitPidFlag` in `::nix::sys::wait` for non-_linux_ + targets. + ([#379](https://github.com/nix-rust/nix/pull/379)) +- Added new module `::nix::sys::reboot` with enumeration `RebootMode` and + functions `reboot` and `set_cad_enabled`. Currently for _linux_ only. + ([#386](https://github.com/nix-rust/nix/pull/386)) +- `FdSet` in `::nix::sys::select` now also implements `Clone`. + ([#405](https://github.com/nix-rust/nix/pull/405)) +- Added `F_FULLFSYNC` to `FcntlArg` in `::nix::fcntl` for _apple_ targets. + ([#407](https://github.com/nix-rust/nix/pull/407)) +- Added `CpuSet::unset` in `::nix::sched`. + ([#402](https://github.com/nix-rust/nix/pull/402)) +- Added constructor method `new()` to `PollFd` in `::nix::poll`, in order to + allow creation of objects, after removing public access to members. + ([#399](https://github.com/nix-rust/nix/pull/399)) +- Added method `revents()` to `PollFd` in `::nix::poll`, in order to provide + read access to formerly public member `revents`. + ([#399](https://github.com/nix-rust/nix/pull/399)) +- Added `MSG_CMSG_CLOEXEC` to `MsgFlags` in `::nix::sys::socket` for _linux_ only. + ([#422](https://github.com/nix-rust/nix/pull/422)) + +### Changed +- Replaced the reexported integer constants for signals by the enumeration + `Signal` in `::nix::sys::signal`. + ([#362](https://github.com/nix-rust/nix/pull/362)) +- Renamed `EventFdFlag` to `EfdFlags` in `::nix::sys::eventfd`. + ([#383](https://github.com/nix-rust/nix/pull/383)) +- Changed the result types of `CpuSet::is_set` and `CpuSet::set` in + `::nix::sched` to `Result` and `Result<()>`, respectively. They now + return `EINVAL`, if an invalid argument for the `field` parameter is passed. + ([#402](https://github.com/nix-rust/nix/pull/402)) +- `MqAttr` in `::nix::mqueue` is now an opaque proxy for `::libc::mq_attr`, + which has the same structure as the old `MqAttr`. The field `mq_flags` of + `::libc::mq_attr` is readable using the new method `flags()` of `MqAttr`. + `MqAttr` also no longer implements `Debug`. + ([#392](https://github.com/nix-rust/nix/pull/392)) +- The parameter `msq_prio` of `mq_receive` with type `u32` in `::nix::mqueue` + was replaced by a parameter named `msg_prio` with type `&mut u32`, so that + the message priority can be obtained by the caller. + ([#392](https://github.com/nix-rust/nix/pull/392)) +- The type alias `MQd` in `::nix::queue` was replaced by the type alias + `libc::mqd_t`, both of which are aliases for the same type. + ([#392](https://github.com/nix-rust/nix/pull/392)) + +### Removed +- Type alias `SigNum` from `::nix::sys::signal`. + ([#362](https://github.com/nix-rust/nix/pull/362)) +- Type alias `CpuMask` from `::nix::shed`. + ([#402](https://github.com/nix-rust/nix/pull/402)) +- Removed public fields from `PollFd` in `::nix::poll`. (See also added method + `revents()`. + ([#399](https://github.com/nix-rust/nix/pull/399)) + +### Fixed +- Fixed the build problem for NetBSD (Note, that we currently do not support + it, so it might already be broken again). + ([#389](https://github.com/nix-rust/nix/pull/389)) +- Fixed the build on FreeBSD, and fixed the getsockopt, sendmsg, and recvmsg + functions on that same OS. + ([#397](https://github.com/nix-rust/nix/pull/397)) + +## [0.6.0] 2016-06-10 + +### Added +- Added `gettid` in `::nix::unistd` for _linux_ and _android_. + ([#293](https://github.com/nix-rust/nix/pull/293)) +- Some _mips_ support in `::nix::sched` and `::nix::sys::syscall`. + ([#301](https://github.com/nix-rust/nix/pull/301)) +- Added `SIGNALFD_SIGINFO_SIZE` in `::nix::sys::signalfd`. + ([#309](https://github.com/nix-rust/nix/pull/309)) +- Added new module `::nix::ucontext` with struct `UContext`. Currently for + _linux_ only. + ([#311](https://github.com/nix-rust/nix/pull/311)) +- Added `EPOLLEXCLUSIVE` to `EpollEventKind` in `::nix::sys::epoll`. + ([#330](https://github.com/nix-rust/nix/pull/330)) +- Added `pause` to `::nix::unistd`. + ([#336](https://github.com/nix-rust/nix/pull/336)) +- Added `sleep` to `::nix::unistd`. + ([#351](https://github.com/nix-rust/nix/pull/351)) +- Added `S_IFDIR`, `S_IFLNK`, `S_IFMT` to `SFlag` in `::nix::sys::stat`. + ([#359](https://github.com/nix-rust/nix/pull/359)) +- Added `clear` and `extend` functions to `SigSet`'s implementation in + `::nix::sys::signal`. + ([#347](https://github.com/nix-rust/nix/pull/347)) +- `sockaddr_storage_to_addr` in `::nix::sys::socket` now supports `sockaddr_nl` + on _linux_ and _android_. + ([#366](https://github.com/nix-rust/nix/pull/366)) +- Added support for `SO_ORIGINAL_DST` in `::nix::sys::socket` on _linux_. + ([#367](https://github.com/nix-rust/nix/pull/367)) +- Added `SIGINFO` in `::nix::sys::signal` for the _macos_ target as well as + `SIGPWR` and `SIGSTKFLT` in `::nix::sys::signal` for non-_macos_ targets. + ([#361](https://github.com/nix-rust/nix/pull/361)) + +### Changed +- Changed the structure `IoVec` in `::nix::sys::uio`. + ([#304](https://github.com/nix-rust/nix/pull/304)) +- Replaced `CREATE_NEW_FD` by `SIGNALFD_NEW` in `::nix::sys::signalfd`. + ([#309](https://github.com/nix-rust/nix/pull/309)) +- Renamed `SaFlag` to `SaFlags` and `SigFlag` to `SigFlags` in + `::nix::sys::signal`. + ([#314](https://github.com/nix-rust/nix/pull/314)) +- Renamed `Fork` to `ForkResult` and changed its fields in `::nix::unistd`. + ([#332](https://github.com/nix-rust/nix/pull/332)) +- Added the `signal` parameter to `clone`'s signature in `::nix::sched`. + ([#344](https://github.com/nix-rust/nix/pull/344)) +- `execv`, `execve`, and `execvp` now return `Result` instead of + `Result<()>` in `::nix::unistd`. + ([#357](https://github.com/nix-rust/nix/pull/357)) + +### Fixed +- Improved the conversion from `std::net::SocketAddr` to `InetAddr` in + `::nix::sys::socket::addr`. + ([#335](https://github.com/nix-rust/nix/pull/335)) + +## [0.5.0] 2016-03-01 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0afc445 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,185 @@ +# 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 = "2018" +rust-version = "1.56" +name = "nix" +version = "0.26.2" +authors = ["The nix-rust Project Developers"] +include = [ + "src/**/*", + "test/**/*", + "LICENSE", + "README.md", + "CHANGELOG.md", +] +description = "Rust friendly bindings to *nix APIs" +readme = "README.md" +categories = ["os::unix-apis"] +license = "MIT" +repository = "https://github.com/nix-rust/nix" + +[package.metadata.docs.rs] +rustdoc-args = [ + "--cfg", + "docsrs", +] +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-linux-android", + "x86_64-apple-darwin", + "aarch64-apple-ios", + "x86_64-unknown-freebsd", + "x86_64-unknown-openbsd", + "x86_64-unknown-netbsd", + "x86_64-unknown-dragonfly", + "x86_64-fuchsia", + "x86_64-unknown-redox", + "x86_64-unknown-illumos", +] + +[[test]] +name = "test" +path = "test/test.rs" + +[[test]] +name = "test-aio-drop" +path = "test/sys/test_aio_drop.rs" + +[[test]] +name = "test-clearenv" +path = "test/test_clearenv.rs" + +[[test]] +name = "test-mount" +path = "test/test_mount.rs" +harness = false + +[[test]] +name = "test-ptymaster-drop" +path = "test/test_ptymaster_drop.rs" + +[dependencies.bitflags] +version = "1.1" + +[dependencies.cfg-if] +version = "1.0" + +[dependencies.libc] +version = "0.2.137" +features = ["extra_traits"] + +[dependencies.pin-utils] +version = "0.1.0" +optional = true + +[dependencies.static_assertions] +version = "1" + +[dev-dependencies.assert-impl] +version = "0.1" + +[dev-dependencies.lazy_static] +version = "1.4" + +[dev-dependencies.parking_lot] +version = "0.12" + +[dev-dependencies.rand] +version = "0.8" + +[dev-dependencies.semver] +version = "1.0.7" + +[dev-dependencies.tempfile] +version = "3.3.0" + +[features] +acct = [] +aio = ["pin-utils"] +default = [ + "acct", + "aio", + "dir", + "env", + "event", + "feature", + "fs", + "hostname", + "inotify", + "ioctl", + "kmod", + "mman", + "mount", + "mqueue", + "net", + "personality", + "poll", + "process", + "pthread", + "ptrace", + "quota", + "reboot", + "resource", + "sched", + "signal", + "socket", + "term", + "time", + "ucontext", + "uio", + "user", + "zerocopy", +] +dir = ["fs"] +env = [] +event = [] +feature = [] +fs = [] +hostname = [] +inotify = [] +ioctl = [] +kmod = [] +mman = [] +mount = ["uio"] +mqueue = ["fs"] +net = ["socket"] +personality = [] +poll = [] +process = [] +pthread = [] +ptrace = ["process"] +quota = [] +reboot = [] +resource = [] +sched = ["process"] +signal = ["process"] +socket = ["memoffset"] +term = [] +time = [] +ucontext = ["signal"] +uio = [] +user = ["feature"] +zerocopy = [ + "fs", + "uio", +] + +[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dev-dependencies.caps] +version = "0.5.3" + +[target."cfg(not(target_os = \"redox\"))".dependencies.memoffset] +version = "0.7" +optional = true + +[target."cfg(target_os = \"freebsd\")".dev-dependencies.sysctl] +version = "0.4" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..8b1d873 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,114 @@ +[package] +name = "nix" +description = "Rust friendly bindings to *nix APIs" +edition = "2018" +version = "0.26.2" +rust-version = "1.56" +authors = ["The nix-rust Project Developers"] +repository = "https://github.com/nix-rust/nix" +license = "MIT" +categories = ["os::unix-apis"] +include = ["src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-linux-android", + "x86_64-apple-darwin", + "aarch64-apple-ios", + "x86_64-unknown-freebsd", + "x86_64-unknown-openbsd", + "x86_64-unknown-netbsd", + "x86_64-unknown-dragonfly", + "x86_64-fuchsia", + "x86_64-unknown-redox", + "x86_64-unknown-illumos" +] + +[dependencies] +libc = { version = "0.2.137", features = [ "extra_traits" ] } +bitflags = "1.1" +cfg-if = "1.0" +pin-utils = { version = "0.1.0", optional = true } +static_assertions = "1" + +[target.'cfg(not(target_os = "redox"))'.dependencies] +memoffset = { version = "0.7", optional = true } + +[features] +default = [ + "acct", "aio", "dir", "env", "event", "feature", "fs", + "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", + "net", "personality", "poll", "process", "pthread", "ptrace", "quota", + "reboot", "resource", "sched", "signal", "socket", "term", "time", + "ucontext", "uio", "user", "zerocopy", +] + +acct = [] +aio = ["pin-utils"] +dir = ["fs"] +env = [] +event = [] +feature = [] +fs = [] +hostname = [] +inotify = [] +ioctl = [] +kmod = [] +mman = [] +mount = ["uio"] +mqueue = ["fs"] +net = ["socket"] +personality = [] +poll = [] +pthread = [] +ptrace = ["process"] +quota = [] +process = [] +reboot = [] +resource = [] +sched = ["process"] +signal = ["process"] +socket = ["memoffset"] +term = [] +time = [] +ucontext = ["signal"] +uio = [] +user = ["feature"] +zerocopy = ["fs", "uio"] + +[dev-dependencies] +assert-impl = "0.1" +lazy_static = "1.4" +parking_lot = "0.12" +rand = "0.8" +tempfile = "3.3.0" +semver = "1.0.7" + +[target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies] +caps = "0.5.3" + +[target.'cfg(target_os = "freebsd")'.dev-dependencies] +sysctl = "0.4" + +[[test]] +name = "test" +path = "test/test.rs" + +[[test]] +name = "test-aio-drop" +path = "test/sys/test_aio_drop.rs" + +[[test]] +name = "test-clearenv" +path = "test/test_clearenv.rs" + +[[test]] +name = "test-mount" +path = "test/test_mount.rs" +harness = false + +[[test]] +name = "test-ptymaster-drop" +path = "test/test_ptymaster_drop.rs" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aff9096 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Carl Lerche + nix-rust Authors + +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/README.md b/README.md new file mode 100644 index 0000000..2c42b90 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Rust bindings to *nix APIs + +[![Cirrus Build Status](https://api.cirrus-ci.com/github/nix-rust/nix.svg)](https://cirrus-ci.com/github/nix-rust/nix) +[![crates.io](https://img.shields.io/crates/v/nix.svg)](https://crates.io/crates/nix) + +[Documentation (Releases)](https://docs.rs/nix/) + +Nix seeks to provide friendly bindings to various *nix platform APIs (Linux, Darwin, +...). The goal is to not provide a 100% unified interface, but to unify +what can be while still providing platform specific APIs. + +For many system APIs, Nix provides a safe alternative to the unsafe APIs +exposed by the [libc crate](https://github.com/rust-lang/libc). This is done by +wrapping the libc functionality with types/abstractions that enforce legal/safe +usage. + + +As an example of what Nix provides, examine the differences between what is +exposed by libc and nix for the +[gethostname](https://man7.org/linux/man-pages/man2/gethostname.2.html) system +call: + +```rust,ignore +// libc api (unsafe, requires handling return code/errno) +pub unsafe extern fn gethostname(name: *mut c_char, len: size_t) -> c_int; + +// nix api (returns a nix::Result) +pub fn gethostname() -> Result; +``` + +## Supported Platforms + +nix target support consists of two tiers. While nix attempts to support all +platforms supported by [libc](https://github.com/rust-lang/libc), only some +platforms are actively supported due to either technical or manpower +limitations. Support for platforms is split into three tiers: + + * Tier 1 - Builds and tests for this target are run in CI. Failures of either + block the inclusion of new code. + * Tier 2 - Builds for this target are run in CI. Failures during the build + blocks the inclusion of new code. Tests may be run, but failures + in tests don't block the inclusion of new code. + * Tier 3 - Builds for this target are run in CI. Failures during the build + *do not* block the inclusion of new code. Testing may be run, but + failures in tests don't block the inclusion of new code. + +The following targets are supported by `nix`: + +Tier 1: + * aarch64-apple-darwin + * aarch64-unknown-linux-gnu + * arm-unknown-linux-gnueabi + * armv7-unknown-linux-gnueabihf + * i686-unknown-freebsd + * i686-unknown-linux-gnu + * i686-unknown-linux-musl + * mips-unknown-linux-gnu + * mips64-unknown-linux-gnuabi64 + * mips64el-unknown-linux-gnuabi64 + * mipsel-unknown-linux-gnu + * powerpc64le-unknown-linux-gnu + * x86_64-unknown-freebsd + * x86_64-unknown-linux-gnu + * x86_64-unknown-linux-musl + +Tier 2: + * aarch64-apple-ios + * aarch64-linux-android + * arm-linux-androideabi + * arm-unknown-linux-musleabi + * armv7-linux-androideabi + * i686-linux-android + * powerpc-unknown-linux-gnu + * s390x-unknown-linux-gnu + * x86_64-apple-ios + * x86_64-linux-android + * x86_64-apple-darwin + * x86_64-unknown-illumos + * x86_64-unknown-netbsd + +Tier 3: + * armv7-unknown-linux-uclibceabihf + * x86_64-fuchsia + * x86_64-unknown-dragonfly + * x86_64-unknown-haiku + * x86_64-unknown-linux-gnux32 + * x86_64-unknown-openbsd + * x86_64-unknown-redox + +## Minimum Supported Rust Version (MSRV) + +nix is supported on Rust 1.56.1 and higher. Its MSRV will not be +changed in the future without bumping the major or minor version. + +## Contributing + +Contributions are very welcome. Please See [CONTRIBUTING](CONTRIBUTING.md) for +additional details. + +Feel free to join us in [the nix-rust/nix](https://gitter.im/nix-rust/nix) channel on Gitter to +discuss `nix` development. + +## License + +Nix is licensed under the MIT license. See [LICENSE](LICENSE) for more details. diff --git a/src/dir.rs b/src/dir.rs new file mode 100644 index 0000000..5ce5036 --- /dev/null +++ b/src/dir.rs @@ -0,0 +1,276 @@ +//! List directory contents + +use crate::errno::Errno; +use crate::fcntl::{self, OFlag}; +use crate::sys; +use crate::{Error, NixPath, Result}; +use cfg_if::cfg_if; +use std::ffi; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::ptr; + +#[cfg(target_os = "linux")] +use libc::{dirent64 as dirent, readdir64_r as readdir_r}; + +#[cfg(not(target_os = "linux"))] +use libc::{dirent, readdir_r}; + +/// An open directory. +/// +/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: +/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing +/// if the path represents a file or directory). +/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. +/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` +/// after the `Dir` is dropped. +/// * can be iterated through multiple times without closing and reopening the file +/// descriptor. Each iteration rewinds when finished. +/// * returns entries for `.` (current directory) and `..` (parent directory). +/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc +/// does). +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct Dir(ptr::NonNull); + +impl Dir { + /// Opens the given path as with `fcntl::open`. + pub fn open( + path: &P, + oflag: OFlag, + mode: sys::stat::Mode, + ) -> Result { + let fd = fcntl::open(path, oflag, mode)?; + Dir::from_fd(fd) + } + + /// Opens the given path as with `fcntl::openat`. + pub fn openat( + dirfd: RawFd, + path: &P, + oflag: OFlag, + mode: sys::stat::Mode, + ) -> Result { + let fd = fcntl::openat(dirfd, path, oflag, mode)?; + Dir::from_fd(fd) + } + + /// Converts from a descriptor-based object, closing the descriptor on success or failure. + #[inline] + pub fn from(fd: F) -> Result { + Dir::from_fd(fd.into_raw_fd()) + } + + /// Converts from a file descriptor, closing it on success or failure. + #[doc(alias("fdopendir"))] + pub fn from_fd(fd: RawFd) -> Result { + let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else( + || { + let e = Error::last(); + unsafe { libc::close(fd) }; + e + }, + )?; + Ok(Dir(d)) + } + + /// Returns an iterator of `Result` which rewinds when finished. + pub fn iter(&mut self) -> Iter { + Iter(self) + } +} + +// `Dir` is not `Sync`. With the current implementation, it could be, but according to +// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, +// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to +// call `readdir` simultaneously from multiple threads. +// +// `Dir` is safe to pass from one thread to another, as it's not reference-counted. +unsafe impl Send for Dir {} + +impl AsRawFd for Dir { + fn as_raw_fd(&self) -> RawFd { + unsafe { libc::dirfd(self.0.as_ptr()) } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) }); + if !std::thread::panicking() && e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; + } +} + +fn next(dir: &mut Dir) -> Option> { + unsafe { + // Note: POSIX specifies that portable applications should dynamically allocate a + // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 + // for the NUL byte. It doesn't look like the std library does this; it just uses + // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). + // Probably fine here too then. + let mut ent = std::mem::MaybeUninit::::uninit(); + let mut result = ptr::null_mut(); + if let Err(e) = Errno::result(readdir_r( + dir.0.as_ptr(), + ent.as_mut_ptr(), + &mut result, + )) { + return Some(Err(e)); + } + if result.is_null() { + return None; + } + assert_eq!(result, ent.as_mut_ptr()); + Some(Ok(Entry(ent.assume_init()))) + } +} + +/// Return type of [`Dir::iter`]. +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct Iter<'d>(&'d mut Dir); + +impl<'d> Iterator for Iter<'d> { + type Item = Result; + + fn next(&mut self) -> Option { + next(self.0) + } +} + +impl<'d> Drop for Iter<'d> { + fn drop(&mut self) { + unsafe { libc::rewinddir((self.0).0.as_ptr()) } + } +} + +/// The return type of [Dir::into_iter] +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct OwningIter(Dir); + +impl Iterator for OwningIter { + type Item = Result; + + fn next(&mut self) -> Option { + next(&mut self.0) + } +} + +/// The file descriptor continues to be owned by the `OwningIter`, +/// so callers must not keep a `RawFd` after the `OwningIter` is dropped. +impl AsRawFd for OwningIter { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl IntoIterator for Dir { + type Item = Result; + type IntoIter = OwningIter; + + /// Creates a owning iterator, that is, one that takes ownership of the + /// `Dir`. The `Dir` cannot be used after calling this. This can be useful + /// when you have a function that both creates a `Dir` instance and returns + /// an `Iterator`. + /// + /// Example: + /// + /// ``` + /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode}; + /// use std::{iter::Iterator, string::String}; + /// + /// fn ls_upper(dirname: &str) -> impl Iterator { + /// let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap(); + /// d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase()) + /// } + /// ``` + fn into_iter(self) -> Self::IntoIter { + OwningIter(self) + } +} + +/// A directory entry, similar to `std::fs::DirEntry`. +/// +/// Note that unlike the std version, this may represent the `.` or `..` entries. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct Entry(dirent); + +/// Type of file referenced by a directory entry +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum Type { + /// FIFO (Named pipe) + Fifo, + /// Character device + CharacterDevice, + /// Directory + Directory, + /// Block device + BlockDevice, + /// Regular file + File, + /// Symbolic link + Symlink, + /// Unix-domain socket + Socket, +} + +impl Entry { + /// Returns the inode number (`d_ino`) of the underlying `dirent`. + #[allow(clippy::useless_conversion)] // Not useless on all OSes + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn ino(&self) -> u64 { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "ios", + target_os = "l4re", + target_os = "linux", + target_os = "macos", + target_os = "solaris"))] { + self.0.d_ino as u64 + } else { + u64::from(self.0.d_fileno) + } + } + } + + /// Returns the bare file name of this directory entry without any other leading path component. + pub fn file_name(&self) -> &ffi::CStr { + unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } + } + + /// Returns the type of this directory entry, if known. + /// + /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; + /// notably, some Linux filesystems don't implement this. The caller should use `stat` or + /// `fstat` if this returns `None`. + pub fn file_type(&self) -> Option { + #[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + )))] + match self.0.d_type { + libc::DT_FIFO => Some(Type::Fifo), + libc::DT_CHR => Some(Type::CharacterDevice), + libc::DT_DIR => Some(Type::Directory), + libc::DT_BLK => Some(Type::BlockDevice), + libc::DT_REG => Some(Type::File), + libc::DT_LNK => Some(Type::Symlink), + libc::DT_SOCK => Some(Type::Socket), + /* libc::DT_UNKNOWN | */ _ => None, + } + + // illumos, Solaris, and Haiku systems do not have the d_type member at all: + #[cfg(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + ))] + None + } +} diff --git a/src/env.rs b/src/env.rs new file mode 100644 index 0000000..95177a1 --- /dev/null +++ b/src/env.rs @@ -0,0 +1,64 @@ +//! Environment variables +use cfg_if::cfg_if; +use std::fmt; + +/// Indicates that [`clearenv`] failed for some unknown reason +#[derive(Clone, Copy, Debug)] +pub struct ClearEnvError; + +impl fmt::Display for ClearEnvError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "clearenv failed") + } +} + +impl std::error::Error for ClearEnvError {} + +/// Clear the environment of all name-value pairs. +/// +/// On platforms where libc provides `clearenv()`, it will be used. libc's +/// `clearenv()` is documented to return an error code but not set errno; if the +/// return value indicates a failure, this function will return +/// [`ClearEnvError`]. +/// +/// On platforms where libc does not provide `clearenv()`, a fallback +/// implementation will be used that iterates over all environment variables and +/// removes them one-by-one. +/// +/// # Safety +/// +/// This function is not threadsafe and can cause undefined behavior in +/// combination with `std::env` or other program components that access the +/// environment. See, for example, the discussion on `std::env::remove_var`; this +/// function is a case of an "inherently unsafe non-threadsafe API" dealing with +/// the environment. +/// +/// The caller must ensure no other threads access the process environment while +/// this function executes and that no raw pointers to an element of libc's +/// `environ` is currently held. The latter is not an issue if the only other +/// environment access in the program is via `std::env`, but the requirement on +/// thread safety must still be upheld. +pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> { + cfg_if! { + if #[cfg(any(target_os = "fuchsia", + target_os = "wasi", + target_env = "uclibc", + target_os = "linux", + target_os = "android", + target_os = "emscripten"))] { + let ret = libc::clearenv(); + } else { + use std::env; + for (name, _) in env::vars_os() { + env::remove_var(name); + } + let ret = 0; + } + } + + if ret == 0 { + Ok(()) + } else { + Err(ClearEnvError) + } +} diff --git a/src/errno.rs b/src/errno.rs new file mode 100644 index 0000000..d8ad28d --- /dev/null +++ b/src/errno.rs @@ -0,0 +1,3133 @@ +use crate::Result; +use cfg_if::cfg_if; +use libc::{c_int, c_void}; +use std::convert::TryFrom; +use std::{error, fmt, io}; + +pub use self::consts::*; + +cfg_if! { + if #[cfg(any(target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] { + unsafe fn errno_location() -> *mut c_int { + libc::__error() + } + } else if #[cfg(any(target_os = "android", + target_os = "netbsd", + target_os = "openbsd"))] { + unsafe fn errno_location() -> *mut c_int { + libc::__errno() + } + } else if #[cfg(any(target_os = "linux", + target_os = "redox", + target_os = "dragonfly", + target_os = "fuchsia"))] { + unsafe fn errno_location() -> *mut c_int { + libc::__errno_location() + } + } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { + unsafe fn errno_location() -> *mut c_int { + libc::___errno() + } + } else if #[cfg(any(target_os = "haiku",))] { + unsafe fn errno_location() -> *mut c_int { + libc::_errnop() + } + } +} + +/// Sets the platform-specific errno to no-error +fn clear() { + // Safe because errno is a thread-local variable + unsafe { + *errno_location() = 0; + } +} + +/// Returns the platform-specific value of errno +pub fn errno() -> i32 { + unsafe { *errno_location() } +} + +impl Errno { + pub fn last() -> Self { + last() + } + + pub fn desc(self) -> &'static str { + desc(self) + } + + pub const fn from_i32(err: i32) -> Errno { + from_i32(err) + } + + pub fn clear() { + clear() + } + + /// Returns `Ok(value)` if it does not contain the sentinel value. This + /// should not be used when `-1` is not the errno sentinel value. + #[inline] + pub fn result>(value: S) -> Result { + if value == S::sentinel() { + Err(Self::last()) + } else { + Ok(value) + } + } +} + +/// The sentinel value indicates that a function failed and more detailed +/// information about the error can be found in `errno` +pub trait ErrnoSentinel: Sized { + fn sentinel() -> Self; +} + +impl ErrnoSentinel for isize { + fn sentinel() -> Self { + -1 + } +} + +impl ErrnoSentinel for i32 { + fn sentinel() -> Self { + -1 + } +} + +impl ErrnoSentinel for i64 { + fn sentinel() -> Self { + -1 + } +} + +impl ErrnoSentinel for *mut c_void { + fn sentinel() -> Self { + -1isize as *mut c_void + } +} + +impl ErrnoSentinel for libc::sighandler_t { + fn sentinel() -> Self { + libc::SIG_ERR + } +} + +impl error::Error for Errno {} + +impl fmt::Display for Errno { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}: {}", self, self.desc()) + } +} + +impl From for io::Error { + fn from(err: Errno) -> Self { + io::Error::from_raw_os_error(err as i32) + } +} + +impl TryFrom for Errno { + type Error = io::Error; + + fn try_from(ioerror: io::Error) -> std::result::Result { + ioerror.raw_os_error().map(Errno::from_i32).ok_or(ioerror) + } +} + +fn last() -> Errno { + Errno::from_i32(errno()) +} + +fn desc(errno: Errno) -> &'static str { + use self::Errno::*; + match errno { + UnknownErrno => "Unknown errno", + EPERM => "Operation not permitted", + ENOENT => "No such file or directory", + ESRCH => "No such process", + EINTR => "Interrupted system call", + EIO => "I/O error", + ENXIO => "No such device or address", + E2BIG => "Argument list too long", + ENOEXEC => "Exec format error", + EBADF => "Bad file number", + ECHILD => "No child processes", + EAGAIN => "Try again", + ENOMEM => "Out of memory", + EACCES => "Permission denied", + EFAULT => "Bad address", + #[cfg(not(target_os = "haiku"))] + ENOTBLK => "Block device required", + EBUSY => "Device or resource busy", + EEXIST => "File exists", + EXDEV => "Cross-device link", + ENODEV => "No such device", + ENOTDIR => "Not a directory", + EISDIR => "Is a directory", + EINVAL => "Invalid argument", + ENFILE => "File table overflow", + EMFILE => "Too many open files", + ENOTTY => "Not a typewriter", + ETXTBSY => "Text file busy", + EFBIG => "File too large", + ENOSPC => "No space left on device", + ESPIPE => "Illegal seek", + EROFS => "Read-only file system", + EMLINK => "Too many links", + EPIPE => "Broken pipe", + EDOM => "Math argument out of domain of func", + ERANGE => "Math result not representable", + EDEADLK => "Resource deadlock would occur", + ENAMETOOLONG => "File name too long", + ENOLCK => "No record locks available", + ENOSYS => "Function not implemented", + ENOTEMPTY => "Directory not empty", + ELOOP => "Too many symbolic links encountered", + ENOMSG => "No message of desired type", + EIDRM => "Identifier removed", + EINPROGRESS => "Operation now in progress", + EALREADY => "Operation already in progress", + ENOTSOCK => "Socket operation on non-socket", + EDESTADDRREQ => "Destination address required", + EMSGSIZE => "Message too long", + EPROTOTYPE => "Protocol wrong type for socket", + ENOPROTOOPT => "Protocol not available", + EPROTONOSUPPORT => "Protocol not supported", + #[cfg(not(target_os = "haiku"))] + ESOCKTNOSUPPORT => "Socket type not supported", + #[cfg(not(target_os = "haiku"))] + EPFNOSUPPORT => "Protocol family not supported", + #[cfg(not(target_os = "haiku"))] + EAFNOSUPPORT => "Address family not supported by protocol", + EADDRINUSE => "Address already in use", + EADDRNOTAVAIL => "Cannot assign requested address", + ENETDOWN => "Network is down", + ENETUNREACH => "Network is unreachable", + ENETRESET => "Network dropped connection because of reset", + ECONNABORTED => "Software caused connection abort", + ECONNRESET => "Connection reset by peer", + ENOBUFS => "No buffer space available", + EISCONN => "Transport endpoint is already connected", + ENOTCONN => "Transport endpoint is not connected", + ESHUTDOWN => "Cannot send after transport endpoint shutdown", + #[cfg(not(target_os = "haiku"))] + ETOOMANYREFS => "Too many references: cannot splice", + ETIMEDOUT => "Connection timed out", + ECONNREFUSED => "Connection refused", + EHOSTDOWN => "Host is down", + EHOSTUNREACH => "No route to host", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ECHRNG => "Channel number out of range", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL2NSYNC => "Level 2 not synchronized", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL3HLT => "Level 3 halted", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL3RST => "Level 3 reset", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ELNRNG => "Link number out of range", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EUNATCH => "Protocol driver not attached", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOCSI => "No CSI structure available", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL2HLT => "Level 2 halted", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EBADE => "Invalid exchange", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EBADR => "Invalid request descriptor", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EXFULL => "Exchange full", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOANO => "No anode", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EBADRQC => "Invalid request code", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EBADSLT => "Invalid slot", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EBFONT => "Bad font file format", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOSTR => "Device not a stream", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENODATA => "No data available", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ETIME => "Timer expired", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOSR => "Out of streams resources", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENONET => "Machine is not on the network", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOPKG => "Package not installed", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EREMOTE => "Object is remote", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOLINK => "Link has been severed", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EADV => "Advertise error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ESRMNT => "Srmount error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ECOMM => "Communication error on send", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EPROTO => "Protocol error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EMULTIHOP => "Multihop attempted", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EDOTDOT => "RFS specific error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EBADMSG => "Not a data message", + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + EBADMSG => "Trying to read unreadable message", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "haiku" + ))] + EOVERFLOW => "Value too large for defined data type", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOTUNIQ => "Name not unique on network", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EBADFD => "File descriptor in bad state", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EREMCHG => "Remote address changed", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ELIBACC => "Can not access a needed shared library", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ELIBBAD => "Accessing a corrupted shared library", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ELIBSCN => ".lib section in a.out corrupted", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ELIBMAX => "Attempting to link in too many shared libraries", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ELIBEXEC => "Cannot exec a shared library directly", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia", + target_os = "openbsd" + ))] + EILSEQ => "Illegal byte sequence", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ERESTART => "Interrupted system call should be restarted", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ESTRPIPE => "Streams pipe error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EUSERS => "Too many users", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "netbsd", + target_os = "redox" + ))] + EOPNOTSUPP => "Operation not supported on transport endpoint", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + ESTALE => "Stale file handle", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EUCLEAN => "Structure needs cleaning", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + ENOTNAM => "Not a XENIX named type file", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + ENAVAIL => "No XENIX semaphores available", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EISNAM => "Is a named type file", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EREMOTEIO => "Remote I/O error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EDQUOT => "Quota exceeded", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "dragonfly" + ))] + ENOMEDIUM => "No medium found", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "openbsd" + ))] + EMEDIUMTYPE => "Wrong medium type", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia", + target_os = "haiku" + ))] + ECANCELED => "Operation canceled", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + ENOKEY => "Required key not available", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EKEYEXPIRED => "Key has expired", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EKEYREVOKED => "Key has been revoked", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EKEYREJECTED => "Key was rejected by service", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + EOWNERDEAD => "Owner died", + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + EOWNERDEAD => "Process died with lock", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia" + ))] + ENOTRECOVERABLE => "State not recoverable", + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + ENOTRECOVERABLE => "Lock is not recoverable", + + #[cfg(any( + all(target_os = "linux", not(target_arch = "mips")), + target_os = "fuchsia" + ))] + ERFKILL => "Operation not possible due to RF-kill", + + #[cfg(any( + all(target_os = "linux", not(target_arch = "mips")), + target_os = "fuchsia" + ))] + EHWPOISON => "Memory page has hardware error", + + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + EDOOFUS => "Programming error", + + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "redox" + ))] + EMULTIHOP => "Multihop attempted", + + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "redox" + ))] + ENOLINK => "Link has been severed", + + #[cfg(target_os = "freebsd")] + ENOTCAPABLE => "Capabilities insufficient", + + #[cfg(target_os = "freebsd")] + ECAPMODE => "Not permitted in capability mode", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + ENEEDAUTH => "Need authenticator", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox", + target_os = "illumos", + target_os = "solaris" + ))] + EOVERFLOW => "Value too large to be stored in data type", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "netbsd", + target_os = "redox", + target_os = "haiku" + ))] + EILSEQ => "Illegal byte sequence", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku" + ))] + ENOATTR => "Attribute not found", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox", + target_os = "haiku" + ))] + EBADMSG => "Bad message", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox", + target_os = "haiku" + ))] + EPROTO => "Protocol error", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd" + ))] + ENOTRECOVERABLE => "State not recoverable", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd" + ))] + EOWNERDEAD => "Previous owner died", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + ))] + ENOTSUP => "Operation not supported", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EPROCLIM => "Too many processes", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox" + ))] + EUSERS => "Too many users", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox", + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + ))] + EDQUOT => "Disc quota exceeded", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox", + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + ))] + ESTALE => "Stale NFS file handle", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox" + ))] + EREMOTE => "Too many levels of remote in path", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EBADRPC => "RPC struct is bad", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + ERPCMISMATCH => "RPC version wrong", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EPROGUNAVAIL => "RPC prog. not avail", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EPROGMISMATCH => "Program version wrong", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EPROCUNAVAIL => "Bad procedure for program", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EFTYPE => "Inappropriate file type or format", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + ))] + EAUTH => "Authentication error", + + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd", + target_os = "redox" + ))] + ECANCELED => "Operation canceled", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + EPWROFF => "Device power is off", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + EDEVERR => "Device error, e.g. paper out", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + EBADEXEC => "Bad executable", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + EBADARCH => "Bad CPU type in executable", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + ESHLIBVERS => "Shared library version mismatch", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + EBADMACHO => "Malformed Macho file", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "haiku" + ))] + EMULTIHOP => "Reserved", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "redox" + ))] + ENODATA => "No message available on STREAM", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "haiku" + ))] + ENOLINK => "Reserved", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "redox" + ))] + ENOSR => "No STREAM resources", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "redox" + ))] + ENOSTR => "Not a STREAM", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "redox" + ))] + ETIME => "STREAM ioctl timeout", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "illumos", + target_os = "solaris" + ))] + EOPNOTSUPP => "Operation not supported on socket", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + ENOPOLICY => "No such policy registered", + + #[cfg(any(target_os = "macos", target_os = "ios"))] + EQFULL => "Interface output queue is full", + + #[cfg(target_os = "openbsd")] + EOPNOTSUPP => "Operation not supported", + + #[cfg(target_os = "openbsd")] + EIPSEC => "IPsec processing failure", + + #[cfg(target_os = "dragonfly")] + EASYNC => "Async", + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + EDEADLOCK => "Resource deadlock would occur", + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + ELOCKUNMAPPED => "Locked lock was unmapped", + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + ENOTACTIVE => "Facility is not active", + } +} + +#[cfg(any(target_os = "linux", target_os = "android", target_os = "fuchsia"))] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EAGAIN = libc::EAGAIN, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EDEADLK = libc::EDEADLK, + ENAMETOOLONG = libc::ENAMETOOLONG, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + ENOTEMPTY = libc::ENOTEMPTY, + ELOOP = libc::ELOOP, + ENOMSG = libc::ENOMSG, + EIDRM = libc::EIDRM, + ECHRNG = libc::ECHRNG, + EL2NSYNC = libc::EL2NSYNC, + EL3HLT = libc::EL3HLT, + EL3RST = libc::EL3RST, + ELNRNG = libc::ELNRNG, + EUNATCH = libc::EUNATCH, + ENOCSI = libc::ENOCSI, + EL2HLT = libc::EL2HLT, + EBADE = libc::EBADE, + EBADR = libc::EBADR, + EXFULL = libc::EXFULL, + ENOANO = libc::ENOANO, + EBADRQC = libc::EBADRQC, + EBADSLT = libc::EBADSLT, + EBFONT = libc::EBFONT, + ENOSTR = libc::ENOSTR, + ENODATA = libc::ENODATA, + ETIME = libc::ETIME, + ENOSR = libc::ENOSR, + ENONET = libc::ENONET, + ENOPKG = libc::ENOPKG, + EREMOTE = libc::EREMOTE, + ENOLINK = libc::ENOLINK, + EADV = libc::EADV, + ESRMNT = libc::ESRMNT, + ECOMM = libc::ECOMM, + EPROTO = libc::EPROTO, + EMULTIHOP = libc::EMULTIHOP, + EDOTDOT = libc::EDOTDOT, + EBADMSG = libc::EBADMSG, + EOVERFLOW = libc::EOVERFLOW, + ENOTUNIQ = libc::ENOTUNIQ, + EBADFD = libc::EBADFD, + EREMCHG = libc::EREMCHG, + ELIBACC = libc::ELIBACC, + ELIBBAD = libc::ELIBBAD, + ELIBSCN = libc::ELIBSCN, + ELIBMAX = libc::ELIBMAX, + ELIBEXEC = libc::ELIBEXEC, + EILSEQ = libc::EILSEQ, + ERESTART = libc::ERESTART, + ESTRPIPE = libc::ESTRPIPE, + EUSERS = libc::EUSERS, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + EOPNOTSUPP = libc::EOPNOTSUPP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + EALREADY = libc::EALREADY, + EINPROGRESS = libc::EINPROGRESS, + ESTALE = libc::ESTALE, + EUCLEAN = libc::EUCLEAN, + ENOTNAM = libc::ENOTNAM, + ENAVAIL = libc::ENAVAIL, + EISNAM = libc::EISNAM, + EREMOTEIO = libc::EREMOTEIO, + EDQUOT = libc::EDQUOT, + ENOMEDIUM = libc::ENOMEDIUM, + EMEDIUMTYPE = libc::EMEDIUMTYPE, + ECANCELED = libc::ECANCELED, + ENOKEY = libc::ENOKEY, + EKEYEXPIRED = libc::EKEYEXPIRED, + EKEYREVOKED = libc::EKEYREVOKED, + EKEYREJECTED = libc::EKEYREJECTED, + EOWNERDEAD = libc::EOWNERDEAD, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + #[cfg(not(any(target_os = "android", target_arch = "mips")))] + ERFKILL = libc::ERFKILL, + #[cfg(not(any(target_os = "android", target_arch = "mips")))] + EHWPOISON = libc::EHWPOISON, + } + + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EAGAIN => EAGAIN, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EDEADLK => EDEADLK, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::ENOTEMPTY => ENOTEMPTY, + libc::ELOOP => ELOOP, + libc::ENOMSG => ENOMSG, + libc::EIDRM => EIDRM, + libc::ECHRNG => ECHRNG, + libc::EL2NSYNC => EL2NSYNC, + libc::EL3HLT => EL3HLT, + libc::EL3RST => EL3RST, + libc::ELNRNG => ELNRNG, + libc::EUNATCH => EUNATCH, + libc::ENOCSI => ENOCSI, + libc::EL2HLT => EL2HLT, + libc::EBADE => EBADE, + libc::EBADR => EBADR, + libc::EXFULL => EXFULL, + libc::ENOANO => ENOANO, + libc::EBADRQC => EBADRQC, + libc::EBADSLT => EBADSLT, + libc::EBFONT => EBFONT, + libc::ENOSTR => ENOSTR, + libc::ENODATA => ENODATA, + libc::ETIME => ETIME, + libc::ENOSR => ENOSR, + libc::ENONET => ENONET, + libc::ENOPKG => ENOPKG, + libc::EREMOTE => EREMOTE, + libc::ENOLINK => ENOLINK, + libc::EADV => EADV, + libc::ESRMNT => ESRMNT, + libc::ECOMM => ECOMM, + libc::EPROTO => EPROTO, + libc::EMULTIHOP => EMULTIHOP, + libc::EDOTDOT => EDOTDOT, + libc::EBADMSG => EBADMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::ENOTUNIQ => ENOTUNIQ, + libc::EBADFD => EBADFD, + libc::EREMCHG => EREMCHG, + libc::ELIBACC => ELIBACC, + libc::ELIBBAD => ELIBBAD, + libc::ELIBSCN => ELIBSCN, + libc::ELIBMAX => ELIBMAX, + libc::ELIBEXEC => ELIBEXEC, + libc::EILSEQ => EILSEQ, + libc::ERESTART => ERESTART, + libc::ESTRPIPE => ESTRPIPE, + libc::EUSERS => EUSERS, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::EALREADY => EALREADY, + libc::EINPROGRESS => EINPROGRESS, + libc::ESTALE => ESTALE, + libc::EUCLEAN => EUCLEAN, + libc::ENOTNAM => ENOTNAM, + libc::ENAVAIL => ENAVAIL, + libc::EISNAM => EISNAM, + libc::EREMOTEIO => EREMOTEIO, + libc::EDQUOT => EDQUOT, + libc::ENOMEDIUM => ENOMEDIUM, + libc::EMEDIUMTYPE => EMEDIUMTYPE, + libc::ECANCELED => ECANCELED, + libc::ENOKEY => ENOKEY, + libc::EKEYEXPIRED => EKEYEXPIRED, + libc::EKEYREVOKED => EKEYREVOKED, + libc::EKEYREJECTED => EKEYREJECTED, + libc::EOWNERDEAD => EOWNERDEAD, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + #[cfg(not(any(target_os = "android", target_arch = "mips")))] + libc::ERFKILL => ERFKILL, + #[cfg(not(any(target_os = "android", target_arch = "mips")))] + libc::EHWPOISON => EHWPOISON, + _ => UnknownErrno, + } + } +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + ENOTSUP = libc::ENOTSUP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + EREMOTE = libc::EREMOTE, + EBADRPC = libc::EBADRPC, + ERPCMISMATCH = libc::ERPCMISMATCH, + EPROGUNAVAIL = libc::EPROGUNAVAIL, + EPROGMISMATCH = libc::EPROGMISMATCH, + EPROCUNAVAIL = libc::EPROCUNAVAIL, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EFTYPE = libc::EFTYPE, + EAUTH = libc::EAUTH, + ENEEDAUTH = libc::ENEEDAUTH, + EPWROFF = libc::EPWROFF, + EDEVERR = libc::EDEVERR, + EOVERFLOW = libc::EOVERFLOW, + EBADEXEC = libc::EBADEXEC, + EBADARCH = libc::EBADARCH, + ESHLIBVERS = libc::ESHLIBVERS, + EBADMACHO = libc::EBADMACHO, + ECANCELED = libc::ECANCELED, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + EILSEQ = libc::EILSEQ, + ENOATTR = libc::ENOATTR, + EBADMSG = libc::EBADMSG, + EMULTIHOP = libc::EMULTIHOP, + ENODATA = libc::ENODATA, + ENOLINK = libc::ENOLINK, + ENOSR = libc::ENOSR, + ENOSTR = libc::ENOSTR, + EPROTO = libc::EPROTO, + ETIME = libc::ETIME, + EOPNOTSUPP = libc::EOPNOTSUPP, + ENOPOLICY = libc::ENOPOLICY, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + EOWNERDEAD = libc::EOWNERDEAD, + EQFULL = libc::EQFULL, + } + + impl Errno { + pub const ELAST: Errno = Errno::EQFULL; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::ENOTSUP => ENOTSUP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::EBADRPC => EBADRPC, + libc::ERPCMISMATCH => ERPCMISMATCH, + libc::EPROGUNAVAIL => EPROGUNAVAIL, + libc::EPROGMISMATCH => EPROGMISMATCH, + libc::EPROCUNAVAIL => EPROCUNAVAIL, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EFTYPE => EFTYPE, + libc::EAUTH => EAUTH, + libc::ENEEDAUTH => ENEEDAUTH, + libc::EPWROFF => EPWROFF, + libc::EDEVERR => EDEVERR, + libc::EOVERFLOW => EOVERFLOW, + libc::EBADEXEC => EBADEXEC, + libc::EBADARCH => EBADARCH, + libc::ESHLIBVERS => ESHLIBVERS, + libc::EBADMACHO => EBADMACHO, + libc::ECANCELED => ECANCELED, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::EILSEQ => EILSEQ, + libc::ENOATTR => ENOATTR, + libc::EBADMSG => EBADMSG, + libc::EMULTIHOP => EMULTIHOP, + libc::ENODATA => ENODATA, + libc::ENOLINK => ENOLINK, + libc::ENOSR => ENOSR, + libc::ENOSTR => ENOSTR, + libc::EPROTO => EPROTO, + libc::ETIME => ETIME, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::ENOPOLICY => ENOPOLICY, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + libc::EOWNERDEAD => EOWNERDEAD, + libc::EQFULL => EQFULL, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "freebsd")] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + ENOTSUP = libc::ENOTSUP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + EREMOTE = libc::EREMOTE, + EBADRPC = libc::EBADRPC, + ERPCMISMATCH = libc::ERPCMISMATCH, + EPROGUNAVAIL = libc::EPROGUNAVAIL, + EPROGMISMATCH = libc::EPROGMISMATCH, + EPROCUNAVAIL = libc::EPROCUNAVAIL, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EFTYPE = libc::EFTYPE, + EAUTH = libc::EAUTH, + ENEEDAUTH = libc::ENEEDAUTH, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + EOVERFLOW = libc::EOVERFLOW, + ECANCELED = libc::ECANCELED, + EILSEQ = libc::EILSEQ, + ENOATTR = libc::ENOATTR, + EDOOFUS = libc::EDOOFUS, + EBADMSG = libc::EBADMSG, + EMULTIHOP = libc::EMULTIHOP, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + ENOTCAPABLE = libc::ENOTCAPABLE, + ECAPMODE = libc::ECAPMODE, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + EOWNERDEAD = libc::EOWNERDEAD, + } + + impl Errno { + pub const ELAST: Errno = Errno::EOWNERDEAD; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::ENOTSUP => ENOTSUP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::EBADRPC => EBADRPC, + libc::ERPCMISMATCH => ERPCMISMATCH, + libc::EPROGUNAVAIL => EPROGUNAVAIL, + libc::EPROGMISMATCH => EPROGMISMATCH, + libc::EPROCUNAVAIL => EPROCUNAVAIL, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EFTYPE => EFTYPE, + libc::EAUTH => EAUTH, + libc::ENEEDAUTH => ENEEDAUTH, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::ECANCELED => ECANCELED, + libc::EILSEQ => EILSEQ, + libc::ENOATTR => ENOATTR, + libc::EDOOFUS => EDOOFUS, + libc::EBADMSG => EBADMSG, + libc::EMULTIHOP => EMULTIHOP, + libc::ENOLINK => ENOLINK, + libc::EPROTO => EPROTO, + libc::ENOTCAPABLE => ENOTCAPABLE, + libc::ECAPMODE => ECAPMODE, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + libc::EOWNERDEAD => EOWNERDEAD, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "dragonfly")] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + ENOTSUP = libc::ENOTSUP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + EREMOTE = libc::EREMOTE, + EBADRPC = libc::EBADRPC, + ERPCMISMATCH = libc::ERPCMISMATCH, + EPROGUNAVAIL = libc::EPROGUNAVAIL, + EPROGMISMATCH = libc::EPROGMISMATCH, + EPROCUNAVAIL = libc::EPROCUNAVAIL, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EFTYPE = libc::EFTYPE, + EAUTH = libc::EAUTH, + ENEEDAUTH = libc::ENEEDAUTH, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + EOVERFLOW = libc::EOVERFLOW, + ECANCELED = libc::ECANCELED, + EILSEQ = libc::EILSEQ, + ENOATTR = libc::ENOATTR, + EDOOFUS = libc::EDOOFUS, + EBADMSG = libc::EBADMSG, + EMULTIHOP = libc::EMULTIHOP, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + ENOMEDIUM = libc::ENOMEDIUM, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + EOWNERDEAD = libc::EOWNERDEAD, + EASYNC = libc::EASYNC, + } + + impl Errno { + pub const ELAST: Errno = Errno::EASYNC; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::ENOTSUP => ENOTSUP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::EBADRPC => EBADRPC, + libc::ERPCMISMATCH => ERPCMISMATCH, + libc::EPROGUNAVAIL => EPROGUNAVAIL, + libc::EPROGMISMATCH => EPROGMISMATCH, + libc::EPROCUNAVAIL => EPROCUNAVAIL, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EFTYPE => EFTYPE, + libc::EAUTH => EAUTH, + libc::ENEEDAUTH => ENEEDAUTH, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::ECANCELED => ECANCELED, + libc::EILSEQ => EILSEQ, + libc::ENOATTR => ENOATTR, + libc::EDOOFUS => EDOOFUS, + libc::EBADMSG => EBADMSG, + libc::EMULTIHOP => EMULTIHOP, + libc::ENOLINK => ENOLINK, + libc::EPROTO => EPROTO, + libc::ENOMEDIUM => ENOMEDIUM, + libc::EASYNC => EASYNC, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "openbsd")] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + EOPNOTSUPP = libc::EOPNOTSUPP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + EREMOTE = libc::EREMOTE, + EBADRPC = libc::EBADRPC, + ERPCMISMATCH = libc::ERPCMISMATCH, + EPROGUNAVAIL = libc::EPROGUNAVAIL, + EPROGMISMATCH = libc::EPROGMISMATCH, + EPROCUNAVAIL = libc::EPROCUNAVAIL, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EFTYPE = libc::EFTYPE, + EAUTH = libc::EAUTH, + ENEEDAUTH = libc::ENEEDAUTH, + EIPSEC = libc::EIPSEC, + ENOATTR = libc::ENOATTR, + EILSEQ = libc::EILSEQ, + ENOMEDIUM = libc::ENOMEDIUM, + EMEDIUMTYPE = libc::EMEDIUMTYPE, + EOVERFLOW = libc::EOVERFLOW, + ECANCELED = libc::ECANCELED, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + ENOTSUP = libc::ENOTSUP, + EBADMSG = libc::EBADMSG, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + EOWNERDEAD = libc::EOWNERDEAD, + EPROTO = libc::EPROTO, + } + + impl Errno { + pub const ELAST: Errno = Errno::ENOTSUP; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::EBADRPC => EBADRPC, + libc::ERPCMISMATCH => ERPCMISMATCH, + libc::EPROGUNAVAIL => EPROGUNAVAIL, + libc::EPROGMISMATCH => EPROGMISMATCH, + libc::EPROCUNAVAIL => EPROCUNAVAIL, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EFTYPE => EFTYPE, + libc::EAUTH => EAUTH, + libc::ENEEDAUTH => ENEEDAUTH, + libc::EIPSEC => EIPSEC, + libc::ENOATTR => ENOATTR, + libc::EILSEQ => EILSEQ, + libc::ENOMEDIUM => ENOMEDIUM, + libc::EMEDIUMTYPE => EMEDIUMTYPE, + libc::EOVERFLOW => EOVERFLOW, + libc::ECANCELED => ECANCELED, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::ENOTSUP => ENOTSUP, + libc::EBADMSG => EBADMSG, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + libc::EOWNERDEAD => EOWNERDEAD, + libc::EPROTO => EPROTO, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "netbsd")] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + EOPNOTSUPP = libc::EOPNOTSUPP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + EREMOTE = libc::EREMOTE, + EBADRPC = libc::EBADRPC, + ERPCMISMATCH = libc::ERPCMISMATCH, + EPROGUNAVAIL = libc::EPROGUNAVAIL, + EPROGMISMATCH = libc::EPROGMISMATCH, + EPROCUNAVAIL = libc::EPROCUNAVAIL, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EFTYPE = libc::EFTYPE, + EAUTH = libc::EAUTH, + ENEEDAUTH = libc::ENEEDAUTH, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + EOVERFLOW = libc::EOVERFLOW, + EILSEQ = libc::EILSEQ, + ENOTSUP = libc::ENOTSUP, + ECANCELED = libc::ECANCELED, + EBADMSG = libc::EBADMSG, + ENODATA = libc::ENODATA, + ENOSR = libc::ENOSR, + ENOSTR = libc::ENOSTR, + ETIME = libc::ETIME, + ENOATTR = libc::ENOATTR, + EMULTIHOP = libc::EMULTIHOP, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + } + + impl Errno { + pub const ELAST: Errno = Errno::ENOTSUP; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::EBADRPC => EBADRPC, + libc::ERPCMISMATCH => ERPCMISMATCH, + libc::EPROGUNAVAIL => EPROGUNAVAIL, + libc::EPROGMISMATCH => EPROGMISMATCH, + libc::EPROCUNAVAIL => EPROCUNAVAIL, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EFTYPE => EFTYPE, + libc::EAUTH => EAUTH, + libc::ENEEDAUTH => ENEEDAUTH, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::EILSEQ => EILSEQ, + libc::ENOTSUP => ENOTSUP, + libc::ECANCELED => ECANCELED, + libc::EBADMSG => EBADMSG, + libc::ENODATA => ENODATA, + libc::ENOSR => ENOSR, + libc::ENOSTR => ENOSTR, + libc::ETIME => ETIME, + libc::ENOATTR => ENOATTR, + libc::EMULTIHOP => EMULTIHOP, + libc::ENOLINK => ENOLINK, + libc::EPROTO => EPROTO, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "redox")] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + EOPNOTSUPP = libc::EOPNOTSUPP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + EREMOTE = libc::EREMOTE, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + EOVERFLOW = libc::EOVERFLOW, + EILSEQ = libc::EILSEQ, + ECANCELED = libc::ECANCELED, + EBADMSG = libc::EBADMSG, + ENODATA = libc::ENODATA, + ENOSR = libc::ENOSR, + ENOSTR = libc::ENOSTR, + ETIME = libc::ETIME, + EMULTIHOP = libc::EMULTIHOP, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + } + + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::EILSEQ => EILSEQ, + libc::ECANCELED => ECANCELED, + libc::EBADMSG => EBADMSG, + libc::ENODATA => ENODATA, + libc::ENOSR => ENOSR, + libc::ENOSTR => ENOSTR, + libc::ETIME => ETIME, + libc::EMULTIHOP => EMULTIHOP, + libc::ENOLINK => ENOLINK, + libc::EPROTO => EPROTO, + _ => UnknownErrno, + } + } +} + +#[cfg(any(target_os = "illumos", target_os = "solaris"))] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EAGAIN = libc::EAGAIN, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + ENOTBLK = libc::ENOTBLK, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + ENOMSG = libc::ENOMSG, + EIDRM = libc::EIDRM, + ECHRNG = libc::ECHRNG, + EL2NSYNC = libc::EL2NSYNC, + EL3HLT = libc::EL3HLT, + EL3RST = libc::EL3RST, + ELNRNG = libc::ELNRNG, + EUNATCH = libc::EUNATCH, + ENOCSI = libc::ENOCSI, + EL2HLT = libc::EL2HLT, + EDEADLK = libc::EDEADLK, + ENOLCK = libc::ENOLCK, + ECANCELED = libc::ECANCELED, + ENOTSUP = libc::ENOTSUP, + EDQUOT = libc::EDQUOT, + EBADE = libc::EBADE, + EBADR = libc::EBADR, + EXFULL = libc::EXFULL, + ENOANO = libc::ENOANO, + EBADRQC = libc::EBADRQC, + EBADSLT = libc::EBADSLT, + EDEADLOCK = libc::EDEADLOCK, + EBFONT = libc::EBFONT, + EOWNERDEAD = libc::EOWNERDEAD, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + ENOSTR = libc::ENOSTR, + ENODATA = libc::ENODATA, + ETIME = libc::ETIME, + ENOSR = libc::ENOSR, + ENONET = libc::ENONET, + ENOPKG = libc::ENOPKG, + EREMOTE = libc::EREMOTE, + ENOLINK = libc::ENOLINK, + EADV = libc::EADV, + ESRMNT = libc::ESRMNT, + ECOMM = libc::ECOMM, + EPROTO = libc::EPROTO, + ELOCKUNMAPPED = libc::ELOCKUNMAPPED, + ENOTACTIVE = libc::ENOTACTIVE, + EMULTIHOP = libc::EMULTIHOP, + EBADMSG = libc::EBADMSG, + ENAMETOOLONG = libc::ENAMETOOLONG, + EOVERFLOW = libc::EOVERFLOW, + ENOTUNIQ = libc::ENOTUNIQ, + EBADFD = libc::EBADFD, + EREMCHG = libc::EREMCHG, + ELIBACC = libc::ELIBACC, + ELIBBAD = libc::ELIBBAD, + ELIBSCN = libc::ELIBSCN, + ELIBMAX = libc::ELIBMAX, + ELIBEXEC = libc::ELIBEXEC, + EILSEQ = libc::EILSEQ, + ENOSYS = libc::ENOSYS, + ELOOP = libc::ELOOP, + ERESTART = libc::ERESTART, + ESTRPIPE = libc::ESTRPIPE, + ENOTEMPTY = libc::ENOTEMPTY, + EUSERS = libc::EUSERS, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + EOPNOTSUPP = libc::EOPNOTSUPP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETOOMANYREFS = libc::ETOOMANYREFS, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + EALREADY = libc::EALREADY, + EINPROGRESS = libc::EINPROGRESS, + ESTALE = libc::ESTALE, + } + + impl Errno { + pub const ELAST: Errno = Errno::ESTALE; + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EAGAIN => EAGAIN, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::ENOMSG => ENOMSG, + libc::EIDRM => EIDRM, + libc::ECHRNG => ECHRNG, + libc::EL2NSYNC => EL2NSYNC, + libc::EL3HLT => EL3HLT, + libc::EL3RST => EL3RST, + libc::ELNRNG => ELNRNG, + libc::EUNATCH => EUNATCH, + libc::ENOCSI => ENOCSI, + libc::EL2HLT => EL2HLT, + libc::EDEADLK => EDEADLK, + libc::ENOLCK => ENOLCK, + libc::ECANCELED => ECANCELED, + libc::ENOTSUP => ENOTSUP, + libc::EDQUOT => EDQUOT, + libc::EBADE => EBADE, + libc::EBADR => EBADR, + libc::EXFULL => EXFULL, + libc::ENOANO => ENOANO, + libc::EBADRQC => EBADRQC, + libc::EBADSLT => EBADSLT, + libc::EDEADLOCK => EDEADLOCK, + libc::EBFONT => EBFONT, + libc::EOWNERDEAD => EOWNERDEAD, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + libc::ENOSTR => ENOSTR, + libc::ENODATA => ENODATA, + libc::ETIME => ETIME, + libc::ENOSR => ENOSR, + libc::ENONET => ENONET, + libc::ENOPKG => ENOPKG, + libc::EREMOTE => EREMOTE, + libc::ENOLINK => ENOLINK, + libc::EADV => EADV, + libc::ESRMNT => ESRMNT, + libc::ECOMM => ECOMM, + libc::EPROTO => EPROTO, + libc::ELOCKUNMAPPED => ELOCKUNMAPPED, + libc::ENOTACTIVE => ENOTACTIVE, + libc::EMULTIHOP => EMULTIHOP, + libc::EBADMSG => EBADMSG, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EOVERFLOW => EOVERFLOW, + libc::ENOTUNIQ => ENOTUNIQ, + libc::EBADFD => EBADFD, + libc::EREMCHG => EREMCHG, + libc::ELIBACC => ELIBACC, + libc::ELIBBAD => ELIBBAD, + libc::ELIBSCN => ELIBSCN, + libc::ELIBMAX => ELIBMAX, + libc::ELIBEXEC => ELIBEXEC, + libc::EILSEQ => EILSEQ, + libc::ENOSYS => ENOSYS, + libc::ELOOP => ELOOP, + libc::ERESTART => ERESTART, + libc::ESTRPIPE => ESTRPIPE, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EUSERS => EUSERS, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::EALREADY => EALREADY, + libc::EINPROGRESS => EINPROGRESS, + libc::ESTALE => ESTALE, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "haiku")] +mod consts { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(i32)] + #[non_exhaustive] + pub enum Errno { + UnknownErrno = 0, + EPERM = libc::EPERM, + ENOENT = libc::ENOENT, + ESRCH = libc::ESRCH, + EINTR = libc::EINTR, + EIO = libc::EIO, + ENXIO = libc::ENXIO, + E2BIG = libc::E2BIG, + ENOEXEC = libc::ENOEXEC, + EBADF = libc::EBADF, + ECHILD = libc::ECHILD, + EDEADLK = libc::EDEADLK, + ENOMEM = libc::ENOMEM, + EACCES = libc::EACCES, + EFAULT = libc::EFAULT, + EBUSY = libc::EBUSY, + EEXIST = libc::EEXIST, + EXDEV = libc::EXDEV, + ENODEV = libc::ENODEV, + ENOTDIR = libc::ENOTDIR, + EISDIR = libc::EISDIR, + EINVAL = libc::EINVAL, + ENFILE = libc::ENFILE, + EMFILE = libc::EMFILE, + ENOTTY = libc::ENOTTY, + ETXTBSY = libc::ETXTBSY, + EFBIG = libc::EFBIG, + ENOSPC = libc::ENOSPC, + ESPIPE = libc::ESPIPE, + EROFS = libc::EROFS, + EMLINK = libc::EMLINK, + EPIPE = libc::EPIPE, + EDOM = libc::EDOM, + ERANGE = libc::ERANGE, + EAGAIN = libc::EAGAIN, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + ENOTSOCK = libc::ENOTSOCK, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTOTYPE = libc::EPROTOTYPE, + ENOPROTOOPT = libc::ENOPROTOOPT, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ENOTSUP = libc::ENOTSUP, + EADDRINUSE = libc::EADDRINUSE, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETDOWN = libc::ENETDOWN, + ENETUNREACH = libc::ENETUNREACH, + ENETRESET = libc::ENETRESET, + ECONNABORTED = libc::ECONNABORTED, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ESHUTDOWN = libc::ESHUTDOWN, + ETIMEDOUT = libc::ETIMEDOUT, + ECONNREFUSED = libc::ECONNREFUSED, + ELOOP = libc::ELOOP, + ENAMETOOLONG = libc::ENAMETOOLONG, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + ENOTEMPTY = libc::ENOTEMPTY, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + ENOLCK = libc::ENOLCK, + ENOSYS = libc::ENOSYS, + EIDRM = libc::EIDRM, + ENOMSG = libc::ENOMSG, + EOVERFLOW = libc::EOVERFLOW, + ECANCELED = libc::ECANCELED, + EILSEQ = libc::EILSEQ, + ENOATTR = libc::ENOATTR, + EBADMSG = libc::EBADMSG, + EMULTIHOP = libc::EMULTIHOP, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + } + + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + } + + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EDEADLK => EDEADLK, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EAGAIN => EAGAIN, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::ENOTSOCK => ENOTSOCK, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ENOTSUP => ENOTSUP, + libc::EADDRINUSE => EADDRINUSE, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETDOWN => ENETDOWN, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETRESET => ENETRESET, + libc::ECONNABORTED => ECONNABORTED, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::EIDRM => EIDRM, + libc::ENOMSG => ENOMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::ECANCELED => ECANCELED, + libc::EILSEQ => EILSEQ, + libc::ENOATTR => ENOATTR, + libc::EBADMSG => EBADMSG, + libc::EMULTIHOP => EMULTIHOP, + libc::ENOLINK => ENOLINK, + libc::EPROTO => EPROTO, + _ => UnknownErrno, + } + } +} diff --git a/src/fcntl.rs b/src/fcntl.rs new file mode 100644 index 0000000..6508283 --- /dev/null +++ b/src/fcntl.rs @@ -0,0 +1,882 @@ +use crate::errno::Errno; +use libc::{self, c_char, c_int, c_uint, size_t, ssize_t}; +use std::ffi::OsString; +#[cfg(not(target_os = "redox"))] +use std::os::raw; +use std::os::unix::ffi::OsStringExt; +use std::os::unix::io::RawFd; + +#[cfg(feature = "fs")] +use crate::{sys::stat::Mode, NixPath, Result}; +#[cfg(any(target_os = "android", target_os = "linux"))] +use std::ptr; // For splice and copy_file_range + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "wasi", + target_env = "uclibc", + target_os = "freebsd" +))] +#[cfg(feature = "fs")] +pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice}; + +#[cfg(not(target_os = "redox"))] +#[cfg(any(feature = "fs", feature = "process"))] +libc_bitflags! { + #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "process"))))] + pub struct AtFlags: c_int { + AT_REMOVEDIR; + AT_SYMLINK_FOLLOW; + AT_SYMLINK_NOFOLLOW; + #[cfg(any(target_os = "android", target_os = "linux"))] + AT_NO_AUTOMOUNT; + #[cfg(any(target_os = "android", target_os = "linux"))] + AT_EMPTY_PATH; + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + AT_EACCESS; + } +} + +#[cfg(any(feature = "fs", feature = "term"))] +libc_bitflags!( + /// Configuration options for opened files. + #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))] + pub struct OFlag: c_int { + /// Mask for the access mode of the file. + O_ACCMODE; + /// Use alternate I/O semantics. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_ALT_IO; + /// Open the file in append-only mode. + O_APPEND; + /// Generate a signal when input or output becomes possible. + #[cfg(not(any(target_os = "illumos", target_os = "solaris", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_ASYNC; + /// Closes the file descriptor once an `execve` call is made. + /// + /// Also sets the file offset to the beginning of the file. + O_CLOEXEC; + /// Create the file if it does not exist. + O_CREAT; + /// Try to minimize cache effects of the I/O for this file. + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_DIRECT; + /// If the specified path isn't a directory, fail. + #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_DIRECTORY; + /// Implicitly follow each `write()` with an `fdatasync()`. + #[cfg(any(target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_DSYNC; + /// Error out if a file was not created. + O_EXCL; + /// Open for execute only. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_EXEC; + /// Open with an exclusive file lock. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_EXLOCK; + /// Same as `O_SYNC`. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + all(target_os = "linux", not(target_env = "musl")), + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_FSYNC; + /// Allow files whose sizes can't be represented in an `off_t` to be opened. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_LARGEFILE; + /// Do not update the file last access time during `read(2)`s. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_NOATIME; + /// Don't attach the device as the process' controlling terminal. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_NOCTTY; + /// Same as `O_NONBLOCK`. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_NDELAY; + /// `open()` will fail if the given path is a symbolic link. + O_NOFOLLOW; + /// When possible, open the file in nonblocking mode. + O_NONBLOCK; + /// Don't deliver `SIGPIPE`. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_NOSIGPIPE; + /// Obtain a file descriptor for low-level access. + /// + /// The file itself is not opened and other file operations will fail. + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_PATH; + /// Only allow reading. + /// + /// This should not be combined with `O_WRONLY` or `O_RDWR`. + O_RDONLY; + /// Allow both reading and writing. + /// + /// This should not be combined with `O_WRONLY` or `O_RDONLY`. + O_RDWR; + /// Similar to `O_DSYNC` but applies to `read`s instead. + #[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_RSYNC; + /// Skip search permission checks. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_SEARCH; + /// Open with a shared file lock. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_SHLOCK; + /// Implicitly follow each `write()` with an `fsync()`. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_SYNC; + /// Create an unnamed temporary file. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_TMPFILE; + /// Truncate an existing regular file to 0 length if it allows writing. + O_TRUNC; + /// Restore default TTY attributes. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_TTY_INIT; + /// Only allow writing. + /// + /// This should not be combined with `O_RDONLY` or `O_RDWR`. + O_WRONLY; + } +); + +feature! { +#![feature = "fs"] + +// The conversion is not identical on all operating systems. +#[allow(clippy::useless_conversion)] +pub fn open(path: &P, oflag: OFlag, mode: Mode) -> Result { + let fd = path.with_nix_path(|cstr| { + unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) } + })?; + + Errno::result(fd) +} + +// The conversion is not identical on all operating systems. +#[allow(clippy::useless_conversion)] +#[cfg(not(target_os = "redox"))] +pub fn openat( + dirfd: RawFd, + path: &P, + oflag: OFlag, + mode: Mode, +) -> Result { + let fd = path.with_nix_path(|cstr| { + unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) } + })?; + Errno::result(fd) +} + +#[cfg(not(target_os = "redox"))] +pub fn renameat( + old_dirfd: Option, + old_path: &P1, + new_dirfd: Option, + new_path: &P2, +) -> Result<()> { + let res = old_path.with_nix_path(|old_cstr| { + new_path.with_nix_path(|new_cstr| unsafe { + libc::renameat( + at_rawfd(old_dirfd), + old_cstr.as_ptr(), + at_rawfd(new_dirfd), + new_cstr.as_ptr(), + ) + }) + })??; + Errno::result(res).map(drop) +} +} + +#[cfg(all(target_os = "linux", target_env = "gnu",))] +#[cfg(feature = "fs")] +libc_bitflags! { + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub struct RenameFlags: u32 { + RENAME_EXCHANGE; + RENAME_NOREPLACE; + RENAME_WHITEOUT; + } +} + +feature! { +#![feature = "fs"] +#[cfg(all( + target_os = "linux", + target_env = "gnu", +))] +pub fn renameat2( + old_dirfd: Option, + old_path: &P1, + new_dirfd: Option, + new_path: &P2, + flags: RenameFlags, +) -> Result<()> { + let res = old_path.with_nix_path(|old_cstr| { + new_path.with_nix_path(|new_cstr| unsafe { + libc::renameat2( + at_rawfd(old_dirfd), + old_cstr.as_ptr(), + at_rawfd(new_dirfd), + new_cstr.as_ptr(), + flags.bits(), + ) + }) + })??; + Errno::result(res).map(drop) +} + +fn wrap_readlink_result(mut v: Vec, len: ssize_t) -> Result { + unsafe { v.set_len(len as usize) } + v.shrink_to_fit(); + Ok(OsString::from_vec(v.to_vec())) +} + +fn readlink_maybe_at( + dirfd: Option, + path: &P, + v: &mut Vec, +) -> Result { + path.with_nix_path(|cstr| unsafe { + match dirfd { + #[cfg(target_os = "redox")] + Some(_) => unreachable!(), + #[cfg(not(target_os = "redox"))] + Some(dirfd) => libc::readlinkat( + dirfd, + cstr.as_ptr(), + v.as_mut_ptr() as *mut c_char, + v.capacity() as size_t, + ), + None => libc::readlink( + cstr.as_ptr(), + v.as_mut_ptr() as *mut c_char, + v.capacity() as size_t, + ), + } + }) +} + +fn inner_readlink(dirfd: Option, path: &P) -> Result { + let mut v = Vec::with_capacity(libc::PATH_MAX as usize); + // simple case: result is strictly less than `PATH_MAX` + let res = readlink_maybe_at(dirfd, path, &mut v)?; + let len = Errno::result(res)?; + debug_assert!(len >= 0); + if (len as usize) < v.capacity() { + return wrap_readlink_result(v, res); + } + // Uh oh, the result is too long... + // Let's try to ask lstat how many bytes to allocate. + let reported_size = match dirfd { + #[cfg(target_os = "redox")] + Some(_) => unreachable!(), + #[cfg(any(target_os = "android", target_os = "linux"))] + Some(dirfd) => { + let flags = if path.is_empty() { AtFlags::AT_EMPTY_PATH } else { AtFlags::empty() }; + super::sys::stat::fstatat(dirfd, path, flags | AtFlags::AT_SYMLINK_NOFOLLOW) + }, + #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "redox")))] + Some(dirfd) => super::sys::stat::fstatat(dirfd, path, AtFlags::AT_SYMLINK_NOFOLLOW), + None => super::sys::stat::lstat(path) + } + .map(|x| x.st_size) + .unwrap_or(0); + let mut try_size = if reported_size > 0 { + // Note: even if `lstat`'s apparently valid answer turns out to be + // wrong, we will still read the full symlink no matter what. + reported_size as usize + 1 + } else { + // If lstat doesn't cooperate, or reports an error, be a little less + // precise. + (libc::PATH_MAX as usize).max(128) << 1 + }; + loop { + v.reserve_exact(try_size); + let res = readlink_maybe_at(dirfd, path, &mut v)?; + let len = Errno::result(res)?; + debug_assert!(len >= 0); + if (len as usize) < v.capacity() { + break wrap_readlink_result(v, res); + } else { + // Ugh! Still not big enough! + match try_size.checked_shl(1) { + Some(next_size) => try_size = next_size, + // It's absurd that this would happen, but handle it sanely + // anyway. + None => break Err(Errno::ENAMETOOLONG), + } + } + } +} + +pub fn readlink(path: &P) -> Result { + inner_readlink(None, path) +} + +#[cfg(not(target_os = "redox"))] +pub fn readlinkat(dirfd: RawFd, path: &P) -> Result { + inner_readlink(Some(dirfd), path) +} + +/// Computes the raw fd consumed by a function of the form `*at`. +#[cfg(not(target_os = "redox"))] +pub(crate) fn at_rawfd(fd: Option) -> raw::c_int { + match fd { + None => libc::AT_FDCWD, + Some(fd) => fd, + } +} +} + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] +#[cfg(feature = "fs")] +libc_bitflags!( + /// Additional flags for file sealing, which allows for limiting operations on a file. + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub struct SealFlag: c_int { + /// Prevents further calls to `fcntl()` with `F_ADD_SEALS`. + F_SEAL_SEAL; + /// The file cannot be reduced in size. + F_SEAL_SHRINK; + /// The size of the file cannot be increased. + F_SEAL_GROW; + /// The file contents cannot be modified. + F_SEAL_WRITE; + } +); + +#[cfg(feature = "fs")] +libc_bitflags!( + /// Additional configuration flags for `fcntl`'s `F_SETFD`. + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub struct FdFlag: c_int { + /// The file descriptor will automatically be closed during a successful `execve(2)`. + FD_CLOEXEC; + } +); + +feature! { +#![feature = "fs"] + +#[cfg(not(target_os = "redox"))] +#[derive(Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum FcntlArg<'a> { + F_DUPFD(RawFd), + F_DUPFD_CLOEXEC(RawFd), + F_GETFD, + F_SETFD(FdFlag), // FD_FLAGS + F_GETFL, + F_SETFL(OFlag), // O_NONBLOCK + F_SETLK(&'a libc::flock), + F_SETLKW(&'a libc::flock), + F_GETLK(&'a mut libc::flock), + #[cfg(any(target_os = "linux", target_os = "android"))] + F_OFD_SETLK(&'a libc::flock), + #[cfg(any(target_os = "linux", target_os = "android"))] + F_OFD_SETLKW(&'a libc::flock), + #[cfg(any(target_os = "linux", target_os = "android"))] + F_OFD_GETLK(&'a mut libc::flock), + #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + F_ADD_SEALS(SealFlag), + #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + F_GET_SEALS, + #[cfg(any(target_os = "macos", target_os = "ios"))] + F_FULLFSYNC, + #[cfg(any(target_os = "linux", target_os = "android"))] + F_GETPIPE_SZ, + #[cfg(any(target_os = "linux", target_os = "android"))] + F_SETPIPE_SZ(c_int), + // TODO: Rest of flags +} + +#[cfg(target_os = "redox")] +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum FcntlArg { + F_DUPFD(RawFd), + F_DUPFD_CLOEXEC(RawFd), + F_GETFD, + F_SETFD(FdFlag), // FD_FLAGS + F_GETFL, + F_SETFL(OFlag), // O_NONBLOCK +} +pub use self::FcntlArg::*; + +// TODO: Figure out how to handle value fcntl returns +pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result { + let res = unsafe { + match arg { + F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd), + F_DUPFD_CLOEXEC(rawfd) => libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd), + F_GETFD => libc::fcntl(fd, libc::F_GETFD), + F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()), + F_GETFL => libc::fcntl(fd, libc::F_GETFL), + F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()), + #[cfg(not(target_os = "redox"))] + F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock), + #[cfg(not(target_os = "redox"))] + F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock), + #[cfg(not(target_os = "redox"))] + F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock), + #[cfg(any(target_os = "android", target_os = "linux"))] + F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock), + #[cfg(any(target_os = "android", target_os = "linux"))] + F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock), + #[cfg(any(target_os = "android", target_os = "linux"))] + F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock), + #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()), + #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS), + #[cfg(any(target_os = "macos", target_os = "ios"))] + F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC), + #[cfg(any(target_os = "linux", target_os = "android"))] + F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ), + #[cfg(any(target_os = "linux", target_os = "android"))] + F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size), + } + }; + + Errno::result(res) +} + +// TODO: convert to libc_enum +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum FlockArg { + LockShared, + LockExclusive, + Unlock, + LockSharedNonblock, + LockExclusiveNonblock, + UnlockNonblock, +} + +#[cfg(not(target_os = "redox"))] +pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> { + use self::FlockArg::*; + + let res = unsafe { + match arg { + LockShared => libc::flock(fd, libc::LOCK_SH), + LockExclusive => libc::flock(fd, libc::LOCK_EX), + Unlock => libc::flock(fd, libc::LOCK_UN), + LockSharedNonblock => libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB), + LockExclusiveNonblock => libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB), + UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB), + } + }; + + Errno::result(res).map(drop) +} +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "zerocopy")] +libc_bitflags! { + /// Additional flags to `splice` and friends. + #[cfg_attr(docsrs, doc(cfg(feature = "zerocopy")))] + pub struct SpliceFFlags: c_uint { + /// Request that pages be moved instead of copied. + /// + /// Not applicable to `vmsplice`. + SPLICE_F_MOVE; + /// Do not block on I/O. + SPLICE_F_NONBLOCK; + /// Hint that more data will be coming in a subsequent splice. + /// + /// Not applicable to `vmsplice`. + SPLICE_F_MORE; + /// Gift the user pages to the kernel. + /// + /// Not applicable to `splice`. + SPLICE_F_GIFT; + } +} + +feature! { +#![feature = "zerocopy"] + +/// Copy a range of data from one file to another +/// +/// The `copy_file_range` system call performs an in-kernel copy between +/// file descriptors `fd_in` and `fd_out` without the additional cost of +/// transferring data from the kernel to user space and then back into the +/// kernel. It copies up to `len` bytes of data from file descriptor `fd_in` to +/// file descriptor `fd_out`, overwriting any data that exists within the +/// requested range of the target file. +/// +/// If the `off_in` and/or `off_out` arguments are used, the values +/// will be mutated to reflect the new position within the file after +/// copying. If they are not used, the relevant filedescriptors will be seeked +/// to the new position. +/// +/// On successful completion the number of bytes actually copied will be +/// returned. +#[cfg(any(target_os = "android", target_os = "linux"))] +pub fn copy_file_range( + fd_in: RawFd, + off_in: Option<&mut libc::loff_t>, + fd_out: RawFd, + off_out: Option<&mut libc::loff_t>, + len: usize, +) -> Result { + let off_in = off_in + .map(|offset| offset as *mut libc::loff_t) + .unwrap_or(ptr::null_mut()); + let off_out = off_out + .map(|offset| offset as *mut libc::loff_t) + .unwrap_or(ptr::null_mut()); + + let ret = unsafe { + libc::syscall( + libc::SYS_copy_file_range, + fd_in, + off_in, + fd_out, + off_out, + len, + 0, + ) + }; + Errno::result(ret).map(|r| r as usize) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn splice( + fd_in: RawFd, + off_in: Option<&mut libc::loff_t>, + fd_out: RawFd, + off_out: Option<&mut libc::loff_t>, + len: usize, + flags: SpliceFFlags, +) -> Result { + let off_in = off_in + .map(|offset| offset as *mut libc::loff_t) + .unwrap_or(ptr::null_mut()); + let off_out = off_out + .map(|offset| offset as *mut libc::loff_t) + .unwrap_or(ptr::null_mut()); + + let ret = unsafe { libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) }; + Errno::result(ret).map(|r| r as usize) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn tee(fd_in: RawFd, fd_out: RawFd, len: usize, flags: SpliceFFlags) -> Result { + let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) }; + Errno::result(ret).map(|r| r as usize) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn vmsplice( + fd: RawFd, + iov: &[std::io::IoSlice<'_>], + flags: SpliceFFlags + ) -> Result +{ + let ret = unsafe { + libc::vmsplice( + fd, + iov.as_ptr() as *const libc::iovec, + iov.len(), + flags.bits(), + ) + }; + Errno::result(ret).map(|r| r as usize) +} +} + +#[cfg(any(target_os = "linux"))] +#[cfg(feature = "fs")] +libc_bitflags!( + /// Mode argument flags for fallocate determining operation performed on a given range. + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub struct FallocateFlags: c_int { + /// File size is not changed. + /// + /// offset + len can be greater than file size. + FALLOC_FL_KEEP_SIZE; + /// Deallocates space by creating a hole. + /// + /// Must be ORed with FALLOC_FL_KEEP_SIZE. Byte range starts at offset and continues for len bytes. + FALLOC_FL_PUNCH_HOLE; + /// Removes byte range from a file without leaving a hole. + /// + /// Byte range to collapse starts at offset and continues for len bytes. + FALLOC_FL_COLLAPSE_RANGE; + /// Zeroes space in specified byte range. + /// + /// Byte range starts at offset and continues for len bytes. + FALLOC_FL_ZERO_RANGE; + /// Increases file space by inserting a hole within the file size. + /// + /// Does not overwrite existing data. Hole starts at offset and continues for len bytes. + FALLOC_FL_INSERT_RANGE; + /// Shared file data extants are made private to the file. + /// + /// Gaurantees that a subsequent write will not fail due to lack of space. + FALLOC_FL_UNSHARE_RANGE; + } +); + +feature! { +#![feature = "fs"] + +/// Manipulates file space. +/// +/// Allows the caller to directly manipulate the allocated disk space for the +/// file referred to by fd. +#[cfg(any(target_os = "linux"))] +#[cfg(feature = "fs")] +pub fn fallocate( + fd: RawFd, + mode: FallocateFlags, + offset: libc::off_t, + len: libc::off_t, +) -> Result<()> { + let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) }; + Errno::result(res).map(drop) +} + +/// Argument to [`fspacectl`] describing the range to zero. The first member is +/// the file offset, and the second is the length of the region. +#[cfg(any(target_os = "freebsd"))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SpacectlRange(pub libc::off_t, pub libc::off_t); + +#[cfg(any(target_os = "freebsd"))] +impl SpacectlRange { + #[inline] + pub fn is_empty(&self) -> bool { + self.1 == 0 + } + + #[inline] + pub fn len(&self) -> libc::off_t { + self.1 + } + + #[inline] + pub fn offset(&self) -> libc::off_t { + self.0 + } +} + +/// Punch holes in a file. +/// +/// `fspacectl` instructs the file system to deallocate a portion of a file. +/// After a successful operation, this region of the file will return all zeroes +/// if read. If the file system supports deallocation, then it may free the +/// underlying storage, too. +/// +/// # Arguments +/// +/// - `fd` - File to operate on +/// - `range.0` - File offset at which to begin deallocation +/// - `range.1` - Length of the region to deallocate +/// +/// # Returns +/// +/// The operation may deallocate less than the entire requested region. On +/// success, it returns the region that still remains to be deallocated. The +/// caller should loop until the returned region is empty. +/// +/// # Example +/// +#[cfg_attr(fbsd14, doc = " ```")] +#[cfg_attr(not(fbsd14), doc = " ```no_run")] +/// # use std::io::Write; +/// # use std::os::unix::fs::FileExt; +/// # use std::os::unix::io::AsRawFd; +/// # use nix::fcntl::*; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"0123456789abcdef"; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// let mut range = SpacectlRange(3, 6); +/// while (!range.is_empty()) { +/// range = fspacectl(f.as_raw_fd(), range).unwrap(); +/// } +/// let mut buf = vec![0; INITIAL.len()]; +/// f.read_exact_at(&mut buf, 0).unwrap(); +/// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); +/// ``` +#[cfg(target_os = "freebsd")] +pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result { + let mut rqsr = libc::spacectl_range{r_offset: range.0, r_len: range.1}; + let res = unsafe { libc::fspacectl( + fd, + libc::SPACECTL_DEALLOC, // Only one command is supported ATM + &rqsr, + 0, // No flags are currently supported + &mut rqsr + )}; + Errno::result(res).map(|_| SpacectlRange(rqsr.r_offset, rqsr.r_len)) +} + +/// Like [`fspacectl`], but will never return incomplete. +/// +/// # Arguments +/// +/// - `fd` - File to operate on +/// - `offset` - File offset at which to begin deallocation +/// - `len` - Length of the region to deallocate +/// +/// # Returns +/// +/// Returns `()` on success. On failure, the region may or may not be partially +/// deallocated. +/// +/// # Example +/// +#[cfg_attr(fbsd14, doc = " ```")] +#[cfg_attr(not(fbsd14), doc = " ```no_run")] +/// # use std::io::Write; +/// # use std::os::unix::fs::FileExt; +/// # use std::os::unix::io::AsRawFd; +/// # use nix::fcntl::*; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"0123456789abcdef"; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// fspacectl_all(f.as_raw_fd(), 3, 6).unwrap(); +/// let mut buf = vec![0; INITIAL.len()]; +/// f.read_exact_at(&mut buf, 0).unwrap(); +/// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); +/// ``` +#[cfg(target_os = "freebsd")] +pub fn fspacectl_all(fd: RawFd, offset: libc::off_t, len: libc::off_t) + -> Result<()> +{ + let mut rqsr = libc::spacectl_range{r_offset: offset, r_len: len}; + while rqsr.r_len > 0 { + let res = unsafe { libc::fspacectl( + fd, + libc::SPACECTL_DEALLOC, // Only one command is supported ATM + &rqsr, + 0, // No flags are currently supported + &mut rqsr + )}; + Errno::result(res)?; + } + Ok(()) +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "wasi", + target_env = "uclibc", + target_os = "freebsd" +))] +mod posix_fadvise { + use crate::errno::Errno; + use std::os::unix::io::RawFd; + use crate::Result; + + #[cfg(feature = "fs")] + libc_enum! { + #[repr(i32)] + #[non_exhaustive] + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub enum PosixFadviseAdvice { + POSIX_FADV_NORMAL, + POSIX_FADV_SEQUENTIAL, + POSIX_FADV_RANDOM, + POSIX_FADV_NOREUSE, + POSIX_FADV_WILLNEED, + POSIX_FADV_DONTNEED, + } + } + + feature! { + #![feature = "fs"] + pub fn posix_fadvise( + fd: RawFd, + offset: libc::off_t, + len: libc::off_t, + advice: PosixFadviseAdvice, + ) -> Result<()> { + let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) }; + + if res == 0 { + Ok(()) + } else { + Err(Errno::from_i32(res)) + } + } + } +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "wasi", + target_os = "freebsd" +))] +pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Result<()> { + let res = unsafe { libc::posix_fallocate(fd, offset, len) }; + match Errno::result(res) { + Err(err) => Err(err), + Ok(0) => Ok(()), + Ok(errno) => Err(Errno::from_i32(errno)), + } +} +} diff --git a/src/features.rs b/src/features.rs new file mode 100644 index 0000000..39d1760 --- /dev/null +++ b/src/features.rs @@ -0,0 +1,126 @@ +//! Feature tests for OS functionality +pub use self::os::*; + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod os { + use crate::sys::utsname::uname; + use crate::Result; + use std::os::unix::ffi::OsStrExt; + + // Features: + // * atomic cloexec on socket: 2.6.27 + // * pipe2: 2.6.27 + // * accept4: 2.6.28 + + static VERS_UNKNOWN: usize = 1; + static VERS_2_6_18: usize = 2; + static VERS_2_6_27: usize = 3; + static VERS_2_6_28: usize = 4; + static VERS_3: usize = 5; + + #[inline] + fn digit(dst: &mut usize, b: u8) { + *dst *= 10; + *dst += (b - b'0') as usize; + } + + fn parse_kernel_version() -> Result { + let u = uname()?; + + let mut curr: usize = 0; + let mut major: usize = 0; + let mut minor: usize = 0; + let mut patch: usize = 0; + + for &b in u.release().as_bytes() { + if curr >= 3 { + break; + } + + match b { + b'.' | b'-' => { + curr += 1; + } + b'0'..=b'9' => match curr { + 0 => digit(&mut major, b), + 1 => digit(&mut minor, b), + _ => digit(&mut patch, b), + }, + _ => break, + } + } + + Ok(if major >= 3 { + VERS_3 + } else if major >= 2 { + if minor >= 7 { + VERS_UNKNOWN + } else if minor >= 6 { + if patch >= 28 { + VERS_2_6_28 + } else if patch >= 27 { + VERS_2_6_27 + } else { + VERS_2_6_18 + } + } else { + VERS_UNKNOWN + } + } else { + VERS_UNKNOWN + }) + } + + fn kernel_version() -> Result { + static mut KERNEL_VERS: usize = 0; + + unsafe { + if KERNEL_VERS == 0 { + KERNEL_VERS = parse_kernel_version()?; + } + + Ok(KERNEL_VERS) + } + } + + /// Check if the OS supports atomic close-on-exec for sockets + pub fn socket_atomic_cloexec() -> bool { + kernel_version() + .map(|version| version >= VERS_2_6_27) + .unwrap_or(false) + } + + #[test] + pub fn test_parsing_kernel_version() { + assert!(kernel_version().unwrap() > 0); + } +} + +#[cfg(any( + target_os = "dragonfly", // Since ??? + target_os = "freebsd", // Since 10.0 + target_os = "illumos", // Since ??? + target_os = "netbsd", // Since 6.0 + target_os = "openbsd", // Since 5.7 + target_os = "redox", // Since 1-july-2020 +))] +mod os { + /// Check if the OS supports atomic close-on-exec for sockets + pub const fn socket_atomic_cloexec() -> bool { + true + } +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "fuchsia", + target_os = "haiku", + target_os = "solaris" +))] +mod os { + /// Check if the OS supports atomic close-on-exec for sockets + pub const fn socket_atomic_cloexec() -> bool { + false + } +} diff --git a/src/ifaddrs.rs b/src/ifaddrs.rs new file mode 100644 index 0000000..70b50b0 --- /dev/null +++ b/src/ifaddrs.rs @@ -0,0 +1,213 @@ +//! Query network interface addresses +//! +//! Uses the Linux and/or BSD specific function `getifaddrs` to query the list +//! of interfaces and their associated addresses. + +use cfg_if::cfg_if; +#[cfg(any(target_os = "ios", target_os = "macos"))] +use std::convert::TryFrom; +use std::ffi; +use std::iter::Iterator; +use std::mem; +use std::option::Option; + +use crate::net::if_::*; +use crate::sys::socket::{SockaddrLike, SockaddrStorage}; +use crate::{Errno, Result}; + +/// Describes a single address for an interface as returned by `getifaddrs`. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct InterfaceAddress { + /// Name of the network interface + pub interface_name: String, + /// Flags as from `SIOCGIFFLAGS` ioctl + pub flags: InterfaceFlags, + /// Network address of this interface + pub address: Option, + /// Netmask of this interface + pub netmask: Option, + /// Broadcast address of this interface, if applicable + pub broadcast: Option, + /// Point-to-point destination address + pub destination: Option, +} + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux"))] { + fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { + info.ifa_ifu + } + } else { + fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { + info.ifa_dstaddr + } + } +} + +/// Workaround a bug in XNU where netmasks will always have the wrong size in +/// the sa_len field due to the kernel ignoring trailing zeroes in the structure +/// when setting the field. See https://github.com/nix-rust/nix/issues/1709#issuecomment-1199304470 +/// +/// To fix this, we stack-allocate a new sockaddr_storage, zero it out, and +/// memcpy sa_len of the netmask to that new storage. Finally, we reset the +/// ss_len field to sizeof(sockaddr_storage). This is supposedly valid as all +/// members of the sockaddr_storage are "ok" with being zeroed out (there are +/// no pointers). +#[cfg(any(target_os = "ios", target_os = "macos"))] +unsafe fn workaround_xnu_bug(info: &libc::ifaddrs) -> Option { + let src_sock = info.ifa_netmask; + if src_sock.is_null() { + return None; + } + + let mut dst_sock = mem::MaybeUninit::::zeroed(); + + // memcpy only sa_len bytes, assume the rest is zero + std::ptr::copy_nonoverlapping( + src_sock as *const u8, + dst_sock.as_mut_ptr() as *mut u8, + (*src_sock).sa_len.into(), + ); + + // Initialize ss_len to sizeof(libc::sockaddr_storage). + (*dst_sock.as_mut_ptr()).ss_len = + u8::try_from(mem::size_of::()).unwrap(); + let dst_sock = dst_sock.assume_init(); + + let dst_sock_ptr = + &dst_sock as *const libc::sockaddr_storage as *const libc::sockaddr; + + SockaddrStorage::from_raw(dst_sock_ptr, None) +} + +impl InterfaceAddress { + /// Create an `InterfaceAddress` from the libc struct. + fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress { + let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) }; + let address = unsafe { SockaddrStorage::from_raw(info.ifa_addr, None) }; + #[cfg(any(target_os = "ios", target_os = "macos"))] + let netmask = unsafe { workaround_xnu_bug(info) }; + #[cfg(not(any(target_os = "ios", target_os = "macos")))] + let netmask = + unsafe { SockaddrStorage::from_raw(info.ifa_netmask, None) }; + let mut addr = InterfaceAddress { + interface_name: ifname.to_string_lossy().to_string(), + flags: InterfaceFlags::from_bits_truncate(info.ifa_flags as i32), + address, + netmask, + broadcast: None, + destination: None, + }; + + let ifu = get_ifu_from_sockaddr(info); + if addr.flags.contains(InterfaceFlags::IFF_POINTOPOINT) { + addr.destination = unsafe { SockaddrStorage::from_raw(ifu, None) }; + } else if addr.flags.contains(InterfaceFlags::IFF_BROADCAST) { + addr.broadcast = unsafe { SockaddrStorage::from_raw(ifu, None) }; + } + + addr + } +} + +/// Holds the results of `getifaddrs`. +/// +/// Use the function `getifaddrs` to create this Iterator. Note that the +/// actual list of interfaces can be iterated once and will be freed as +/// soon as the Iterator goes out of scope. +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct InterfaceAddressIterator { + base: *mut libc::ifaddrs, + next: *mut libc::ifaddrs, +} + +impl Drop for InterfaceAddressIterator { + fn drop(&mut self) { + unsafe { libc::freeifaddrs(self.base) }; + } +} + +impl Iterator for InterfaceAddressIterator { + type Item = InterfaceAddress; + fn next(&mut self) -> Option<::Item> { + match unsafe { self.next.as_ref() } { + Some(ifaddr) => { + self.next = ifaddr.ifa_next; + Some(InterfaceAddress::from_libc_ifaddrs(ifaddr)) + } + None => None, + } + } +} + +/// Get interface addresses using libc's `getifaddrs` +/// +/// Note that the underlying implementation differs between OSes. Only the +/// most common address families are supported by the nix crate (due to +/// lack of time and complexity of testing). The address family is encoded +/// in the specific variant of `SockaddrStorage` returned for the fields +/// `address`, `netmask`, `broadcast`, and `destination`. For any entry not +/// supported, the returned list will contain a `None` entry. +/// +/// # Example +/// ``` +/// let addrs = nix::ifaddrs::getifaddrs().unwrap(); +/// for ifaddr in addrs { +/// match ifaddr.address { +/// Some(address) => { +/// println!("interface {} address {}", +/// ifaddr.interface_name, address); +/// }, +/// None => { +/// println!("interface {} with unsupported address family", +/// ifaddr.interface_name); +/// } +/// } +/// } +/// ``` +pub fn getifaddrs() -> Result { + let mut addrs = mem::MaybeUninit::<*mut libc::ifaddrs>::uninit(); + unsafe { + Errno::result(libc::getifaddrs(addrs.as_mut_ptr())).map(|_| { + InterfaceAddressIterator { + base: addrs.assume_init(), + next: addrs.assume_init(), + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Only checks if `getifaddrs` can be invoked without panicking. + #[test] + fn test_getifaddrs() { + let _ = getifaddrs(); + } + + // Ensures getting the netmask works, and in particular that + // `workaround_xnu_bug` works properly. + #[test] + fn test_getifaddrs_netmask_correct() { + let addrs = getifaddrs().unwrap(); + for iface in addrs { + let sock = if let Some(sock) = iface.netmask { + sock + } else { + continue; + }; + if sock.family() == Some(crate::sys::socket::AddressFamily::Inet) { + let _ = sock.as_sockaddr_in().unwrap(); + return; + } else if sock.family() + == Some(crate::sys::socket::AddressFamily::Inet6) + { + let _ = sock.as_sockaddr_in6().unwrap(); + return; + } + } + panic!("No address?"); + } +} diff --git a/src/kmod.rs b/src/kmod.rs new file mode 100644 index 0000000..1fa6c17 --- /dev/null +++ b/src/kmod.rs @@ -0,0 +1,128 @@ +//! Load and unload kernel modules. +//! +//! For more details see + +use std::ffi::CStr; +use std::os::unix::io::AsRawFd; + +use crate::errno::Errno; +use crate::Result; + +/// Loads a kernel module from a buffer. +/// +/// It loads an ELF image into kernel space, +/// performs any necessary symbol relocations, +/// initializes module parameters to values provided by the caller, +/// and then runs the module's init function. +/// +/// This function requires `CAP_SYS_MODULE` privilege. +/// +/// The `module_image` argument points to a buffer containing the binary image +/// to be loaded. The buffer should contain a valid ELF image +/// built for the running kernel. +/// +/// The `param_values` argument is a string containing space-delimited specifications +/// of the values for module parameters. +/// Each of the parameter specifications has the form: +/// +/// `name[=value[,value...]]` +/// +/// # Example +/// +/// ```no_run +/// use std::fs::File; +/// use std::io::Read; +/// use std::ffi::CString; +/// use nix::kmod::init_module; +/// +/// let mut f = File::open("mykernel.ko").unwrap(); +/// let mut contents: Vec = Vec::new(); +/// f.read_to_end(&mut contents).unwrap(); +/// init_module(&mut contents, &CString::new("who=Rust when=Now,12").unwrap()).unwrap(); +/// ``` +/// +/// See [`man init_module(2)`](https://man7.org/linux/man-pages/man2/init_module.2.html) for more information. +pub fn init_module(module_image: &[u8], param_values: &CStr) -> Result<()> { + let res = unsafe { + libc::syscall( + libc::SYS_init_module, + module_image.as_ptr(), + module_image.len(), + param_values.as_ptr(), + ) + }; + + Errno::result(res).map(drop) +} + +libc_bitflags!( + /// Flags used by the `finit_module` function. + pub struct ModuleInitFlags: libc::c_uint { + /// Ignore symbol version hashes. + MODULE_INIT_IGNORE_MODVERSIONS; + /// Ignore kernel version magic. + MODULE_INIT_IGNORE_VERMAGIC; + } +); + +/// Loads a kernel module from a given file descriptor. +/// +/// # Example +/// +/// ```no_run +/// use std::fs::File; +/// use std::ffi::CString; +/// use nix::kmod::{finit_module, ModuleInitFlags}; +/// +/// let f = File::open("mymod.ko").unwrap(); +/// finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()).unwrap(); +/// ``` +/// +/// See [`man init_module(2)`](https://man7.org/linux/man-pages/man2/init_module.2.html) for more information. +pub fn finit_module( + fd: &T, + param_values: &CStr, + flags: ModuleInitFlags, +) -> Result<()> { + let res = unsafe { + libc::syscall( + libc::SYS_finit_module, + fd.as_raw_fd(), + param_values.as_ptr(), + flags.bits(), + ) + }; + + Errno::result(res).map(drop) +} + +libc_bitflags!( + /// Flags used by `delete_module`. + /// + /// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html) + /// for a detailed description how these flags work. + pub struct DeleteModuleFlags: libc::c_int { + O_NONBLOCK; + O_TRUNC; + } +); + +/// Unloads the kernel module with the given name. +/// +/// # Example +/// +/// ```no_run +/// use std::ffi::CString; +/// use nix::kmod::{delete_module, DeleteModuleFlags}; +/// +/// delete_module(&CString::new("mymod").unwrap(), DeleteModuleFlags::O_NONBLOCK).unwrap(); +/// ``` +/// +/// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html) for more information. +pub fn delete_module(name: &CStr, flags: DeleteModuleFlags) -> Result<()> { + let res = unsafe { + libc::syscall(libc::SYS_delete_module, name.as_ptr(), flags.bits()) + }; + + Errno::result(res).map(drop) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6b82125 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,333 @@ +//! Rust friendly bindings to the various *nix system functions. +//! +//! Modules are structured according to the C header file that they would be +//! defined in. +//! +//! # Features +//! +//! Nix uses the following Cargo features to enable optional functionality. +//! They may be enabled in any combination. +//! * `acct` - Process accounting +//! * `aio` - POSIX AIO +//! * `dir` - Stuff relating to directory iteration +//! * `env` - Manipulate environment variables +//! * `event` - Event-driven APIs, like `kqueue` and `epoll` +//! * `feature` - Query characteristics of the OS at runtime +//! * `fs` - File system functionality +//! * `hostname` - Get and set the system's hostname +//! * `inotify` - Linux's `inotify` file system notification API +//! * `ioctl` - The `ioctl` syscall, and wrappers for my specific instances +//! * `kmod` - Load and unload kernel modules +//! * `mman` - Stuff relating to memory management +//! * `mount` - Mount and unmount file systems +//! * `mqueue` - POSIX message queues +//! * `net` - Networking-related functionality +//! * `personality` - Set the process execution domain +//! * `poll` - APIs like `poll` and `select` +//! * `process` - Stuff relating to running processes +//! * `pthread` - POSIX threads +//! * `ptrace` - Process tracing and debugging +//! * `quota` - File system quotas +//! * `reboot` - Reboot the system +//! * `resource` - Process resource limits +//! * `sched` - Manipulate process's scheduling +//! * `socket` - Sockets, whether for networking or local use +//! * `signal` - Send and receive signals to processes +//! * `term` - Terminal control APIs +//! * `time` - Query the operating system's clocks +//! * `ucontext` - User thread context +//! * `uio` - Vectored I/O +//! * `user` - Stuff relating to users and groups +//! * `zerocopy` - APIs like `sendfile` and `copy_file_range` +#![crate_name = "nix"] +#![cfg(unix)] +#![cfg_attr(docsrs, doc(cfg(all())))] +#![allow(non_camel_case_types)] +#![cfg_attr(test, deny(warnings))] +#![recursion_limit = "500"] +#![deny(unused)] +#![allow(unused_macros)] +#![cfg_attr(not(feature = "default"), allow(unused_imports))] +#![deny(unstable_features)] +#![deny(missing_copy_implementations)] +#![deny(missing_debug_implementations)] +#![warn(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny(clippy::cast_ptr_alignment)] + +// Re-exported external crates +pub use libc; + +// Private internal modules +#[macro_use] +mod macros; + +// Public crates +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "dir"] + pub mod dir; +} +feature! { + #![feature = "env"] + pub mod env; +} +#[allow(missing_docs)] +pub mod errno; +feature! { + #![feature = "feature"] + + #[deny(missing_docs)] + pub mod features; +} +#[allow(missing_docs)] +pub mod fcntl; +feature! { + #![feature = "net"] + + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd"))] + #[deny(missing_docs)] + pub mod ifaddrs; + #[cfg(not(target_os = "redox"))] + #[deny(missing_docs)] + pub mod net; +} +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "kmod"] + #[allow(missing_docs)] + pub mod kmod; +} +feature! { + #![feature = "mount"] + pub mod mount; +} +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" +))] +feature! { + #![feature = "mqueue"] + pub mod mqueue; +} +feature! { + #![feature = "poll"] + pub mod poll; +} +#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +feature! { + #![feature = "term"] + #[deny(missing_docs)] + pub mod pty; +} +feature! { + #![feature = "sched"] + pub mod sched; +} +pub mod sys; +feature! { + #![feature = "time"] + #[allow(missing_docs)] + pub mod time; +} +// This can be implemented for other platforms as soon as libc +// provides bindings for them. +#[cfg(all( + target_os = "linux", + any(target_arch = "s390x", target_arch = "x86", target_arch = "x86_64") +))] +feature! { + #![feature = "ucontext"] + #[allow(missing_docs)] + pub mod ucontext; +} +#[allow(missing_docs)] +pub mod unistd; + +use std::ffi::{CStr, CString, OsStr}; +use std::mem::MaybeUninit; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; +use std::{ptr, result, slice}; + +use errno::Errno; + +/// Nix Result Type +pub type Result = result::Result; + +/// Nix's main error type. +/// +/// It's a wrapper around Errno. As such, it's very interoperable with +/// [`std::io::Error`], but it has the advantages of: +/// * `Clone` +/// * `Copy` +/// * `Eq` +/// * Small size +/// * Represents all of the system's errnos, instead of just the most common +/// ones. +pub type Error = Errno; + +/// Common trait used to represent file system paths by many Nix functions. +pub trait NixPath { + /// Is the path empty? + fn is_empty(&self) -> bool; + + /// Length of the path in bytes + fn len(&self) -> usize; + + /// Execute a function with this path as a `CStr`. + /// + /// Mostly used internally by Nix. + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T; +} + +impl NixPath for str { + fn is_empty(&self) -> bool { + NixPath::is_empty(OsStr::new(self)) + } + + fn len(&self) -> usize { + NixPath::len(OsStr::new(self)) + } + + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { + OsStr::new(self).with_nix_path(f) + } +} + +impl NixPath for OsStr { + fn is_empty(&self) -> bool { + self.as_bytes().is_empty() + } + + fn len(&self) -> usize { + self.as_bytes().len() + } + + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { + self.as_bytes().with_nix_path(f) + } +} + +impl NixPath for CStr { + fn is_empty(&self) -> bool { + self.to_bytes().is_empty() + } + + fn len(&self) -> usize { + self.to_bytes().len() + } + + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { + Ok(f(self)) + } +} + +impl NixPath for [u8] { + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn len(&self) -> usize { + self.len() + } + + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { + // The real PATH_MAX is typically 4096, but it's statistically unlikely to have a path + // longer than ~300 bytes. See the the PR description to get stats for your own machine. + // https://github.com/nix-rust/nix/pull/1656 + // + // By being smaller than a memory page, we also avoid the compiler inserting a probe frame: + // https://docs.rs/compiler_builtins/latest/compiler_builtins/probestack/index.html + const MAX_STACK_ALLOCATION: usize = 1024; + + if self.len() >= MAX_STACK_ALLOCATION { + return with_nix_path_allocating(self, f); + } + + let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit(); + let buf_ptr = buf.as_mut_ptr() as *mut u8; + + unsafe { + ptr::copy_nonoverlapping(self.as_ptr(), buf_ptr, self.len()); + buf_ptr.add(self.len()).write(0); + } + + match CStr::from_bytes_with_nul(unsafe { + slice::from_raw_parts(buf_ptr, self.len() + 1) + }) { + Ok(s) => Ok(f(s)), + Err(_) => Err(Errno::EINVAL), + } + } +} + +#[cold] +#[inline(never)] +fn with_nix_path_allocating(from: &[u8], f: F) -> Result +where + F: FnOnce(&CStr) -> T, +{ + match CString::new(from) { + Ok(s) => Ok(f(&s)), + Err(_) => Err(Errno::EINVAL), + } +} + +impl NixPath for Path { + fn is_empty(&self) -> bool { + NixPath::is_empty(self.as_os_str()) + } + + fn len(&self) -> usize { + NixPath::len(self.as_os_str()) + } + + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { + self.as_os_str().with_nix_path(f) + } +} + +impl NixPath for PathBuf { + fn is_empty(&self) -> bool { + NixPath::is_empty(self.as_os_str()) + } + + fn len(&self) -> usize { + NixPath::len(self.as_os_str()) + } + + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { + self.as_os_str().with_nix_path(f) + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..99e0de8 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,328 @@ +// Thanks to Tokio for this macro +macro_rules! feature { + ( + #![$meta:meta] + $($item:item)* + ) => { + $( + #[cfg($meta)] + #[cfg_attr(docsrs, doc(cfg($meta)))] + $item + )* + } +} + +/// The `libc_bitflags!` macro helps with a common use case of defining a public bitflags type +/// with values from the libc crate. It is used the same way as the `bitflags!` macro, except +/// that only the name of the flag value has to be given. +/// +/// The `libc` crate must be in scope with the name `libc`. +/// +/// # Example +/// ```ignore +/// libc_bitflags!{ +/// pub struct ProtFlags: libc::c_int { +/// PROT_NONE; +/// PROT_READ; +/// /// PROT_WRITE enables write protect +/// PROT_WRITE; +/// PROT_EXEC; +/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// PROT_GROWSDOWN; +/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// PROT_GROWSUP; +/// } +/// } +/// ``` +/// +/// Example with casting, due to a mistake in libc. In this example, the +/// various flags have different types, so we cast the broken ones to the right +/// type. +/// +/// ```ignore +/// libc_bitflags!{ +/// pub struct SaFlags: libc::c_ulong { +/// SA_NOCLDSTOP as libc::c_ulong; +/// SA_NOCLDWAIT; +/// SA_NODEFER as libc::c_ulong; +/// SA_ONSTACK; +/// SA_RESETHAND as libc::c_ulong; +/// SA_RESTART as libc::c_ulong; +/// SA_SIGINFO; +/// } +/// } +/// ``` +macro_rules! libc_bitflags { + ( + $(#[$outer:meta])* + pub struct $BitFlags:ident: $T:ty { + $( + $(#[$inner:ident $($args:tt)*])* + $Flag:ident $(as $cast:ty)*; + )+ + } + ) => { + ::bitflags::bitflags! { + $(#[$outer])* + pub struct $BitFlags: $T { + $( + $(#[$inner $($args)*])* + const $Flag = libc::$Flag $(as $cast)*; + )+ + } + } + }; +} + +/// The `libc_enum!` macro helps with a common use case of defining an enum exclusively using +/// values from the `libc` crate. This macro supports both `pub` and private `enum`s. +/// +/// The `libc` crate must be in scope with the name `libc`. +/// +/// # Example +/// ```ignore +/// libc_enum!{ +/// pub enum ProtFlags { +/// PROT_NONE, +/// PROT_READ, +/// PROT_WRITE, +/// PROT_EXEC, +/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// PROT_GROWSDOWN, +/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// PROT_GROWSUP, +/// } +/// } +/// ``` +// Some targets don't use all rules. +#[allow(unknown_lints)] +#[allow(unused_macro_rules)] +macro_rules! libc_enum { + // Exit rule. + (@make_enum + name: $BitFlags:ident, + { + $v:vis + attrs: [$($attrs:tt)*], + entries: [$($entries:tt)*], + } + ) => { + $($attrs)* + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + $v enum $BitFlags { + $($entries)* + } + }; + + // Exit rule including TryFrom + (@make_enum + name: $BitFlags:ident, + { + $v:vis + attrs: [$($attrs:tt)*], + entries: [$($entries:tt)*], + from_type: $repr:path, + try_froms: [$($try_froms:tt)*] + } + ) => { + $($attrs)* + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + $v enum $BitFlags { + $($entries)* + } + impl ::std::convert::TryFrom<$repr> for $BitFlags { + type Error = $crate::Error; + #[allow(unused_doc_comments)] + fn try_from(x: $repr) -> $crate::Result { + match x { + $($try_froms)* + _ => Err($crate::Error::EINVAL) + } + } + } + }; + + // Done accumulating. + (@accumulate_entries + name: $BitFlags:ident, + { + $v:vis + attrs: $attrs:tt, + }, + $entries:tt, + $try_froms:tt; + ) => { + libc_enum! { + @make_enum + name: $BitFlags, + { + $v + attrs: $attrs, + entries: $entries, + } + } + }; + + // Done accumulating and want TryFrom + (@accumulate_entries + name: $BitFlags:ident, + { + $v:vis + attrs: $attrs:tt, + from_type: $repr:path, + }, + $entries:tt, + $try_froms:tt; + ) => { + libc_enum! { + @make_enum + name: $BitFlags, + { + $v + attrs: $attrs, + entries: $entries, + from_type: $repr, + try_froms: $try_froms + } + } + }; + + // Munch an attr. + (@accumulate_entries + name: $BitFlags:ident, + $prefix:tt, + [$($entries:tt)*], + [$($try_froms:tt)*]; + #[$attr:meta] $($tail:tt)* + ) => { + libc_enum! { + @accumulate_entries + name: $BitFlags, + $prefix, + [ + $($entries)* + #[$attr] + ], + [ + $($try_froms)* + #[$attr] + ]; + $($tail)* + } + }; + + // Munch last ident if not followed by a comma. + (@accumulate_entries + name: $BitFlags:ident, + $prefix:tt, + [$($entries:tt)*], + [$($try_froms:tt)*]; + $entry:ident + ) => { + libc_enum! { + @accumulate_entries + name: $BitFlags, + $prefix, + [ + $($entries)* + $entry = libc::$entry, + ], + [ + $($try_froms)* + libc::$entry => Ok($BitFlags::$entry), + ]; + } + }; + + // Munch an ident; covers terminating comma case. + (@accumulate_entries + name: $BitFlags:ident, + $prefix:tt, + [$($entries:tt)*], + [$($try_froms:tt)*]; + $entry:ident, + $($tail:tt)* + ) => { + libc_enum! { + @accumulate_entries + name: $BitFlags, + $prefix, + [ + $($entries)* + $entry = libc::$entry, + ], + [ + $($try_froms)* + libc::$entry => Ok($BitFlags::$entry), + ]; + $($tail)* + } + }; + + // Munch an ident and cast it to the given type; covers terminating comma. + (@accumulate_entries + name: $BitFlags:ident, + $prefix:tt, + [$($entries:tt)*], + [$($try_froms:tt)*]; + $entry:ident as $ty:ty, + $($tail:tt)* + ) => { + libc_enum! { + @accumulate_entries + name: $BitFlags, + $prefix, + [ + $($entries)* + $entry = libc::$entry as $ty, + ], + [ + $($try_froms)* + libc::$entry as $ty => Ok($BitFlags::$entry), + ]; + $($tail)* + } + }; + + // Entry rule. + ( + $(#[$attr:meta])* + $v:vis enum $BitFlags:ident { + $($vals:tt)* + } + ) => { + libc_enum! { + @accumulate_entries + name: $BitFlags, + { + $v + attrs: [$(#[$attr])*], + }, + [], + []; + $($vals)* + } + }; + + // Entry rule including TryFrom + ( + $(#[$attr:meta])* + $v:vis enum $BitFlags:ident { + $($vals:tt)* + } + impl TryFrom<$repr:path> + ) => { + libc_enum! { + @accumulate_entries + name: $BitFlags, + { + $v + attrs: [$(#[$attr])*], + from_type: $repr, + }, + [], + []; + $($vals)* + } + }; +} diff --git a/src/mount/bsd.rs b/src/mount/bsd.rs new file mode 100644 index 0000000..d124f1f --- /dev/null +++ b/src/mount/bsd.rs @@ -0,0 +1,453 @@ +#[cfg(target_os = "freebsd")] +use crate::Error; +use crate::{Errno, NixPath, Result}; +use libc::c_int; +#[cfg(target_os = "freebsd")] +use libc::{c_char, c_uint, c_void}; +#[cfg(target_os = "freebsd")] +use std::{ + borrow::Cow, + ffi::{CStr, CString}, + fmt, io, + marker::PhantomData, +}; + +libc_bitflags!( + /// Used with [`Nmount::nmount`]. + pub struct MntFlags: c_int { + /// ACL support enabled. + #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_ACLS; + /// All I/O to the file system should be done asynchronously. + MNT_ASYNC; + /// dir should instead be a file system ID encoded as “FSID:val0:val1”. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_BYFSID; + /// Force a read-write mount even if the file system appears to be + /// unclean. + MNT_FORCE; + /// GEOM journal support enabled. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_GJOURNAL; + /// MAC support for objects. + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_MULTILABEL; + /// Disable read clustering. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_NOCLUSTERR; + /// Disable write clustering. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_NOCLUSTERW; + /// Enable NFS version 4 ACLs. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_NFS4ACLS; + /// Do not update access times. + MNT_NOATIME; + /// Disallow program execution. + MNT_NOEXEC; + /// Do not honor setuid or setgid bits on files when executing them. + MNT_NOSUID; + /// Do not follow symlinks. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_NOSYMFOLLOW; + /// Mount read-only. + MNT_RDONLY; + /// Causes the vfs subsystem to update its data structures pertaining to + /// the specified already mounted file system. + MNT_RELOAD; + /// Create a snapshot of the file system. + /// + /// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs) + #[cfg(any(target_os = "macos", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_SNAPSHOT; + /// Using soft updates. + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_SOFTDEP; + /// Directories with the SUID bit set chown new files to their own + /// owner. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_SUIDDIR; + /// All I/O to the file system should be done synchronously. + MNT_SYNCHRONOUS; + /// Union with underlying fs. + #[cfg(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_UNION; + /// Indicates that the mount command is being applied to an already + /// mounted file system. + MNT_UPDATE; + /// Check vnode use counts. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MNT_NONBUSY; + } +); + +/// The Error type of [`Nmount::nmount`]. +/// +/// It wraps an [`Errno`], but also may contain an additional message returned +/// by `nmount(2)`. +#[cfg(target_os = "freebsd")] +#[derive(Debug)] +pub struct NmountError { + errno: Error, + errmsg: Option, +} + +#[cfg(target_os = "freebsd")] +impl NmountError { + /// Returns the additional error string sometimes generated by `nmount(2)`. + pub fn errmsg(&self) -> Option<&str> { + self.errmsg.as_deref() + } + + /// Returns the inner [`Error`] + pub const fn error(&self) -> Error { + self.errno + } + + fn new(error: Error, errmsg: Option<&CStr>) -> Self { + Self { + errno: error, + errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned), + } + } +} + +#[cfg(target_os = "freebsd")] +impl std::error::Error for NmountError {} + +#[cfg(target_os = "freebsd")] +impl fmt::Display for NmountError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(errmsg) = &self.errmsg { + write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc()) + } else { + write!(f, "{:?}: {}", self.errno, self.errno.desc()) + } + } +} + +#[cfg(target_os = "freebsd")] +impl From for io::Error { + fn from(err: NmountError) -> Self { + err.errno.into() + } +} + +/// Result type of [`Nmount::nmount`]. +#[cfg(target_os = "freebsd")] +pub type NmountResult = std::result::Result<(), NmountError>; + +/// Mount a FreeBSD file system. +/// +/// The `nmount(2)` system call works similarly to the `mount(8)` program; it +/// takes its options as a series of name-value pairs. Most of the values are +/// strings, as are all of the names. The `Nmount` structure builds up an +/// argument list and then executes the syscall. +/// +/// # Examples +/// +/// To mount `target` onto `mountpoint` with `nullfs`: +/// ``` +/// # use nix::unistd::Uid; +/// # use ::sysctl::{CtlValue, Sysctl}; +/// # let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); +/// # if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() { +/// # return; +/// # }; +/// use nix::mount::{MntFlags, Nmount, unmount}; +/// use std::ffi::CString; +/// use tempfile::tempdir; +/// +/// let mountpoint = tempdir().unwrap(); +/// let target = tempdir().unwrap(); +/// +/// let fstype = CString::new("fstype").unwrap(); +/// let nullfs = CString::new("nullfs").unwrap(); +/// Nmount::new() +/// .str_opt(&fstype, &nullfs) +/// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) +/// .str_opt_owned("target", target.path().to_str().unwrap()) +/// .nmount(MntFlags::empty()).unwrap(); +/// +/// unmount(mountpoint.path(), MntFlags::empty()).unwrap(); +/// ``` +/// +/// # See Also +/// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount) +/// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs) +#[cfg(target_os = "freebsd")] +#[cfg_attr(docsrs, doc(cfg(all())))] +#[derive(Debug, Default)] +pub struct Nmount<'a> { + // n.b. notgull: In reality, this is a list that contains + // both mutable and immutable pointers. + // Be careful using this. + iov: Vec, + is_owned: Vec, + marker: PhantomData<&'a ()>, +} + +#[cfg(target_os = "freebsd")] +#[cfg_attr(docsrs, doc(cfg(all())))] +impl<'a> Nmount<'a> { + /// Helper function to push a slice onto the `iov` array. + fn push_slice(&mut self, val: &'a [u8], is_owned: bool) { + self.iov.push(libc::iovec { + iov_base: val.as_ptr() as *mut _, + iov_len: val.len(), + }); + self.is_owned.push(is_owned); + } + + /// Helper function to push a pointer and its length onto the `iov` array. + fn push_pointer_and_length( + &mut self, + val: *const u8, + len: usize, + is_owned: bool, + ) { + self.iov.push(libc::iovec { + iov_base: val as *mut _, + iov_len: len, + }); + self.is_owned.push(is_owned); + } + + /// Helper function to push a `nix` path as owned. + fn push_nix_path(&mut self, val: &P) { + val.with_nix_path(|s| { + let len = s.to_bytes_with_nul().len(); + let ptr = s.to_owned().into_raw() as *const u8; + + self.push_pointer_and_length(ptr, len, true); + }) + .unwrap(); + } + + /// Add an opaque mount option. + /// + /// Some file systems take binary-valued mount options. They can be set + /// with this method. + /// + /// # Safety + /// + /// Unsafe because it will cause `Nmount::nmount` to dereference a raw + /// pointer. The user is responsible for ensuring that `val` is valid and + /// its lifetime outlives `self`! An easy way to do that is to give the + /// value a larger scope than `name` + /// + /// # Examples + /// ``` + /// use libc::c_void; + /// use nix::mount::Nmount; + /// use std::ffi::CString; + /// use std::mem; + /// + /// // Note that flags outlives name + /// let mut flags: u32 = 0xdeadbeef; + /// let name = CString::new("flags").unwrap(); + /// let p = &mut flags as *mut u32 as *mut c_void; + /// let len = mem::size_of_val(&flags); + /// let mut nmount = Nmount::new(); + /// unsafe { nmount.mut_ptr_opt(&name, p, len) }; + /// ``` + pub unsafe fn mut_ptr_opt( + &mut self, + name: &'a CStr, + val: *mut c_void, + len: usize, + ) -> &mut Self { + self.push_slice(name.to_bytes_with_nul(), false); + self.push_pointer_and_length(val.cast(), len, false); + self + } + + /// Add a mount option that does not take a value. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// use std::ffi::CString; + /// + /// let read_only = CString::new("ro").unwrap(); + /// Nmount::new() + /// .null_opt(&read_only); + /// ``` + pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self { + self.push_slice(name.to_bytes_with_nul(), false); + self.push_slice(&[], false); + self + } + + /// Add a mount option that does not take a value, but whose name must be + /// owned. + /// + /// + /// This has higher runtime cost than [`Nmount::null_opt`], but is useful + /// when the name's lifetime doesn't outlive the `Nmount`, or it's a + /// different string type than `CStr`. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// + /// let read_only = "ro"; + /// let mut nmount: Nmount<'static> = Nmount::new(); + /// nmount.null_opt_owned(read_only); + /// ``` + pub fn null_opt_owned( + &mut self, + name: &P, + ) -> &mut Self { + self.push_nix_path(name); + self.push_slice(&[], false); + self + } + + /// Add a mount option as a [`CStr`]. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// use std::ffi::CString; + /// + /// let fstype = CString::new("fstype").unwrap(); + /// let nullfs = CString::new("nullfs").unwrap(); + /// Nmount::new() + /// .str_opt(&fstype, &nullfs); + /// ``` + pub fn str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self { + self.push_slice(name.to_bytes_with_nul(), false); + self.push_slice(val.to_bytes_with_nul(), false); + self + } + + /// Add a mount option as an owned string. + /// + /// This has higher runtime cost than [`Nmount::str_opt`], but is useful + /// when the value's lifetime doesn't outlive the `Nmount`, or it's a + /// different string type than `CStr`. + /// + /// # Examples + /// ``` + /// use nix::mount::Nmount; + /// use std::path::Path; + /// + /// let mountpoint = Path::new("/mnt"); + /// Nmount::new() + /// .str_opt_owned("fspath", mountpoint.to_str().unwrap()); + /// ``` + pub fn str_opt_owned(&mut self, name: &P1, val: &P2) -> &mut Self + where + P1: ?Sized + NixPath, + P2: ?Sized + NixPath, + { + self.push_nix_path(name); + self.push_nix_path(val); + self + } + + /// Create a new `Nmount` struct with no options + pub fn new() -> Self { + Self::default() + } + + /// Actually mount the file system. + pub fn nmount(&mut self, flags: MntFlags) -> NmountResult { + const ERRMSG_NAME: &[u8] = b"errmsg\0"; + let mut errmsg = vec![0u8; 255]; + + // nmount can return extra error information via a "errmsg" return + // argument. + self.push_slice(ERRMSG_NAME, false); + + // SAFETY: we are pushing a mutable iovec here, so we can't use + // the above method + self.iov.push(libc::iovec { + iov_base: errmsg.as_mut_ptr() as *mut c_void, + iov_len: errmsg.len(), + }); + + let niov = self.iov.len() as c_uint; + let iovp = self.iov.as_mut_ptr() as *mut libc::iovec; + let res = unsafe { libc::nmount(iovp, niov, flags.bits) }; + match Errno::result(res) { + Ok(_) => Ok(()), + Err(error) => { + let errmsg = match errmsg.iter().position(|&x| x == 0) { + None => None, + Some(0) => None, + Some(n) => { + let sl = &errmsg[0..n + 1]; + Some(CStr::from_bytes_with_nul(sl).unwrap()) + } + }; + Err(NmountError::new(error, errmsg)) + } + } + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> Drop for Nmount<'a> { + fn drop(&mut self) { + for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) { + if *is_owned { + // Free the owned string. Safe because we recorded ownership, + // and Nmount does not implement Clone. + unsafe { + drop(CString::from_raw(iov.iov_base as *mut c_char)); + } + } + } + } +} + +/// Unmount the file system mounted at `mountpoint`. +/// +/// Useful flags include +/// * `MNT_FORCE` - Unmount even if still in use. +#[cfg_attr( + target_os = "freebsd", + doc = " +* `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID + encoded as `FSID:val0:val1`, where `val0` and `val1` + are the contents of the `fsid_t val[]` array in decimal. + The file system that has the specified file system ID + will be unmounted. See + [`statfs`](crate::sys::statfs::statfs) to determine the + `fsid`. +" +)] +pub fn unmount

(mountpoint: &P, flags: MntFlags) -> Result<()> +where + P: ?Sized + NixPath, +{ + let res = mountpoint.with_nix_path(|cstr| unsafe { + libc::unmount(cstr.as_ptr(), flags.bits) + })?; + + Errno::result(res).map(drop) +} diff --git a/src/mount/linux.rs b/src/mount/linux.rs new file mode 100644 index 0000000..cf6a60b --- /dev/null +++ b/src/mount/linux.rs @@ -0,0 +1,115 @@ +#![allow(missing_docs)] +use crate::errno::Errno; +use crate::{NixPath, Result}; +use libc::{self, c_int, c_ulong}; + +libc_bitflags!( + pub struct MsFlags: c_ulong { + /// Mount read-only + MS_RDONLY; + /// Ignore suid and sgid bits + MS_NOSUID; + /// Disallow access to device special files + MS_NODEV; + /// Disallow program execution + MS_NOEXEC; + /// Writes are synced at once + MS_SYNCHRONOUS; + /// Alter flags of a mounted FS + MS_REMOUNT; + /// Allow mandatory locks on a FS + MS_MANDLOCK; + /// Directory modifications are synchronous + MS_DIRSYNC; + /// Do not update access times + MS_NOATIME; + /// Do not update directory access times + MS_NODIRATIME; + /// Linux 2.4.0 - Bind directory at different place + MS_BIND; + MS_MOVE; + MS_REC; + MS_SILENT; + MS_POSIXACL; + MS_UNBINDABLE; + MS_PRIVATE; + MS_SLAVE; + MS_SHARED; + MS_RELATIME; + MS_KERNMOUNT; + MS_I_VERSION; + MS_STRICTATIME; + MS_LAZYTIME; + MS_ACTIVE; + MS_NOUSER; + MS_RMT_MASK; + MS_MGC_VAL; + MS_MGC_MSK; + } +); + +libc_bitflags!( + pub struct MntFlags: c_int { + MNT_FORCE; + MNT_DETACH; + MNT_EXPIRE; + UMOUNT_NOFOLLOW; + } +); + +pub fn mount< + P1: ?Sized + NixPath, + P2: ?Sized + NixPath, + P3: ?Sized + NixPath, + P4: ?Sized + NixPath, +>( + source: Option<&P1>, + target: &P2, + fstype: Option<&P3>, + flags: MsFlags, + data: Option<&P4>, +) -> Result<()> { + fn with_opt_nix_path(p: Option<&P>, f: F) -> Result + where + P: ?Sized + NixPath, + F: FnOnce(*const libc::c_char) -> T, + { + match p { + Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), + None => Ok(f(std::ptr::null())), + } + } + + let res = with_opt_nix_path(source, |s| { + target.with_nix_path(|t| { + with_opt_nix_path(fstype, |ty| { + with_opt_nix_path(data, |d| unsafe { + libc::mount( + s, + t.as_ptr(), + ty, + flags.bits, + d as *const libc::c_void, + ) + }) + }) + }) + })????; + + Errno::result(res).map(drop) +} + +pub fn umount(target: &P) -> Result<()> { + let res = + target.with_nix_path(|cstr| unsafe { libc::umount(cstr.as_ptr()) })?; + + Errno::result(res).map(drop) +} + +pub fn umount2(target: &P, flags: MntFlags) -> Result<()> { + let res = target.with_nix_path(|cstr| unsafe { + libc::umount2(cstr.as_ptr(), flags.bits) + })?; + + Errno::result(res).map(drop) +} diff --git a/src/mount/mod.rs b/src/mount/mod.rs new file mode 100644 index 0000000..e98b49c --- /dev/null +++ b/src/mount/mod.rs @@ -0,0 +1,26 @@ +//! Mount file systems +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod linux; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::linux::*; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod bsd; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +pub use self::bsd::*; diff --git a/src/mqueue.rs b/src/mqueue.rs new file mode 100644 index 0000000..33599bf --- /dev/null +++ b/src/mqueue.rs @@ -0,0 +1,276 @@ +//! Posix Message Queue functions +//! +//! # Example +//! +// no_run because a kernel module may be required. +//! ```no_run +//! # use std::ffi::CString; +//! # use nix::mqueue::*; +//! use nix::sys::stat::Mode; +//! +//! const MSG_SIZE: mq_attr_member_t = 32; +//! let mq_name= CString::new("/a_nix_test_queue").unwrap(); +//! +//! let oflag0 = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; +//! let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; +//! let mqd0 = mq_open(&mq_name, oflag0, mode, None).unwrap(); +//! let msg_to_send = b"msg_1"; +//! mq_send(&mqd0, msg_to_send, 1).unwrap(); +//! +//! let oflag1 = MQ_OFlag::O_CREAT | MQ_OFlag::O_RDONLY; +//! let mqd1 = mq_open(&mq_name, oflag1, mode, None).unwrap(); +//! let mut buf = [0u8; 32]; +//! let mut prio = 0u32; +//! let len = mq_receive(&mqd1, &mut buf, &mut prio).unwrap(); +//! assert_eq!(prio, 1); +//! assert_eq!(msg_to_send, &buf[0..len]); +//! +//! mq_close(mqd1).unwrap(); +//! mq_close(mqd0).unwrap(); +//! ``` +//! [Further reading and details on the C API](https://man7.org/linux/man-pages/man7/mq_overview.7.html) + +use crate::errno::Errno; +use crate::Result; + +use crate::sys::stat::Mode; +use libc::{self, c_char, mqd_t, size_t}; +use std::ffi::CStr; +use std::mem; + +libc_bitflags! { + /// Used with [`mq_open`]. + pub struct MQ_OFlag: libc::c_int { + /// Open the message queue for receiving messages. + O_RDONLY; + /// Open the queue for sending messages. + O_WRONLY; + /// Open the queue for both receiving and sending messages + O_RDWR; + /// Create a message queue. + O_CREAT; + /// If set along with `O_CREAT`, `mq_open` will fail if the message + /// queue name exists. + O_EXCL; + /// `mq_send` and `mq_receive` should fail with `EAGAIN` rather than + /// wait for resources that are not currently available. + O_NONBLOCK; + /// Set the close-on-exec flag for the message queue descriptor. + O_CLOEXEC; + } +} + +/// A message-queue attribute, optionally used with [`mq_setattr`] and +/// [`mq_getattr`] and optionally [`mq_open`], +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct MqAttr { + mq_attr: libc::mq_attr, +} + +/// Identifies an open POSIX Message Queue +// A safer wrapper around libc::mqd_t, which is a pointer on some platforms +// Deliberately is not Clone to prevent use-after-close scenarios +#[repr(transparent)] +#[derive(Debug)] +#[allow(missing_copy_implementations)] +pub struct MqdT(mqd_t); + +// x32 compatibility +// See https://sourceware.org/bugzilla/show_bug.cgi?id=21279 +/// Size of a message queue attribute member +#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub type mq_attr_member_t = i64; +/// Size of a message queue attribute member +#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub type mq_attr_member_t = libc::c_long; + +impl MqAttr { + /// Create a new message queue attribute + /// + /// # Arguments + /// + /// - `mq_flags`: Either `0` or `O_NONBLOCK`. + /// - `mq_maxmsg`: Maximum number of messages on the queue. + /// - `mq_msgsize`: Maximum message size in bytes. + /// - `mq_curmsgs`: Number of messages currently in the queue. + pub fn new( + mq_flags: mq_attr_member_t, + mq_maxmsg: mq_attr_member_t, + mq_msgsize: mq_attr_member_t, + mq_curmsgs: mq_attr_member_t, + ) -> MqAttr { + let mut attr = mem::MaybeUninit::::uninit(); + unsafe { + let p = attr.as_mut_ptr(); + (*p).mq_flags = mq_flags; + (*p).mq_maxmsg = mq_maxmsg; + (*p).mq_msgsize = mq_msgsize; + (*p).mq_curmsgs = mq_curmsgs; + MqAttr { + mq_attr: attr.assume_init(), + } + } + } + + /// The current flags, either `0` or `O_NONBLOCK`. + pub const fn flags(&self) -> mq_attr_member_t { + self.mq_attr.mq_flags + } + + /// The max number of messages that can be held by the queue + pub const fn maxmsg(&self) -> mq_attr_member_t { + self.mq_attr.mq_maxmsg + } + + /// The maximum size of each message (in bytes) + pub const fn msgsize(&self) -> mq_attr_member_t { + self.mq_attr.mq_msgsize + } + + /// The number of messages currently held in the queue + pub const fn curmsgs(&self) -> mq_attr_member_t { + self.mq_attr.mq_curmsgs + } +} + +/// Open a message queue +/// +/// See also [`mq_open(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_open.html) +// The mode.bits cast is only lossless on some OSes +#[allow(clippy::cast_lossless)] +pub fn mq_open( + name: &CStr, + oflag: MQ_OFlag, + mode: Mode, + attr: Option<&MqAttr>, +) -> Result { + let res = match attr { + Some(mq_attr) => unsafe { + libc::mq_open( + name.as_ptr(), + oflag.bits(), + mode.bits() as libc::c_int, + &mq_attr.mq_attr as *const libc::mq_attr, + ) + }, + None => unsafe { libc::mq_open(name.as_ptr(), oflag.bits()) }, + }; + Errno::result(res).map(MqdT) +} + +/// Remove a message queue +/// +/// See also [`mq_unlink(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_unlink.html) +pub fn mq_unlink(name: &CStr) -> Result<()> { + let res = unsafe { libc::mq_unlink(name.as_ptr()) }; + Errno::result(res).map(drop) +} + +/// Close a message queue +/// +/// See also [`mq_close(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_close.html) +pub fn mq_close(mqdes: MqdT) -> Result<()> { + let res = unsafe { libc::mq_close(mqdes.0) }; + Errno::result(res).map(drop) +} + +/// Receive a message from a message queue +/// +/// See also [`mq_receive(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_receive.html) +pub fn mq_receive( + mqdes: &MqdT, + message: &mut [u8], + msg_prio: &mut u32, +) -> Result { + let len = message.len() as size_t; + let res = unsafe { + libc::mq_receive( + mqdes.0, + message.as_mut_ptr() as *mut c_char, + len, + msg_prio as *mut u32, + ) + }; + Errno::result(res).map(|r| r as usize) +} + +/// Send a message to a message queue +/// +/// See also [`mq_send(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_send.html) +pub fn mq_send(mqdes: &MqdT, message: &[u8], msq_prio: u32) -> Result<()> { + let res = unsafe { + libc::mq_send( + mqdes.0, + message.as_ptr() as *const c_char, + message.len(), + msq_prio, + ) + }; + Errno::result(res).map(drop) +} + +/// Get message queue attributes +/// +/// See also [`mq_getattr(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_getattr.html) +pub fn mq_getattr(mqd: &MqdT) -> Result { + let mut attr = mem::MaybeUninit::::uninit(); + let res = unsafe { libc::mq_getattr(mqd.0, attr.as_mut_ptr()) }; + Errno::result(res).map(|_| unsafe { + MqAttr { + mq_attr: attr.assume_init(), + } + }) +} + +/// Set the attributes of the message queue. Only `O_NONBLOCK` can be set, everything else will be ignored +/// Returns the old attributes +/// It is recommend to use the `mq_set_nonblock()` and `mq_remove_nonblock()` convenience functions as they are easier to use +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_setattr.html) +pub fn mq_setattr(mqd: &MqdT, newattr: &MqAttr) -> Result { + let mut attr = mem::MaybeUninit::::uninit(); + let res = unsafe { + libc::mq_setattr( + mqd.0, + &newattr.mq_attr as *const libc::mq_attr, + attr.as_mut_ptr(), + ) + }; + Errno::result(res).map(|_| unsafe { + MqAttr { + mq_attr: attr.assume_init(), + } + }) +} + +/// Convenience function. +/// Sets the `O_NONBLOCK` attribute for a given message queue descriptor +/// Returns the old attributes +#[allow(clippy::useless_conversion)] // Not useless on all OSes +pub fn mq_set_nonblock(mqd: &MqdT) -> Result { + let oldattr = mq_getattr(mqd)?; + let newattr = MqAttr::new( + mq_attr_member_t::from(MQ_OFlag::O_NONBLOCK.bits()), + oldattr.mq_attr.mq_maxmsg, + oldattr.mq_attr.mq_msgsize, + oldattr.mq_attr.mq_curmsgs, + ); + mq_setattr(mqd, &newattr) +} + +/// Convenience function. +/// Removes `O_NONBLOCK` attribute for a given message queue descriptor +/// Returns the old attributes +pub fn mq_remove_nonblock(mqd: &MqdT) -> Result { + let oldattr = mq_getattr(mqd)?; + let newattr = MqAttr::new( + 0, + oldattr.mq_attr.mq_maxmsg, + oldattr.mq_attr.mq_msgsize, + oldattr.mq_attr.mq_curmsgs, + ); + mq_setattr(mqd, &newattr) +} diff --git a/src/net/if_.rs b/src/net/if_.rs new file mode 100644 index 0000000..b2423bc --- /dev/null +++ b/src/net/if_.rs @@ -0,0 +1,469 @@ +//! Network interface name resolution. +//! +//! Uses Linux and/or POSIX functions to resolve interface names like "eth0" +//! or "socan1" into device numbers. + +use crate::{Error, NixPath, Result}; +use libc::c_uint; + +/// Resolve an interface into a interface number. +pub fn if_nametoindex(name: &P) -> Result { + let if_index = name + .with_nix_path(|name| unsafe { libc::if_nametoindex(name.as_ptr()) })?; + + if if_index == 0 { + Err(Error::last()) + } else { + Ok(if_index) + } +} + +libc_bitflags!( + /// Standard interface flags, used by `getifaddrs` + pub struct InterfaceFlags: libc::c_int { + /// Interface is running. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_UP; + /// Valid broadcast address set. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_BROADCAST; + /// Internal debugging flag. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(not(target_os = "haiku"))] + IFF_DEBUG; + /// Interface is a loopback interface. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_LOOPBACK; + /// Interface is a point-to-point link. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_POINTOPOINT; + /// Avoid use of trailers. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NOTRAILERS; + /// Interface manages own routes. + #[cfg(any(target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_SMART; + /// Resources allocated. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_RUNNING; + /// No arp protocol, L2 destination address not set. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_NOARP; + /// Interface is in promiscuous mode. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_PROMISC; + /// Receive all multicast packets. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_ALLMULTI; + /// Master of a load balancing bundle. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_MASTER; + /// transmission in progress, tx hardware queue is full + #[cfg(any(target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_OACTIVE; + /// Protocol code on board. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_INTELLIGENT; + /// Slave of a load balancing bundle. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_SLAVE; + /// Can't hear own transmissions. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_SIMPLEX; + /// Supports multicast. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_MULTICAST; + /// Per link layer defined bit. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_LINK0; + /// Multicast using broadcast. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_MULTI_BCAST; + /// Is able to select media type via ifmap. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_PORTSEL; + /// Per link layer defined bit. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_LINK1; + /// Non-unique address. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_UNNUMBERED; + /// Auto media selection active. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_AUTOMEDIA; + /// Per link layer defined bit. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_LINK2; + /// Use alternate physical connection. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_ALTPHYS; + /// DHCP controls interface. + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_DHCPRUNNING; + /// The addresses are lost when the interface goes down. (see + /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_DYNAMIC; + /// Do not advertise. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_PRIVATE; + /// Driver signals L1 up. Volatile. + #[cfg(any(target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_LOWER_UP; + /// Interface is in polling mode. + #[cfg(any(target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_POLLING_COMPAT; + /// Unconfigurable using ioctl(2). + #[cfg(any(target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_CANTCONFIG; + /// Do not transmit packets. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NOXMIT; + /// Driver signals dormant. Volatile. + #[cfg(any(target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_DORMANT; + /// User-requested promisc mode. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_PPROMISC; + /// Just on-link subnet. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NOLOCAL; + /// Echo sent packets. Volatile. + #[cfg(any(target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_ECHO; + /// User-requested monitor mode. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_MONITOR; + /// Address is deprecated. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_DEPRECATED; + /// Static ARP. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_STATICARP; + /// Address from stateless addrconf. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_ADDRCONF; + /// Interface is in polling mode. + #[cfg(any(target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NPOLLING; + /// Router on interface. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_ROUTER; + /// Interface is in polling mode. + #[cfg(any(target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_IDIRECT; + /// Interface is winding down + #[cfg(any(target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_DYING; + /// No NUD on interface. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NONUD; + /// Interface is being renamed + #[cfg(any(target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_RENAMING; + /// Anycast address. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_ANYCAST; + /// Don't exchange routing info. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NORTEXCH; + /// Do not provide packet information + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NO_PI as libc::c_int; + /// TUN device (no Ethernet headers) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_TUN as libc::c_int; + /// TAP device + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_TAP as libc::c_int; + /// IPv4 interface. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_IPV4; + /// IPv6 interface. + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_IPV6; + /// in.mpathd test address + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_NOFAILOVER; + /// Interface has failed + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_FAILED; + /// Interface is a hot-spare + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_STANDBY; + /// Functioning but not used + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_INACTIVE; + /// Interface is offline + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_OFFLINE; + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_COS_ENABLED; + /// Prefer as source addr. + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_PREFERRED; + /// RFC3041 + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_TEMPORARY; + /// MTU set with SIOCSLIFMTU + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_FIXEDMTU; + /// Cannot send / receive packets + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_VIRTUAL; + /// Local address in use + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_DUPLICATE; + /// IPMP IP interface + #[cfg(target_os = "solaris")] + #[cfg_attr(docsrs, doc(cfg(all())))] + IFF_IPMP; + } +); + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod if_nameindex { + use super::*; + + use std::ffi::CStr; + use std::fmt; + use std::marker::PhantomData; + use std::ptr::NonNull; + + /// A network interface. Has a name like "eth0" or "wlp4s0" or "wlan0", as well as an index + /// (1, 2, 3, etc) that identifies it in the OS's networking stack. + #[allow(missing_copy_implementations)] + #[repr(transparent)] + pub struct Interface(libc::if_nameindex); + + impl Interface { + /// Obtain the index of this interface. + pub fn index(&self) -> c_uint { + self.0.if_index + } + + /// Obtain the name of this interface. + pub fn name(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.if_name) } + } + } + + impl fmt::Debug for Interface { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Interface") + .field("index", &self.index()) + .field("name", &self.name()) + .finish() + } + } + + /// A list of the network interfaces available on this system. Obtained from [`if_nameindex()`]. + pub struct Interfaces { + ptr: NonNull, + } + + impl Interfaces { + /// Iterate over the interfaces in this list. + #[inline] + pub fn iter(&self) -> InterfacesIter<'_> { + self.into_iter() + } + + /// Convert this to a slice of interfaces. Note that the underlying interfaces list is + /// null-terminated, so calling this calculates the length. If random access isn't needed, + /// [`Interfaces::iter()`] should be used instead. + pub fn to_slice(&self) -> &[Interface] { + let ifs = self.ptr.as_ptr() as *const Interface; + let len = self.iter().count(); + unsafe { std::slice::from_raw_parts(ifs, len) } + } + } + + impl Drop for Interfaces { + fn drop(&mut self) { + unsafe { libc::if_freenameindex(self.ptr.as_ptr()) }; + } + } + + impl fmt::Debug for Interfaces { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_slice().fmt(f) + } + } + + impl<'a> IntoIterator for &'a Interfaces { + type IntoIter = InterfacesIter<'a>; + type Item = &'a Interface; + #[inline] + fn into_iter(self) -> Self::IntoIter { + InterfacesIter { + ptr: self.ptr.as_ptr(), + _marker: PhantomData, + } + } + } + + /// An iterator over the interfaces in an [`Interfaces`]. + #[derive(Debug)] + pub struct InterfacesIter<'a> { + ptr: *const libc::if_nameindex, + _marker: PhantomData<&'a Interfaces>, + } + + impl<'a> Iterator for InterfacesIter<'a> { + type Item = &'a Interface; + #[inline] + fn next(&mut self) -> Option { + unsafe { + if (*self.ptr).if_index == 0 { + None + } else { + let ret = &*(self.ptr as *const Interface); + self.ptr = self.ptr.add(1); + Some(ret) + } + } + } + } + + /// Retrieve a list of the network interfaces available on the local system. + /// + /// ``` + /// let interfaces = nix::net::if_::if_nameindex().unwrap(); + /// for iface in &interfaces { + /// println!("Interface #{} is called {}", iface.index(), iface.name().to_string_lossy()); + /// } + /// ``` + pub fn if_nameindex() -> Result { + unsafe { + let ifs = libc::if_nameindex(); + let ptr = NonNull::new(ifs).ok_or_else(Error::last)?; + Ok(Interfaces { ptr }) + } + } +} +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +pub use if_nameindex::*; diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 0000000..079fcfd --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,4 @@ +//! Functionality involving network interfaces +// To avoid clashing with the keyword "if", we use "if_" as the module name. +// The original header is called "net/if.h". +pub mod if_; diff --git a/src/poll.rs b/src/poll.rs new file mode 100644 index 0000000..6f227fe --- /dev/null +++ b/src/poll.rs @@ -0,0 +1,197 @@ +//! Wait for events to trigger on specific file descriptors +use std::os::unix::io::{AsRawFd, RawFd}; + +use crate::errno::Errno; +use crate::Result; + +/// This is a wrapper around `libc::pollfd`. +/// +/// It's meant to be used as an argument to the [`poll`](fn.poll.html) and +/// [`ppoll`](fn.ppoll.html) functions to specify the events of interest +/// for a specific file descriptor. +/// +/// After a call to `poll` or `ppoll`, the events that occurred can be +/// retrieved by calling [`revents()`](#method.revents) on the `PollFd`. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct PollFd { + pollfd: libc::pollfd, +} + +impl PollFd { + /// Creates a new `PollFd` specifying the events of interest + /// for a given file descriptor. + pub const fn new(fd: RawFd, events: PollFlags) -> PollFd { + PollFd { + pollfd: libc::pollfd { + fd, + events: events.bits(), + revents: PollFlags::empty().bits(), + }, + } + } + + /// Returns the events that occurred in the last call to `poll` or `ppoll`. Will only return + /// `None` if the kernel provides status flags that Nix does not know about. + pub fn revents(self) -> Option { + PollFlags::from_bits(self.pollfd.revents) + } + + /// Returns if any of the events of interest occured in the last call to `poll` or `ppoll`. Will + /// only return `None` if the kernel provides status flags that Nix does not know about. + /// + /// Equivalent to `x.revents()? != PollFlags::empty()`. + /// + /// This is marginally more efficient than [`PollFd::all`]. + pub fn any(self) -> Option { + Some(self.revents()? != PollFlags::empty()) + } + + /// Returns if all the events of interest occured in the last call to `poll` or `ppoll`. Will + /// only return `None` if the kernel provides status flags that Nix does not know about. + /// + /// Equivalent to `x.revents()? & x.events() == x.events()`. + /// + /// This is marginally less efficient than [`PollFd::any`]. + pub fn all(self) -> Option { + Some(self.revents()? & self.events() == self.events()) + } + + /// The events of interest for this `PollFd`. + pub fn events(self) -> PollFlags { + PollFlags::from_bits(self.pollfd.events).unwrap() + } + + /// Modify the events of interest for this `PollFd`. + pub fn set_events(&mut self, events: PollFlags) { + self.pollfd.events = events.bits(); + } +} + +impl AsRawFd for PollFd { + fn as_raw_fd(&self) -> RawFd { + self.pollfd.fd + } +} + +libc_bitflags! { + /// These flags define the different events that can be monitored by `poll` and `ppoll` + pub struct PollFlags: libc::c_short { + /// There is data to read. + POLLIN; + /// There is some exceptional condition on the file descriptor. + /// + /// Possibilities include: + /// + /// * There is out-of-band data on a TCP socket (see + /// [tcp(7)](https://man7.org/linux/man-pages/man7/tcp.7.html)). + /// * A pseudoterminal master in packet mode has seen a state + /// change on the slave (see + /// [ioctl_tty(2)](https://man7.org/linux/man-pages/man2/ioctl_tty.2.html)). + /// * A cgroup.events file has been modified (see + /// [cgroups(7)](https://man7.org/linux/man-pages/man7/cgroups.7.html)). + POLLPRI; + /// Writing is now possible, though a write larger that the + /// available space in a socket or pipe will still block (unless + /// `O_NONBLOCK` is set). + POLLOUT; + /// Equivalent to [`POLLIN`](constant.POLLIN.html) + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + POLLRDNORM; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Equivalent to [`POLLOUT`](constant.POLLOUT.html) + POLLWRNORM; + /// Priority band data can be read (generally unused on Linux). + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + POLLRDBAND; + /// Priority data may be written. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + POLLWRBAND; + /// Error condition (only returned in + /// [`PollFd::revents`](struct.PollFd.html#method.revents); + /// ignored in [`PollFd::new`](struct.PollFd.html#method.new)). + /// This bit is also set for a file descriptor referring to the + /// write end of a pipe when the read end has been closed. + POLLERR; + /// Hang up (only returned in [`PollFd::revents`](struct.PollFd.html#method.revents); + /// ignored in [`PollFd::new`](struct.PollFd.html#method.new)). + /// Note that when reading from a channel such as a pipe or a stream + /// socket, this event merely indicates that the peer closed its + /// end of the channel. Subsequent reads from the channel will + /// return 0 (end of file) only after all outstanding data in the + /// channel has been consumed. + POLLHUP; + /// Invalid request: `fd` not open (only returned in + /// [`PollFd::revents`](struct.PollFd.html#method.revents); + /// ignored in [`PollFd::new`](struct.PollFd.html#method.new)). + POLLNVAL; + } +} + +/// `poll` waits for one of a set of file descriptors to become ready to perform I/O. +/// ([`poll(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html)) +/// +/// `fds` contains all [`PollFd`](struct.PollFd.html) to poll. +/// The function will return as soon as any event occur for any of these `PollFd`s. +/// +/// The `timeout` argument specifies the number of milliseconds that `poll()` +/// should block waiting for a file descriptor to become ready. The call +/// will block until either: +/// +/// * a file descriptor becomes ready; +/// * the call is interrupted by a signal handler; or +/// * the timeout expires. +/// +/// Note that the timeout interval will be rounded up to the system clock +/// granularity, and kernel scheduling delays mean that the blocking +/// interval may overrun by a small amount. Specifying a negative value +/// in timeout means an infinite timeout. Specifying a timeout of zero +/// causes `poll()` to return immediately, even if no file descriptors are +/// ready. +pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result { + let res = unsafe { + libc::poll( + fds.as_mut_ptr() as *mut libc::pollfd, + fds.len() as libc::nfds_t, + timeout, + ) + }; + + Errno::result(res) +} + +feature! { +#![feature = "signal"] +/// `ppoll()` allows an application to safely wait until either a file +/// descriptor becomes ready or until a signal is caught. +/// ([`poll(2)`](https://man7.org/linux/man-pages/man2/poll.2.html)) +/// +/// `ppoll` behaves like `poll`, but let you specify what signals may interrupt it +/// with the `sigmask` argument. If you want `ppoll` to block indefinitely, +/// specify `None` as `timeout` (it is like `timeout = -1` for `poll`). +/// If `sigmask` is `None`, then no signal mask manipulation is performed, +/// so in that case `ppoll` differs from `poll` only in the precision of the +/// timeout argument. +/// +#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] +pub fn ppoll( + fds: &mut [PollFd], + timeout: Option, + sigmask: Option + ) -> Result +{ + let timeout = timeout.as_ref().map_or(core::ptr::null(), |r| r.as_ref()); + let sigmask = sigmask.as_ref().map_or(core::ptr::null(), |r| r.as_ref()); + let res = unsafe { + libc::ppoll(fds.as_mut_ptr() as *mut libc::pollfd, + fds.len() as libc::nfds_t, + timeout, + sigmask) + }; + Errno::result(res) +} +} diff --git a/src/pty.rs b/src/pty.rs new file mode 100644 index 0000000..28ae5e9 --- /dev/null +++ b/src/pty.rs @@ -0,0 +1,371 @@ +//! Create master and slave virtual pseudo-terminals (PTYs) + +pub use libc::pid_t as SessionId; +pub use libc::winsize as Winsize; + +use std::ffi::CStr; +use std::io; +use std::mem; +use std::os::unix::prelude::*; + +use crate::errno::Errno; +use crate::sys::termios::Termios; +#[cfg(feature = "process")] +use crate::unistd::{ForkResult, Pid}; +use crate::{fcntl, unistd, Result}; + +/// Representation of a master/slave pty pair +/// +/// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user +/// must manually close the file descriptors. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct OpenptyResult { + /// The master port in a virtual pty pair + pub master: RawFd, + /// The slave port in a virtual pty pair + pub slave: RawFd, +} + +feature! { +#![feature = "process"] +/// Representation of a master with a forked pty +/// +/// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user +/// must manually close the file descriptors. +#[derive(Clone, Copy, Debug)] +pub struct ForkptyResult { + /// The master port in a virtual pty pair + pub master: RawFd, + /// Metadata about forked process + pub fork_result: ForkResult, +} +} + +/// Representation of the Master device in a master/slave pty pair +/// +/// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY +/// functions are given the correct file descriptor. Additionally this type implements `Drop`, +/// so that when it's consumed or goes out of scope, it's automatically cleaned-up. +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct PtyMaster(RawFd); + +impl AsRawFd for PtyMaster { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl IntoRawFd for PtyMaster { + fn into_raw_fd(self) -> RawFd { + let fd = self.0; + mem::forget(self); + fd + } +} + +impl Drop for PtyMaster { + fn drop(&mut self) { + // On drop, we ignore errors like EINTR and EIO because there's no clear + // way to handle them, we can't return anything, and (on FreeBSD at + // least) the file descriptor is deallocated in these cases. However, + // we must panic on EBADF, because it is always an error to close an + // invalid file descriptor. That frequently indicates a double-close + // condition, which can cause confusing errors for future I/O + // operations. + let e = unistd::close(self.0); + if e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; + } +} + +impl io::Read for PtyMaster { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unistd::read(self.0, buf).map_err(io::Error::from) + } +} + +impl io::Write for PtyMaster { + fn write(&mut self, buf: &[u8]) -> io::Result { + unistd::write(self.0, buf).map_err(io::Error::from) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl io::Read for &PtyMaster { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unistd::read(self.0, buf).map_err(io::Error::from) + } +} + +impl io::Write for &PtyMaster { + fn write(&mut self, buf: &[u8]) -> io::Result { + unistd::write(self.0, buf).map_err(io::Error::from) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Grant access to a slave pseudoterminal (see +/// [`grantpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html)) +/// +/// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the +/// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave. +#[inline] +pub fn grantpt(fd: &PtyMaster) -> Result<()> { + if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 { + return Err(Errno::last()); + } + + Ok(()) +} + +/// Open a pseudoterminal device (see +/// [`posix_openpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html)) +/// +/// `posix_openpt()` returns a file descriptor to an existing unused pseudoterminal master device. +/// +/// # Examples +/// +/// A common use case with this function is to open both a master and slave PTY pair. This can be +/// done as follows: +/// +/// ``` +/// use std::path::Path; +/// use nix::fcntl::{OFlag, open}; +/// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt}; +/// use nix::sys::stat::Mode; +/// +/// # #[allow(dead_code)] +/// # fn run() -> nix::Result<()> { +/// // Open a new PTY master +/// let master_fd = posix_openpt(OFlag::O_RDWR)?; +/// +/// // Allow a slave to be generated for it +/// grantpt(&master_fd)?; +/// unlockpt(&master_fd)?; +/// +/// // Get the name of the slave +/// let slave_name = unsafe { ptsname(&master_fd) }?; +/// +/// // Try to open the slave +/// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn posix_openpt(flags: fcntl::OFlag) -> Result { + let fd = unsafe { libc::posix_openpt(flags.bits()) }; + + if fd < 0 { + return Err(Errno::last()); + } + + Ok(PtyMaster(fd)) +} + +/// Get the name of the slave pseudoterminal (see +/// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html)) +/// +/// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master +/// referred to by `fd`. +/// +/// This value is useful for opening the slave pty once the master has already been opened with +/// `posix_openpt()`. +/// +/// # Safety +/// +/// `ptsname()` mutates global variables and is *not* threadsafe. +/// Mutating global variables is always considered `unsafe` by Rust and this +/// function is marked as `unsafe` to reflect that. +/// +/// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`. +#[inline] +pub unsafe fn ptsname(fd: &PtyMaster) -> Result { + let name_ptr = libc::ptsname(fd.as_raw_fd()); + if name_ptr.is_null() { + return Err(Errno::last()); + } + + let name = CStr::from_ptr(name_ptr); + Ok(name.to_string_lossy().into_owned()) +} + +/// Get the name of the slave pseudoterminal (see +/// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html)) +/// +/// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master +/// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the +/// POSIX standard and is instead a Linux-specific extension. +/// +/// This value is useful for opening the slave ptty once the master has already been opened with +/// `posix_openpt()`. +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +#[inline] +pub fn ptsname_r(fd: &PtyMaster) -> Result { + let mut name_buf = Vec::::with_capacity(64); + let name_buf_ptr = name_buf.as_mut_ptr(); + let cname = unsafe { + let cap = name_buf.capacity(); + if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 { + return Err(crate::Error::last()); + } + CStr::from_ptr(name_buf.as_ptr()) + }; + + let name = cname.to_string_lossy().into_owned(); + Ok(name) +} + +/// Unlock a pseudoterminal master/slave pseudoterminal pair (see +/// [`unlockpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html)) +/// +/// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal +/// referred to by `fd`. This must be called before trying to open the slave side of a +/// pseudoterminal. +#[inline] +pub fn unlockpt(fd: &PtyMaster) -> Result<()> { + if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { + return Err(Errno::last()); + } + + Ok(()) +} + +/// Create a new pseudoterminal, returning the slave and master file descriptors +/// in `OpenptyResult` +/// (see [`openpty`](https://man7.org/linux/man-pages/man3/openpty.3.html)). +/// +/// If `winsize` is not `None`, the window size of the slave will be set to +/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's +/// terminal settings of the slave will be set to the values in `termios`. +#[inline] +pub fn openpty< + 'a, + 'b, + T: Into>, + U: Into>, +>( + winsize: T, + termios: U, +) -> Result { + use std::ptr; + + let mut slave = mem::MaybeUninit::::uninit(); + let mut master = mem::MaybeUninit::::uninit(); + let ret = { + match (termios.into(), winsize.into()) { + (Some(termios), Some(winsize)) => { + let inner_termios = termios.get_libc_termios(); + unsafe { + libc::openpty( + master.as_mut_ptr(), + slave.as_mut_ptr(), + ptr::null_mut(), + &*inner_termios as *const libc::termios as *mut _, + winsize as *const Winsize as *mut _, + ) + } + } + (None, Some(winsize)) => unsafe { + libc::openpty( + master.as_mut_ptr(), + slave.as_mut_ptr(), + ptr::null_mut(), + ptr::null_mut(), + winsize as *const Winsize as *mut _, + ) + }, + (Some(termios), None) => { + let inner_termios = termios.get_libc_termios(); + unsafe { + libc::openpty( + master.as_mut_ptr(), + slave.as_mut_ptr(), + ptr::null_mut(), + &*inner_termios as *const libc::termios as *mut _, + ptr::null_mut(), + ) + } + } + (None, None) => unsafe { + libc::openpty( + master.as_mut_ptr(), + slave.as_mut_ptr(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + }, + } + }; + + Errno::result(ret)?; + + unsafe { + Ok(OpenptyResult { + master: master.assume_init(), + slave: slave.assume_init(), + }) + } +} + +feature! { +#![feature = "process"] +/// Create a new pseudoterminal, returning the master file descriptor and forked pid. +/// in `ForkptyResult` +/// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)). +/// +/// If `winsize` is not `None`, the window size of the slave will be set to +/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's +/// terminal settings of the slave will be set to the values in `termios`. +/// +/// # Safety +/// +/// In a multithreaded program, only [async-signal-safe] functions like `pause` +/// and `_exit` may be called by the child (the parent isn't restricted). Note +/// that memory allocation may **not** be async-signal-safe and thus must be +/// prevented. +/// +/// Those functions are only a small subset of your operating system's API, so +/// special care must be taken to only invoke code you can control and audit. +/// +/// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html +pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into>>( + winsize: T, + termios: U, +) -> Result { + use std::ptr; + + let mut master = mem::MaybeUninit::::uninit(); + + let term = match termios.into() { + Some(termios) => { + let inner_termios = termios.get_libc_termios(); + &*inner_termios as *const libc::termios as *mut _ + }, + None => ptr::null_mut(), + }; + + let win = winsize + .into() + .map(|ws| ws as *const Winsize as *mut _) + .unwrap_or(ptr::null_mut()); + + let res = libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win); + + let fork_result = Errno::result(res).map(|res| match res { + 0 => ForkResult::Child, + res => ForkResult::Parent { child: Pid::from_raw(res) }, + })?; + + Ok(ForkptyResult { + master: master.assume_init(), + fork_result, + }) +} +} diff --git a/src/sched.rs b/src/sched.rs new file mode 100644 index 0000000..d5b1233 --- /dev/null +++ b/src/sched.rs @@ -0,0 +1,324 @@ +//! Execution scheduling +//! +//! See Also +//! [sched.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sched.h.html) +use crate::{Errno, Result}; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::sched_linux_like::*; + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod sched_linux_like { + use crate::errno::Errno; + use crate::unistd::Pid; + use crate::Result; + use libc::{self, c_int, c_void}; + use std::mem; + use std::option::Option; + use std::os::unix::io::RawFd; + + // For some functions taking with a parameter of type CloneFlags, + // only a subset of these flags have an effect. + libc_bitflags! { + /// Options for use with [`clone`] + pub struct CloneFlags: c_int { + /// The calling process and the child process run in the same + /// memory space. + CLONE_VM; + /// The caller and the child process share the same filesystem + /// information. + CLONE_FS; + /// The calling process and the child process share the same file + /// descriptor table. + CLONE_FILES; + /// The calling process and the child process share the same table + /// of signal handlers. + CLONE_SIGHAND; + /// If the calling process is being traced, then trace the child + /// also. + CLONE_PTRACE; + /// The execution of the calling process is suspended until the + /// child releases its virtual memory resources via a call to + /// execve(2) or _exit(2) (as with vfork(2)). + CLONE_VFORK; + /// The parent of the new child (as returned by getppid(2)) + /// will be the same as that of the calling process. + CLONE_PARENT; + /// The child is placed in the same thread group as the calling + /// process. + CLONE_THREAD; + /// The cloned child is started in a new mount namespace. + CLONE_NEWNS; + /// The child and the calling process share a single list of System + /// V semaphore adjustment values + CLONE_SYSVSEM; + // Not supported by Nix due to lack of varargs support in Rust FFI + // CLONE_SETTLS; + // Not supported by Nix due to lack of varargs support in Rust FFI + // CLONE_PARENT_SETTID; + // Not supported by Nix due to lack of varargs support in Rust FFI + // CLONE_CHILD_CLEARTID; + /// Unused since Linux 2.6.2 + #[deprecated(since = "0.23.0", note = "Deprecated by Linux 2.6.2")] + CLONE_DETACHED; + /// A tracing process cannot force `CLONE_PTRACE` on this child + /// process. + CLONE_UNTRACED; + // Not supported by Nix due to lack of varargs support in Rust FFI + // CLONE_CHILD_SETTID; + /// Create the process in a new cgroup namespace. + CLONE_NEWCGROUP; + /// Create the process in a new UTS namespace. + CLONE_NEWUTS; + /// Create the process in a new IPC namespace. + CLONE_NEWIPC; + /// Create the process in a new user namespace. + CLONE_NEWUSER; + /// Create the process in a new PID namespace. + CLONE_NEWPID; + /// Create the process in a new network namespace. + CLONE_NEWNET; + /// The new process shares an I/O context with the calling process. + CLONE_IO; + } + } + + /// Type for the function executed by [`clone`]. + pub type CloneCb<'a> = Box isize + 'a>; + + /// `clone` create a child process + /// ([`clone(2)`](https://man7.org/linux/man-pages/man2/clone.2.html)) + /// + /// `stack` is a reference to an array which will hold the stack of the new + /// process. Unlike when calling `clone(2)` from C, the provided stack + /// address need not be the highest address of the region. Nix will take + /// care of that requirement. The user only needs to provide a reference to + /// a normally allocated buffer. + pub fn clone( + mut cb: CloneCb, + stack: &mut [u8], + flags: CloneFlags, + signal: Option, + ) -> Result { + extern "C" fn callback(data: *mut CloneCb) -> c_int { + let cb: &mut CloneCb = unsafe { &mut *data }; + (*cb)() as c_int + } + + let res = unsafe { + let combined = flags.bits() | signal.unwrap_or(0); + let ptr = stack.as_mut_ptr().add(stack.len()); + let ptr_aligned = ptr.sub(ptr as usize % 16); + libc::clone( + mem::transmute( + callback + as extern "C" fn(*mut Box isize>) -> i32, + ), + ptr_aligned as *mut c_void, + combined, + &mut cb as *mut _ as *mut c_void, + ) + }; + + Errno::result(res).map(Pid::from_raw) + } + + /// disassociate parts of the process execution context + /// + /// See also [unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html) + pub fn unshare(flags: CloneFlags) -> Result<()> { + let res = unsafe { libc::unshare(flags.bits()) }; + + Errno::result(res).map(drop) + } + + /// reassociate thread with a namespace + /// + /// See also [setns(2)](https://man7.org/linux/man-pages/man2/setns.2.html) + pub fn setns(fd: RawFd, nstype: CloneFlags) -> Result<()> { + let res = unsafe { libc::setns(fd, nstype.bits()) }; + + Errno::result(res).map(drop) + } +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" +))] +pub use self::sched_affinity::*; + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" +))] +mod sched_affinity { + use crate::errno::Errno; + use crate::unistd::Pid; + use crate::Result; + use std::mem; + + /// CpuSet represent a bit-mask of CPUs. + /// CpuSets are used by sched_setaffinity and + /// sched_getaffinity for example. + /// + /// This is a wrapper around `libc::cpu_set_t`. + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct CpuSet { + #[cfg(not(target_os = "freebsd"))] + cpu_set: libc::cpu_set_t, + #[cfg(target_os = "freebsd")] + cpu_set: libc::cpuset_t, + } + + impl CpuSet { + /// Create a new and empty CpuSet. + pub fn new() -> CpuSet { + CpuSet { + cpu_set: unsafe { mem::zeroed() }, + } + } + + /// Test to see if a CPU is in the CpuSet. + /// `field` is the CPU id to test + pub fn is_set(&self, field: usize) -> Result { + if field >= CpuSet::count() { + Err(Errno::EINVAL) + } else { + Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) }) + } + } + + /// Add a CPU to CpuSet. + /// `field` is the CPU id to add + pub fn set(&mut self, field: usize) -> Result<()> { + if field >= CpuSet::count() { + Err(Errno::EINVAL) + } else { + unsafe { + libc::CPU_SET(field, &mut self.cpu_set); + } + Ok(()) + } + } + + /// Remove a CPU from CpuSet. + /// `field` is the CPU id to remove + pub fn unset(&mut self, field: usize) -> Result<()> { + if field >= CpuSet::count() { + Err(Errno::EINVAL) + } else { + unsafe { + libc::CPU_CLR(field, &mut self.cpu_set); + } + Ok(()) + } + } + + /// Return the maximum number of CPU in CpuSet + pub const fn count() -> usize { + #[cfg(not(target_os = "freebsd"))] + let bytes = mem::size_of::(); + #[cfg(target_os = "freebsd")] + let bytes = mem::size_of::(); + + 8 * bytes + } + } + + impl Default for CpuSet { + fn default() -> Self { + Self::new() + } + } + + /// `sched_setaffinity` set a thread's CPU affinity mask + /// ([`sched_setaffinity(2)`](https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html)) + /// + /// `pid` is the thread ID to update. + /// If pid is zero, then the calling thread is updated. + /// + /// The `cpuset` argument specifies the set of CPUs on which the thread + /// will be eligible to run. + /// + /// # Example + /// + /// Binding the current thread to CPU 0 can be done as follows: + /// + /// ```rust,no_run + /// use nix::sched::{CpuSet, sched_setaffinity}; + /// use nix::unistd::Pid; + /// + /// let mut cpu_set = CpuSet::new(); + /// cpu_set.set(0).unwrap(); + /// sched_setaffinity(Pid::from_raw(0), &cpu_set).unwrap(); + /// ``` + pub fn sched_setaffinity(pid: Pid, cpuset: &CpuSet) -> Result<()> { + let res = unsafe { + libc::sched_setaffinity( + pid.into(), + mem::size_of::() as libc::size_t, + &cpuset.cpu_set, + ) + }; + + Errno::result(res).map(drop) + } + + /// `sched_getaffinity` get a thread's CPU affinity mask + /// ([`sched_getaffinity(2)`](https://man7.org/linux/man-pages/man2/sched_getaffinity.2.html)) + /// + /// `pid` is the thread ID to check. + /// If pid is zero, then the calling thread is checked. + /// + /// Returned `cpuset` is the set of CPUs on which the thread + /// is eligible to run. + /// + /// # Example + /// + /// Checking if the current thread can run on CPU 0 can be done as follows: + /// + /// ```rust,no_run + /// use nix::sched::sched_getaffinity; + /// use nix::unistd::Pid; + /// + /// let cpu_set = sched_getaffinity(Pid::from_raw(0)).unwrap(); + /// if cpu_set.is_set(0).unwrap() { + /// println!("Current thread can run on CPU 0"); + /// } + /// ``` + pub fn sched_getaffinity(pid: Pid) -> Result { + let mut cpuset = CpuSet::new(); + let res = unsafe { + libc::sched_getaffinity( + pid.into(), + mem::size_of::() as libc::size_t, + &mut cpuset.cpu_set, + ) + }; + + Errno::result(res).and(Ok(cpuset)) + } + + /// Determines the CPU on which the calling thread is running. + pub fn sched_getcpu() -> Result { + let res = unsafe { libc::sched_getcpu() }; + + Errno::result(res).map(|int| int as usize) + } +} + +/// Explicitly yield the processor to other threads. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html) +pub fn sched_yield() -> Result<()> { + let res = unsafe { libc::sched_yield() }; + + Errno::result(res).map(drop) +} diff --git a/src/sys/aio.rs b/src/sys/aio.rs new file mode 100644 index 0000000..e2ce19b --- /dev/null +++ b/src/sys/aio.rs @@ -0,0 +1,1241 @@ +// vim: tw=80 +//! POSIX Asynchronous I/O +//! +//! The POSIX AIO interface is used for asynchronous I/O on files and disk-like +//! devices. It supports [`read`](struct.AioRead.html#method.new), +//! [`write`](struct.AioWrite.html#method.new), +//! [`fsync`](struct.AioFsync.html#method.new), +//! [`readv`](struct.AioReadv.html#method.new), and +//! [`writev`](struct.AioWritev.html#method.new), operations, subject to +//! platform support. Completion +//! notifications can optionally be delivered via +//! [signals](../signal/enum.SigevNotify.html#variant.SigevSignal), via the +//! [`aio_suspend`](fn.aio_suspend.html) function, or via polling. Some +//! platforms support other completion +//! notifications, such as +//! [kevent](../signal/enum.SigevNotify.html#variant.SigevKevent). +//! +//! Multiple operations may be submitted in a batch with +//! [`lio_listio`](fn.lio_listio.html), though the standard does not guarantee +//! that they will be executed atomically. +//! +//! Outstanding operations may be cancelled with +//! [`cancel`](trait.Aio.html#method.cancel) or +//! [`aio_cancel_all`](fn.aio_cancel_all.html), though the operating system may +//! not support this for all filesystems and devices. +#[cfg(target_os = "freebsd")] +use std::io::{IoSlice, IoSliceMut}; +use std::{ + convert::TryFrom, + fmt::{self, Debug}, + marker::{PhantomData, PhantomPinned}, + mem, + os::unix::io::RawFd, + pin::Pin, + ptr, thread, +}; + +use libc::{c_void, off_t}; +use pin_utils::unsafe_pinned; + +use crate::{ + errno::Errno, + sys::{signal::*, time::TimeSpec}, + Result, +}; + +libc_enum! { + /// Mode for `AioCb::fsync`. Controls whether only data or both data and + /// metadata are synced. + #[repr(i32)] + #[non_exhaustive] + pub enum AioFsyncMode { + /// do it like `fsync` + O_SYNC, + /// on supported operating systems only, do it like `fdatasync` + #[cfg(any(target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + O_DSYNC + } + impl TryFrom +} + +libc_enum! { + /// Mode for [`lio_listio`](fn.lio_listio.html) + #[repr(i32)] + pub enum LioMode { + /// Requests that [`lio_listio`](fn.lio_listio.html) block until all + /// requested operations have been completed + LIO_WAIT, + /// Requests that [`lio_listio`](fn.lio_listio.html) return immediately + LIO_NOWAIT, + } +} + +/// Return values for [`AioCb::cancel`](struct.AioCb.html#method.cancel) and +/// [`aio_cancel_all`](fn.aio_cancel_all.html) +#[repr(i32)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum AioCancelStat { + /// All outstanding requests were canceled + AioCanceled = libc::AIO_CANCELED, + /// Some requests were not canceled. Their status should be checked with + /// `AioCb::error` + AioNotCanceled = libc::AIO_NOTCANCELED, + /// All of the requests have already finished + AioAllDone = libc::AIO_ALLDONE, +} + +/// Newtype that adds Send and Sync to libc::aiocb, which contains raw pointers +#[repr(transparent)] +struct LibcAiocb(libc::aiocb); + +unsafe impl Send for LibcAiocb {} +unsafe impl Sync for LibcAiocb {} + +/// Base class for all AIO operations. Should only be used directly when +/// checking for completion. +// We could create some kind of AsPinnedMut trait, and implement it for all aio +// ops, allowing the crate's users to get pinned references to `AioCb`. That +// could save some code for things like polling methods. But IMHO it would +// provide polymorphism at the wrong level. Instead, the best place for +// polymorphism is at the level of `Futures`. +#[repr(C)] +struct AioCb { + aiocb: LibcAiocb, + /// Could this `AioCb` potentially have any in-kernel state? + // It would be really nice to perform the in-progress check entirely at + // compile time. But I can't figure out how, because: + // * Future::poll takes a `Pin<&mut self>` rather than `self`, and + // * Rust's lack of an equivalent of C++'s Guaranteed Copy Elision means + // that there's no way to write an AioCb constructor that neither boxes + // the object itself, nor moves it during return. + in_progress: bool, +} + +impl AioCb { + pin_utils::unsafe_unpinned!(aiocb: LibcAiocb); + + fn aio_return(mut self: Pin<&mut Self>) -> Result { + self.in_progress = false; + unsafe { + let p: *mut libc::aiocb = &mut self.aiocb.0; + Errno::result(libc::aio_return(p)) + } + .map(|r| r as usize) + } + + fn cancel(mut self: Pin<&mut Self>) -> Result { + let r = unsafe { + libc::aio_cancel(self.aiocb.0.aio_fildes, &mut self.aiocb.0) + }; + match r { + libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), + libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), + libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), + -1 => Err(Errno::last()), + _ => panic!("unknown aio_cancel return value"), + } + } + + fn common_init(fd: RawFd, prio: i32, sigev_notify: SigevNotify) -> Self { + // Use mem::zeroed instead of explicitly zeroing each field, because the + // number and name of reserved fields is OS-dependent. On some OSes, + // some reserved fields are used the kernel for state, and must be + // explicitly zeroed when allocated. + let mut a = unsafe { mem::zeroed::() }; + a.aio_fildes = fd; + a.aio_reqprio = prio; + a.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); + AioCb { + aiocb: LibcAiocb(a), + in_progress: false, + } + } + + fn error(self: Pin<&mut Self>) -> Result<()> { + let r = unsafe { libc::aio_error(&self.aiocb().0) }; + match r { + 0 => Ok(()), + num if num > 0 => Err(Errno::from_i32(num)), + -1 => Err(Errno::last()), + num => panic!("unknown aio_error return value {:?}", num), + } + } + + fn in_progress(&self) -> bool { + self.in_progress + } + + fn set_in_progress(mut self: Pin<&mut Self>) { + self.as_mut().in_progress = true; + } + + /// Update the notification settings for an existing AIO operation that has + /// not yet been submitted. + // Takes a normal reference rather than a pinned one because this method is + // normally called before the object needs to be pinned, that is, before + // it's been submitted to the kernel. + fn set_sigev_notify(&mut self, sigev_notify: SigevNotify) { + assert!( + !self.in_progress, + "Can't change notification settings for an in-progress operation" + ); + self.aiocb.0.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); + } +} + +impl Debug for AioCb { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("AioCb") + .field("aiocb", &self.aiocb.0) + .field("in_progress", &self.in_progress) + .finish() + } +} + +impl Drop for AioCb { + /// If the `AioCb` has no remaining state in the kernel, just drop it. + /// Otherwise, dropping constitutes a resource leak, which is an error + fn drop(&mut self) { + assert!( + thread::panicking() || !self.in_progress, + "Dropped an in-progress AioCb" + ); + } +} + +/// Methods common to all AIO operations +pub trait Aio { + /// The return type of [`Aio::aio_return`]. + type Output; + + /// Retrieve return status of an asynchronous operation. + /// + /// Should only be called once for each operation, after [`Aio::error`] + /// indicates that it has completed. The result is the same as for the + /// synchronous `read(2)`, `write(2)`, of `fsync(2)` functions. + /// + /// # References + /// + /// [aio_return](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_return.html) + fn aio_return(self: Pin<&mut Self>) -> Result; + + /// Cancels an outstanding AIO request. + /// + /// The operating system is not required to implement cancellation for all + /// file and device types. Even if it does, there is no guarantee that the + /// operation has not already completed. So the caller must check the + /// result and handle operations that were not canceled or that have already + /// completed. + /// + /// # Examples + /// + /// Cancel an outstanding aio operation. Note that we must still call + /// `aio_return` to free resources, even though we don't care about the + /// result. + /// + /// ``` + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::io::Write; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// let wbuf = b"CDEF"; + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// 2, //offset + /// &wbuf[..], + /// 0, //priority + /// SigevNotify::SigevNone)); + /// aiocb.as_mut().submit().unwrap(); + /// let cs = aiocb.as_mut().cancel().unwrap(); + /// if cs == AioCancelStat::AioNotCanceled { + /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// } + /// // Must call `aio_return`, but ignore the result + /// let _ = aiocb.as_mut().aio_return(); + /// ``` + /// + /// # References + /// + /// [aio_cancel](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) + fn cancel(self: Pin<&mut Self>) -> Result; + + /// Retrieve error status of an asynchronous operation. + /// + /// If the request has not yet completed, returns `EINPROGRESS`. Otherwise, + /// returns `Ok` or any other error. + /// + /// # Examples + /// + /// Issue an aio operation and use `error` to poll for completion. Polling + /// is an alternative to `aio_suspend`, used by most of the other examples. + /// + /// ``` + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// const WBUF: &[u8] = b"abcdef123456"; + /// let mut f = tempfile().unwrap(); + /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// 2, //offset + /// WBUF, + /// 0, //priority + /// SigevNotify::SigevNone)); + /// aiocb.as_mut().submit().unwrap(); + /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// assert_eq!(aiocb.as_mut().aio_return().unwrap(), WBUF.len()); + /// ``` + /// + /// # References + /// + /// [aio_error](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_error.html) + fn error(self: Pin<&mut Self>) -> Result<()>; + + /// Returns the underlying file descriptor associated with the operation. + fn fd(&self) -> RawFd; + + /// Does this operation currently have any in-kernel state? + /// + /// Dropping an operation that does have in-kernel state constitutes a + /// resource leak. + /// + /// # Examples + /// + /// ``` + /// # use nix::errno::Errno; + /// # use nix::Error; + /// # use nix::sys::aio::*; + /// # use nix::sys::signal::SigevNotify::SigevNone; + /// # use std::{thread, time}; + /// # use std::os::unix::io::AsRawFd; + /// # use tempfile::tempfile; + /// let f = tempfile().unwrap(); + /// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, + /// 0, SigevNone)); + /// assert!(!aiof.as_mut().in_progress()); + /// aiof.as_mut().submit().expect("aio_fsync failed early"); + /// assert!(aiof.as_mut().in_progress()); + /// while (aiof.as_mut().error() == Err(Errno::EINPROGRESS)) { + /// thread::sleep(time::Duration::from_millis(10)); + /// } + /// aiof.as_mut().aio_return().expect("aio_fsync failed late"); + /// assert!(!aiof.as_mut().in_progress()); + /// ``` + fn in_progress(&self) -> bool; + + /// Returns the priority of the `AioCb` + fn priority(&self) -> i32; + + /// Update the notification settings for an existing AIO operation that has + /// not yet been submitted. + fn set_sigev_notify(&mut self, sev: SigevNotify); + + /// Returns the `SigEvent` that will be used for notification. + fn sigevent(&self) -> SigEvent; + + /// Actually start the I/O operation. + /// + /// After calling this method and until [`Aio::aio_return`] returns `Ok`, + /// the structure may not be moved in memory. + fn submit(self: Pin<&mut Self>) -> Result<()>; +} + +macro_rules! aio_methods { + () => { + fn cancel(self: Pin<&mut Self>) -> Result { + self.aiocb().cancel() + } + + fn error(self: Pin<&mut Self>) -> Result<()> { + self.aiocb().error() + } + + fn fd(&self) -> RawFd { + self.aiocb.aiocb.0.aio_fildes + } + + fn in_progress(&self) -> bool { + self.aiocb.in_progress() + } + + fn priority(&self) -> i32 { + self.aiocb.aiocb.0.aio_reqprio + } + + fn set_sigev_notify(&mut self, sev: SigevNotify) { + self.aiocb.set_sigev_notify(sev) + } + + fn sigevent(&self) -> SigEvent { + SigEvent::from(&self.aiocb.aiocb.0.aio_sigevent) + } + }; + ($func:ident) => { + aio_methods!(); + + fn aio_return(self: Pin<&mut Self>) -> Result<::Output> { + self.aiocb().aio_return() + } + + fn submit(mut self: Pin<&mut Self>) -> Result<()> { + let p: *mut libc::aiocb = &mut self.as_mut().aiocb().aiocb.0; + Errno::result({ unsafe { libc::$func(p) } }).map(|_| { + self.aiocb().set_in_progress(); + }) + } + }; +} + +/// An asynchronous version of `fsync(2)`. +/// +/// # References +/// +/// [aio_fsync](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_fsync.html) +/// # Examples +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify::SigevNone; +/// # use std::{thread, time}; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// let f = tempfile().unwrap(); +/// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, +/// 0, SigevNone)); +/// aiof.as_mut().submit().expect("aio_fsync failed early"); +/// while (aiof.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// aiof.as_mut().aio_return().expect("aio_fsync failed late"); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct AioFsync { + aiocb: AioCb, + _pin: PhantomPinned, +} + +impl AioFsync { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the operation's fsync mode: data and metadata or data only? + pub fn mode(&self) -> AioFsyncMode { + AioFsyncMode::try_from(self.aiocb.aiocb.0.aio_lio_opcode).unwrap() + } + + /// Create a new `AioFsync`. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to sync. + /// * `mode`: Whether to sync file metadata too, or just data. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio`. + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + mode: AioFsyncMode, + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + // To save some memory, store mode in an unused field of the AioCb. + // True it isn't very much memory, but downstream creates will likely + // create an enum containing this and other AioCb variants and pack + // those enums into data structures like Vec, so it adds up. + aiocb.aiocb.0.aio_lio_opcode = mode as libc::c_int; + AioFsync { + aiocb, + _pin: PhantomPinned, + } + } +} + +impl Aio for AioFsync { + type Output = (); + + aio_methods!(); + + fn aio_return(self: Pin<&mut Self>) -> Result<()> { + self.aiocb().aio_return().map(drop) + } + + fn submit(mut self: Pin<&mut Self>) -> Result<()> { + let aiocb = &mut self.as_mut().aiocb().aiocb.0; + let mode = mem::replace(&mut aiocb.aio_lio_opcode, 0); + let p: *mut libc::aiocb = aiocb; + Errno::result(unsafe { libc::aio_fsync(mode, p) }).map(|_| { + self.aiocb().set_in_progress(); + }) + } +} + +// AioFsync does not need AsMut, since it can't be used with lio_listio + +impl AsRef for AioFsync { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously reads from a file descriptor into a buffer +/// +/// # References +/// +/// [aio_read](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_read.html) +/// +/// # Examples +/// +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::Write; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"abcdef123456"; +/// const LEN: usize = 4; +/// let mut rbuf = vec![0; LEN]; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// { +/// let mut aior = Box::pin( +/// AioRead::new( +/// f.as_raw_fd(), +/// 2, //offset +/// &mut rbuf, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aior.as_mut().submit().unwrap(); +/// while (aior.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aior.as_mut().aio_return().unwrap(), LEN); +/// } +/// assert_eq!(rbuf, b"cdef"); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct AioRead<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [u8]>, + _pin: PhantomPinned, +} + +impl<'a> AioRead<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the requested length of the aio operation in bytes + /// + /// This method returns the *requested* length of the operation. To get the + /// number of bytes actually read or written by a completed operation, use + /// `aio_return` instead. + pub fn nbytes(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Create a new `AioRead`, placing the data in a mutable slice. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to read from + /// * `offs`: File offset + /// * `buf`: A memory buffer. It must outlive the `AioRead`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + buf: &'a mut [u8], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + aiocb.aiocb.0.aio_nbytes = buf.len(); + aiocb.aiocb.0.aio_buf = buf.as_mut_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_READ; + aiocb.aiocb.0.aio_offset = offs; + AioRead { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +impl<'a> Aio for AioRead<'a> { + type Output = usize; + + aio_methods!(aio_read); +} + +impl<'a> AsMut for AioRead<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +impl<'a> AsRef for AioRead<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously reads from a file descriptor into a scatter/gather list of buffers. +/// +/// # References +/// +/// [aio_readv](https://www.freebsd.org/cgi/man.cgi?query=aio_readv) +/// +/// # Examples +/// +/// +#[cfg_attr(fbsd14, doc = " ```")] +#[cfg_attr(not(fbsd14), doc = " ```no_run")] +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::{IoSliceMut, Write}; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const INITIAL: &[u8] = b"abcdef123456"; +/// let mut rbuf0 = vec![0; 4]; +/// let mut rbuf1 = vec![0; 2]; +/// let expected_len = rbuf0.len() + rbuf1.len(); +/// let mut rbufs = [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)]; +/// let mut f = tempfile().unwrap(); +/// f.write_all(INITIAL).unwrap(); +/// { +/// let mut aior = Box::pin( +/// AioReadv::new( +/// f.as_raw_fd(), +/// 2, //offset +/// &mut rbufs, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aior.as_mut().submit().unwrap(); +/// while (aior.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aior.as_mut().aio_return().unwrap(), expected_len); +/// } +/// assert_eq!(rbuf0, b"cdef"); +/// assert_eq!(rbuf1, b"12"); +/// ``` +#[cfg(target_os = "freebsd")] +#[derive(Debug)] +#[repr(transparent)] +pub struct AioReadv<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [&'a [u8]]>, + _pin: PhantomPinned, +} + +#[cfg(target_os = "freebsd")] +impl<'a> AioReadv<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the number of buffers the operation will read into. + pub fn iovlen(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Create a new `AioReadv`, placing the data in a list of mutable slices. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to read from + /// * `offs`: File offset + /// * `bufs`: A scatter/gather list of memory buffers. They must + /// outlive the `AioReadv`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + bufs: &mut [IoSliceMut<'a>], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + // In vectored mode, aio_nbytes stores the length of the iovec array, + // not the byte count. + aiocb.aiocb.0.aio_nbytes = bufs.len(); + aiocb.aiocb.0.aio_buf = bufs.as_mut_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_READV; + aiocb.aiocb.0.aio_offset = offs; + AioReadv { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> Aio for AioReadv<'a> { + type Output = usize; + + aio_methods!(aio_readv); +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsMut for AioReadv<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsRef for AioReadv<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously writes from a buffer to a file descriptor +/// +/// # References +/// +/// [aio_write](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_write.html) +/// +/// # Examples +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin( +/// AioWrite::new( +/// f.as_raw_fd(), +/// 2, //offset +/// WBUF, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aiow.as_mut().submit().unwrap(); +/// while (aiow.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct AioWrite<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [u8]>, + _pin: PhantomPinned, +} + +impl<'a> AioWrite<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the requested length of the aio operation in bytes + /// + /// This method returns the *requested* length of the operation. To get the + /// number of bytes actually read or written by a completed operation, use + /// `aio_return` instead. + pub fn nbytes(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Construct a new `AioWrite`. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to write to + /// * `offs`: File offset + /// * `buf`: A memory buffer. It must outlive the `AioWrite`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + buf: &'a [u8], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + aiocb.aiocb.0.aio_nbytes = buf.len(); + // casting an immutable buffer to a mutable pointer looks unsafe, + // but technically its only unsafe to dereference it, not to create + // it. Type Safety guarantees that we'll never pass aiocb to + // aio_read or aio_readv. + aiocb.aiocb.0.aio_buf = buf.as_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_WRITE; + aiocb.aiocb.0.aio_offset = offs; + AioWrite { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +impl<'a> Aio for AioWrite<'a> { + type Output = usize; + + aio_methods!(aio_write); +} + +impl<'a> AsMut for AioWrite<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +impl<'a> AsRef for AioWrite<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Asynchronously writes from a scatter/gather list of buffers to a file descriptor. +/// +/// # References +/// +/// [aio_writev](https://www.freebsd.org/cgi/man.cgi?query=aio_writev) +/// +/// # Examples +/// +#[cfg_attr(fbsd14, doc = " ```")] +#[cfg_attr(not(fbsd14), doc = " ```no_run")] +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::IoSlice; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const wbuf0: &[u8] = b"abcdef"; +/// const wbuf1: &[u8] = b"123456"; +/// let len = wbuf0.len() + wbuf1.len(); +/// let wbufs = [IoSlice::new(wbuf0), IoSlice::new(wbuf1)]; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin( +/// AioWritev::new( +/// f.as_raw_fd(), +/// 2, //offset +/// &wbufs, +/// 0, //priority +/// SigevNotify::SigevNone +/// ) +/// ); +/// aiow.as_mut().submit().unwrap(); +/// while (aiow.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), len); +/// ``` +#[cfg(target_os = "freebsd")] +#[derive(Debug)] +#[repr(transparent)] +pub struct AioWritev<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [&'a [u8]]>, + _pin: PhantomPinned, +} + +#[cfg(target_os = "freebsd")] +impl<'a> AioWritev<'a> { + unsafe_pinned!(aiocb: AioCb); + + /// Returns the number of buffers the operation will read into. + pub fn iovlen(&self) -> usize { + self.aiocb.aiocb.0.aio_nbytes + } + + /// Construct a new `AioWritev`. + /// + /// # Arguments + /// + /// * `fd`: File descriptor to write to + /// * `offs`: File offset + /// * `bufs`: A scatter/gather list of memory buffers. They must + /// outlive the `AioWritev`. + /// * `prio`: If POSIX Prioritized IO is supported, then the + /// operation will be prioritized at the process's + /// priority level minus `prio` + /// * `sigev_notify`: Determines how you will be notified of event + /// completion. + pub fn new( + fd: RawFd, + offs: off_t, + bufs: &[IoSlice<'a>], + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { + let mut aiocb = AioCb::common_init(fd, prio, sigev_notify); + // In vectored mode, aio_nbytes stores the length of the iovec array, + // not the byte count. + aiocb.aiocb.0.aio_nbytes = bufs.len(); + // casting an immutable buffer to a mutable pointer looks unsafe, + // but technically its only unsafe to dereference it, not to create + // it. Type Safety guarantees that we'll never pass aiocb to + // aio_read or aio_readv. + aiocb.aiocb.0.aio_buf = bufs.as_ptr() as *mut c_void; + aiocb.aiocb.0.aio_lio_opcode = libc::LIO_WRITEV; + aiocb.aiocb.0.aio_offset = offs; + AioWritev { + aiocb, + _data: PhantomData, + _pin: PhantomPinned, + } + } + + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> Aio for AioWritev<'a> { + type Output = usize; + + aio_methods!(aio_writev); +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsMut for AioWritev<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 + } +} + +#[cfg(target_os = "freebsd")] +impl<'a> AsRef for AioWritev<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 + } +} + +/// Cancels outstanding AIO requests for a given file descriptor. +/// +/// # Examples +/// +/// Issue an aio operation, then cancel all outstanding operations on that file +/// descriptor. +/// +/// ``` +/// # use nix::errno::Errno; +/// # use nix::Error; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::{thread, time}; +/// # use std::io::Write; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// let wbuf = b"CDEF"; +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// 2, //offset +/// &wbuf[..], +/// 0, //priority +/// SigevNotify::SigevNone)); +/// aiocb.as_mut().submit().unwrap(); +/// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); +/// if cs == AioCancelStat::AioNotCanceled { +/// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// } +/// // Must call `aio_return`, but ignore the result +/// let _ = aiocb.as_mut().aio_return(); +/// ``` +/// +/// # References +/// +/// [`aio_cancel`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) +pub fn aio_cancel_all(fd: RawFd) -> Result { + match unsafe { libc::aio_cancel(fd, ptr::null_mut()) } { + libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), + libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), + libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), + -1 => Err(Errno::last()), + _ => panic!("unknown aio_cancel return value"), + } +} + +/// Suspends the calling process until at least one of the specified operations +/// have completed, a signal is delivered, or the timeout has passed. +/// +/// If `timeout` is `None`, `aio_suspend` will block indefinitely. +/// +/// # Examples +/// +/// Use `aio_suspend` to block until an aio operation completes. +/// +/// ``` +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use std::os::unix::io::AsRawFd; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// 2, //offset +/// WBUF, +/// 0, //priority +/// SigevNotify::SigevNone)); +/// aiocb.as_mut().submit().unwrap(); +/// aio_suspend(&[&*aiocb], None).expect("aio_suspend failed"); +/// assert_eq!(aiocb.as_mut().aio_return().unwrap() as usize, WBUF.len()); +/// ``` +/// # References +/// +/// [`aio_suspend`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_suspend.html) +pub fn aio_suspend( + list: &[&dyn AsRef], + timeout: Option, +) -> Result<()> { + let p = list as *const [&dyn AsRef] + as *const [*const libc::aiocb] as *const *const libc::aiocb; + let timep = match timeout { + None => ptr::null::(), + Some(x) => x.as_ref() as *const libc::timespec, + }; + Errno::result(unsafe { libc::aio_suspend(p, list.len() as i32, timep) }) + .map(drop) +} + +/// Submits multiple asynchronous I/O requests with a single system call. +/// +/// They are not guaranteed to complete atomically, and the order in which the +/// requests are carried out is not specified. Reads, and writes may be freely +/// mixed. +/// +/// # Examples +/// +/// Use `lio_listio` to submit an aio operation and wait for its completion. In +/// this case, there is no need to use aio_suspend to wait or `error` to poll. +/// This mode is useful for otherwise-synchronous programs that want to execute +/// a handful of I/O operations in parallel. +/// ``` +/// # use std::os::unix::io::AsRawFd; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin(AioWrite::new( +/// f.as_raw_fd(), +/// 2, // offset +/// WBUF, +/// 0, // priority +/// SigevNotify::SigevNone +/// )); +/// lio_listio(LioMode::LIO_WAIT, &mut[aiow.as_mut()], SigevNotify::SigevNone) +/// .unwrap(); +/// // At this point, we are guaranteed that aiow is complete. +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +/// +/// Use `lio_listio` to submit multiple asynchronous operations with a single +/// syscall, but receive notification individually. This is an efficient +/// technique for reducing overall context-switch overhead, especially when +/// combined with kqueue. +/// ``` +/// # use std::os::unix::io::AsRawFd; +/// # use std::thread; +/// # use std::time; +/// # use nix::errno::Errno; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::SigevNotify; +/// # use tempfile::tempfile; +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin(AioWrite::new( +/// f.as_raw_fd(), +/// 2, // offset +/// WBUF, +/// 0, // priority +/// SigevNotify::SigevNone +/// )); +/// lio_listio(LioMode::LIO_NOWAIT, &mut[aiow.as_mut()], SigevNotify::SigevNone) +/// .unwrap(); +/// // We must wait for the completion of each individual operation +/// while (aiow.as_mut().error() == Err(Errno::EINPROGRESS)) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +/// +/// Use `lio_listio` to submit multiple operations, and receive notification +/// only when all of them are complete. This can be useful when there is some +/// logical relationship between the operations. But beware! Errors or system +/// resource limitations may cause `lio_listio` to return `EIO`, `EAGAIN`, or +/// `EINTR`, in which case some but not all operations may have been submitted. +/// In that case, you must check the status of each individual operation, and +/// possibly resubmit some. +/// ``` +/// # use libc::c_int; +/// # use std::os::unix::io::AsRawFd; +/// # use std::sync::atomic::{AtomicBool, Ordering}; +/// # use std::thread; +/// # use std::time; +/// # use lazy_static::lazy_static; +/// # use nix::errno::Errno; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::*; +/// # use tempfile::tempfile; +/// lazy_static! { +/// pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); +/// } +/// +/// extern fn sigfunc(_: c_int) { +/// SIGNALED.store(true, Ordering::Relaxed); +/// } +/// let sa = SigAction::new(SigHandler::Handler(sigfunc), +/// SaFlags::SA_RESETHAND, +/// SigSet::empty()); +/// SIGNALED.store(false, Ordering::Relaxed); +/// unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); +/// +/// const WBUF: &[u8] = b"abcdef123456"; +/// let mut f = tempfile().unwrap(); +/// let mut aiow = Box::pin(AioWrite::new( +/// f.as_raw_fd(), +/// 2, // offset +/// WBUF, +/// 0, // priority +/// SigevNotify::SigevNone +/// )); +/// let sev = SigevNotify::SigevSignal { signal: Signal::SIGUSR2, si_value: 0 }; +/// lio_listio(LioMode::LIO_NOWAIT, &mut[aiow.as_mut()], sev).unwrap(); +/// while !SIGNALED.load(Ordering::Relaxed) { +/// thread::sleep(time::Duration::from_millis(10)); +/// } +/// // At this point, since `lio_listio` returned success and delivered its +/// // notification, we know that all operations are complete. +/// assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); +/// ``` +pub fn lio_listio( + mode: LioMode, + list: &mut [Pin<&mut dyn AsMut>], + sigev_notify: SigevNotify, +) -> Result<()> { + let p = list as *mut [Pin<&mut dyn AsMut>] + as *mut [*mut libc::aiocb] as *mut *mut libc::aiocb; + let sigev = SigEvent::new(sigev_notify); + let sigevp = &mut sigev.sigevent() as *mut libc::sigevent; + Errno::result(unsafe { + libc::lio_listio(mode as i32, p, list.len() as i32, sigevp) + }) + .map(drop) +} + +#[cfg(test)] +mod t { + use super::*; + + /// aio_suspend relies on casting Rust Aio* struct pointers to libc::aiocb + /// pointers. This test ensures that such casts are valid. + #[test] + fn casting() { + let sev = SigevNotify::SigevNone; + let aiof = AioFsync::new(666, AioFsyncMode::O_SYNC, 0, sev); + assert_eq!( + aiof.as_ref() as *const libc::aiocb, + &aiof as *const AioFsync as *const libc::aiocb + ); + + let mut rbuf = []; + let aior = AioRead::new(666, 0, &mut rbuf, 0, sev); + assert_eq!( + aior.as_ref() as *const libc::aiocb, + &aior as *const AioRead as *const libc::aiocb + ); + + let wbuf = []; + let aiow = AioWrite::new(666, 0, &wbuf, 0, sev); + assert_eq!( + aiow.as_ref() as *const libc::aiocb, + &aiow as *const AioWrite as *const libc::aiocb + ); + } + + #[cfg(target_os = "freebsd")] + #[test] + fn casting_vectored() { + let sev = SigevNotify::SigevNone; + + let mut rbuf = []; + let mut rbufs = [IoSliceMut::new(&mut rbuf)]; + let aiorv = AioReadv::new(666, 0, &mut rbufs[..], 0, sev); + assert_eq!( + aiorv.as_ref() as *const libc::aiocb, + &aiorv as *const AioReadv as *const libc::aiocb + ); + + let wbuf = []; + let wbufs = [IoSlice::new(&wbuf)]; + let aiowv = AioWritev::new(666, 0, &wbufs, 0, sev); + assert_eq!( + aiowv.as_ref() as *const libc::aiocb, + &aiowv as *const AioWritev as *const libc::aiocb + ); + } +} diff --git a/src/sys/epoll.rs b/src/sys/epoll.rs new file mode 100644 index 0000000..58def2e --- /dev/null +++ b/src/sys/epoll.rs @@ -0,0 +1,128 @@ +use crate::errno::Errno; +use crate::Result; +use libc::{self, c_int}; +use std::mem; +use std::os::unix::io::RawFd; +use std::ptr; + +libc_bitflags!( + pub struct EpollFlags: c_int { + EPOLLIN; + EPOLLPRI; + EPOLLOUT; + EPOLLRDNORM; + EPOLLRDBAND; + EPOLLWRNORM; + EPOLLWRBAND; + EPOLLMSG; + EPOLLERR; + EPOLLHUP; + EPOLLRDHUP; + EPOLLEXCLUSIVE; + #[cfg(not(target_arch = "mips"))] + EPOLLWAKEUP; + EPOLLONESHOT; + EPOLLET; + } +); + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(i32)] +#[non_exhaustive] +pub enum EpollOp { + EpollCtlAdd = libc::EPOLL_CTL_ADD, + EpollCtlDel = libc::EPOLL_CTL_DEL, + EpollCtlMod = libc::EPOLL_CTL_MOD, +} + +libc_bitflags! { + pub struct EpollCreateFlags: c_int { + EPOLL_CLOEXEC; + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct EpollEvent { + event: libc::epoll_event, +} + +impl EpollEvent { + pub fn new(events: EpollFlags, data: u64) -> Self { + EpollEvent { + event: libc::epoll_event { + events: events.bits() as u32, + u64: data, + }, + } + } + + pub fn empty() -> Self { + unsafe { mem::zeroed::() } + } + + pub fn events(&self) -> EpollFlags { + EpollFlags::from_bits(self.event.events as c_int).unwrap() + } + + pub fn data(&self) -> u64 { + self.event.u64 + } +} + +#[inline] +pub fn epoll_create() -> Result { + let res = unsafe { libc::epoll_create(1024) }; + + Errno::result(res) +} + +#[inline] +pub fn epoll_create1(flags: EpollCreateFlags) -> Result { + let res = unsafe { libc::epoll_create1(flags.bits()) }; + + Errno::result(res) +} + +#[inline] +pub fn epoll_ctl<'a, T>( + epfd: RawFd, + op: EpollOp, + fd: RawFd, + event: T, +) -> Result<()> +where + T: Into>, +{ + let mut event: Option<&mut EpollEvent> = event.into(); + if event.is_none() && op != EpollOp::EpollCtlDel { + Err(Errno::EINVAL) + } else { + let res = unsafe { + if let Some(ref mut event) = event { + libc::epoll_ctl(epfd, op as c_int, fd, &mut event.event) + } else { + libc::epoll_ctl(epfd, op as c_int, fd, ptr::null_mut()) + } + }; + Errno::result(res).map(drop) + } +} + +#[inline] +pub fn epoll_wait( + epfd: RawFd, + events: &mut [EpollEvent], + timeout_ms: isize, +) -> Result { + let res = unsafe { + libc::epoll_wait( + epfd, + events.as_mut_ptr() as *mut libc::epoll_event, + events.len() as c_int, + timeout_ms as c_int, + ) + }; + + Errno::result(res).map(|r| r as usize) +} diff --git a/src/sys/event.rs b/src/sys/event.rs new file mode 100644 index 0000000..d8ad628 --- /dev/null +++ b/src/sys/event.rs @@ -0,0 +1,374 @@ +/* TOOD: Implement for other kqueue based systems + */ + +use crate::{Errno, Result}; +#[cfg(not(target_os = "netbsd"))] +use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t}; +#[cfg(target_os = "netbsd")] +use libc::{c_long, intptr_t, size_t, time_t, timespec, uintptr_t}; +use std::convert::TryInto; +use std::mem; +use std::os::unix::io::RawFd; +use std::ptr; + +// Redefine kevent in terms of programmer-friendly enums and bitfields. +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct KEvent { + kevent: libc::kevent, +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "openbsd" +))] +type type_of_udata = *mut libc::c_void; +#[cfg(any(target_os = "netbsd"))] +type type_of_udata = intptr_t; + +#[cfg(target_os = "netbsd")] +type type_of_event_filter = u32; +#[cfg(not(target_os = "netbsd"))] +type type_of_event_filter = i16; +libc_enum! { + #[cfg_attr(target_os = "netbsd", repr(u32))] + #[cfg_attr(not(target_os = "netbsd"), repr(i16))] + #[non_exhaustive] + pub enum EventFilter { + EVFILT_AIO, + /// Returns whenever there is no remaining data in the write buffer + #[cfg(target_os = "freebsd")] + EVFILT_EMPTY, + #[cfg(target_os = "dragonfly")] + EVFILT_EXCEPT, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] + EVFILT_FS, + #[cfg(target_os = "freebsd")] + EVFILT_LIO, + #[cfg(any(target_os = "ios", target_os = "macos"))] + EVFILT_MACHPORT, + EVFILT_PROC, + /// Returns events associated with the process referenced by a given + /// process descriptor, created by `pdfork()`. The events to monitor are: + /// + /// - NOTE_EXIT: the process has exited. The exit status will be stored in data. + #[cfg(target_os = "freebsd")] + EVFILT_PROCDESC, + EVFILT_READ, + /// Returns whenever an asynchronous `sendfile()` call completes. + #[cfg(target_os = "freebsd")] + EVFILT_SENDFILE, + EVFILT_SIGNAL, + EVFILT_TIMER, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] + EVFILT_USER, + #[cfg(any(target_os = "ios", target_os = "macos"))] + EVFILT_VM, + EVFILT_VNODE, + EVFILT_WRITE, + } + impl TryFrom +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "openbsd" +))] +pub type type_of_event_flag = u16; +#[cfg(any(target_os = "netbsd"))] +pub type type_of_event_flag = u32; +libc_bitflags! { + pub struct EventFlag: type_of_event_flag { + EV_ADD; + EV_CLEAR; + EV_DELETE; + EV_DISABLE; + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + EV_DISPATCH; + #[cfg(target_os = "freebsd")] + EV_DROP; + EV_ENABLE; + EV_EOF; + EV_ERROR; + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_FLAG0; + EV_FLAG1; + #[cfg(target_os = "dragonfly")] + EV_NODATA; + EV_ONESHOT; + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_OOBAND; + #[cfg(any(target_os = "macos", target_os = "ios"))] + EV_POLL; + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + EV_RECEIPT; + } +} + +libc_bitflags!( + pub struct FilterFlag: u32 { + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_ABSOLUTE; + NOTE_ATTRIB; + NOTE_CHILD; + NOTE_DELETE; + #[cfg(target_os = "openbsd")] + NOTE_EOF; + NOTE_EXEC; + NOTE_EXIT; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_EXITSTATUS; + NOTE_EXTEND; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFAND; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFCOPY; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFCTRLMASK; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFLAGSMASK; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFNOP; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_FFOR; + NOTE_FORK; + NOTE_LINK; + NOTE_LOWAT; + #[cfg(target_os = "freebsd")] + NOTE_MSECONDS; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_NONE; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + NOTE_NSECONDS; + #[cfg(target_os = "dragonfly")] + NOTE_OOB; + NOTE_PCTRLMASK; + NOTE_PDATAMASK; + NOTE_RENAME; + NOTE_REVOKE; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + NOTE_SECONDS; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_SIGNAL; + NOTE_TRACK; + NOTE_TRACKERR; + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly"))] + NOTE_TRIGGER; + #[cfg(target_os = "openbsd")] + NOTE_TRUNCATE; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + NOTE_USECONDS; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_ERROR; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_PRESSURE; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_PRESSURE_SUDDEN_TERMINATE; + #[cfg(any(target_os = "macos", target_os = "ios"))] + NOTE_VM_PRESSURE_TERMINATE; + NOTE_WRITE; + } +); + +pub fn kqueue() -> Result { + let res = unsafe { libc::kqueue() }; + + Errno::result(res) +} + +// KEvent can't derive Send because on some operating systems, udata is defined +// as a void*. However, KEvent's public API always treats udata as an intptr_t, +// which is safe to Send. +unsafe impl Send for KEvent {} + +impl KEvent { + #[allow(clippy::needless_update)] // Not needless on all platforms. + pub fn new( + ident: uintptr_t, + filter: EventFilter, + flags: EventFlag, + fflags: FilterFlag, + data: intptr_t, + udata: intptr_t, + ) -> KEvent { + KEvent { + kevent: libc::kevent { + ident, + filter: filter as type_of_event_filter, + flags: flags.bits(), + fflags: fflags.bits(), + // data can be either i64 or intptr_t, depending on platform + data: data as _, + udata: udata as type_of_udata, + ..unsafe { mem::zeroed() } + }, + } + } + + pub fn ident(&self) -> uintptr_t { + self.kevent.ident + } + + pub fn filter(&self) -> Result { + self.kevent.filter.try_into() + } + + pub fn flags(&self) -> EventFlag { + EventFlag::from_bits(self.kevent.flags).unwrap() + } + + pub fn fflags(&self) -> FilterFlag { + FilterFlag::from_bits(self.kevent.fflags).unwrap() + } + + pub fn data(&self) -> intptr_t { + self.kevent.data as intptr_t + } + + pub fn udata(&self) -> intptr_t { + self.kevent.udata as intptr_t + } +} + +pub fn kevent( + kq: RawFd, + changelist: &[KEvent], + eventlist: &mut [KEvent], + timeout_ms: usize, +) -> Result { + // Convert ms to timespec + let timeout = timespec { + tv_sec: (timeout_ms / 1000) as time_t, + tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long, + }; + + kevent_ts(kq, changelist, eventlist, Some(timeout)) +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" +))] +type type_of_nchanges = c_int; +#[cfg(target_os = "netbsd")] +type type_of_nchanges = size_t; + +pub fn kevent_ts( + kq: RawFd, + changelist: &[KEvent], + eventlist: &mut [KEvent], + timeout_opt: Option, +) -> Result { + let res = unsafe { + libc::kevent( + kq, + changelist.as_ptr() as *const libc::kevent, + changelist.len() as type_of_nchanges, + eventlist.as_mut_ptr() as *mut libc::kevent, + eventlist.len() as type_of_nchanges, + if let Some(ref timeout) = timeout_opt { + timeout as *const timespec + } else { + ptr::null() + }, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +#[inline] +pub fn ev_set( + ev: &mut KEvent, + ident: usize, + filter: EventFilter, + flags: EventFlag, + fflags: FilterFlag, + udata: intptr_t, +) { + ev.kevent.ident = ident as uintptr_t; + ev.kevent.filter = filter as type_of_event_filter; + ev.kevent.flags = flags.bits(); + ev.kevent.fflags = fflags.bits(); + ev.kevent.data = 0; + ev.kevent.udata = udata as type_of_udata; +} + +#[test] +fn test_struct_kevent() { + use std::mem; + + let udata: intptr_t = 12345; + + let actual = KEvent::new( + 0xdead_beef, + EventFilter::EVFILT_READ, + EventFlag::EV_ONESHOT | EventFlag::EV_ADD, + FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, + 0x1337, + udata, + ); + assert_eq!(0xdead_beef, actual.ident()); + let filter = actual.kevent.filter; + assert_eq!(libc::EVFILT_READ, filter); + assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits()); + assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits()); + assert_eq!(0x1337, actual.data()); + assert_eq!(udata as type_of_udata, actual.udata() as type_of_udata); + assert_eq!(mem::size_of::(), mem::size_of::()); +} + +#[test] +fn test_kevent_filter() { + let udata: intptr_t = 12345; + + let actual = KEvent::new( + 0xdead_beef, + EventFilter::EVFILT_READ, + EventFlag::EV_ONESHOT | EventFlag::EV_ADD, + FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, + 0x1337, + udata, + ); + assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); +} diff --git a/src/sys/eventfd.rs b/src/sys/eventfd.rs new file mode 100644 index 0000000..cd90672 --- /dev/null +++ b/src/sys/eventfd.rs @@ -0,0 +1,17 @@ +use crate::errno::Errno; +use crate::Result; +use std::os::unix::io::RawFd; + +libc_bitflags! { + pub struct EfdFlags: libc::c_int { + EFD_CLOEXEC; // Since Linux 2.6.27 + EFD_NONBLOCK; // Since Linux 2.6.27 + EFD_SEMAPHORE; // Since Linux 2.6.30 + } +} + +pub fn eventfd(initval: libc::c_uint, flags: EfdFlags) -> Result { + let res = unsafe { libc::eventfd(initval, flags.bits()) }; + + Errno::result(res).map(|r| r as RawFd) +} diff --git a/src/sys/inotify.rs b/src/sys/inotify.rs new file mode 100644 index 0000000..84356ec --- /dev/null +++ b/src/sys/inotify.rs @@ -0,0 +1,248 @@ +//! Monitoring API for filesystem events. +//! +//! Inotify is a Linux-only API to monitor filesystems events. +//! +//! For more documentation, please read [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html). +//! +//! # Examples +//! +//! Monitor all events happening in directory "test": +//! ```no_run +//! # use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify}; +//! # +//! // We create a new inotify instance. +//! let instance = Inotify::init(InitFlags::empty()).unwrap(); +//! +//! // We add a new watch on directory "test" for all events. +//! let wd = instance.add_watch("test", AddWatchFlags::IN_ALL_EVENTS).unwrap(); +//! +//! loop { +//! // We read from our inotify instance for events. +//! let events = instance.read_events().unwrap(); +//! println!("Events: {:?}", events); +//! } +//! ``` + +use crate::errno::Errno; +use crate::unistd::read; +use crate::NixPath; +use crate::Result; +use cfg_if::cfg_if; +use libc::{c_char, c_int}; +use std::ffi::{CStr, OsStr, OsString}; +use std::mem::{size_of, MaybeUninit}; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::ptr; + +libc_bitflags! { + /// Configuration options for [`inotify_add_watch`](fn.inotify_add_watch.html). + pub struct AddWatchFlags: u32 { + /// File was accessed. + IN_ACCESS; + /// File was modified. + IN_MODIFY; + /// Metadata changed. + IN_ATTRIB; + /// Writable file was closed. + IN_CLOSE_WRITE; + /// Nonwritable file was closed. + IN_CLOSE_NOWRITE; + /// File was opened. + IN_OPEN; + /// File was moved from X. + IN_MOVED_FROM; + /// File was moved to Y. + IN_MOVED_TO; + /// Subfile was created. + IN_CREATE; + /// Subfile was deleted. + IN_DELETE; + /// Self was deleted. + IN_DELETE_SELF; + /// Self was moved. + IN_MOVE_SELF; + + /// Backing filesystem was unmounted. + IN_UNMOUNT; + /// Event queue overflowed. + IN_Q_OVERFLOW; + /// File was ignored. + IN_IGNORED; + + /// Combination of `IN_CLOSE_WRITE` and `IN_CLOSE_NOWRITE`. + IN_CLOSE; + /// Combination of `IN_MOVED_FROM` and `IN_MOVED_TO`. + IN_MOVE; + + /// Only watch the path if it is a directory. + IN_ONLYDIR; + /// Don't follow symlinks. + IN_DONT_FOLLOW; + + /// Event occurred against directory. + IN_ISDIR; + /// Only send event once. + IN_ONESHOT; + /// All of the events. + IN_ALL_EVENTS; + } +} + +libc_bitflags! { + /// Configuration options for [`inotify_init1`](fn.inotify_init1.html). + pub struct InitFlags: c_int { + /// Set the `FD_CLOEXEC` flag on the file descriptor. + IN_CLOEXEC; + /// Set the `O_NONBLOCK` flag on the open file description referred to by the new file descriptor. + IN_NONBLOCK; + } +} + +/// An inotify instance. This is also a file descriptor, you can feed it to +/// other interfaces consuming file descriptors, epoll for example. +#[derive(Debug, Clone, Copy)] +pub struct Inotify { + fd: RawFd, +} + +/// This object is returned when you create a new watch on an inotify instance. +/// It is then returned as part of an event once triggered. It allows you to +/// know which watch triggered which event. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct WatchDescriptor { + wd: i32, +} + +/// A single inotify event. +/// +/// For more documentation see, [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html). +#[derive(Debug)] +pub struct InotifyEvent { + /// Watch descriptor. This field corresponds to the watch descriptor you + /// were issued when calling add_watch. It allows you to know which watch + /// this event comes from. + pub wd: WatchDescriptor, + /// Event mask. This field is a bitfield describing the exact event that + /// occured. + pub mask: AddWatchFlags, + /// This cookie is a number that allows you to connect related events. For + /// now only IN_MOVED_FROM and IN_MOVED_TO can be connected. + pub cookie: u32, + /// Filename. This field exists only if the event was triggered for a file + /// inside the watched directory. + pub name: Option, +} + +impl Inotify { + /// Initialize a new inotify instance. + /// + /// Returns a Result containing an inotify instance. + /// + /// For more information see, [inotify_init(2)](https://man7.org/linux/man-pages/man2/inotify_init.2.html). + pub fn init(flags: InitFlags) -> Result { + let res = Errno::result(unsafe { libc::inotify_init1(flags.bits()) }); + + res.map(|fd| Inotify { fd }) + } + + /// Adds a new watch on the target file or directory. + /// + /// Returns a watch descriptor. This is not a File Descriptor! + /// + /// For more information see, [inotify_add_watch(2)](https://man7.org/linux/man-pages/man2/inotify_add_watch.2.html). + pub fn add_watch( + self, + path: &P, + mask: AddWatchFlags, + ) -> Result { + let res = path.with_nix_path(|cstr| unsafe { + libc::inotify_add_watch(self.fd, cstr.as_ptr(), mask.bits()) + })?; + + Errno::result(res).map(|wd| WatchDescriptor { wd }) + } + + /// Removes an existing watch using the watch descriptor returned by + /// inotify_add_watch. + /// + /// Returns an EINVAL error if the watch descriptor is invalid. + /// + /// For more information see, [inotify_rm_watch(2)](https://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html). + pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> { + cfg_if! { + if #[cfg(target_os = "linux")] { + let arg = wd.wd; + } else if #[cfg(target_os = "android")] { + let arg = wd.wd as u32; + } + } + let res = unsafe { libc::inotify_rm_watch(self.fd, arg) }; + + Errno::result(res).map(drop) + } + + /// Reads a collection of events from the inotify file descriptor. This call + /// can either be blocking or non blocking depending on whether IN_NONBLOCK + /// was set at initialization. + /// + /// Returns as many events as available. If the call was non blocking and no + /// events could be read then the EAGAIN error is returned. + pub fn read_events(self) -> Result> { + let header_size = size_of::(); + const BUFSIZ: usize = 4096; + let mut buffer = [0u8; BUFSIZ]; + let mut events = Vec::new(); + let mut offset = 0; + + let nread = read(self.fd, &mut buffer)?; + + while (nread - offset) >= header_size { + let event = unsafe { + let mut event = MaybeUninit::::uninit(); + ptr::copy_nonoverlapping( + buffer.as_ptr().add(offset), + event.as_mut_ptr() as *mut u8, + (BUFSIZ - offset).min(header_size), + ); + event.assume_init() + }; + + let name = match event.len { + 0 => None, + _ => { + let ptr = unsafe { + buffer.as_ptr().add(offset + header_size) + as *const c_char + }; + let cstr = unsafe { CStr::from_ptr(ptr) }; + + Some(OsStr::from_bytes(cstr.to_bytes()).to_owned()) + } + }; + + events.push(InotifyEvent { + wd: WatchDescriptor { wd: event.wd }, + mask: AddWatchFlags::from_bits_truncate(event.mask), + cookie: event.cookie, + name, + }); + + offset += header_size + event.len as usize; + } + + Ok(events) + } +} + +impl AsRawFd for Inotify { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl FromRawFd for Inotify { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Inotify { fd } + } +} diff --git a/src/sys/ioctl/bsd.rs b/src/sys/ioctl/bsd.rs new file mode 100644 index 0000000..307994c --- /dev/null +++ b/src/sys/ioctl/bsd.rs @@ -0,0 +1,129 @@ +/// The datatype used for the ioctl number +#[doc(hidden)] +#[cfg(not(target_os = "illumos"))] +pub type ioctl_num_type = ::libc::c_ulong; + +#[doc(hidden)] +#[cfg(target_os = "illumos")] +pub type ioctl_num_type = ::libc::c_int; + +/// The datatype used for the 3rd argument +#[doc(hidden)] +pub type ioctl_param_type = ::libc::c_int; + +mod consts { + use crate::sys::ioctl::ioctl_num_type; + #[doc(hidden)] + pub const VOID: ioctl_num_type = 0x2000_0000; + #[doc(hidden)] + pub const OUT: ioctl_num_type = 0x4000_0000; + #[doc(hidden)] + #[allow(overflowing_literals)] + pub const IN: ioctl_num_type = 0x8000_0000; + #[doc(hidden)] + pub const INOUT: ioctl_num_type = IN | OUT; + #[doc(hidden)] + pub const IOCPARM_MASK: ioctl_num_type = 0x1fff; +} + +pub use self::consts::*; + +#[macro_export] +#[doc(hidden)] +macro_rules! ioc { + ($inout:expr, $group:expr, $num:expr, $len:expr) => { + $inout + | (($len as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::IOCPARM_MASK) + << 16) + | (($group as $crate::sys::ioctl::ioctl_num_type) << 8) + | ($num as $crate::sys::ioctl::ioctl_num_type) + }; +} + +/// Generate an ioctl request code for a command that passes no data. +/// +/// This is equivalent to the `_IO()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_none!()` directly. +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const KVMIO: u8 = 0xAE; +/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! request_code_none { + ($g:expr, $n:expr) => { + ioc!($crate::sys::ioctl::VOID, $g, $n, 0) + }; +} + +/// Generate an ioctl request code for a command that passes an integer +/// +/// This is equivalent to the `_IOWINT()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_write_int!()` directly. +#[macro_export(local_inner_macros)] +macro_rules! request_code_write_int { + ($g:expr, $n:expr) => { + ioc!( + $crate::sys::ioctl::VOID, + $g, + $n, + ::std::mem::size_of::<$crate::libc::c_int>() + ) + }; +} + +/// Generate an ioctl request code for a command that reads. +/// +/// This is equivalent to the `_IOR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_read!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is reading and the kernel is +/// writing. +#[macro_export(local_inner_macros)] +macro_rules! request_code_read { + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::OUT, $g, $n, $len) + }; +} + +/// Generate an ioctl request code for a command that writes. +/// +/// This is equivalent to the `_IOW()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_write!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is writing and the kernel is +/// reading. +#[macro_export(local_inner_macros)] +macro_rules! request_code_write { + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::IN, $g, $n, $len) + }; +} + +/// Generate an ioctl request code for a command that reads and writes. +/// +/// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_readwrite!()` directly. +#[macro_export(local_inner_macros)] +macro_rules! request_code_readwrite { + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::INOUT, $g, $n, $len) + }; +} diff --git a/src/sys/ioctl/linux.rs b/src/sys/ioctl/linux.rs new file mode 100644 index 0000000..0c0a209 --- /dev/null +++ b/src/sys/ioctl/linux.rs @@ -0,0 +1,172 @@ +/// The datatype used for the ioctl number +#[cfg(any(target_os = "android", target_env = "musl"))] +#[doc(hidden)] +pub type ioctl_num_type = ::libc::c_int; +#[cfg(not(any(target_os = "android", target_env = "musl")))] +#[doc(hidden)] +pub type ioctl_num_type = ::libc::c_ulong; +/// The datatype used for the 3rd argument +#[doc(hidden)] +pub type ioctl_param_type = ::libc::c_ulong; + +#[doc(hidden)] +pub const NRBITS: ioctl_num_type = 8; +#[doc(hidden)] +pub const TYPEBITS: ioctl_num_type = 8; + +#[cfg(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "sparc64" +))] +mod consts { + #[doc(hidden)] + pub const NONE: u8 = 1; + #[doc(hidden)] + pub const READ: u8 = 2; + #[doc(hidden)] + pub const WRITE: u8 = 4; + #[doc(hidden)] + pub const SIZEBITS: u8 = 13; + #[doc(hidden)] + pub const DIRBITS: u8 = 3; +} + +// "Generic" ioctl protocol +#[cfg(any( + target_arch = "x86", + target_arch = "arm", + target_arch = "s390x", + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv32", + target_arch = "riscv64" +))] +mod consts { + #[doc(hidden)] + pub const NONE: u8 = 0; + #[doc(hidden)] + pub const READ: u8 = 2; + #[doc(hidden)] + pub const WRITE: u8 = 1; + #[doc(hidden)] + pub const SIZEBITS: u8 = 14; + #[doc(hidden)] + pub const DIRBITS: u8 = 2; +} + +pub use self::consts::*; + +#[doc(hidden)] +pub const NRSHIFT: ioctl_num_type = 0; +#[doc(hidden)] +pub const TYPESHIFT: ioctl_num_type = NRSHIFT + NRBITS as ioctl_num_type; +#[doc(hidden)] +pub const SIZESHIFT: ioctl_num_type = TYPESHIFT + TYPEBITS as ioctl_num_type; +#[doc(hidden)] +pub const DIRSHIFT: ioctl_num_type = SIZESHIFT + SIZEBITS as ioctl_num_type; + +#[doc(hidden)] +pub const NRMASK: ioctl_num_type = (1 << NRBITS) - 1; +#[doc(hidden)] +pub const TYPEMASK: ioctl_num_type = (1 << TYPEBITS) - 1; +#[doc(hidden)] +pub const SIZEMASK: ioctl_num_type = (1 << SIZEBITS) - 1; +#[doc(hidden)] +pub const DIRMASK: ioctl_num_type = (1 << DIRBITS) - 1; + +/// Encode an ioctl command. +#[macro_export] +#[doc(hidden)] +macro_rules! ioc { + ($dir:expr, $ty:expr, $nr:expr, $sz:expr) => { + (($dir as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::DIRMASK) + << $crate::sys::ioctl::DIRSHIFT) + | (($ty as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::TYPEMASK) + << $crate::sys::ioctl::TYPESHIFT) + | (($nr as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::NRMASK) + << $crate::sys::ioctl::NRSHIFT) + | (($sz as $crate::sys::ioctl::ioctl_num_type + & $crate::sys::ioctl::SIZEMASK) + << $crate::sys::ioctl::SIZESHIFT) + }; +} + +/// Generate an ioctl request code for a command that passes no data. +/// +/// This is equivalent to the `_IO()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_none!()` directly. +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const KVMIO: u8 = 0xAE; +/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! request_code_none { + ($ty:expr, $nr:expr) => { + ioc!($crate::sys::ioctl::NONE, $ty, $nr, 0) + }; +} + +/// Generate an ioctl request code for a command that reads. +/// +/// This is equivalent to the `_IOR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_read!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is reading and the kernel is +/// writing. +#[macro_export(local_inner_macros)] +macro_rules! request_code_read { + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!($crate::sys::ioctl::READ, $ty, $nr, $sz) + }; +} + +/// Generate an ioctl request code for a command that writes. +/// +/// This is equivalent to the `_IOW()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_write!()` directly. +/// +/// The read/write direction is relative to userland, so this +/// command would be userland is writing and the kernel is +/// reading. +#[macro_export(local_inner_macros)] +macro_rules! request_code_write { + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!($crate::sys::ioctl::WRITE, $ty, $nr, $sz) + }; +} + +/// Generate an ioctl request code for a command that reads and writes. +/// +/// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API. +/// +/// You should only use this macro directly if the `ioctl` you're working +/// with is "bad" and you cannot use `ioctl_readwrite!()` directly. +#[macro_export(local_inner_macros)] +macro_rules! request_code_readwrite { + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!( + $crate::sys::ioctl::READ | $crate::sys::ioctl::WRITE, + $ty, + $nr, + $sz + ) + }; +} diff --git a/src/sys/ioctl/mod.rs b/src/sys/ioctl/mod.rs new file mode 100644 index 0000000..98d6b5c --- /dev/null +++ b/src/sys/ioctl/mod.rs @@ -0,0 +1,786 @@ +//! Provide helpers for making ioctl system calls. +//! +//! This library is pretty low-level and messy. `ioctl` is not fun. +//! +//! What is an `ioctl`? +//! =================== +//! +//! The `ioctl` syscall is the grab-bag syscall on POSIX systems. Don't want to add a new +//! syscall? Make it an `ioctl`! `ioctl` refers to both the syscall, and the commands that can be +//! sent with it. `ioctl` stands for "IO control", and the commands are always sent to a file +//! descriptor. +//! +//! It is common to see `ioctl`s used for the following purposes: +//! +//! * Provide read/write access to out-of-band data related to a device such as configuration +//! (for instance, setting serial port options) +//! * Provide a mechanism for performing full-duplex data transfers (for instance, xfer on SPI +//! devices). +//! * Provide access to control functions on a device (for example, on Linux you can send +//! commands like pause, resume, and eject to the CDROM device. +//! * Do whatever else the device driver creator thought made most sense. +//! +//! `ioctl`s are synchronous system calls and are similar to read and write calls in that regard. +//! They operate on file descriptors and have an identifier that specifies what the ioctl is. +//! Additionally they may read or write data and therefore need to pass along a data pointer. +//! Besides the semantics of the ioctls being confusing, the generation of this identifer can also +//! be difficult. +//! +//! Historically `ioctl` numbers were arbitrary hard-coded values. In Linux (before 2.6) and some +//! unices this has changed to a more-ordered system where the ioctl numbers are partitioned into +//! subcomponents (For linux this is documented in +//! [`Documentation/ioctl/ioctl-number.rst`](https://elixir.bootlin.com/linux/latest/source/Documentation/userspace-api/ioctl/ioctl-number.rst)): +//! +//! * Number: The actual ioctl ID +//! * Type: A grouping of ioctls for a common purpose or driver +//! * Size: The size in bytes of the data that will be transferred +//! * Direction: Whether there is any data and if it's read, write, or both +//! +//! Newer drivers should not generate complete integer identifiers for their `ioctl`s instead +//! preferring to use the 4 components above to generate the final ioctl identifier. Because of +//! how old `ioctl`s are, however, there are many hard-coded `ioctl` identifiers. These are +//! commonly referred to as "bad" in `ioctl` documentation. +//! +//! Defining `ioctl`s +//! ================= +//! +//! This library provides several `ioctl_*!` macros for binding `ioctl`s. These generate public +//! unsafe functions that can then be used for calling the ioctl. This macro has a few different +//! ways it can be used depending on the specific ioctl you're working with. +//! +//! A simple `ioctl` is `SPI_IOC_RD_MODE`. This ioctl works with the SPI interface on Linux. This +//! specific `ioctl` reads the mode of the SPI device as a `u8`. It's declared in +//! `/include/uapi/linux/spi/spidev.h` as `_IOR(SPI_IOC_MAGIC, 1, __u8)`. Since it uses the `_IOR` +//! macro, we know it's a `read` ioctl and can use the `ioctl_read!` macro as follows: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +//! const SPI_IOC_TYPE_MODE: u8 = 1; +//! ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8); +//! # fn main() {} +//! ``` +//! +//! This generates the function: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # use std::mem; +//! # use nix::{libc, Result}; +//! # use nix::errno::Errno; +//! # use nix::libc::c_int as c_int; +//! # const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +//! # const SPI_IOC_TYPE_MODE: u8 = 1; +//! pub unsafe fn spi_read_mode(fd: c_int, data: *mut u8) -> Result { +//! let res = libc::ioctl(fd, request_code_read!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, mem::size_of::()), data); +//! Errno::result(res) +//! } +//! # fn main() {} +//! ``` +//! +//! The return value for the wrapper functions generated by the `ioctl_*!` macros are `nix::Error`s. +//! These are generated by assuming the return value of the ioctl is `-1` on error and everything +//! else is a valid return value. If this is not the case, `Result::map` can be used to map some +//! of the range of "good" values (-Inf..-2, 0..Inf) into a smaller range in a helper function. +//! +//! Writing `ioctl`s generally use pointers as their data source and these should use the +//! `ioctl_write_ptr!`. But in some cases an `int` is passed directly. For these `ioctl`s use the +//! `ioctl_write_int!` macro. This variant does not take a type as the last argument: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! const HCI_IOC_MAGIC: u8 = b'k'; +//! const HCI_IOC_HCIDEVUP: u8 = 1; +//! ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP); +//! # fn main() {} +//! ``` +//! +//! Some `ioctl`s don't transfer any data, and those should use `ioctl_none!`. This macro +//! doesn't take a type and so it is declared similar to the `write_int` variant shown above. +//! +//! The mode for a given `ioctl` should be clear from the documentation if it has good +//! documentation. Otherwise it will be clear based on the macro used to generate the `ioctl` +//! number where `_IO`, `_IOR`, `_IOW`, and `_IOWR` map to "none", "read", "write_*", and "readwrite" +//! respectively. To determine the specific `write_` variant to use you'll need to find +//! what the argument type is supposed to be. If it's an `int`, then `write_int` should be used, +//! otherwise it should be a pointer and `write_ptr` should be used. On Linux the +//! [`ioctl_list` man page](https://man7.org/linux/man-pages/man2/ioctl_list.2.html) describes a +//! large number of `ioctl`s and describes their argument data type. +//! +//! Using "bad" `ioctl`s +//! -------------------- +//! +//! As mentioned earlier, there are many old `ioctl`s that do not use the newer method of +//! generating `ioctl` numbers and instead use hardcoded values. These can be used with the +//! `ioctl_*_bad!` macros. This naming comes from the Linux kernel which refers to these +//! `ioctl`s as "bad". These are a different variant as they bypass calling the macro that generates +//! the ioctl number and instead use the defined value directly. +//! +//! For example the `TCGETS` `ioctl` reads a `termios` data structure for a given file descriptor. +//! It's defined as `0x5401` in `ioctls.h` on Linux and can be implemented as: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # use nix::libc::TCGETS as TCGETS; +//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # use nix::libc::termios as termios; +//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! ioctl_read_bad!(tcgets, TCGETS, termios); +//! # fn main() {} +//! ``` +//! +//! The generated function has the same form as that generated by `ioctl_read!`: +//! +//! ```text +//! pub unsafe fn tcgets(fd: c_int, data: *mut termios) -> Result; +//! ``` +//! +//! Working with Arrays +//! ------------------- +//! +//! Some `ioctl`s work with entire arrays of elements. These are supported by the `ioctl_*_buf` +//! family of macros: `ioctl_read_buf`, `ioctl_write_buf`, and `ioctl_readwrite_buf`. Note that +//! there are no "bad" versions for working with buffers. The generated functions include a `len` +//! argument to specify the number of elements (where the type of each element is specified in the +//! macro). +//! +//! Again looking to the SPI `ioctl`s on Linux for an example, there is a `SPI_IOC_MESSAGE` `ioctl` +//! that queues up multiple SPI messages by writing an entire array of `spi_ioc_transfer` structs. +//! `linux/spi/spidev.h` defines a macro to calculate the `ioctl` number like: +//! +//! ```C +//! #define SPI_IOC_MAGIC 'k' +//! #define SPI_MSGSIZE(N) ... +//! #define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)]) +//! ``` +//! +//! The `SPI_MSGSIZE(N)` calculation is already handled by the `ioctl_*!` macros, so all that's +//! needed to define this `ioctl` is: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +//! const SPI_IOC_TYPE_MESSAGE: u8 = 0; +//! # pub struct spi_ioc_transfer(u64); +//! ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer); +//! # fn main() {} +//! ``` +//! +//! This generates a function like: +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # use std::mem; +//! # use nix::{libc, Result}; +//! # use nix::errno::Errno; +//! # use nix::libc::c_int as c_int; +//! # const SPI_IOC_MAGIC: u8 = b'k'; +//! # const SPI_IOC_TYPE_MESSAGE: u8 = 0; +//! # pub struct spi_ioc_transfer(u64); +//! pub unsafe fn spi_message(fd: c_int, data: &mut [spi_ioc_transfer]) -> Result { +//! let res = libc::ioctl(fd, +//! request_code_write!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, data.len() * mem::size_of::()), +//! data); +//! Errno::result(res) +//! } +//! # fn main() {} +//! ``` +//! +//! Finding `ioctl` Documentation +//! ----------------------------- +//! +//! For Linux, look at your system's headers. For example, `/usr/include/linux/input.h` has a lot +//! of lines defining macros which use `_IO`, `_IOR`, `_IOW`, `_IOC`, and `_IOWR`. Some `ioctl`s are +//! documented directly in the headers defining their constants, but others have more extensive +//! documentation in man pages (like termios' `ioctl`s which are in `tty_ioctl(4)`). +//! +//! Documenting the Generated Functions +//! =================================== +//! +//! In many cases, users will wish for the functions generated by the `ioctl` +//! macro to be public and documented. For this reason, the generated functions +//! are public by default. If you wish to hide the ioctl, you will need to put +//! them in a private module. +//! +//! For documentation, it is possible to use doc comments inside the `ioctl_*!` macros. Here is an +//! example : +//! +//! ``` +//! # #[macro_use] extern crate nix; +//! # use nix::libc::c_int; +//! ioctl_read! { +//! /// Make the given terminal the controlling terminal of the calling process. The calling +//! /// process must be a session leader and not have a controlling terminal already. If the +//! /// terminal is already the controlling terminal of a different session group then the +//! /// ioctl will fail with **EPERM**, unless the caller is root (more precisely: has the +//! /// **CAP_SYS_ADMIN** capability) and arg equals 1, in which case the terminal is stolen +//! /// and all processes that had it as controlling terminal lose it. +//! tiocsctty, b't', 19, c_int +//! } +//! +//! # fn main() {} +//! ``` +use cfg_if::cfg_if; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] +#[macro_use] +mod linux; + +#[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "redox" +))] +pub use self::linux::*; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" +))] +#[macro_use] +mod bsd; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" +))] +pub use self::bsd::*; + +/// Convert raw ioctl return value to a Nix result +#[macro_export] +#[doc(hidden)] +macro_rules! convert_ioctl_res { + ($w:expr) => {{ + $crate::errno::Errno::result($w) + }}; +} + +/// Generates a wrapper function for an ioctl that passes no data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// The `videodev2` driver on Linux defines the `log_status` `ioctl` as: +/// +/// ```C +/// #define VIDIOC_LOG_STATUS _IO('V', 70) +/// ``` +/// +/// This can be implemented in Rust like: +/// +/// ```no_run +/// # #[macro_use] extern crate nix; +/// ioctl_none!(log_status, b'V', 70); +/// fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_none { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_none!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that passes no data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ```no_run +/// # #[macro_use] extern crate nix; +/// # use libc::TIOCNXCL; +/// # use std::fs::File; +/// # use std::os::unix::io::AsRawFd; +/// ioctl_none_bad!(tiocnxcl, TIOCNXCL); +/// fn main() { +/// let file = File::open("/dev/ttyUSB0").unwrap(); +/// unsafe { tiocnxcl(file.as_raw_fd()) }.unwrap(); +/// } +/// ``` +// TODO: add an example using request_code_*!() +#[macro_export(local_inner_macros)] +macro_rules! ioctl_none_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads data from the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +/// const SPI_IOC_TYPE_MODE: u8 = 1; +/// ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_read { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that reads data from the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// ioctl_read_bad!(tcgets, libc::TCGETS, libc::termios); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_read_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that writes data through a pointer to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *const DATA_TYPE) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # pub struct v4l2_audio {} +/// ioctl_write_ptr!(s_audio, b'V', 34, v4l2_audio); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_ptr { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *const $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that writes data through a pointer to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *const DATA_TYPE) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// ioctl_write_ptr_bad!(tcsets, libc::TCSETS, libc::termios); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_ptr_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *const $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] { + /// Generates a wrapper function for a ioctl that writes an integer to the kernel. + /// + /// The arguments to this macro are: + /// + /// * The function name + /// * The ioctl identifier + /// * The ioctl sequence number + /// + /// The generated function has the following signature: + /// + /// ```rust,ignore + /// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: nix::sys::ioctl::ioctl_param_type) -> Result + /// ``` + /// + /// `nix::sys::ioctl::ioctl_param_type` depends on the OS: + /// * BSD - `libc::c_int` + /// * Linux - `libc::c_ulong` + /// + /// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). + /// + /// # Example + /// + /// ``` + /// # #[macro_use] extern crate nix; + /// ioctl_write_int!(vt_activate, b'v', 4); + /// # fn main() {} + /// ``` + #[macro_export(local_inner_macros)] + macro_rules! ioctl_write_int { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: $crate::sys::ioctl::ioctl_param_type) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write_int!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) + } + } else { + /// Generates a wrapper function for a ioctl that writes an integer to the kernel. + /// + /// The arguments to this macro are: + /// + /// * The function name + /// * The ioctl identifier + /// * The ioctl sequence number + /// + /// The generated function has the following signature: + /// + /// ```rust,ignore + /// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: nix::sys::ioctl::ioctl_param_type) -> Result + /// ``` + /// + /// `nix::sys::ioctl::ioctl_param_type` depends on the OS: + /// * BSD - `libc::c_int` + /// * Linux - `libc::c_ulong` + /// + /// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). + /// + /// # Example + /// + /// ``` + /// # #[macro_use] extern crate nix; + /// const HCI_IOC_MAGIC: u8 = b'k'; + /// const HCI_IOC_HCIDEVUP: u8 = 1; + /// ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP); + /// # fn main() {} + /// ``` + #[macro_export(local_inner_macros)] + macro_rules! ioctl_write_int { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: $crate::sys::ioctl::ioctl_param_type) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$crate::libc::c_int>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) + } + } +} + +/// Generates a wrapper function for a "bad" ioctl that writes an integer to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: libc::c_int) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// ioctl_write_int_bad!(tcsbrk, libc::TCSBRK); +/// # fn main() {} +/// ``` +/// +/// ```rust +/// # #[macro_use] extern crate nix; +/// const KVMIO: u8 = 0xAE; +/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_int_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: $crate::libc::c_int) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads and writes data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # pub struct v4l2_audio {} +/// ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_readwrite { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for a "bad" ioctl that reads and writes data to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl request code +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +// TODO: Find an example for ioctl_readwrite_bad +#[macro_export(local_inner_macros)] +macro_rules! ioctl_readwrite_bad { + ($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: *mut $ty) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads an array of elements from the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &mut [DATA_TYPE]) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +// TODO: Find an example for ioctl_read_buf +#[macro_export(local_inner_macros)] +macro_rules! ioctl_read_buf { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: &mut [$ty]) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that writes an array of elements to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &[DATA_TYPE]) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h +/// const SPI_IOC_TYPE_MESSAGE: u8 = 0; +/// # pub struct spi_ioc_transfer(u64); +/// ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer); +/// # fn main() {} +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! ioctl_write_buf { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: &[$ty]) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} + +/// Generates a wrapper function for an ioctl that reads and writes an array of elements to the kernel. +/// +/// The arguments to this macro are: +/// +/// * The function name +/// * The ioctl identifier +/// * The ioctl sequence number +/// * The data type passed by this ioctl +/// +/// The generated function has the following signature: +/// +/// ```rust,ignore +/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &mut [DATA_TYPE]) -> Result +/// ``` +/// +/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html). +// TODO: Find an example for readwrite_buf +#[macro_export(local_inner_macros)] +macro_rules! ioctl_readwrite_buf { + ($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => ( + $(#[$attr])* + pub unsafe fn $name(fd: $crate::libc::c_int, + data: &mut [$ty]) + -> $crate::Result<$crate::libc::c_int> { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } + ) +} diff --git a/src/sys/memfd.rs b/src/sys/memfd.rs new file mode 100644 index 0000000..ad9345e --- /dev/null +++ b/src/sys/memfd.rs @@ -0,0 +1,64 @@ +//! Interfaces for managing memory-backed files. + +use cfg_if::cfg_if; +use std::os::unix::io::RawFd; + +use crate::errno::Errno; +use crate::Result; +use std::ffi::CStr; + +libc_bitflags!( + /// Options that change the behavior of [`memfd_create`]. + pub struct MemFdCreateFlag: libc::c_uint { + /// Set the close-on-exec ([`FD_CLOEXEC`]) flag on the new file descriptor. + /// + /// By default, the new file descriptor is set to remain open across an [`execve`] + /// (the `FD_CLOEXEC` flag is initially disabled). This flag can be used to change + /// this default. The file offset is set to the beginning of the file (see [`lseek`]). + /// + /// See also the description of the `O_CLOEXEC` flag in [`open(2)`]. + /// + /// [`execve`]: crate::unistd::execve + /// [`lseek`]: crate::unistd::lseek + /// [`FD_CLOEXEC`]: crate::fcntl::FdFlag::FD_CLOEXEC + /// [`open(2)`]: https://man7.org/linux/man-pages/man2/open.2.html + MFD_CLOEXEC; + /// Allow sealing operations on this file. + /// + /// See also the file sealing notes given in [`memfd_create(2)`]. + /// + /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html + MFD_ALLOW_SEALING; + } +); + +/// Creates an anonymous file that lives in memory, and return a file-descriptor to it. +/// +/// The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on. +/// However, unlike a regular file, it lives in RAM and has a volatile backing storage. +/// +/// For more information, see [`memfd_create(2)`]. +/// +/// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html +pub fn memfd_create(name: &CStr, flags: MemFdCreateFlag) -> Result { + let res = unsafe { + cfg_if! { + if #[cfg(all( + // Android does not have a memfd_create symbol + not(target_os = "android"), + any( + target_os = "freebsd", + // If the OS is Linux, gnu and musl expose a memfd_create symbol but not uclibc + target_env = "gnu", + target_env = "musl", + )))] + { + libc::memfd_create(name.as_ptr(), flags.bits()) + } else { + libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags.bits()) + } + } + }; + + Errno::result(res).map(|r| r as RawFd) +} diff --git a/src/sys/mman.rs b/src/sys/mman.rs new file mode 100644 index 0000000..2bee091 --- /dev/null +++ b/src/sys/mman.rs @@ -0,0 +1,599 @@ +//! Memory management declarations. + +use crate::errno::Errno; +#[cfg(not(target_os = "android"))] +use crate::NixPath; +use crate::Result; +#[cfg(not(target_os = "android"))] +#[cfg(feature = "fs")] +use crate::{fcntl::OFlag, sys::stat::Mode}; +use libc::{self, c_int, c_void, off_t, size_t}; +use std::{os::unix::io::RawFd, num::NonZeroUsize}; + +libc_bitflags! { + /// Desired memory protection of a memory mapping. + pub struct ProtFlags: c_int { + /// Pages cannot be accessed. + PROT_NONE; + /// Pages can be read. + PROT_READ; + /// Pages can be written. + PROT_WRITE; + /// Pages can be executed + PROT_EXEC; + /// Apply protection up to the end of a mapping that grows upwards. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PROT_GROWSDOWN; + /// Apply protection down to the beginning of a mapping that grows downwards. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PROT_GROWSUP; + } +} + +libc_bitflags! { + /// Additional parameters for [`mmap`]. + pub struct MapFlags: c_int { + /// Compatibility flag. Ignored. + MAP_FILE; + /// Share this mapping. Mutually exclusive with `MAP_PRIVATE`. + MAP_SHARED; + /// Create a private copy-on-write mapping. Mutually exclusive with `MAP_SHARED`. + MAP_PRIVATE; + /// Place the mapping at exactly the address specified in `addr`. + MAP_FIXED; + /// Place the mapping at exactly the address specified in `addr`, but never clobber an existing range. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_FIXED_NOREPLACE; + /// To be used with `MAP_FIXED`, to forbid the system + /// to select a different address than the one specified. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_EXCL; + /// Synonym for `MAP_ANONYMOUS`. + MAP_ANON; + /// The mapping is not backed by any file. + MAP_ANONYMOUS; + /// Put the mapping into the first 2GB of the process address space. + #[cfg(any(all(any(target_os = "android", target_os = "linux"), + any(target_arch = "x86", target_arch = "x86_64")), + all(target_os = "linux", target_env = "musl", any(target_arch = "x86", target_arch = "x86_64")), + all(target_os = "freebsd", target_pointer_width = "64")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_32BIT; + /// Used for stacks; indicates to the kernel that the mapping should extend downward in memory. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_GROWSDOWN; + /// Compatibility flag. Ignored. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_DENYWRITE; + /// Compatibility flag. Ignored. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_EXECUTABLE; + /// Mark the mmaped region to be locked in the same way as `mlock(2)`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_LOCKED; + /// Do not reserve swap space for this mapping. + /// + /// This was removed in FreeBSD 11 and is unused in DragonFlyBSD. + #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NORESERVE; + /// Populate page tables for a mapping. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_POPULATE; + /// Only meaningful when used with `MAP_POPULATE`. Don't perform read-ahead. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NONBLOCK; + /// Allocate the mapping using "huge pages." + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGETLB; + /// Make use of 64KB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_64KB; + /// Make use of 512KB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_512KB; + /// Make use of 1MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_1MB; + /// Make use of 2MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_2MB; + /// Make use of 8MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_8MB; + /// Make use of 16MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_16MB; + /// Make use of 32MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_32MB; + /// Make use of 256MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_256MB; + /// Make use of 512MB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_512MB; + /// Make use of 1GB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_1GB; + /// Make use of 2GB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_2GB; + /// Make use of 16GB huge page (must be supported by the system) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HUGE_16GB; + + /// Lock the mapped region into memory as with `mlock(2)`. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_WIRED; + /// Causes dirtied data in the specified range to be flushed to disk only when necessary. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NOSYNC; + /// Rename private pages to a file. + /// + /// This was removed in FreeBSD 11 and is unused in DragonFlyBSD. + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_RENAME; + /// Region may contain semaphores. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_HASSEMAPHORE; + /// Region grows down, like a stack. + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_STACK; + /// Pages in this mapping are not retained in the kernel's memory cache. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_NOCACHE; + /// Allows the W/X bit on the page, it's necessary on aarch64 architecture. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_JIT; + /// Allows to use large pages, underlying alignment based on size. + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_ALIGNED_SUPER; + /// Pages will be discarded in the core dumps. + #[cfg(target_os = "openbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_CONCEAL; + } +} + +#[cfg(any(target_os = "linux", target_os = "netbsd"))] +libc_bitflags! { + /// Options for [`mremap`]. + pub struct MRemapFlags: c_int { + /// Permit the kernel to relocate the mapping to a new virtual address, if necessary. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MREMAP_MAYMOVE; + /// Place the mapping at exactly the address specified in `new_address`. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MREMAP_FIXED; + /// Place the mapping at exactly the address specified in `new_address`. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_FIXED; + /// Allows to duplicate the mapping to be able to apply different flags on the copy. + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MAP_REMAPDUP; + } +} + +libc_enum! { + /// Usage information for a range of memory to allow for performance optimizations by the kernel. + /// + /// Used by [`madvise`]. + #[repr(i32)] + #[non_exhaustive] + pub enum MmapAdvise { + /// No further special treatment. This is the default. + MADV_NORMAL, + /// Expect random page references. + MADV_RANDOM, + /// Expect sequential page references. + MADV_SEQUENTIAL, + /// Expect access in the near future. + MADV_WILLNEED, + /// Do not expect access in the near future. + MADV_DONTNEED, + /// Free up a given range of pages and its associated backing store. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_REMOVE, + /// Do not make pages in this range available to the child after a `fork(2)`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DONTFORK, + /// Undo the effect of `MADV_DONTFORK`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DOFORK, + /// Poison the given pages. + /// + /// Subsequent references to those pages are treated like hardware memory corruption. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_HWPOISON, + /// Enable Kernel Samepage Merging (KSM) for the given pages. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_MERGEABLE, + /// Undo the effect of `MADV_MERGEABLE` + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_UNMERGEABLE, + /// Preserve the memory of each page but offline the original page. + #[cfg(any(target_os = "android", + all(target_os = "linux", any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + target_arch = "sparc64"))))] + MADV_SOFT_OFFLINE, + /// Enable Transparent Huge Pages (THP) for pages in the given range. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_HUGEPAGE, + /// Undo the effect of `MADV_HUGEPAGE`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_NOHUGEPAGE, + /// Exclude the given range from a core dump. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DONTDUMP, + /// Undo the effect of an earlier `MADV_DONTDUMP`. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_DODUMP, + /// Specify that the application no longer needs the pages in the given range. + MADV_FREE, + /// Request that the system not flush the current range to disk unless it needs to. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_NOSYNC, + /// Undoes the effects of `MADV_NOSYNC` for any future pages dirtied within the given range. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_AUTOSYNC, + /// Region is not included in a core file. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_NOCORE, + /// Include region in a core file + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_CORE, + /// This process should not be killed when swap space is exhausted. + #[cfg(any(target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_PROTECT, + /// Invalidate the hardware page table for the given region. + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_INVAL, + /// Set the offset of the page directory page to `value` for the virtual page table. + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_SETMAP, + /// Indicates that the application will not need the data in the given range. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_ZERO_WIRED_PAGES, + /// Pages can be reused (by anyone). + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_FREE_REUSABLE, + /// Caller wants to reuse those pages. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MADV_FREE_REUSE, + // Darwin doesn't document this flag's behavior. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(missing_docs)] + MADV_CAN_REUSE, + } +} + +libc_bitflags! { + /// Configuration flags for [`msync`]. + pub struct MsFlags: c_int { + /// Schedule an update but return immediately. + MS_ASYNC; + /// Invalidate all cached data. + MS_INVALIDATE; + /// Invalidate pages, but leave them mapped. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MS_KILLPAGES; + /// Deactivate pages, but leave them mapped. + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MS_DEACTIVATE; + /// Perform an update and wait for it to complete. + MS_SYNC; + } +} + +#[cfg(not(target_os = "haiku"))] +libc_bitflags! { + /// Flags for [`mlockall`]. + pub struct MlockAllFlags: c_int { + /// Lock pages that are currently mapped into the address space of the process. + MCL_CURRENT; + /// Lock pages which will become mapped into the address space of the process in the future. + MCL_FUTURE; + } +} + +/// Locks all memory pages that contain part of the address range with `length` +/// bytes starting at `addr`. +/// +/// Locked pages never move to the swap area. +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`mlock(2)`] man page. +/// +/// [`mlock(2)`]: https://man7.org/linux/man-pages/man2/mlock.2.html +pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> { + Errno::result(libc::mlock(addr, length)).map(drop) +} + +/// Unlocks all memory pages that contain part of the address range with +/// `length` bytes starting at `addr`. +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`munlock(2)`] man +/// page. +/// +/// [`munlock(2)`]: https://man7.org/linux/man-pages/man2/munlock.2.html +pub unsafe fn munlock(addr: *const c_void, length: size_t) -> Result<()> { + Errno::result(libc::munlock(addr, length)).map(drop) +} + +/// Locks all memory pages mapped into this process' address space. +/// +/// Locked pages never move to the swap area. For more information, see [`mlockall(2)`]. +/// +/// [`mlockall(2)`]: https://man7.org/linux/man-pages/man2/mlockall.2.html +#[cfg(not(target_os = "haiku"))] +pub fn mlockall(flags: MlockAllFlags) -> Result<()> { + unsafe { Errno::result(libc::mlockall(flags.bits())) }.map(drop) +} + +/// Unlocks all memory pages mapped into this process' address space. +/// +/// For more information, see [`munlockall(2)`]. +/// +/// [`munlockall(2)`]: https://man7.org/linux/man-pages/man2/munlockall.2.html +#[cfg(not(target_os = "haiku"))] +pub fn munlockall() -> Result<()> { + unsafe { Errno::result(libc::munlockall()) }.map(drop) +} + +/// allocate memory, or map files or devices into memory +/// +/// # Safety +/// +/// See the [`mmap(2)`] man page for detailed requirements. +/// +/// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html +pub unsafe fn mmap( + addr: Option, + length: NonZeroUsize, + prot: ProtFlags, + flags: MapFlags, + fd: RawFd, + offset: off_t, +) -> Result<*mut c_void> { + let ptr = addr.map_or( + std::ptr::null_mut(), + |a| usize::from(a) as *mut c_void + ); + + let ret = libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset); + + if ret == libc::MAP_FAILED { + Err(Errno::last()) + } else { + Ok(ret) + } +} + +/// Expands (or shrinks) an existing memory mapping, potentially moving it at +/// the same time. +/// +/// # Safety +/// +/// See the `mremap(2)` [man page](https://man7.org/linux/man-pages/man2/mremap.2.html) for +/// detailed requirements. +#[cfg(any(target_os = "linux", target_os = "netbsd"))] +pub unsafe fn mremap( + addr: *mut c_void, + old_size: size_t, + new_size: size_t, + flags: MRemapFlags, + new_address: Option<*mut c_void>, +) -> Result<*mut c_void> { + #[cfg(target_os = "linux")] + let ret = libc::mremap( + addr, + old_size, + new_size, + flags.bits(), + new_address.unwrap_or(std::ptr::null_mut()), + ); + #[cfg(target_os = "netbsd")] + let ret = libc::mremap( + addr, + old_size, + new_address.unwrap_or(std::ptr::null_mut()), + new_size, + flags.bits(), + ); + + if ret == libc::MAP_FAILED { + Err(Errno::last()) + } else { + Ok(ret) + } +} + +/// remove a mapping +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`munmap(2)`] man +/// page. +/// +/// [`munmap(2)`]: https://man7.org/linux/man-pages/man2/munmap.2.html +pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> { + Errno::result(libc::munmap(addr, len)).map(drop) +} + +/// give advice about use of memory +/// +/// # Safety +/// +/// See the [`madvise(2)`] man page. Take special care when using +/// [`MmapAdvise::MADV_FREE`]. +/// +/// [`madvise(2)`]: https://man7.org/linux/man-pages/man2/madvise.2.html +pub unsafe fn madvise( + addr: *mut c_void, + length: size_t, + advise: MmapAdvise, +) -> Result<()> { + Errno::result(libc::madvise(addr, length, advise as i32)).map(drop) +} + +/// Set protection of memory mapping. +/// +/// See [`mprotect(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html) for +/// details. +/// +/// # Safety +/// +/// Calls to `mprotect` are inherently unsafe, as changes to memory protections can lead to +/// SIGSEGVs. +/// +/// ``` +/// # use nix::libc::size_t; +/// # use nix::sys::mman::{mmap, mprotect, MapFlags, ProtFlags}; +/// # use std::ptr; +/// const ONE_K: size_t = 1024; +/// let one_k_non_zero = std::num::NonZeroUsize::new(ONE_K).unwrap(); +/// let mut slice: &mut [u8] = unsafe { +/// let mem = mmap(None, one_k_non_zero, ProtFlags::PROT_NONE, +/// MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, -1, 0).unwrap(); +/// mprotect(mem, ONE_K, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE).unwrap(); +/// std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) +/// }; +/// assert_eq!(slice[0], 0x00); +/// slice[0] = 0xFF; +/// assert_eq!(slice[0], 0xFF); +/// ``` +pub unsafe fn mprotect( + addr: *mut c_void, + length: size_t, + prot: ProtFlags, +) -> Result<()> { + Errno::result(libc::mprotect(addr, length, prot.bits())).map(drop) +} + +/// synchronize a mapped region +/// +/// # Safety +/// +/// `addr` must meet all the requirements described in the [`msync(2)`] man +/// page. +/// +/// [`msync(2)`]: https://man7.org/linux/man-pages/man2/msync.2.html +pub unsafe fn msync( + addr: *mut c_void, + length: size_t, + flags: MsFlags, +) -> Result<()> { + Errno::result(libc::msync(addr, length, flags.bits())).map(drop) +} + +#[cfg(not(target_os = "android"))] +feature! { +#![feature = "fs"] +/// Creates and opens a new, or opens an existing, POSIX shared memory object. +/// +/// For more information, see [`shm_open(3)`]. +/// +/// [`shm_open(3)`]: https://man7.org/linux/man-pages/man3/shm_open.3.html +pub fn shm_open

( + name: &P, + flag: OFlag, + mode: Mode + ) -> Result + where P: ?Sized + NixPath +{ + let ret = name.with_nix_path(|cstr| { + #[cfg(any(target_os = "macos", target_os = "ios"))] + unsafe { + libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::c_uint) + } + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + unsafe { + libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::mode_t) + } + })?; + + Errno::result(ret) +} +} + +/// Performs the converse of [`shm_open`], removing an object previously created. +/// +/// For more information, see [`shm_unlink(3)`]. +/// +/// [`shm_unlink(3)`]: https://man7.org/linux/man-pages/man3/shm_unlink.3.html +#[cfg(not(target_os = "android"))] +pub fn shm_unlink(name: &P) -> Result<()> { + let ret = + name.with_nix_path(|cstr| unsafe { libc::shm_unlink(cstr.as_ptr()) })?; + + Errno::result(ret).map(drop) +} diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..2065059 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,228 @@ +//! Mostly platform-specific functionality +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + target_os = "netbsd" +))] +feature! { + #![feature = "aio"] + pub mod aio; +} + +feature! { + #![feature = "event"] + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[allow(missing_docs)] + pub mod epoll; + + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[allow(missing_docs)] + pub mod event; + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[allow(missing_docs)] + pub mod eventfd; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" +))] +#[cfg(feature = "ioctl")] +#[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] +#[macro_use] +pub mod ioctl; + +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +feature! { + #![feature = "fs"] + pub mod memfd; +} + +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "mman"] + pub mod mman; +} + +#[cfg(target_os = "linux")] +feature! { + #![feature = "personality"] + pub mod personality; +} + +feature! { + #![feature = "pthread"] + pub mod pthread; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +feature! { + #![feature = "ptrace"] + #[allow(missing_docs)] + pub mod ptrace; +} + +#[cfg(target_os = "linux")] +feature! { + #![feature = "quota"] + pub mod quota; +} + +#[cfg(target_os = "linux")] +feature! { + #![feature = "reboot"] + pub mod reboot; +} + +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "illumos", + target_os = "haiku" +)))] +feature! { + #![feature = "resource"] + pub mod resource; +} + +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "poll"] + pub mod select; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos" +))] +feature! { + #![feature = "zerocopy"] + pub mod sendfile; +} + +pub mod signal; + +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "signal"] + #[allow(missing_docs)] + pub mod signalfd; +} + +#[cfg(not(target_os = "redox"))] +feature! { + #![feature = "socket"] + #[allow(missing_docs)] + pub mod socket; +} + +feature! { + #![feature = "fs"] + #[allow(missing_docs)] + pub mod stat; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "openbsd" +))] +feature! { + #![feature = "fs"] + pub mod statfs; +} + +feature! { + #![feature = "fs"] + pub mod statvfs; +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +#[allow(missing_docs)] +pub mod sysinfo; + +feature! { + #![feature = "term"] + #[allow(missing_docs)] + pub mod termios; +} + +#[allow(missing_docs)] +pub mod time; + +feature! { + #![feature = "uio"] + pub mod uio; +} + +feature! { + #![feature = "feature"] + pub mod utsname; +} + +feature! { + #![feature = "process"] + pub mod wait; +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "inotify"] + pub mod inotify; +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +feature! { + #![feature = "time"] + pub mod timerfd; +} + +#[cfg(all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" +))] +feature! { + #![feature = "time"] + pub mod timer; +} diff --git a/src/sys/personality.rs b/src/sys/personality.rs new file mode 100644 index 0000000..f295a05 --- /dev/null +++ b/src/sys/personality.rs @@ -0,0 +1,93 @@ +//! Process execution domains +use crate::errno::Errno; +use crate::Result; + +use libc::{self, c_int, c_ulong}; + +libc_bitflags! { + /// Flags used and returned by [`get()`](fn.get.html) and + /// [`set()`](fn.set.html). + pub struct Persona: c_int { + /// Provide the legacy virtual address space layout. + ADDR_COMPAT_LAYOUT; + /// Disable address-space-layout randomization. + ADDR_NO_RANDOMIZE; + /// Limit the address space to 32 bits. + ADDR_LIMIT_32BIT; + /// Use `0xc0000000` as the offset at which to search a virtual memory + /// chunk on [`mmap(2)`], otherwise use `0xffffe000`. + /// + /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html + ADDR_LIMIT_3GB; + /// User-space function pointers to signal handlers point to descriptors. + #[cfg(not(any(target_env = "musl", target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FDPIC_FUNCPTRS; + /// Map page 0 as read-only. + MMAP_PAGE_ZERO; + /// `PROT_READ` implies `PROT_EXEC` for [`mmap(2)`]. + /// + /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html + READ_IMPLIES_EXEC; + /// No effects. + SHORT_INODE; + /// [`select(2)`], [`pselect(2)`], and [`ppoll(2)`] do not modify the + /// returned timeout argument when interrupted by a signal handler. + /// + /// [`select(2)`]: https://man7.org/linux/man-pages/man2/select.2.html + /// [`pselect(2)`]: https://man7.org/linux/man-pages/man2/pselect.2.html + /// [`ppoll(2)`]: https://man7.org/linux/man-pages/man2/ppoll.2.html + STICKY_TIMEOUTS; + /// Have [`uname(2)`] report a 2.6.40+ version number rather than a 3.x + /// version number. + /// + /// [`uname(2)`]: https://man7.org/linux/man-pages/man2/uname.2.html + #[cfg(not(any(target_env = "musl", target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + UNAME26; + /// No effects. + WHOLE_SECONDS; + } +} + +/// Retrieve the current process personality. +/// +/// Returns a Result containing a Persona instance. +/// +/// Example: +/// +/// ``` +/// # use nix::sys::personality::{self, Persona}; +/// let pers = personality::get().unwrap(); +/// assert!(!pers.contains(Persona::WHOLE_SECONDS)); +/// ``` +pub fn get() -> Result { + let res = unsafe { libc::personality(0xFFFFFFFF) }; + + Errno::result(res).map(Persona::from_bits_truncate) +} + +/// Set the current process personality. +/// +/// Returns a Result containing the *previous* personality for the +/// process, as a Persona. +/// +/// For more information, see [personality(2)](https://man7.org/linux/man-pages/man2/personality.2.html) +/// +/// **NOTE**: This call **replaces** the current personality entirely. +/// To **update** the personality, first call `get()` and then `set()` +/// with the modified persona. +/// +/// Example: +/// +/// ``` +/// # use nix::sys::personality::{self, Persona}; +/// let mut pers = personality::get().unwrap(); +/// assert!(!pers.contains(Persona::ADDR_NO_RANDOMIZE)); +/// personality::set(pers | Persona::ADDR_NO_RANDOMIZE).unwrap(); +/// ``` +pub fn set(persona: Persona) -> Result { + let res = unsafe { libc::personality(persona.bits() as c_ulong) }; + + Errno::result(res).map(Persona::from_bits_truncate) +} diff --git a/src/sys/pthread.rs b/src/sys/pthread.rs new file mode 100644 index 0000000..6bad03a --- /dev/null +++ b/src/sys/pthread.rs @@ -0,0 +1,43 @@ +//! Low level threading primitives + +#[cfg(not(target_os = "redox"))] +use crate::errno::Errno; +#[cfg(not(target_os = "redox"))] +use crate::Result; +use libc::{self, pthread_t}; + +/// Identifies an individual thread. +pub type Pthread = pthread_t; + +/// Obtain ID of the calling thread (see +/// [`pthread_self(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html) +/// +/// The thread ID returned by `pthread_self()` is not the same thing as +/// the kernel thread ID returned by a call to `gettid(2)`. +#[inline] +pub fn pthread_self() -> Pthread { + unsafe { libc::pthread_self() } +} + +feature! { +#![feature = "signal"] + +/// Send a signal to a thread (see [`pthread_kill(3)`]). +/// +/// If `signal` is `None`, `pthread_kill` will only preform error checking and +/// won't send any signal. +/// +/// [`pthread_kill(3)`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[cfg(not(target_os = "redox"))] +pub fn pthread_kill(thread: Pthread, signal: T) -> Result<()> + where T: Into> +{ + let sig = match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }; + let res = unsafe { libc::pthread_kill(thread, sig) }; + Errno::result(res).map(drop) +} +} diff --git a/src/sys/ptrace/bsd.rs b/src/sys/ptrace/bsd.rs new file mode 100644 index 0000000..ba267c6 --- /dev/null +++ b/src/sys/ptrace/bsd.rs @@ -0,0 +1,195 @@ +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::unistd::Pid; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int}; +use std::ptr; + +pub type RequestType = c_int; + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "openbsd"))] { + #[doc(hidden)] + pub type AddressType = *mut ::libc::c_char; + } else { + #[doc(hidden)] + pub type AddressType = *mut ::libc::c_void; + } +} + +libc_enum! { + #[repr(i32)] + /// Ptrace Request enum defining the action to be taken. + #[non_exhaustive] + pub enum Request { + PT_TRACE_ME, + PT_READ_I, + PT_READ_D, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_READ_U, + PT_WRITE_I, + PT_WRITE_D, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_WRITE_U, + PT_CONTINUE, + PT_KILL, + #[cfg(any(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos"), + all(target_os = "openbsd", target_arch = "x86_64"), + all(target_os = "netbsd", any(target_arch = "x86_64", + target_arch = "powerpc"))))] + PT_STEP, + PT_ATTACH, + PT_DETACH, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_SIGEXC, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_THUPDATE, + #[cfg(target_os = "macos")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PT_ATTACHEXC + } +} + +unsafe fn ptrace_other( + request: Request, + pid: Pid, + addr: AddressType, + data: c_int, +) -> Result { + Errno::result(libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data, + )) + .map(|_| 0) +} + +/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { + ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0) + .map(drop) + } +} + +/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)` +/// +/// Attaches to the process specified by `pid`, making it a tracee of the calling process. +pub fn attach(pid: Pid) -> Result<()> { + unsafe { + ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) + } +} + +/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)` +/// +/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a +/// signal specified by `sig`. +pub fn detach>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop) + } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + // Ignore the useless return value + ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data) + .map(drop) + } +} + +/// Issues a kill request as with `ptrace(PT_KILL, ...)` +/// +/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);` +pub fn kill(pid: Pid) -> Result<()> { + unsafe { + ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(drop) + } +} + +/// Move the stopped tracee process forward by a single step as with +/// `ptrace(PT_STEP, ...)` +/// +/// Advances the execution of the process with PID `pid` by a single step optionally delivering a +/// signal specified by `sig`. +/// +/// # Example +/// ```rust +/// use nix::sys::ptrace::step; +/// use nix::unistd::Pid; +/// use nix::sys::signal::Signal; +/// use nix::sys::wait::*; +/// // If a process changes state to the stopped state because of a SIGUSR1 +/// // signal, this will step the process forward and forward the user +/// // signal to the stopped process +/// match waitpid(Pid::from_raw(-1), None) { +/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { +/// let _ = step(pid, Signal::SIGUSR1); +/// } +/// _ => {}, +/// } +/// ``` +#[cfg(any( + any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"), + all(target_os = "openbsd", target_arch = "x86_64"), + all( + target_os = "netbsd", + any(target_arch = "x86_64", target_arch = "powerpc") + ) +))] +pub fn step>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(drop) + } +} + +/// Reads a word from a processes memory at the given address +// Technically, ptrace doesn't dereference the pointer. It passes it directly +// to the kernel. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn read(pid: Pid, addr: AddressType) -> Result { + unsafe { + // Traditionally there was a difference between reading data or + // instruction memory but not in modern systems. + ptrace_other(Request::PT_READ_D, pid, addr, 0) + } +} + +/// Writes a word into the processes memory at the given address +// Technically, ptrace doesn't dereference the pointer. It passes it directly +// to the kernel. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> { + unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(drop) } +} diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs new file mode 100644 index 0000000..9687e05 --- /dev/null +++ b/src/sys/ptrace/linux.rs @@ -0,0 +1,558 @@ +//! For detailed description of the ptrace requests, consult `man ptrace`. + +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::unistd::Pid; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_long, c_void, siginfo_t}; +use std::{mem, ptr}; + +pub type AddressType = *mut ::libc::c_void; + +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "x86_64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "x86", target_env = "gnu") + ) +))] +use libc::user_regs_struct; + +cfg_if! { + if #[cfg(any(all(target_os = "linux", target_arch = "s390x"), + all(target_os = "linux", target_env = "gnu"), + target_env = "uclibc"))] { + #[doc(hidden)] + pub type RequestType = ::libc::c_uint; + } else { + #[doc(hidden)] + pub type RequestType = ::libc::c_int; + } +} + +libc_enum! { + #[cfg_attr(not(any(target_env = "musl", target_env = "uclibc", target_os = "android")), repr(u32))] + #[cfg_attr(any(target_env = "musl", target_env = "uclibc", target_os = "android"), repr(i32))] + /// Ptrace Request enum defining the action to be taken. + #[non_exhaustive] + pub enum Request { + PTRACE_TRACEME, + PTRACE_PEEKTEXT, + PTRACE_PEEKDATA, + PTRACE_PEEKUSER, + PTRACE_POKETEXT, + PTRACE_POKEDATA, + PTRACE_POKEUSER, + PTRACE_CONT, + PTRACE_KILL, + PTRACE_SINGLESTEP, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_GETREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_SETREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_GETFPREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_SETFPREGS, + PTRACE_ATTACH, + PTRACE_DETACH, + #[cfg(all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86", + target_arch = "x86_64")))] + PTRACE_GETFPXREGS, + #[cfg(all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "x86", + target_arch = "x86_64")))] + PTRACE_SETFPXREGS, + PTRACE_SYSCALL, + PTRACE_SETOPTIONS, + PTRACE_GETEVENTMSG, + PTRACE_GETSIGINFO, + PTRACE_SETSIGINFO, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_GETREGSET, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_SETREGSET, + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTRACE_SEIZE, + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTRACE_INTERRUPT, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_LISTEN, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_PEEKSIGINFO, + #[cfg(all(target_os = "linux", target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64")))] + PTRACE_SYSEMU, + #[cfg(all(target_os = "linux", target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64")))] + PTRACE_SYSEMU_SINGLESTEP, + } +} + +libc_enum! { + #[repr(i32)] + /// Using the ptrace options the tracer can configure the tracee to stop + /// at certain events. This enum is used to define those events as defined + /// in `man ptrace`. + #[non_exhaustive] + pub enum Event { + /// Event that stops before a return from fork or clone. + PTRACE_EVENT_FORK, + /// Event that stops before a return from vfork or clone. + PTRACE_EVENT_VFORK, + /// Event that stops before a return from clone. + PTRACE_EVENT_CLONE, + /// Event that stops before a return from execve. + PTRACE_EVENT_EXEC, + /// Event for a return from vfork. + PTRACE_EVENT_VFORK_DONE, + /// Event for a stop before an exit. Unlike the waitpid Exit status program. + /// registers can still be examined + PTRACE_EVENT_EXIT, + /// Stop triggered by a seccomp rule on a tracee. + PTRACE_EVENT_SECCOMP, + /// Stop triggered by the `INTERRUPT` syscall, or a group stop, + /// or when a new child is attached. + PTRACE_EVENT_STOP, + } +} + +libc_bitflags! { + /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request. + /// See `man ptrace` for more details. + pub struct Options: libc::c_int { + /// When delivering system call traps set a bit to allow tracer to + /// distinguish between normal stops or syscall stops. May not work on + /// all systems. + PTRACE_O_TRACESYSGOOD; + /// Stop tracee at next fork and start tracing the forked process. + PTRACE_O_TRACEFORK; + /// Stop tracee at next vfork call and trace the vforked process. + PTRACE_O_TRACEVFORK; + /// Stop tracee at next clone call and trace the cloned process. + PTRACE_O_TRACECLONE; + /// Stop tracee at next execve call. + PTRACE_O_TRACEEXEC; + /// Stop tracee at vfork completion. + PTRACE_O_TRACEVFORKDONE; + /// Stop tracee at next exit call. Stops before exit commences allowing + /// tracer to see location of exit and register states. + PTRACE_O_TRACEEXIT; + /// Stop tracee when a SECCOMP_RET_TRACE rule is triggered. See `man seccomp` for more + /// details. + PTRACE_O_TRACESECCOMP; + /// Send a SIGKILL to the tracee if the tracer exits. This is useful + /// for ptrace jailers to prevent tracees from escaping their control. + PTRACE_O_EXITKILL; + } +} + +fn ptrace_peek( + request: Request, + pid: Pid, + addr: AddressType, + data: *mut c_void, +) -> Result { + let ret = unsafe { + Errno::clear(); + libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data) + }; + match Errno::result(ret) { + Ok(..) | Err(Errno::UnknownErrno) => Ok(ret), + err @ Err(..) => err, + } +} + +/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)` +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "x86_64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "x86", target_env = "gnu") + ) +))] +pub fn getregs(pid: Pid) -> Result { + ptrace_get_data::(Request::PTRACE_GETREGS, pid) +} + +/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)` +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "x86_64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "x86", target_env = "gnu") + ) +))] +pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { + let res = unsafe { + libc::ptrace( + Request::PTRACE_SETREGS as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::(), + ®s as *const _ as *const c_void, + ) + }; + Errno::result(res).map(drop) +} + +/// Function for ptrace requests that return values from the data field. +/// Some ptrace get requests populate structs or larger elements than `c_long` +/// and therefore use the data field to return values. This function handles these +/// requests. +fn ptrace_get_data(request: Request, pid: Pid) -> Result { + let mut data = mem::MaybeUninit::uninit(); + let res = unsafe { + libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::(), + data.as_mut_ptr() as *const _ as *const c_void, + ) + }; + Errno::result(res)?; + Ok(unsafe { data.assume_init() }) +} + +unsafe fn ptrace_other( + request: Request, + pid: Pid, + addr: AddressType, + data: *mut c_void, +) -> Result { + Errno::result(libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data, + )) + .map(|_| 0) +} + +/// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`. +pub fn setoptions(pid: Pid, options: Options) -> Result<()> { + let res = unsafe { + libc::ptrace( + Request::PTRACE_SETOPTIONS as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::(), + options.bits() as *mut c_void, + ) + }; + Errno::result(res).map(drop) +} + +/// Gets a ptrace event as described by `ptrace(PTRACE_GETEVENTMSG,...)` +pub fn getevent(pid: Pid) -> Result { + ptrace_get_data::(Request::PTRACE_GETEVENTMSG, pid) +} + +/// Get siginfo as with `ptrace(PTRACE_GETSIGINFO,...)` +pub fn getsiginfo(pid: Pid) -> Result { + ptrace_get_data::(Request::PTRACE_GETSIGINFO, pid) +} + +/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)` +pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { + let ret = unsafe { + Errno::clear(); + libc::ptrace( + Request::PTRACE_SETSIGINFO as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::(), + sig as *const _ as *const c_void, + ) + }; + match Errno::result(ret) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} + +/// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_TRACEME, + Pid::from_raw(0), + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) // ignore the useless return value + } +} + +/// Continue execution until the next syscall, as with `ptrace(PTRACE_SYSCALL, ...)` +/// +/// Arranges for the tracee to be stopped at the next entry to or exit from a system call, +/// optionally delivering a signal specified by `sig`. +pub fn syscall>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SYSCALL, pid, ptr::null_mut(), data) + .map(drop) // ignore the useless return value + } +} + +/// Continue execution until the next syscall, as with `ptrace(PTRACE_SYSEMU, ...)` +/// +/// In contrast to the `syscall` function, the syscall stopped at will not be executed. +/// Thus the the tracee will only be stopped once per syscall, +/// optionally delivering a signal specified by `sig`. +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64") +))] +pub fn sysemu>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SYSEMU, pid, ptr::null_mut(), data) + .map(drop) + // ignore the useless return value + } +} + +/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)` +/// +/// Attaches to the process specified by `pid`, making it a tracee of the calling process. +pub fn attach(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_ATTACH, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) // ignore the useless return value + } +} + +/// Attach to a running process, as with `ptrace(PTRACE_SEIZE, ...)` +/// +/// Attaches to the process specified in pid, making it a tracee of the calling process. +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn seize(pid: Pid, options: Options) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_SEIZE, + pid, + ptr::null_mut(), + options.bits() as *mut c_void, + ) + .map(drop) // ignore the useless return value + } +} + +/// Detaches the current running process, as with `ptrace(PTRACE_DETACH, ...)` +/// +/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a +/// signal specified by `sig`. +pub fn detach>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_DETACH, pid, ptr::null_mut(), data) + .map(drop) + } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_CONT, pid, ptr::null_mut(), data).map(drop) + // ignore the useless return value + } +} + +/// Stop a tracee, as with `ptrace(PTRACE_INTERRUPT, ...)` +/// +/// This request is equivalent to `ptrace(PTRACE_INTERRUPT, ...)` +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn interrupt(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_INTERRUPT, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) + } +} + +/// Issues a kill request as with `ptrace(PTRACE_KILL, ...)` +/// +/// This request is equivalent to `ptrace(PTRACE_CONT, ..., SIGKILL);` +pub fn kill(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_KILL, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) + } +} + +/// Move the stopped tracee process forward by a single step as with +/// `ptrace(PTRACE_SINGLESTEP, ...)` +/// +/// Advances the execution of the process with PID `pid` by a single step optionally delivering a +/// signal specified by `sig`. +/// +/// # Example +/// ```rust +/// use nix::sys::ptrace::step; +/// use nix::unistd::Pid; +/// use nix::sys::signal::Signal; +/// use nix::sys::wait::*; +/// +/// // If a process changes state to the stopped state because of a SIGUSR1 +/// // signal, this will step the process forward and forward the user +/// // signal to the stopped process +/// match waitpid(Pid::from_raw(-1), None) { +/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { +/// let _ = step(pid, Signal::SIGUSR1); +/// } +/// _ => {}, +/// } +/// ``` +pub fn step>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data) + .map(drop) + } +} + +/// Move the stopped tracee process forward by a single step or stop at the next syscall +/// as with `ptrace(PTRACE_SYSEMU_SINGLESTEP, ...)` +/// +/// Advances the execution by a single step or until the next syscall. +/// In case the tracee is stopped at a syscall, the syscall will not be executed. +/// Optionally, the signal specified by `sig` is delivered to the tracee upon continuation. +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "x86", target_arch = "x86_64") +))] +pub fn sysemu_step>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other( + Request::PTRACE_SYSEMU_SINGLESTEP, + pid, + ptr::null_mut(), + data, + ) + .map(drop) // ignore the useless return value + } +} + +/// Reads a word from a processes memory at the given address +pub fn read(pid: Pid, addr: AddressType) -> Result { + ptrace_peek(Request::PTRACE_PEEKDATA, pid, addr, ptr::null_mut()) +} + +/// Writes a word into the processes memory at the given address +/// +/// # Safety +/// +/// The `data` argument is passed directly to `ptrace(2)`. Read that man page +/// for guidance. +pub unsafe fn write( + pid: Pid, + addr: AddressType, + data: *mut c_void, +) -> Result<()> { + ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(drop) +} + +/// Reads a word from a user area at `offset`. +/// The user struct definition can be found in `/usr/include/sys/user.h`. +pub fn read_user(pid: Pid, offset: AddressType) -> Result { + ptrace_peek(Request::PTRACE_PEEKUSER, pid, offset, ptr::null_mut()) +} + +/// Writes a word to a user area at `offset`. +/// The user struct definition can be found in `/usr/include/sys/user.h`. +/// +/// # Safety +/// +/// The `data` argument is passed directly to `ptrace(2)`. Read that man page +/// for guidance. +pub unsafe fn write_user( + pid: Pid, + offset: AddressType, + data: *mut c_void, +) -> Result<()> { + ptrace_other(Request::PTRACE_POKEUSER, pid, offset, data).map(drop) +} diff --git a/src/sys/ptrace/mod.rs b/src/sys/ptrace/mod.rs new file mode 100644 index 0000000..2b121c0 --- /dev/null +++ b/src/sys/ptrace/mod.rs @@ -0,0 +1,25 @@ +///! Provides helpers for making ptrace system calls + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::linux::*; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +mod bsd; + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +pub use self::bsd::*; diff --git a/src/sys/quota.rs b/src/sys/quota.rs new file mode 100644 index 0000000..b3c44ca --- /dev/null +++ b/src/sys/quota.rs @@ -0,0 +1,338 @@ +//! Set and configure disk quotas for users, groups, or projects. +//! +//! # Examples +//! +//! Enabling and setting a quota: +//! +//! ```rust,no_run +//! # use nix::sys::quota::{Dqblk, quotactl_on, quotactl_set, QuotaFmt, QuotaType, QuotaValidFlags}; +//! quotactl_on(QuotaType::USRQUOTA, "/dev/sda1", QuotaFmt::QFMT_VFS_V1, "aquota.user").unwrap(); +//! let mut dqblk: Dqblk = Default::default(); +//! dqblk.set_blocks_hard_limit(10000); +//! dqblk.set_blocks_soft_limit(8000); +//! quotactl_set(QuotaType::USRQUOTA, "/dev/sda1", 50, &dqblk, QuotaValidFlags::QIF_BLIMITS).unwrap(); +//! ``` +use crate::errno::Errno; +use crate::{NixPath, Result}; +use libc::{self, c_char, c_int}; +use std::default::Default; +use std::{mem, ptr}; + +struct QuotaCmd(QuotaSubCmd, QuotaType); + +impl QuotaCmd { + #[allow(unused_unsafe)] + fn as_int(&self) -> c_int { + unsafe { libc::QCMD(self.0 as i32, self.1 as i32) } + } +} + +// linux quota version >= 2 +libc_enum! { + #[repr(i32)] + enum QuotaSubCmd { + Q_SYNC, + Q_QUOTAON, + Q_QUOTAOFF, + Q_GETQUOTA, + Q_SETQUOTA, + } +} + +libc_enum! { + /// The scope of the quota. + #[repr(i32)] + #[non_exhaustive] + pub enum QuotaType { + /// Specify a user quota + USRQUOTA, + /// Specify a group quota + GRPQUOTA, + } +} + +libc_enum! { + /// The type of quota format to use. + #[repr(i32)] + #[non_exhaustive] + pub enum QuotaFmt { + /// Use the original quota format. + QFMT_VFS_OLD, + /// Use the standard VFS v0 quota format. + /// + /// Handles 32-bit UIDs/GIDs and quota limits up to 232 bytes/232 inodes. + QFMT_VFS_V0, + /// Use the VFS v1 quota format. + /// + /// Handles 32-bit UIDs/GIDs and quota limits of 264 bytes/264 inodes. + QFMT_VFS_V1, + } +} + +libc_bitflags!( + /// Indicates the quota fields that are valid to read from. + #[derive(Default)] + pub struct QuotaValidFlags: u32 { + /// The block hard & soft limit fields. + QIF_BLIMITS; + /// The current space field. + QIF_SPACE; + /// The inode hard & soft limit fields. + QIF_ILIMITS; + /// The current inodes field. + QIF_INODES; + /// The disk use time limit field. + QIF_BTIME; + /// The file quote time limit field. + QIF_ITIME; + /// All block & inode limits. + QIF_LIMITS; + /// The space & inodes usage fields. + QIF_USAGE; + /// The time limit fields. + QIF_TIMES; + /// All fields. + QIF_ALL; + } +); + +/// Wrapper type for `if_dqblk` +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Dqblk(libc::dqblk); + +impl Default for Dqblk { + fn default() -> Dqblk { + Dqblk(libc::dqblk { + dqb_bhardlimit: 0, + dqb_bsoftlimit: 0, + dqb_curspace: 0, + dqb_ihardlimit: 0, + dqb_isoftlimit: 0, + dqb_curinodes: 0, + dqb_btime: 0, + dqb_itime: 0, + dqb_valid: 0, + }) + } +} + +impl Dqblk { + /// The absolute limit on disk quota blocks allocated. + pub fn blocks_hard_limit(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) { + Some(self.0.dqb_bhardlimit) + } else { + None + } + } + + /// Set the absolute limit on disk quota blocks allocated. + pub fn set_blocks_hard_limit(&mut self, limit: u64) { + self.0.dqb_bhardlimit = limit; + } + + /// Preferred limit on disk quota blocks + pub fn blocks_soft_limit(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) { + Some(self.0.dqb_bsoftlimit) + } else { + None + } + } + + /// Set the preferred limit on disk quota blocks allocated. + pub fn set_blocks_soft_limit(&mut self, limit: u64) { + self.0.dqb_bsoftlimit = limit; + } + + /// Current occupied space (bytes). + pub fn occupied_space(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_SPACE) { + Some(self.0.dqb_curspace) + } else { + None + } + } + + /// Maximum number of allocated inodes. + pub fn inodes_hard_limit(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) { + Some(self.0.dqb_ihardlimit) + } else { + None + } + } + + /// Set the maximum number of allocated inodes. + pub fn set_inodes_hard_limit(&mut self, limit: u64) { + self.0.dqb_ihardlimit = limit; + } + + /// Preferred inode limit + pub fn inodes_soft_limit(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) { + Some(self.0.dqb_isoftlimit) + } else { + None + } + } + + /// Set the preferred limit of allocated inodes. + pub fn set_inodes_soft_limit(&mut self, limit: u64) { + self.0.dqb_isoftlimit = limit; + } + + /// Current number of allocated inodes. + pub fn allocated_inodes(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_INODES) { + Some(self.0.dqb_curinodes) + } else { + None + } + } + + /// Time limit for excessive disk use. + pub fn block_time_limit(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_BTIME) { + Some(self.0.dqb_btime) + } else { + None + } + } + + /// Set the time limit for excessive disk use. + pub fn set_block_time_limit(&mut self, limit: u64) { + self.0.dqb_btime = limit; + } + + /// Time limit for excessive files. + pub fn inode_time_limit(&self) -> Option { + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + if valid_fields.contains(QuotaValidFlags::QIF_ITIME) { + Some(self.0.dqb_itime) + } else { + None + } + } + + /// Set the time limit for excessive files. + pub fn set_inode_time_limit(&mut self, limit: u64) { + self.0.dqb_itime = limit; + } +} + +fn quotactl( + cmd: QuotaCmd, + special: Option<&P>, + id: c_int, + addr: *mut c_char, +) -> Result<()> { + unsafe { + Errno::clear(); + let res = match special { + Some(dev) => dev.with_nix_path(|path| { + libc::quotactl(cmd.as_int(), path.as_ptr(), id, addr) + }), + None => Ok(libc::quotactl(cmd.as_int(), ptr::null(), id, addr)), + }?; + + Errno::result(res).map(drop) + } +} + +/// Turn on disk quotas for a block device. +pub fn quotactl_on( + which: QuotaType, + special: &P, + format: QuotaFmt, + quota_file: &P, +) -> Result<()> { + quota_file.with_nix_path(|path| { + let mut path_copy = path.to_bytes_with_nul().to_owned(); + let p: *mut c_char = path_copy.as_mut_ptr() as *mut c_char; + quotactl( + QuotaCmd(QuotaSubCmd::Q_QUOTAON, which), + Some(special), + format as c_int, + p, + ) + })? +} + +/// Disable disk quotas for a block device. +pub fn quotactl_off( + which: QuotaType, + special: &P, +) -> Result<()> { + quotactl( + QuotaCmd(QuotaSubCmd::Q_QUOTAOFF, which), + Some(special), + 0, + ptr::null_mut(), + ) +} + +/// Update the on-disk copy of quota usages for a filesystem. +/// +/// If `special` is `None`, then all file systems with active quotas are sync'd. +pub fn quotactl_sync( + which: QuotaType, + special: Option<&P>, +) -> Result<()> { + quotactl( + QuotaCmd(QuotaSubCmd::Q_SYNC, which), + special, + 0, + ptr::null_mut(), + ) +} + +/// Get disk quota limits and current usage for the given user/group id. +pub fn quotactl_get( + which: QuotaType, + special: &P, + id: c_int, +) -> Result { + let mut dqblk = mem::MaybeUninit::uninit(); + quotactl( + QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which), + Some(special), + id, + dqblk.as_mut_ptr() as *mut c_char, + )?; + Ok(unsafe { Dqblk(dqblk.assume_init()) }) +} + +/// Configure quota values for the specified fields for a given user/group id. +pub fn quotactl_set( + which: QuotaType, + special: &P, + id: c_int, + dqblk: &Dqblk, + fields: QuotaValidFlags, +) -> Result<()> { + let mut dqblk_copy = *dqblk; + dqblk_copy.0.dqb_valid = fields.bits(); + quotactl( + QuotaCmd(QuotaSubCmd::Q_SETQUOTA, which), + Some(special), + id, + &mut dqblk_copy as *mut _ as *mut c_char, + ) +} diff --git a/src/sys/reboot.rs b/src/sys/reboot.rs new file mode 100644 index 0000000..02d9816 --- /dev/null +++ b/src/sys/reboot.rs @@ -0,0 +1,48 @@ +//! Reboot/shutdown or enable/disable Ctrl-Alt-Delete. + +use crate::errno::Errno; +use crate::Result; +use std::convert::Infallible; +use std::mem::drop; + +libc_enum! { + /// How exactly should the system be rebooted. + /// + /// See [`set_cad_enabled()`](fn.set_cad_enabled.html) for + /// enabling/disabling Ctrl-Alt-Delete. + #[repr(i32)] + #[non_exhaustive] + pub enum RebootMode { + /// Halt the system. + RB_HALT_SYSTEM, + /// Execute a kernel that has been loaded earlier with + /// [`kexec_load(2)`](https://man7.org/linux/man-pages/man2/kexec_load.2.html). + RB_KEXEC, + /// Stop the system and switch off power, if possible. + RB_POWER_OFF, + /// Restart the system. + RB_AUTOBOOT, + // we do not support Restart2. + /// Suspend the system using software suspend. + RB_SW_SUSPEND, + } +} + +/// Reboots or shuts down the system. +pub fn reboot(how: RebootMode) -> Result { + unsafe { libc::reboot(how as libc::c_int) }; + Err(Errno::last()) +} + +/// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). +/// +/// Corresponds to calling `reboot(RB_ENABLE_CAD)` or `reboot(RB_DISABLE_CAD)` in C. +pub fn set_cad_enabled(enable: bool) -> Result<()> { + let cmd = if enable { + libc::RB_ENABLE_CAD + } else { + libc::RB_DISABLE_CAD + }; + let res = unsafe { libc::reboot(cmd) }; + Errno::result(res).map(drop) +} diff --git a/src/sys/resource.rs b/src/sys/resource.rs new file mode 100644 index 0000000..8927737 --- /dev/null +++ b/src/sys/resource.rs @@ -0,0 +1,443 @@ +//! Configure the process resource limits. +use cfg_if::cfg_if; +use libc::{c_int, c_long, rusage}; + +use crate::errno::Errno; +use crate::sys::time::TimeVal; +use crate::Result; +pub use libc::rlim_t; +pub use libc::RLIM_INFINITY; +use std::mem; + +cfg_if! { + if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + use libc::{__rlimit_resource_t, rlimit}; + } else if #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "dragonfly", + all(target_os = "linux", not(target_env = "gnu")) + ))]{ + use libc::rlimit; + } +} + +libc_enum! { + /// Types of process resources. + /// + /// The Resource enum is platform dependent. Check different platform + /// manuals for more details. Some platform links have been provided for + /// easier reference (non-exhaustive). + /// + /// * [Linux](https://man7.org/linux/man-pages/man2/getrlimit.2.html) + /// * [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=setrlimit) + /// * [NetBSD](https://man.netbsd.org/setrlimit.2) + + // linux-gnu uses u_int as resource enum, which is implemented in libc as + // well. + // + // https://gcc.gnu.org/legacy-ml/gcc/2015-08/msg00441.html + // https://github.com/rust-lang/libc/blob/master/src/unix/linux_like/linux/gnu/mod.rs + #[cfg_attr(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), repr(u32))] + #[cfg_attr(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "dragonfly", + all(target_os = "linux", not(any(target_env = "gnu", target_env = "uclibc"))) + ), repr(i32))] + #[non_exhaustive] + pub enum Resource { + #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum amount (in bytes) of virtual memory the process is + /// allowed to map. + RLIMIT_AS, + /// The largest size (in bytes) core(5) file that may be created. + RLIMIT_CORE, + /// The maximum amount of cpu time (in seconds) to be used by each + /// process. + RLIMIT_CPU, + /// The maximum size (in bytes) of the data segment for a process + RLIMIT_DATA, + /// The largest size (in bytes) file that may be created. + RLIMIT_FSIZE, + /// The maximum number of open files for this process. + RLIMIT_NOFILE, + /// The maximum size (in bytes) of the stack segment for a process. + RLIMIT_STACK, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum number of kqueues this user id is allowed to create. + RLIMIT_KQUEUES, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit on the combined number of flock locks and fcntl leases that + /// this process may establish. + RLIMIT_LOCKS, + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "openbsd", + target_os = "linux", + target_os = "netbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum size (in bytes) which a process may lock into memory + /// using the mlock(2) system call. + RLIMIT_MEMLOCK, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit on the number of bytes that can be allocated for POSIX + /// message queues for the real user ID of the calling process. + RLIMIT_MSGQUEUE, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A ceiling to which the process's nice value can be raised using + /// setpriority or nice. + RLIMIT_NICE, + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum number of simultaneous processes for this user id. + RLIMIT_NPROC, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum number of pseudo-terminals this user id is allowed to + /// create. + RLIMIT_NPTS, + + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// When there is memory pressure and swap is available, prioritize + /// eviction of a process' resident pages beyond this amount (in bytes). + RLIMIT_RSS, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A ceiling on the real-time priority that may be set for this process + /// using sched_setscheduler and sched_set‐ param. + RLIMIT_RTPRIO, + + #[cfg(any(target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit (in microseconds) on the amount of CPU time that a process + /// scheduled under a real-time scheduling policy may con‐ sume without + /// making a blocking system call. + RLIMIT_RTTIME, + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// A limit on the number of signals that may be queued for the real + /// user ID of the calling process. + RLIMIT_SIGPENDING, + + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum size (in bytes) of socket buffer usage for this user. + RLIMIT_SBSIZE, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum size (in bytes) of the swap space that may be reserved + /// or used by all of this user id's processes. + RLIMIT_SWAP, + + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// An alias for RLIMIT_AS. + RLIMIT_VMEM, + } +} + +/// Get the current processes resource limits +/// +/// The special value [`RLIM_INFINITY`] indicates that no limit will be +/// enforced. +/// +/// # Parameters +/// +/// * `resource`: The [`Resource`] that we want to get the limits of. +/// +/// # Examples +/// +/// ``` +/// # use nix::sys::resource::{getrlimit, Resource}; +/// +/// let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); +/// println!("current soft_limit: {}", soft_limit); +/// println!("current hard_limit: {}", hard_limit); +/// ``` +/// +/// # References +/// +/// [getrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215) +/// +/// [`Resource`]: enum.Resource.html +pub fn getrlimit(resource: Resource) -> Result<(rlim_t, rlim_t)> { + let mut old_rlim = mem::MaybeUninit::::uninit(); + + cfg_if! { + if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + let res = unsafe { libc::getrlimit(resource as __rlimit_resource_t, old_rlim.as_mut_ptr()) }; + } else { + let res = unsafe { libc::getrlimit(resource as c_int, old_rlim.as_mut_ptr()) }; + } + } + + Errno::result(res).map(|_| { + let rlimit { rlim_cur, rlim_max } = unsafe { old_rlim.assume_init() }; + (rlim_cur, rlim_max) + }) +} + +/// Set the current processes resource limits +/// +/// # Parameters +/// +/// * `resource`: The [`Resource`] that we want to set the limits of. +/// * `soft_limit`: The value that the kernel enforces for the corresponding +/// resource. +/// * `hard_limit`: The ceiling for the soft limit. Must be lower or equal to +/// the current hard limit for non-root users. +/// +/// The special value [`RLIM_INFINITY`] indicates that no limit will be +/// enforced. +/// +/// # Examples +/// +/// ``` +/// # use nix::sys::resource::{setrlimit, Resource}; +/// +/// let soft_limit = 512; +/// let hard_limit = 1024; +/// setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); +/// ``` +/// +/// # References +/// +/// [setrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215) +/// +/// [`Resource`]: enum.Resource.html +/// +/// Note: `setrlimit` provides a safe wrapper to libc's `setrlimit`. +pub fn setrlimit( + resource: Resource, + soft_limit: rlim_t, + hard_limit: rlim_t, +) -> Result<()> { + let new_rlim = rlimit { + rlim_cur: soft_limit, + rlim_max: hard_limit, + }; + cfg_if! { + if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + let res = unsafe { libc::setrlimit(resource as __rlimit_resource_t, &new_rlim as *const rlimit) }; + }else{ + let res = unsafe { libc::setrlimit(resource as c_int, &new_rlim as *const rlimit) }; + } + } + + Errno::result(res).map(drop) +} + +libc_enum! { + /// Whose resource usage should be returned by [`getrusage`]. + #[repr(i32)] + #[non_exhaustive] + pub enum UsageWho { + /// Resource usage for the current process. + RUSAGE_SELF, + + /// Resource usage for all the children that have terminated and been waited for. + RUSAGE_CHILDREN, + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Resource usage for the calling thread. + RUSAGE_THREAD, + } +} + +/// Output of `getrusage` with information about resource usage. Some of the fields +/// may be unused in some platforms, and will be always zeroed out. See their manuals +/// for details. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Usage(rusage); + +impl AsRef for Usage { + fn as_ref(&self) -> &rusage { + &self.0 + } +} + +impl AsMut for Usage { + fn as_mut(&mut self) -> &mut rusage { + &mut self.0 + } +} + +impl Usage { + /// Total amount of time spent executing in user mode. + pub fn user_time(&self) -> TimeVal { + TimeVal::from(self.0.ru_utime) + } + + /// Total amount of time spent executing in kernel mode. + pub fn system_time(&self) -> TimeVal { + TimeVal::from(self.0.ru_stime) + } + + /// The resident set size at its peak, in kilobytes. + pub fn max_rss(&self) -> c_long { + self.0.ru_maxrss + } + + /// Integral value expressed in kilobytes times ticks of execution indicating + /// the amount of text memory shared with other processes. + pub fn shared_integral(&self) -> c_long { + self.0.ru_ixrss + } + + /// Integral value expressed in kilobytes times ticks of execution indicating + /// the amount of unshared memory used by data. + pub fn unshared_data_integral(&self) -> c_long { + self.0.ru_idrss + } + + /// Integral value expressed in kilobytes times ticks of execution indicating + /// the amount of unshared memory used for stack space. + pub fn unshared_stack_integral(&self) -> c_long { + self.0.ru_isrss + } + + /// Number of page faults that were served without resorting to I/O, with pages + /// that have been allocated previously by the kernel. + pub fn minor_page_faults(&self) -> c_long { + self.0.ru_minflt + } + + /// Number of page faults that were served through I/O (i.e. swap). + pub fn major_page_faults(&self) -> c_long { + self.0.ru_majflt + } + + /// Number of times all of the memory was fully swapped out. + pub fn full_swaps(&self) -> c_long { + self.0.ru_nswap + } + + /// Number of times a read was done from a block device. + pub fn block_reads(&self) -> c_long { + self.0.ru_inblock + } + + /// Number of times a write was done to a block device. + pub fn block_writes(&self) -> c_long { + self.0.ru_oublock + } + + /// Number of IPC messages sent. + pub fn ipc_sends(&self) -> c_long { + self.0.ru_msgsnd + } + + /// Number of IPC messages received. + pub fn ipc_receives(&self) -> c_long { + self.0.ru_msgrcv + } + + /// Number of signals received. + pub fn signals(&self) -> c_long { + self.0.ru_nsignals + } + + /// Number of times a context switch was voluntarily invoked. + pub fn voluntary_context_switches(&self) -> c_long { + self.0.ru_nvcsw + } + + /// Number of times a context switch was imposed by the kernel (usually due to + /// time slice expiring or preemption by a higher priority process). + pub fn involuntary_context_switches(&self) -> c_long { + self.0.ru_nivcsw + } +} + +/// Get usage information for a process, its children or the current thread +/// +/// Real time information can be obtained for either the current process or (in some +/// systems) thread, but information about children processes is only provided for +/// those that have terminated and been waited for (see [`super::wait::wait`]). +/// +/// Some information may be missing depending on the platform, and the way information +/// is provided for children may also vary. Check the manuals for details. +/// +/// # References +/// +/// * [getrusage(2)](https://pubs.opengroup.org/onlinepubs/009696699/functions/getrusage.html) +/// * [Linux](https://man7.org/linux/man-pages/man2/getrusage.2.html) +/// * [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=getrusage) +/// * [NetBSD](https://man.netbsd.org/getrusage.2) +/// * [MacOS](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrusage.2.html) +/// +/// [`UsageWho`]: enum.UsageWho.html +/// +/// Note: `getrusage` provides a safe wrapper to libc's [`libc::getrusage`]. +pub fn getrusage(who: UsageWho) -> Result { + unsafe { + let mut rusage = mem::MaybeUninit::::uninit(); + let res = libc::getrusage(who as c_int, rusage.as_mut_ptr()); + Errno::result(res).map(|_| Usage(rusage.assume_init())) + } +} + +#[cfg(test)] +mod test { + use super::{getrusage, UsageWho}; + + #[test] + pub fn test_self_cpu_time() { + // Make sure some CPU time is used. + let mut numbers: Vec = (1..1_000_000).collect(); + numbers.iter_mut().for_each(|item| *item *= 2); + + // FIXME: this is here to help ensure the compiler does not optimize the whole + // thing away. Replace the assert with test::black_box once stabilized. + assert_eq!(numbers[100..200].iter().sum::(), 30_100); + + let usage = getrusage(UsageWho::RUSAGE_SELF) + .expect("Failed to call getrusage for SELF"); + let rusage = usage.as_ref(); + + let user = usage.user_time(); + assert!(user.tv_sec() > 0 || user.tv_usec() > 0); + assert_eq!(user.tv_sec(), rusage.ru_utime.tv_sec); + assert_eq!(user.tv_usec(), rusage.ru_utime.tv_usec); + } +} diff --git a/src/sys/select.rs b/src/sys/select.rs new file mode 100644 index 0000000..7a94cff --- /dev/null +++ b/src/sys/select.rs @@ -0,0 +1,455 @@ +//! Portably monitor a group of file descriptors for readiness. +use crate::errno::Errno; +use crate::sys::time::{TimeSpec, TimeVal}; +use crate::Result; +use libc::{self, c_int}; +use std::convert::TryFrom; +use std::iter::FusedIterator; +use std::mem; +use std::ops::Range; +use std::os::unix::io::RawFd; +use std::ptr::{null, null_mut}; + +pub use libc::FD_SETSIZE; + +/// Contains a set of file descriptors used by [`select`] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct FdSet(libc::fd_set); + +fn assert_fd_valid(fd: RawFd) { + assert!( + usize::try_from(fd).map_or(false, |fd| fd < FD_SETSIZE), + "fd must be in the range 0..FD_SETSIZE", + ); +} + +impl FdSet { + /// Create an empty `FdSet` + pub fn new() -> FdSet { + let mut fdset = mem::MaybeUninit::uninit(); + unsafe { + libc::FD_ZERO(fdset.as_mut_ptr()); + FdSet(fdset.assume_init()) + } + } + + /// Add a file descriptor to an `FdSet` + pub fn insert(&mut self, fd: RawFd) { + assert_fd_valid(fd); + unsafe { libc::FD_SET(fd, &mut self.0) }; + } + + /// Remove a file descriptor from an `FdSet` + pub fn remove(&mut self, fd: RawFd) { + assert_fd_valid(fd); + unsafe { libc::FD_CLR(fd, &mut self.0) }; + } + + /// Test an `FdSet` for the presence of a certain file descriptor. + pub fn contains(&self, fd: RawFd) -> bool { + assert_fd_valid(fd); + unsafe { libc::FD_ISSET(fd, &self.0) } + } + + /// Remove all file descriptors from this `FdSet`. + pub fn clear(&mut self) { + unsafe { libc::FD_ZERO(&mut self.0) }; + } + + /// Finds the highest file descriptor in the set. + /// + /// Returns `None` if the set is empty. + /// + /// This can be used to calculate the `nfds` parameter of the [`select`] function. + /// + /// # Example + /// + /// ``` + /// # use nix::sys::select::FdSet; + /// let mut set = FdSet::new(); + /// set.insert(4); + /// set.insert(9); + /// assert_eq!(set.highest(), Some(9)); + /// ``` + /// + /// [`select`]: fn.select.html + pub fn highest(&self) -> Option { + self.fds(None).next_back() + } + + /// Returns an iterator over the file descriptors in the set. + /// + /// For performance, it takes an optional higher bound: the iterator will + /// not return any elements of the set greater than the given file + /// descriptor. + /// + /// # Examples + /// + /// ``` + /// # use nix::sys::select::FdSet; + /// # use std::os::unix::io::RawFd; + /// let mut set = FdSet::new(); + /// set.insert(4); + /// set.insert(9); + /// let fds: Vec = set.fds(None).collect(); + /// assert_eq!(fds, vec![4, 9]); + /// ``` + #[inline] + pub fn fds(&self, highest: Option) -> Fds { + Fds { + set: self, + range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE), + } + } +} + +impl Default for FdSet { + fn default() -> Self { + Self::new() + } +} + +/// Iterator over `FdSet`. +#[derive(Debug)] +pub struct Fds<'a> { + set: &'a FdSet, + range: Range, +} + +impl<'a> Iterator for Fds<'a> { + type Item = RawFd; + + fn next(&mut self) -> Option { + for i in &mut self.range { + if self.set.contains(i as RawFd) { + return Some(i as RawFd); + } + } + None + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (_, upper) = self.range.size_hint(); + (0, upper) + } +} + +impl<'a> DoubleEndedIterator for Fds<'a> { + #[inline] + fn next_back(&mut self) -> Option { + while let Some(i) = self.range.next_back() { + if self.set.contains(i as RawFd) { + return Some(i as RawFd); + } + } + None + } +} + +impl<'a> FusedIterator for Fds<'a> {} + +/// Monitors file descriptors for readiness +/// +/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all +/// file descriptors that are ready for the given operation are set. +/// +/// When this function returns, `timeout` has an implementation-defined value. +/// +/// # Parameters +/// +/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this +/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1 +/// to the maximum of that. +/// * `readfds`: File descriptors to check for being ready to read. +/// * `writefds`: File descriptors to check for being ready to write. +/// * `errorfds`: File descriptors to check for pending error conditions. +/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block +/// indefinitely). +/// +/// # References +/// +/// [select(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html) +/// +/// [`FdSet::highest`]: struct.FdSet.html#method.highest +pub fn select<'a, N, R, W, E, T>( + nfds: N, + readfds: R, + writefds: W, + errorfds: E, + timeout: T, +) -> Result +where + N: Into>, + R: Into>, + W: Into>, + E: Into>, + T: Into>, +{ + let mut readfds = readfds.into(); + let mut writefds = writefds.into(); + let mut errorfds = errorfds.into(); + let timeout = timeout.into(); + + let nfds = nfds.into().unwrap_or_else(|| { + readfds + .iter_mut() + .chain(writefds.iter_mut()) + .chain(errorfds.iter_mut()) + .map(|set| set.highest().unwrap_or(-1)) + .max() + .unwrap_or(-1) + + 1 + }); + + let readfds = readfds + .map(|set| set as *mut _ as *mut libc::fd_set) + .unwrap_or(null_mut()); + let writefds = writefds + .map(|set| set as *mut _ as *mut libc::fd_set) + .unwrap_or(null_mut()); + let errorfds = errorfds + .map(|set| set as *mut _ as *mut libc::fd_set) + .unwrap_or(null_mut()); + let timeout = timeout + .map(|tv| tv as *mut _ as *mut libc::timeval) + .unwrap_or(null_mut()); + + let res = + unsafe { libc::select(nfds, readfds, writefds, errorfds, timeout) }; + + Errno::result(res) +} + +feature! { +#![feature = "signal"] + +use crate::sys::signal::SigSet; + +/// Monitors file descriptors for readiness with an altered signal mask. +/// +/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all +/// file descriptors that are ready for the given operation are set. +/// +/// When this function returns, the original signal mask is restored. +/// +/// Unlike [`select`](#fn.select), `pselect` does not mutate the `timeout` value. +/// +/// # Parameters +/// +/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this +/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1 +/// to the maximum of that. +/// * `readfds`: File descriptors to check for read readiness +/// * `writefds`: File descriptors to check for write readiness +/// * `errorfds`: File descriptors to check for pending error conditions. +/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block +/// indefinitely). +/// * `sigmask`: Signal mask to activate while waiting for file descriptors to turn +/// ready (`None` to set no alternative signal mask). +/// +/// # References +/// +/// [pselect(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html) +/// +/// [The new pselect() system call](https://lwn.net/Articles/176911/) +/// +/// [`FdSet::highest`]: struct.FdSet.html#method.highest +pub fn pselect<'a, N, R, W, E, T, S>(nfds: N, + readfds: R, + writefds: W, + errorfds: E, + timeout: T, + sigmask: S) -> Result +where + N: Into>, + R: Into>, + W: Into>, + E: Into>, + T: Into>, + S: Into>, +{ + let mut readfds = readfds.into(); + let mut writefds = writefds.into(); + let mut errorfds = errorfds.into(); + let sigmask = sigmask.into(); + let timeout = timeout.into(); + + let nfds = nfds.into().unwrap_or_else(|| { + readfds.iter_mut() + .chain(writefds.iter_mut()) + .chain(errorfds.iter_mut()) + .map(|set| set.highest().unwrap_or(-1)) + .max() + .unwrap_or(-1) + 1 + }); + + let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); + let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); + let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); + let timeout = timeout.map(|ts| ts.as_ref() as *const libc::timespec).unwrap_or(null()); + let sigmask = sigmask.map(|sm| sm.as_ref() as *const libc::sigset_t).unwrap_or(null()); + + let res = unsafe { + libc::pselect(nfds, readfds, writefds, errorfds, timeout, sigmask) + }; + + Errno::result(res) +} +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sys::time::{TimeVal, TimeValLike}; + use crate::unistd::{pipe, write}; + use std::os::unix::io::RawFd; + + #[test] + fn fdset_insert() { + let mut fd_set = FdSet::new(); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + + fd_set.insert(7); + + assert!(fd_set.contains(7)); + } + + #[test] + fn fdset_remove() { + let mut fd_set = FdSet::new(); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + + fd_set.insert(7); + fd_set.remove(7); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + } + + #[test] + fn fdset_clear() { + let mut fd_set = FdSet::new(); + fd_set.insert(1); + fd_set.insert((FD_SETSIZE / 2) as RawFd); + fd_set.insert((FD_SETSIZE - 1) as RawFd); + + fd_set.clear(); + + for i in 0..FD_SETSIZE { + assert!(!fd_set.contains(i as RawFd)); + } + } + + #[test] + fn fdset_highest() { + let mut set = FdSet::new(); + assert_eq!(set.highest(), None); + set.insert(0); + assert_eq!(set.highest(), Some(0)); + set.insert(90); + assert_eq!(set.highest(), Some(90)); + set.remove(0); + assert_eq!(set.highest(), Some(90)); + set.remove(90); + assert_eq!(set.highest(), None); + + set.insert(4); + set.insert(5); + set.insert(7); + assert_eq!(set.highest(), Some(7)); + } + + #[test] + fn fdset_fds() { + let mut set = FdSet::new(); + assert_eq!(set.fds(None).collect::>(), vec![]); + set.insert(0); + assert_eq!(set.fds(None).collect::>(), vec![0]); + set.insert(90); + assert_eq!(set.fds(None).collect::>(), vec![0, 90]); + + // highest limit + assert_eq!(set.fds(Some(89)).collect::>(), vec![0]); + assert_eq!(set.fds(Some(90)).collect::>(), vec![0, 90]); + } + + #[test] + fn test_select() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select(None, &mut fd_set, None, None, &mut timeout).unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); + } + + #[test] + fn test_select_nfds() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select( + Some(fd_set.highest().unwrap() + 1), + &mut fd_set, + None, + None, + &mut timeout + ) + .unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); + } + + #[test] + fn test_select_nfds2() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select( + ::std::cmp::max(r1, r2) + 1, + &mut fd_set, + None, + None, + &mut timeout + ) + .unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); + } +} diff --git a/src/sys/sendfile.rs b/src/sys/sendfile.rs new file mode 100644 index 0000000..fb293a4 --- /dev/null +++ b/src/sys/sendfile.rs @@ -0,0 +1,277 @@ +//! Send data from a file to a socket, bypassing userland. + +use cfg_if::cfg_if; +use std::os::unix::io::RawFd; +use std::ptr; + +use libc::{self, off_t}; + +use crate::errno::Errno; +use crate::Result; + +/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`. +/// +/// Returns a `Result` with the number of bytes written. +/// +/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will +/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified +/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to +/// the byte after the last byte copied. +/// +/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket. +/// +/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn sendfile( + out_fd: RawFd, + in_fd: RawFd, + offset: Option<&mut off_t>, + count: usize, +) -> Result { + let offset = offset + .map(|offset| offset as *mut _) + .unwrap_or(ptr::null_mut()); + let ret = unsafe { libc::sendfile(out_fd, in_fd, offset, count) }; + Errno::result(ret).map(|r| r as usize) +} + +/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`. +/// +/// Returns a `Result` with the number of bytes written. +/// +/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will +/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified +/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to +/// the byte after the last byte copied. +/// +/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket. +/// +/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn sendfile64( + out_fd: RawFd, + in_fd: RawFd, + offset: Option<&mut libc::off64_t>, + count: usize, +) -> Result { + let offset = offset + .map(|offset| offset as *mut _) + .unwrap_or(ptr::null_mut()); + let ret = unsafe { libc::sendfile64(out_fd, in_fd, offset, count) }; + Errno::result(ret).map(|r| r as usize) +} + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos"))] { + use std::io::IoSlice; + + #[derive(Clone, Debug)] + struct SendfileHeaderTrailer<'a>( + libc::sf_hdtr, + Option>>, + Option>>, + ); + + impl<'a> SendfileHeaderTrailer<'a> { + fn new( + headers: Option<&'a [&'a [u8]]>, + trailers: Option<&'a [&'a [u8]]> + ) -> SendfileHeaderTrailer<'a> { + let header_iovecs: Option>> = + headers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect()); + let trailer_iovecs: Option>> = + trailers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect()); + SendfileHeaderTrailer( + libc::sf_hdtr { + headers: { + header_iovecs + .as_ref() + .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec + }, + hdr_cnt: header_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32, + trailers: { + trailer_iovecs + .as_ref() + .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec + }, + trl_cnt: trailer_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32 + }, + header_iovecs, + trailer_iovecs, + ) + } + } + } +} + +cfg_if! { + if #[cfg(target_os = "freebsd")] { + use libc::c_int; + + libc_bitflags!{ + /// Configuration options for [`sendfile`.](fn.sendfile.html) + pub struct SfFlags: c_int { + /// Causes `sendfile` to return EBUSY instead of blocking when attempting to read a + /// busy page. + SF_NODISKIO; + /// Causes `sendfile` to sleep until the network stack releases its reference to the + /// VM pages read. When `sendfile` returns, the data is not guaranteed to have been + /// sent, but it is safe to modify the file. + SF_SYNC; + /// Causes `sendfile` to cache exactly the number of pages specified in the + /// `readahead` parameter, disabling caching heuristics. + SF_USER_READAHEAD; + /// Causes `sendfile` not to cache the data read. + SF_NOCACHE; + } + } + + /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`. + /// + /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if + /// an error occurs. + /// + /// `in_fd` must describe a regular file or shared memory object. `out_sock` must describe a + /// stream socket. + /// + /// If `offset` falls past the end of the file, the function returns success and zero bytes + /// written. + /// + /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of + /// file (EOF). + /// + /// `headers` and `trailers` specify optional slices of byte slices to be sent before and + /// after the data read from `in_fd`, respectively. The length of headers and trailers sent + /// is included in the returned count of bytes written. The values of `offset` and `count` + /// do not apply to headers or trailers. + /// + /// `readahead` specifies the minimum number of pages to cache in memory ahead of the page + /// currently being sent. + /// + /// For more information, see + /// [the sendfile(2) man page.](https://www.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2) + #[allow(clippy::too_many_arguments)] + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]>, + flags: SfFlags, + readahead: u16 + ) -> (Result<()>, off_t) { + // Readahead goes in upper 16 bits + // Flags goes in lower 16 bits + // see `man 2 sendfile` + let ra32 = u32::from(readahead); + let flags: u32 = (ra32 << 16) | (flags.bits() as u32); + let mut bytes_sent: off_t = 0; + let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let return_code = unsafe { + libc::sendfile(in_fd, + out_sock, + offset, + count.unwrap_or(0), + hdtr_ptr as *mut libc::sf_hdtr, + &mut bytes_sent as *mut off_t, + flags as c_int) + }; + (Errno::result(return_code).and(Ok(())), bytes_sent) + } + } else if #[cfg(target_os = "dragonfly")] { + /// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`. + /// + /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if + /// an error occurs. + /// + /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket. + /// + /// If `offset` falls past the end of the file, the function returns success and zero bytes + /// written. + /// + /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of + /// file (EOF). + /// + /// `headers` and `trailers` specify optional slices of byte slices to be sent before and + /// after the data read from `in_fd`, respectively. The length of headers and trailers sent + /// is included in the returned count of bytes written. The values of `offset` and `count` + /// do not apply to headers or trailers. + /// + /// For more information, see + /// [the sendfile(2) man page.](https://leaf.dragonflybsd.org/cgi/web-man?command=sendfile§ion=2) + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]>, + ) -> (Result<()>, off_t) { + let mut bytes_sent: off_t = 0; + let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let return_code = unsafe { + libc::sendfile(in_fd, + out_sock, + offset, + count.unwrap_or(0), + hdtr_ptr as *mut libc::sf_hdtr, + &mut bytes_sent as *mut off_t, + 0) + }; + (Errno::result(return_code).and(Ok(())), bytes_sent) + } + } else if #[cfg(any(target_os = "ios", target_os = "macos"))] { + /// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to + /// `out_sock`. + /// + /// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if + /// an error occurs. + /// + /// `in_fd` must describe a regular file. `out_sock` must describe a stream socket. + /// + /// If `offset` falls past the end of the file, the function returns success and zero bytes + /// written. + /// + /// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of + /// file (EOF). + /// + /// `hdtr` specifies an optional list of headers and trailers to be sent before and after + /// the data read from `in_fd`, respectively. The length of headers and trailers sent is + /// included in the returned count of bytes written. If any headers are specified and + /// `count` is non-zero, the length of the headers will be counted in the limit of total + /// bytes sent. Trailers do not count toward the limit of bytes sent and will always be sent + /// regardless. The value of `offset` does not affect headers or trailers. + /// + /// For more information, see + /// [the sendfile(2) man page.](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/sendfile.2.html) + pub fn sendfile( + in_fd: RawFd, + out_sock: RawFd, + offset: off_t, + count: Option, + headers: Option<&[&[u8]]>, + trailers: Option<&[&[u8]]> + ) -> (Result<()>, off_t) { + let mut len = count.unwrap_or(0); + let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let return_code = unsafe { + libc::sendfile(in_fd, + out_sock, + offset, + &mut len as *mut off_t, + hdtr_ptr as *mut libc::sf_hdtr, + 0) + }; + (Errno::result(return_code).and(Ok(())), len) + } + } +} diff --git a/src/sys/signal.rs b/src/sys/signal.rs new file mode 100644 index 0000000..d3746e6 --- /dev/null +++ b/src/sys/signal.rs @@ -0,0 +1,1348 @@ +// Portions of this file are Copyright 2014 The Rust Project Developers. +// See https://www.rust-lang.org/policies/licenses. + +//! Operating system signals. + +use crate::errno::Errno; +use crate::{Error, Result}; +use cfg_if::cfg_if; +use std::fmt; +use std::mem; +#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] +use std::os::unix::io::RawFd; +use std::ptr; +use std::str::FromStr; + +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[cfg(any(feature = "aio", feature = "signal"))] +pub use self::sigevent::*; + +#[cfg(any(feature = "aio", feature = "process", feature = "signal"))] +libc_enum! { + /// Types of operating system signals + // Currently there is only one definition of c_int in libc, as well as only one + // type for signal constants. + // We would prefer to use the libc::c_int alias in the repr attribute. Unfortunately + // this is not (yet) possible. + #[repr(i32)] + #[non_exhaustive] + #[cfg_attr(docsrs, doc(cfg(any(feature = "aio", feature = "signal"))))] + pub enum Signal { + /// Hangup + SIGHUP, + /// Interrupt + SIGINT, + /// Quit + SIGQUIT, + /// Illegal instruction (not reset when caught) + SIGILL, + /// Trace trap (not reset when caught) + SIGTRAP, + /// Abort + SIGABRT, + /// Bus error + SIGBUS, + /// Floating point exception + SIGFPE, + /// Kill (cannot be caught or ignored) + SIGKILL, + /// User defined signal 1 + SIGUSR1, + /// Segmentation violation + SIGSEGV, + /// User defined signal 2 + SIGUSR2, + /// Write on a pipe with no one to read it + SIGPIPE, + /// Alarm clock + SIGALRM, + /// Software termination signal from kill + SIGTERM, + /// Stack fault (obsolete) + #[cfg(all(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux"), + not(any(target_arch = "mips", target_arch = "mips64", + target_arch = "sparc64"))))] + SIGSTKFLT, + /// To parent on child stop or exit + SIGCHLD, + /// Continue a stopped process + SIGCONT, + /// Sendable stop signal not from tty + SIGSTOP, + /// Stop signal from tty + SIGTSTP, + /// To readers pgrp upon background tty read + SIGTTIN, + /// Like TTIN if (tp->t_local<OSTOP) + SIGTTOU, + /// Urgent condition on IO channel + SIGURG, + /// Exceeded CPU time limit + SIGXCPU, + /// Exceeded file size limit + SIGXFSZ, + /// Virtual time alarm + SIGVTALRM, + /// Profiling time alarm + SIGPROF, + /// Window size changes + SIGWINCH, + /// Input/output possible signal + #[cfg(not(target_os = "haiku"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SIGIO, + #[cfg(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Power failure imminent. + SIGPWR, + /// Bad system call + SIGSYS, + #[cfg(not(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux", + target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Emulator trap + SIGEMT, + #[cfg(not(any(target_os = "android", target_os = "emscripten", + target_os = "fuchsia", target_os = "linux", + target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Information request + SIGINFO, + } + impl TryFrom +} + +#[cfg(feature = "signal")] +impl FromStr for Signal { + type Err = Error; + fn from_str(s: &str) -> Result { + Ok(match s { + "SIGHUP" => Signal::SIGHUP, + "SIGINT" => Signal::SIGINT, + "SIGQUIT" => Signal::SIGQUIT, + "SIGILL" => Signal::SIGILL, + "SIGTRAP" => Signal::SIGTRAP, + "SIGABRT" => Signal::SIGABRT, + "SIGBUS" => Signal::SIGBUS, + "SIGFPE" => Signal::SIGFPE, + "SIGKILL" => Signal::SIGKILL, + "SIGUSR1" => Signal::SIGUSR1, + "SIGSEGV" => Signal::SIGSEGV, + "SIGUSR2" => Signal::SIGUSR2, + "SIGPIPE" => Signal::SIGPIPE, + "SIGALRM" => Signal::SIGALRM, + "SIGTERM" => Signal::SIGTERM, + #[cfg(all( + any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ), + not(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "sparc64" + )) + ))] + "SIGSTKFLT" => Signal::SIGSTKFLT, + "SIGCHLD" => Signal::SIGCHLD, + "SIGCONT" => Signal::SIGCONT, + "SIGSTOP" => Signal::SIGSTOP, + "SIGTSTP" => Signal::SIGTSTP, + "SIGTTIN" => Signal::SIGTTIN, + "SIGTTOU" => Signal::SIGTTOU, + "SIGURG" => Signal::SIGURG, + "SIGXCPU" => Signal::SIGXCPU, + "SIGXFSZ" => Signal::SIGXFSZ, + "SIGVTALRM" => Signal::SIGVTALRM, + "SIGPROF" => Signal::SIGPROF, + "SIGWINCH" => Signal::SIGWINCH, + #[cfg(not(target_os = "haiku"))] + "SIGIO" => Signal::SIGIO, + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + "SIGPWR" => Signal::SIGPWR, + "SIGSYS" => Signal::SIGSYS, + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + "SIGEMT" => Signal::SIGEMT, + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + "SIGINFO" => Signal::SIGINFO, + _ => return Err(Errno::EINVAL), + }) + } +} + +#[cfg(feature = "signal")] +impl Signal { + /// Returns name of signal. + /// + /// This function is equivalent to `>::as_ref()`, + /// with difference that returned string is `'static` + /// and not bound to `self`'s lifetime. + pub const fn as_str(self) -> &'static str { + match self { + Signal::SIGHUP => "SIGHUP", + Signal::SIGINT => "SIGINT", + Signal::SIGQUIT => "SIGQUIT", + Signal::SIGILL => "SIGILL", + Signal::SIGTRAP => "SIGTRAP", + Signal::SIGABRT => "SIGABRT", + Signal::SIGBUS => "SIGBUS", + Signal::SIGFPE => "SIGFPE", + Signal::SIGKILL => "SIGKILL", + Signal::SIGUSR1 => "SIGUSR1", + Signal::SIGSEGV => "SIGSEGV", + Signal::SIGUSR2 => "SIGUSR2", + Signal::SIGPIPE => "SIGPIPE", + Signal::SIGALRM => "SIGALRM", + Signal::SIGTERM => "SIGTERM", + #[cfg(all( + any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ), + not(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "sparc64" + )) + ))] + Signal::SIGSTKFLT => "SIGSTKFLT", + Signal::SIGCHLD => "SIGCHLD", + Signal::SIGCONT => "SIGCONT", + Signal::SIGSTOP => "SIGSTOP", + Signal::SIGTSTP => "SIGTSTP", + Signal::SIGTTIN => "SIGTTIN", + Signal::SIGTTOU => "SIGTTOU", + Signal::SIGURG => "SIGURG", + Signal::SIGXCPU => "SIGXCPU", + Signal::SIGXFSZ => "SIGXFSZ", + Signal::SIGVTALRM => "SIGVTALRM", + Signal::SIGPROF => "SIGPROF", + Signal::SIGWINCH => "SIGWINCH", + #[cfg(not(target_os = "haiku"))] + Signal::SIGIO => "SIGIO", + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + Signal::SIGPWR => "SIGPWR", + Signal::SIGSYS => "SIGSYS", + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + Signal::SIGEMT => "SIGEMT", + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "haiku" + )))] + Signal::SIGINFO => "SIGINFO", + } + } +} + +#[cfg(feature = "signal")] +impl AsRef for Signal { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[cfg(feature = "signal")] +impl fmt::Display for Signal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.as_ref()) + } +} + +#[cfg(feature = "signal")] +pub use self::Signal::*; + +#[cfg(target_os = "redox")] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 29] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGIO, SIGSYS, +]; +#[cfg(target_os = "haiku")] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 28] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGSYS, +]; +#[cfg(all( + any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + ), + not(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "sparc64" + )) +))] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 31] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGSTKFLT, SIGCHLD, + SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, + SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, +]; +#[cfg(all( + any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + ), + any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64") +))] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 30] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, +]; +#[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "redox", + target_os = "haiku" +)))] +#[cfg(feature = "signal")] +const SIGNALS: [Signal; 31] = [ + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, + SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, + SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, + SIGPROF, SIGWINCH, SIGIO, SIGSYS, SIGEMT, SIGINFO, +]; + +feature! { +#![feature = "signal"] + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +/// Iterate through all signals defined by this operating system +pub struct SignalIterator { + next: usize, +} + +impl Iterator for SignalIterator { + type Item = Signal; + + fn next(&mut self) -> Option { + if self.next < SIGNALS.len() { + let next_signal = SIGNALS[self.next]; + self.next += 1; + Some(next_signal) + } else { + None + } + } +} + +impl Signal { + /// Iterate through all signals defined by this OS + pub const fn iterator() -> SignalIterator { + SignalIterator{next: 0} + } +} + +/// Alias for [`SIGABRT`] +pub const SIGIOT : Signal = SIGABRT; +/// Alias for [`SIGIO`] +#[cfg(not(target_os = "haiku"))] +pub const SIGPOLL : Signal = SIGIO; +/// Alias for [`SIGSYS`] +pub const SIGUNUSED : Signal = SIGSYS; + +cfg_if! { + if #[cfg(target_os = "redox")] { + type SaFlags_t = libc::c_ulong; + } else if #[cfg(target_env = "uclibc")] { + type SaFlags_t = libc::c_ulong; + } else { + type SaFlags_t = libc::c_int; + } +} +} + +#[cfg(feature = "signal")] +libc_bitflags! { + /// Controls the behavior of a [`SigAction`] + #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] + pub struct SaFlags: SaFlags_t { + /// When catching a [`Signal::SIGCHLD`] signal, the signal will be + /// generated only when a child process exits, not when a child process + /// stops. + SA_NOCLDSTOP; + /// When catching a [`Signal::SIGCHLD`] signal, the system will not + /// create zombie processes when children of the calling process exit. + SA_NOCLDWAIT; + /// Further occurrences of the delivered signal are not masked during + /// the execution of the handler. + SA_NODEFER; + /// The system will deliver the signal to the process on a signal stack, + /// specified by each thread with sigaltstack(2). + SA_ONSTACK; + /// The handler is reset back to the default at the moment the signal is + /// delivered. + SA_RESETHAND; + /// Requests that certain system calls restart if interrupted by this + /// signal. See the man page for complete details. + SA_RESTART; + /// This flag is controlled internally by Nix. + SA_SIGINFO; + } +} + +#[cfg(feature = "signal")] +libc_enum! { + /// Specifies how certain functions should manipulate a signal mask + #[repr(i32)] + #[non_exhaustive] + #[cfg_attr(docsrs, doc(cfg(feature = "signal")))] + pub enum SigmaskHow { + /// The new mask is the union of the current mask and the specified set. + SIG_BLOCK, + /// The new mask is the intersection of the current mask and the + /// complement of the specified set. + SIG_UNBLOCK, + /// The current mask is replaced by the specified set. + SIG_SETMASK, + } +} + +feature! { +#![feature = "signal"] + +use crate::unistd::Pid; +use std::iter::Extend; +use std::iter::FromIterator; +use std::iter::IntoIterator; + +/// Specifies a set of [`Signal`]s that may be blocked, waited for, etc. +// We are using `transparent` here to be super sure that `SigSet` +// is represented exactly like the `sigset_t` struct from C. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SigSet { + sigset: libc::sigset_t +} + +impl SigSet { + /// Initialize to include all signals. + #[doc(alias("sigfillset"))] + pub fn all() -> SigSet { + let mut sigset = mem::MaybeUninit::uninit(); + let _ = unsafe { libc::sigfillset(sigset.as_mut_ptr()) }; + + unsafe{ SigSet { sigset: sigset.assume_init() } } + } + + /// Initialize to include nothing. + #[doc(alias("sigemptyset"))] + pub fn empty() -> SigSet { + let mut sigset = mem::MaybeUninit::uninit(); + let _ = unsafe { libc::sigemptyset(sigset.as_mut_ptr()) }; + + unsafe{ SigSet { sigset: sigset.assume_init() } } + } + + /// Add the specified signal to the set. + #[doc(alias("sigaddset"))] + pub fn add(&mut self, signal: Signal) { + unsafe { libc::sigaddset(&mut self.sigset as *mut libc::sigset_t, signal as libc::c_int) }; + } + + /// Remove all signals from this set. + #[doc(alias("sigemptyset"))] + pub fn clear(&mut self) { + unsafe { libc::sigemptyset(&mut self.sigset as *mut libc::sigset_t) }; + } + + /// Remove the specified signal from this set. + #[doc(alias("sigdelset"))] + pub fn remove(&mut self, signal: Signal) { + unsafe { libc::sigdelset(&mut self.sigset as *mut libc::sigset_t, signal as libc::c_int) }; + } + + /// Return whether this set includes the specified signal. + #[doc(alias("sigismember"))] + pub fn contains(&self, signal: Signal) -> bool { + let res = unsafe { libc::sigismember(&self.sigset as *const libc::sigset_t, signal as libc::c_int) }; + + match res { + 1 => true, + 0 => false, + _ => unreachable!("unexpected value from sigismember"), + } + } + + /// Returns an iterator that yields the signals contained in this set. + pub fn iter(&self) -> SigSetIter<'_> { + self.into_iter() + } + + /// Gets the currently blocked (masked) set of signals for the calling thread. + pub fn thread_get_mask() -> Result { + let mut oldmask = mem::MaybeUninit::uninit(); + do_pthread_sigmask(SigmaskHow::SIG_SETMASK, None, Some(oldmask.as_mut_ptr()))?; + Ok(unsafe{ SigSet{sigset: oldmask.assume_init()}}) + } + + /// Sets the set of signals as the signal mask for the calling thread. + pub fn thread_set_mask(&self) -> Result<()> { + pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(self), None) + } + + /// Adds the set of signals to the signal mask for the calling thread. + pub fn thread_block(&self) -> Result<()> { + pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(self), None) + } + + /// Removes the set of signals from the signal mask for the calling thread. + pub fn thread_unblock(&self) -> Result<()> { + pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(self), None) + } + + /// Sets the set of signals as the signal mask, and returns the old mask. + pub fn thread_swap_mask(&self, how: SigmaskHow) -> Result { + let mut oldmask = mem::MaybeUninit::uninit(); + do_pthread_sigmask(how, Some(self), Some(oldmask.as_mut_ptr()))?; + Ok(unsafe{ SigSet{sigset: oldmask.assume_init()}}) + } + + /// Suspends execution of the calling thread until one of the signals in the + /// signal mask becomes pending, and returns the accepted signal. + #[cfg(not(target_os = "redox"))] // RedoxFS does not yet support sigwait + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn wait(&self) -> Result { + use std::convert::TryFrom; + + let mut signum = mem::MaybeUninit::uninit(); + let res = unsafe { libc::sigwait(&self.sigset as *const libc::sigset_t, signum.as_mut_ptr()) }; + + Errno::result(res).map(|_| unsafe { + Signal::try_from(signum.assume_init()).unwrap() + }) + } + + /// Converts a `libc::sigset_t` object to a [`SigSet`] without checking whether the + /// `libc::sigset_t` is already initialized. + /// + /// # Safety + /// + /// The `sigset` passed in must be a valid an initialized `libc::sigset_t` by calling either + /// [`sigemptyset(3)`](https://man7.org/linux/man-pages/man3/sigemptyset.3p.html) or + /// [`sigfillset(3)`](https://man7.org/linux/man-pages/man3/sigfillset.3p.html). + /// Otherwise, the results are undefined. + pub unsafe fn from_sigset_t_unchecked(sigset: libc::sigset_t) -> SigSet { + SigSet { sigset } + } +} + +impl AsRef for SigSet { + fn as_ref(&self) -> &libc::sigset_t { + &self.sigset + } +} + +// TODO: Consider specialization for the case where T is &SigSet and libc::sigorset is available. +impl Extend for SigSet { + fn extend(&mut self, iter: T) + where T: IntoIterator { + for signal in iter { + self.add(signal); + } + } +} + +impl FromIterator for SigSet { + fn from_iter(iter: T) -> Self + where T: IntoIterator { + let mut sigset = SigSet::empty(); + sigset.extend(iter); + sigset + } +} + +/// Iterator for a [`SigSet`]. +/// +/// Call [`SigSet::iter`] to create an iterator. +#[derive(Clone, Debug)] +pub struct SigSetIter<'a> { + sigset: &'a SigSet, + inner: SignalIterator, +} + +impl Iterator for SigSetIter<'_> { + type Item = Signal; + fn next(&mut self) -> Option { + loop { + match self.inner.next() { + None => return None, + Some(signal) if self.sigset.contains(signal) => return Some(signal), + Some(_signal) => continue, + } + } + } +} + +impl<'a> IntoIterator for &'a SigSet { + type Item = Signal; + type IntoIter = SigSetIter<'a>; + fn into_iter(self) -> Self::IntoIter { + SigSetIter { sigset: self, inner: Signal::iterator() } + } +} + +/// A signal handler. +#[allow(unknown_lints)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SigHandler { + /// Default signal handling. + SigDfl, + /// Request that the signal be ignored. + SigIgn, + /// Use the given signal-catching function, which takes in the signal. + Handler(extern fn(libc::c_int)), + /// Use the given signal-catching function, which takes in the signal, information about how + /// the signal was generated, and a pointer to the threads `ucontext_t`. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SigAction(extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void)) +} + +/// Action to take on receipt of a signal. Corresponds to `sigaction`. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SigAction { + sigaction: libc::sigaction +} + +impl SigAction { + /// Creates a new action. + /// + /// The `SA_SIGINFO` bit in the `flags` argument is ignored (it will be set only if `handler` + /// is the `SigAction` variant). `mask` specifies other signals to block during execution of + /// the signal-catching function. + pub fn new(handler: SigHandler, flags: SaFlags, mask: SigSet) -> SigAction { + unsafe fn install_sig(p: *mut libc::sigaction, handler: SigHandler) { + (*p).sa_sigaction = match handler { + SigHandler::SigDfl => libc::SIG_DFL, + SigHandler::SigIgn => libc::SIG_IGN, + SigHandler::Handler(f) => f as *const extern fn(libc::c_int) as usize, + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(f) => f as *const extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void) as usize, + }; + } + + let mut s = mem::MaybeUninit::::uninit(); + unsafe { + let p = s.as_mut_ptr(); + install_sig(p, handler); + (*p).sa_flags = match handler { + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(_) => (flags | SaFlags::SA_SIGINFO).bits(), + _ => (flags - SaFlags::SA_SIGINFO).bits(), + }; + (*p).sa_mask = mask.sigset; + + SigAction { sigaction: s.assume_init() } + } + } + + /// Returns the flags set on the action. + pub fn flags(&self) -> SaFlags { + SaFlags::from_bits_truncate(self.sigaction.sa_flags) + } + + /// Returns the set of signals that are blocked during execution of the action's + /// signal-catching function. + pub fn mask(&self) -> SigSet { + SigSet { sigset: self.sigaction.sa_mask } + } + + /// Returns the action's handler. + pub fn handler(&self) -> SigHandler { + match self.sigaction.sa_sigaction { + libc::SIG_DFL => SigHandler::SigDfl, + libc::SIG_IGN => SigHandler::SigIgn, + #[cfg(not(target_os = "redox"))] + p if self.flags().contains(SaFlags::SA_SIGINFO) => + SigHandler::SigAction( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(_, _, _)) + } + as extern fn(_, _, _)), + p => SigHandler::Handler( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(libc::c_int)) + } + as extern fn(libc::c_int)), + } + } +} + +/// Changes the action taken by a process on receipt of a specific signal. +/// +/// `signal` can be any signal except `SIGKILL` or `SIGSTOP`. On success, it returns the previous +/// action for the given signal. If `sigaction` fails, no new signal handler is installed. +/// +/// # Safety +/// +/// * Signal handlers may be called at any point during execution, which limits +/// what is safe to do in the body of the signal-catching function. Be certain +/// to only make syscalls that are explicitly marked safe for signal handlers +/// and only share global data using atomics. +/// +/// * There is also no guarantee that the old signal handler was installed +/// correctly. If it was installed by this crate, it will be. But if it was +/// installed by, for example, C code, then there is no guarantee its function +/// pointer is valid. In that case, this function effectively dereferences a +/// raw pointer of unknown provenance. +pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result { + let mut oldact = mem::MaybeUninit::::uninit(); + + let res = libc::sigaction(signal as libc::c_int, + &sigaction.sigaction as *const libc::sigaction, + oldact.as_mut_ptr()); + + Errno::result(res).map(|_| SigAction { sigaction: oldact.assume_init() }) +} + +/// Signal management (see [signal(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html)) +/// +/// Installs `handler` for the given `signal`, returning the previous signal +/// handler. `signal` should only be used following another call to `signal` or +/// if the current handler is the default. The return value of `signal` is +/// undefined after setting the handler with [`sigaction`][SigActionFn]. +/// +/// # Safety +/// +/// If the pointer to the previous signal handler is invalid, undefined +/// behavior could be invoked when casting it back to a [`SigAction`][SigActionStruct]. +/// +/// # Examples +/// +/// Ignore `SIGINT`: +/// +/// ```no_run +/// # use nix::sys::signal::{self, Signal, SigHandler}; +/// unsafe { signal::signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap(); +/// ``` +/// +/// Use a signal handler to set a flag variable: +/// +/// ```no_run +/// # #[macro_use] extern crate lazy_static; +/// # use std::convert::TryFrom; +/// # use std::sync::atomic::{AtomicBool, Ordering}; +/// # use nix::sys::signal::{self, Signal, SigHandler}; +/// lazy_static! { +/// static ref SIGNALED: AtomicBool = AtomicBool::new(false); +/// } +/// +/// extern fn handle_sigint(signal: libc::c_int) { +/// let signal = Signal::try_from(signal).unwrap(); +/// SIGNALED.store(signal == Signal::SIGINT, Ordering::Relaxed); +/// } +/// +/// fn main() { +/// let handler = SigHandler::Handler(handle_sigint); +/// unsafe { signal::signal(Signal::SIGINT, handler) }.unwrap(); +/// } +/// ``` +/// +/// # Errors +/// +/// Returns [`Error(Errno::EOPNOTSUPP)`] if `handler` is +/// [`SigAction`][SigActionStruct]. Use [`sigaction`][SigActionFn] instead. +/// +/// `signal` also returns any error from `libc::signal`, such as when an attempt +/// is made to catch a signal that cannot be caught or to ignore a signal that +/// cannot be ignored. +/// +/// [`Error::UnsupportedOperation`]: ../../enum.Error.html#variant.UnsupportedOperation +/// [SigActionStruct]: struct.SigAction.html +/// [sigactionFn]: fn.sigaction.html +pub unsafe fn signal(signal: Signal, handler: SigHandler) -> Result { + let signal = signal as libc::c_int; + let res = match handler { + SigHandler::SigDfl => libc::signal(signal, libc::SIG_DFL), + SigHandler::SigIgn => libc::signal(signal, libc::SIG_IGN), + SigHandler::Handler(handler) => libc::signal(signal, handler as libc::sighandler_t), + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(_) => return Err(Errno::ENOTSUP), + }; + Errno::result(res).map(|oldhandler| { + match oldhandler { + libc::SIG_DFL => SigHandler::SigDfl, + libc::SIG_IGN => SigHandler::SigIgn, + p => SigHandler::Handler( + *(&p as *const usize + as *const extern fn(libc::c_int)) + as extern fn(libc::c_int)), + } + }) +} + +fn do_pthread_sigmask(how: SigmaskHow, + set: Option<&SigSet>, + oldset: Option<*mut libc::sigset_t>) -> Result<()> { + if set.is_none() && oldset.is_none() { + return Ok(()) + } + + let res = unsafe { + // if set or oldset is None, pass in null pointers instead + libc::pthread_sigmask(how as libc::c_int, + set.map_or_else(ptr::null::, + |s| &s.sigset as *const libc::sigset_t), + oldset.unwrap_or(ptr::null_mut()) + ) + }; + + Errno::result(res).map(drop) +} + +/// Manages the signal mask (set of blocked signals) for the calling thread. +/// +/// If the `set` parameter is `Some(..)`, then the signal mask will be updated with the signal set. +/// The `how` flag decides the type of update. If `set` is `None`, `how` will be ignored, +/// and no modification will take place. +/// +/// If the 'oldset' parameter is `Some(..)` then the current signal mask will be written into it. +/// +/// If both `set` and `oldset` is `Some(..)`, the current signal mask will be written into oldset, +/// and then it will be updated with `set`. +/// +/// If both `set` and `oldset` is None, this function is a no-op. +/// +/// For more information, visit the [`pthread_sigmask`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html), +/// or [`sigprocmask`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigprocmask.html) man pages. +pub fn pthread_sigmask(how: SigmaskHow, + set: Option<&SigSet>, + oldset: Option<&mut SigSet>) -> Result<()> +{ + do_pthread_sigmask(how, set, oldset.map(|os| &mut os.sigset as *mut _ )) +} + +/// Examine and change blocked signals. +/// +/// For more information see the [`sigprocmask` man +/// pages](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigprocmask.html). +pub fn sigprocmask(how: SigmaskHow, set: Option<&SigSet>, oldset: Option<&mut SigSet>) -> Result<()> { + if set.is_none() && oldset.is_none() { + return Ok(()) + } + + let res = unsafe { + // if set or oldset is None, pass in null pointers instead + libc::sigprocmask(how as libc::c_int, + set.map_or_else(ptr::null::, + |s| &s.sigset as *const libc::sigset_t), + oldset.map_or_else(ptr::null_mut::, + |os| &mut os.sigset as *mut libc::sigset_t)) + }; + + Errno::result(res).map(drop) +} + +/// Send a signal to a process +/// +/// # Arguments +/// +/// * `pid` - Specifies which processes should receive the signal. +/// - If positive, specifies an individual process. +/// - If zero, the signal will be sent to all processes whose group +/// ID is equal to the process group ID of the sender. This is a +#[cfg_attr(target_os = "fuchsia", doc = "variant of `killpg`.")] +#[cfg_attr(not(target_os = "fuchsia"), doc = "variant of [`killpg`].")] +/// - If `-1` and the process has super-user privileges, the signal +/// is sent to all processes exclusing system processes. +/// - If less than `-1`, the signal is sent to all processes whose +/// process group ID is equal to the absolute value of `pid`. +/// * `signal` - Signal to send. If `None`, error checking is performed +/// but no signal is actually sent. +/// +/// See Also +/// [`kill(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html) +pub fn kill>>(pid: Pid, signal: T) -> Result<()> { + let res = unsafe { libc::kill(pid.into(), + match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }) }; + + Errno::result(res).map(drop) +} + +/// Send a signal to a process group +/// +/// # Arguments +/// +/// * `pgrp` - Process group to signal. If less then or equal 1, the behavior +/// is platform-specific. +/// * `signal` - Signal to send. If `None`, `killpg` will only preform error +/// checking and won't send any signal. +/// +/// See Also [killpg(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html). +#[cfg(not(target_os = "fuchsia"))] +pub fn killpg>>(pgrp: Pid, signal: T) -> Result<()> { + let res = unsafe { libc::killpg(pgrp.into(), + match signal.into() { + Some(s) => s as libc::c_int, + None => 0, + }) }; + + Errno::result(res).map(drop) +} + +/// Send a signal to the current thread +/// +/// See Also [raise(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html) +pub fn raise(signal: Signal) -> Result<()> { + let res = unsafe { libc::raise(signal as libc::c_int) }; + + Errno::result(res).map(drop) +} +} + +feature! { +#![any(feature = "aio", feature = "signal")] + +/// Identifies a thread for [`SigevNotify::SigevThreadId`] +#[cfg(target_os = "freebsd")] +pub type type_of_thread_id = libc::lwpid_t; +/// Identifies a thread for [`SigevNotify::SigevThreadId`] +#[cfg(target_os = "linux")] +pub type type_of_thread_id = libc::pid_t; + +/// Specifies the notification method used by a [`SigEvent`] +// sigval is actually a union of a int and a void*. But it's never really used +// as a pointer, because neither libc nor the kernel ever dereference it. nix +// therefore presents it as an intptr_t, which is how kevent uses it. +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SigevNotify { + /// No notification will be delivered + SigevNone, + /// Notify by delivering a signal to the process. + SigevSignal { + /// Signal to deliver + signal: Signal, + /// Will be present in the `si_value` field of the [`libc::siginfo_t`] + /// structure of the queued signal. + si_value: libc::intptr_t + }, + // Note: SIGEV_THREAD is not implemented because libc::sigevent does not + // expose a way to set the union members needed by SIGEV_THREAD. + /// Notify by delivering an event to a kqueue. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SigevKevent { + /// File descriptor of the kqueue to notify. + kq: RawFd, + /// Will be contained in the kevent's `udata` field. + udata: libc::intptr_t + }, + /// Notify by delivering a signal to a thread. + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SigevThreadId { + /// Signal to send + signal: Signal, + /// LWP ID of the thread to notify + thread_id: type_of_thread_id, + /// Will be present in the `si_value` field of the [`libc::siginfo_t`] + /// structure of the queued signal. + si_value: libc::intptr_t + }, +} +} + +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod sigevent { + feature! { + #![any(feature = "aio", feature = "signal")] + + use std::mem; + use std::ptr; + use super::SigevNotify; + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + use super::type_of_thread_id; + + /// Used to request asynchronous notification of the completion of certain + /// events, such as POSIX AIO and timers. + #[repr(C)] + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct SigEvent { + sigevent: libc::sigevent + } + + impl SigEvent { + /// **Note:** this constructor does not allow the user to set the + /// `sigev_notify_kevent_flags` field. That's considered ok because on FreeBSD + /// at least those flags don't do anything useful. That field is part of a + /// union that shares space with the more genuinely useful fields. + /// + /// **Note:** This constructor also doesn't allow the caller to set the + /// `sigev_notify_function` or `sigev_notify_attributes` fields, which are + /// required for `SIGEV_THREAD`. That's considered ok because on no operating + /// system is `SIGEV_THREAD` the most efficient way to deliver AIO + /// notification. FreeBSD and DragonFly BSD programs should prefer `SIGEV_KEVENT`. + /// Linux, Solaris, and portable programs should prefer `SIGEV_THREAD_ID` or + /// `SIGEV_SIGNAL`. That field is part of a union that shares space with the + /// more genuinely useful `sigev_notify_thread_id` + // Allow invalid_value warning on Fuchsia only. + // See https://github.com/nix-rust/nix/issues/1441 + #[cfg_attr(target_os = "fuchsia", allow(invalid_value))] + pub fn new(sigev_notify: SigevNotify) -> SigEvent { + let mut sev = unsafe { mem::MaybeUninit::::zeroed().assume_init() }; + sev.sigev_notify = match sigev_notify { + SigevNotify::SigevNone => libc::SIGEV_NONE, + SigevNotify::SigevSignal{..} => libc::SIGEV_SIGNAL, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT, + #[cfg(target_os = "freebsd")] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, + #[cfg(all(target_os = "linux", target_env = "gnu", not(target_arch = "mips")))] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, + #[cfg(any(all(target_os = "linux", target_env = "musl"), target_arch = "mips"))] + SigevNotify::SigevThreadId{..} => 4 // No SIGEV_THREAD_ID defined + }; + sev.sigev_signo = match sigev_notify { + SigevNotify::SigevSignal{ signal, .. } => signal as libc::c_int, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{ kq, ..} => kq, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + SigevNotify::SigevThreadId{ signal, .. } => signal as libc::c_int, + _ => 0 + }; + sev.sigev_value.sival_ptr = match sigev_notify { + SigevNotify::SigevNone => ptr::null_mut::(), + SigevNotify::SigevSignal{ si_value, .. } => si_value as *mut libc::c_void, + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + SigevNotify::SigevKevent{ udata, .. } => udata as *mut libc::c_void, + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + SigevNotify::SigevThreadId{ si_value, .. } => si_value as *mut libc::c_void, + }; + SigEvent::set_tid(&mut sev, &sigev_notify); + SigEvent{sigevent: sev} + } + + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + fn set_tid(sev: &mut libc::sigevent, sigev_notify: &SigevNotify) { + sev.sigev_notify_thread_id = match *sigev_notify { + SigevNotify::SigevThreadId { thread_id, .. } => thread_id, + _ => 0 as type_of_thread_id + }; + } + + #[cfg(not(any(target_os = "freebsd", target_os = "linux")))] + fn set_tid(_sev: &mut libc::sigevent, _sigev_notify: &SigevNotify) { + } + + /// Return a copy of the inner structure + pub fn sigevent(&self) -> libc::sigevent { + self.sigevent + } + + /// Returns a mutable pointer to the `sigevent` wrapped by `self` + pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent { + &mut self.sigevent + } + } + + impl<'a> From<&'a libc::sigevent> for SigEvent { + fn from(sigevent: &libc::sigevent) -> Self { + SigEvent{ sigevent: *sigevent } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(not(target_os = "redox"))] + use std::thread; + + #[test] + fn test_contains() { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + assert!(mask.contains(SIGUSR1)); + assert!(!mask.contains(SIGUSR2)); + + let all = SigSet::all(); + assert!(all.contains(SIGUSR1)); + assert!(all.contains(SIGUSR2)); + } + + #[test] + fn test_clear() { + let mut set = SigSet::all(); + set.clear(); + for signal in Signal::iterator() { + assert!(!set.contains(signal)); + } + } + + #[test] + fn test_from_str_round_trips() { + for signal in Signal::iterator() { + assert_eq!(signal.as_ref().parse::().unwrap(), signal); + assert_eq!(signal.to_string().parse::().unwrap(), signal); + } + } + + #[test] + fn test_from_str_invalid_value() { + let errval = Err(Errno::EINVAL); + assert_eq!("NOSIGNAL".parse::(), errval); + assert_eq!("kill".parse::(), errval); + assert_eq!("9".parse::(), errval); + } + + #[test] + fn test_extend() { + let mut one_signal = SigSet::empty(); + one_signal.add(SIGUSR1); + + let mut two_signals = SigSet::empty(); + two_signals.add(SIGUSR2); + two_signals.extend(&one_signal); + + assert!(two_signals.contains(SIGUSR1)); + assert!(two_signals.contains(SIGUSR2)); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_set_mask() { + thread::spawn(|| { + let prev_mask = SigSet::thread_get_mask() + .expect("Failed to get existing signal mask!"); + + let mut test_mask = prev_mask; + test_mask.add(SIGUSR1); + + test_mask.thread_set_mask().expect("assertion failed"); + let new_mask = + SigSet::thread_get_mask().expect("Failed to get new mask!"); + + assert!(new_mask.contains(SIGUSR1)); + assert!(!new_mask.contains(SIGUSR2)); + + prev_mask + .thread_set_mask() + .expect("Failed to revert signal mask!"); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_block() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + mask.thread_block().expect("assertion failed"); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_unblock() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + mask.thread_unblock().expect("assertion failed"); + + assert!(!SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_thread_signal_swap() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + mask.thread_block().unwrap(); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + + let mut mask2 = SigSet::empty(); + mask2.add(SIGUSR2); + + let oldmask = + mask2.thread_swap_mask(SigmaskHow::SIG_SETMASK).unwrap(); + + assert!(oldmask.contains(SIGUSR1)); + assert!(!oldmask.contains(SIGUSR2)); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR2)); + }) + .join() + .unwrap(); + } + + #[test] + fn test_from_and_into_iterator() { + let sigset = SigSet::from_iter(vec![Signal::SIGUSR1, Signal::SIGUSR2]); + let signals = sigset.into_iter().collect::>(); + assert_eq!(signals, [Signal::SIGUSR1, Signal::SIGUSR2]); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_sigaction() { + thread::spawn(|| { + extern "C" fn test_sigaction_handler(_: libc::c_int) {} + extern "C" fn test_sigaction_action( + _: libc::c_int, + _: *mut libc::siginfo_t, + _: *mut libc::c_void, + ) { + } + + let handler_sig = SigHandler::Handler(test_sigaction_handler); + + let flags = + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART | SaFlags::SA_SIGINFO; + + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + let action_sig = SigAction::new(handler_sig, flags, mask); + + assert_eq!( + action_sig.flags(), + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART + ); + assert_eq!(action_sig.handler(), handler_sig); + + mask = action_sig.mask(); + assert!(mask.contains(SIGUSR1)); + assert!(!mask.contains(SIGUSR2)); + + let handler_act = SigHandler::SigAction(test_sigaction_action); + let action_act = SigAction::new(handler_act, flags, mask); + assert_eq!(action_act.handler(), handler_act); + + let action_dfl = SigAction::new(SigHandler::SigDfl, flags, mask); + assert_eq!(action_dfl.handler(), SigHandler::SigDfl); + + let action_ign = SigAction::new(SigHandler::SigIgn, flags, mask); + assert_eq!(action_ign.handler(), SigHandler::SigIgn); + }) + .join() + .unwrap(); + } + + #[test] + #[cfg(not(target_os = "redox"))] + fn test_sigwait() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + mask.add(SIGUSR2); + mask.thread_block().unwrap(); + + raise(SIGUSR1).unwrap(); + assert_eq!(mask.wait().unwrap(), SIGUSR1); + }) + .join() + .unwrap(); + } + + #[test] + fn test_from_sigset_t_unchecked() { + let src_set = SigSet::empty(); + let set = unsafe { SigSet::from_sigset_t_unchecked(src_set.sigset) }; + + for signal in Signal::iterator() { + assert!(!set.contains(signal)); + } + + let src_set = SigSet::all(); + let set = unsafe { SigSet::from_sigset_t_unchecked(src_set.sigset) }; + + for signal in Signal::iterator() { + assert!(set.contains(signal)); + } + } +} diff --git a/src/sys/signalfd.rs b/src/sys/signalfd.rs new file mode 100644 index 0000000..095e590 --- /dev/null +++ b/src/sys/signalfd.rs @@ -0,0 +1,175 @@ +//! Interface for the `signalfd` syscall. +//! +//! # Signal discarding +//! When a signal can't be delivered to a process (or thread), it will become a pending signal. +//! Failure to deliver could happen if the signal is blocked by every thread in the process or if +//! the signal handler is still handling a previous signal. +//! +//! If a signal is sent to a process (or thread) that already has a pending signal of the same +//! type, it will be discarded. This means that if signals of the same type are received faster than +//! they are processed, some of those signals will be dropped. Because of this limitation, +//! `signalfd` in itself cannot be used for reliable communication between processes or threads. +//! +//! Once the signal is unblocked, or the signal handler is finished, and a signal is still pending +//! (ie. not consumed from a signalfd) it will be delivered to the signal handler. +//! +//! Please note that signal discarding is not specific to `signalfd`, but also happens with regular +//! signal handlers. +use crate::errno::Errno; +pub use crate::sys::signal::{self, SigSet}; +use crate::unistd; +use crate::Result; +pub use libc::signalfd_siginfo as siginfo; + +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; + +libc_bitflags! { + pub struct SfdFlags: libc::c_int { + SFD_NONBLOCK; + SFD_CLOEXEC; + } +} + +pub const SIGNALFD_NEW: RawFd = -1; +#[deprecated(since = "0.23.0", note = "use mem::size_of::() instead")] +pub const SIGNALFD_SIGINFO_SIZE: usize = mem::size_of::(); + +/// Creates a new file descriptor for reading signals. +/// +/// **Important:** please read the module level documentation about signal discarding before using +/// this function! +/// +/// The `mask` parameter specifies the set of signals that can be accepted via this file descriptor. +/// +/// A signal must be blocked on every thread in a process, otherwise it won't be visible from +/// signalfd (the default handler will be invoked instead). +/// +/// See [the signalfd man page for more information](https://man7.org/linux/man-pages/man2/signalfd.2.html) +pub fn signalfd(fd: RawFd, mask: &SigSet, flags: SfdFlags) -> Result { + unsafe { + Errno::result(libc::signalfd( + fd as libc::c_int, + mask.as_ref(), + flags.bits(), + )) + } +} + +/// A helper struct for creating, reading and closing a `signalfd` instance. +/// +/// **Important:** please read the module level documentation about signal discarding before using +/// this struct! +/// +/// # Examples +/// +/// ``` +/// # use nix::sys::signalfd::*; +/// // Set the thread to block the SIGUSR1 signal, otherwise the default handler will be used +/// let mut mask = SigSet::empty(); +/// mask.add(signal::SIGUSR1); +/// mask.thread_block().unwrap(); +/// +/// // Signals are queued up on the file descriptor +/// let mut sfd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); +/// +/// match sfd.read_signal() { +/// // we caught a signal +/// Ok(Some(sig)) => (), +/// // there were no signals waiting (only happens when the SFD_NONBLOCK flag is set, +/// // otherwise the read_signal call blocks) +/// Ok(None) => (), +/// Err(err) => (), // some error happend +/// } +/// ``` +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct SignalFd(RawFd); + +impl SignalFd { + pub fn new(mask: &SigSet) -> Result { + Self::with_flags(mask, SfdFlags::empty()) + } + + pub fn with_flags(mask: &SigSet, flags: SfdFlags) -> Result { + let fd = signalfd(SIGNALFD_NEW, mask, flags)?; + + Ok(SignalFd(fd)) + } + + pub fn set_mask(&mut self, mask: &SigSet) -> Result<()> { + signalfd(self.0, mask, SfdFlags::empty()).map(drop) + } + + pub fn read_signal(&mut self) -> Result> { + let mut buffer = mem::MaybeUninit::::uninit(); + + let size = mem::size_of_val(&buffer); + let res = Errno::result(unsafe { + libc::read(self.0, buffer.as_mut_ptr() as *mut libc::c_void, size) + }) + .map(|r| r as usize); + match res { + Ok(x) if x == size => Ok(Some(unsafe { buffer.assume_init() })), + Ok(_) => unreachable!("partial read on signalfd"), + Err(Errno::EAGAIN) => Ok(None), + Err(error) => Err(error), + } + } +} + +impl Drop for SignalFd { + fn drop(&mut self) { + let e = unistd::close(self.0); + if !std::thread::panicking() && e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; + } +} + +impl AsRawFd for SignalFd { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl Iterator for SignalFd { + type Item = siginfo; + + fn next(&mut self) -> Option { + match self.read_signal() { + Ok(Some(sig)) => Some(sig), + Ok(None) | Err(_) => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_signalfd() { + let mask = SigSet::empty(); + SignalFd::new(&mask).unwrap(); + } + + #[test] + fn create_signalfd_with_opts() { + let mask = SigSet::empty(); + SignalFd::with_flags( + &mask, + SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK, + ) + .unwrap(); + } + + #[test] + fn read_empty_signalfd() { + let mask = SigSet::empty(); + let mut fd = + SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); + + let res = fd.read_signal(); + assert!(res.unwrap().is_none()); + } +} diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs new file mode 100644 index 0000000..4e565a5 --- /dev/null +++ b/src/sys/socket/addr.rs @@ -0,0 +1,3247 @@ +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "haiku", + target_os = "fuchsia" +))] +#[cfg(feature = "net")] +pub use self::datalink::LinkAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::vsock::VsockAddr; +use super::sa_family_t; +use crate::errno::Errno; +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::sys::socket::addr::alg::AlgAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::sys::socket::addr::netlink::NetlinkAddr; +#[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") +))] +use crate::sys::socket::addr::sys_control::SysControlAddr; +use crate::{NixPath, Result}; +use cfg_if::cfg_if; +use memoffset::offset_of; +use std::convert::TryInto; +use std::ffi::OsStr; +use std::hash::{Hash, Hasher}; +use std::os::unix::ffi::OsStrExt; +#[cfg(any(target_os = "ios", target_os = "macos"))] +use std::os::unix::io::RawFd; +use std::path::Path; +use std::{fmt, mem, net, ptr, slice}; + +/// Convert a std::net::Ipv4Addr into the libc form. +#[cfg(feature = "net")] +pub(crate) const fn ipv4addr_to_libc(addr: net::Ipv4Addr) -> libc::in_addr { + static_assertions::assert_eq_size!(net::Ipv4Addr, libc::in_addr); + // Safe because both types have the same memory layout, and no fancy Drop + // impls. + unsafe { + mem::transmute(addr) + } +} + +/// Convert a std::net::Ipv6Addr into the libc form. +#[cfg(feature = "net")] +pub(crate) const fn ipv6addr_to_libc(addr: &net::Ipv6Addr) -> libc::in6_addr { + static_assertions::assert_eq_size!(net::Ipv6Addr, libc::in6_addr); + // Safe because both are Newtype wrappers around the same libc type + unsafe { + mem::transmute(*addr) + } +} + +/// These constants specify the protocol family to be used +/// in [`socket`](fn.socket.html) and [`socketpair`](fn.socketpair.html) +/// +/// # References +/// +/// [address_families(7)](https://man7.org/linux/man-pages/man7/address_families.7.html) +// Should this be u8? +#[repr(i32)] +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum AddressFamily { + /// Local communication (see [`unix(7)`](https://man7.org/linux/man-pages/man7/unix.7.html)) + Unix = libc::AF_UNIX, + /// IPv4 Internet protocols (see [`ip(7)`](https://man7.org/linux/man-pages/man7/ip.7.html)) + Inet = libc::AF_INET, + /// IPv6 Internet protocols (see [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html)) + Inet6 = libc::AF_INET6, + /// Kernel user interface device (see [`netlink(7)`](https://man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Netlink = libc::AF_NETLINK, + /// Low level packet interface (see [`packet(7)`](https://man7.org/linux/man-pages/man7/packet.7.html)) + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "illumos", + target_os = "fuchsia", + target_os = "solaris" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Packet = libc::AF_PACKET, + /// KEXT Controls and Notifications + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + System = libc::AF_SYSTEM, + /// Amateur radio AX.25 protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ax25 = libc::AF_AX25, + /// IPX - Novell protocols + Ipx = libc::AF_IPX, + /// AppleTalk + AppleTalk = libc::AF_APPLETALK, + /// AX.25 packet layer protocol. + /// (see [netrom(4)](https://www.unix.com/man-page/linux/4/netrom/)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetRom = libc::AF_NETROM, + /// Can't be used for creating sockets; mostly used for bridge + /// links in + /// [rtnetlink(7)](https://man7.org/linux/man-pages/man7/rtnetlink.7.html) + /// protocol commands. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Bridge = libc::AF_BRIDGE, + /// Access to raw ATM PVCs + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AtmPvc = libc::AF_ATMPVC, + /// ITU-T X.25 / ISO-8208 protocol (see [`x25(7)`](https://man7.org/linux/man-pages/man7/x25.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + X25 = libc::AF_X25, + /// RATS (Radio Amateur Telecommunications Society) Open + /// Systems environment (ROSE) AX.25 packet layer protocol. + /// (see [netrom(4)](https://www.unix.com/man-page/linux/4/netrom/)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Rose = libc::AF_ROSE, + /// DECet protocol sockets. + #[cfg(not(target_os = "haiku"))] + Decnet = libc::AF_DECnet, + /// Reserved for "802.2LLC project"; never used. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetBeui = libc::AF_NETBEUI, + /// This was a short-lived (between Linux 2.1.30 and + /// 2.1.99pre2) protocol family for firewall upcalls. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Security = libc::AF_SECURITY, + /// Key management protocol. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Key = libc::AF_KEY, + #[allow(missing_docs)] // Not documented anywhere that I can find + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ash = libc::AF_ASH, + /// Acorn Econet protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Econet = libc::AF_ECONET, + /// Access to ATM Switched Virtual Circuits + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AtmSvc = libc::AF_ATMSVC, + /// Reliable Datagram Sockets (RDS) protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Rds = libc::AF_RDS, + /// IBM SNA + #[cfg(not(target_os = "haiku"))] + Sna = libc::AF_SNA, + /// Socket interface over IrDA + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Irda = libc::AF_IRDA, + /// Generic PPP transport layer, for setting up L2 tunnels (L2TP and PPPoE) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Pppox = libc::AF_PPPOX, + /// Legacy protocol for wide area network (WAN) connectivity that was used + /// by Sangoma WAN cards + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Wanpipe = libc::AF_WANPIPE, + /// Logical link control (IEEE 802.2 LLC) protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Llc = libc::AF_LLC, + /// InfiniBand native addressing + #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ib = libc::AF_IB, + /// Multiprotocol Label Switching + #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Mpls = libc::AF_MPLS, + /// Controller Area Network automotive bus protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Can = libc::AF_CAN, + /// TIPC, "cluster domain sockets" protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Tipc = libc::AF_TIPC, + /// Bluetooth low-level socket protocol + #[cfg(not(any( + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "solaris" + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Bluetooth = libc::AF_BLUETOOTH, + /// IUCV (inter-user communication vehicle) z/VM protocol for + /// hypervisor-guest interaction + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Iucv = libc::AF_IUCV, + /// Rx, Andrew File System remote procedure call protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RxRpc = libc::AF_RXRPC, + /// New "modular ISDN" driver interface protocol + #[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Isdn = libc::AF_ISDN, + /// Nokia cellular modem IPC/RPC interface + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Phonet = libc::AF_PHONET, + /// IEEE 802.15.4 WPAN (wireless personal area network) raw packet protocol + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ieee802154 = libc::AF_IEEE802154, + /// Ericsson's Communication CPU to Application CPU interface (CAIF) + /// protocol. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Caif = libc::AF_CAIF, + /// Interface to kernel crypto API + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Alg = libc::AF_ALG, + /// Near field communication + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + Nfc = libc::AF_NFC, + /// VMWare VSockets protocol for hypervisor-guest interaction. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Vsock = libc::AF_VSOCK, + /// ARPANet IMP addresses + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ImpLink = libc::AF_IMPLINK, + /// PUP protocols, e.g. BSP + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Pup = libc::AF_PUP, + /// MIT CHAOS protocols + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Chaos = libc::AF_CHAOS, + /// Novell and Xerox protocol + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ns = libc::AF_NS, + #[allow(missing_docs)] // Not documented anywhere that I can find + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Iso = libc::AF_ISO, + /// Bell Labs virtual circuit switch ? + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Datakit = libc::AF_DATAKIT, + /// CCITT protocols, X.25 etc + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Ccitt = libc::AF_CCITT, + /// DEC Direct data link interface + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Dli = libc::AF_DLI, + #[allow(missing_docs)] // Not documented anywhere that I can find + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Lat = libc::AF_LAT, + /// NSC Hyperchannel + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Hylink = libc::AF_HYLINK, + /// Link layer interface + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Link = libc::AF_LINK, + /// connection-oriented IP, aka ST II + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Coip = libc::AF_COIP, + /// Computer Network Technology + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Cnt = libc::AF_CNT, + /// Native ATM access + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Natm = libc::AF_NATM, + /// Unspecified address family, (see [`getaddrinfo(3)`](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Unspec = libc::AF_UNSPEC, +} + +impl AddressFamily { + /// Create a new `AddressFamily` from an integer value retrieved from `libc`, usually from + /// the `sa_family` field of a `sockaddr`. + /// + /// Currently only supports these address families: Unix, Inet (v4 & v6), Netlink, Link/Packet + /// and System. Returns None for unsupported or unknown address families. + pub const fn from_i32(family: i32) -> Option { + match family { + libc::AF_UNIX => Some(AddressFamily::Unix), + libc::AF_INET => Some(AddressFamily::Inet), + libc::AF_INET6 => Some(AddressFamily::Inet6), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => Some(AddressFamily::Netlink), + #[cfg(any(target_os = "macos", target_os = "macos"))] + libc::AF_SYSTEM => Some(AddressFamily::System), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_PACKET => Some(AddressFamily::Packet), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + libc::AF_LINK => Some(AddressFamily::Link), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => Some(AddressFamily::Vsock), + _ => None, + } + } +} + +feature! { +#![feature = "net"] + +#[deprecated( + since = "0.24.0", + note = "use SockaddrIn, SockaddrIn6, or SockaddrStorage instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum InetAddr { + V4(libc::sockaddr_in), + V6(libc::sockaddr_in6), +} + +#[allow(missing_docs)] // It's deprecated anyway +#[allow(deprecated)] +impl InetAddr { + #[allow(clippy::needless_update)] // It isn't needless on all OSes + pub fn from_std(std: &net::SocketAddr) -> InetAddr { + match *std { + net::SocketAddr::V4(ref addr) => { + InetAddr::V4(libc::sockaddr_in { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "haiku", target_os = "hermit", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + sin_len: mem::size_of::() as u8, + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: addr.port().to_be(), // network byte order + sin_addr: Ipv4Addr::from_std(addr.ip()).0, + .. unsafe { mem::zeroed() } + }) + } + net::SocketAddr::V6(ref addr) => { + InetAddr::V6(libc::sockaddr_in6 { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "haiku", target_os = "hermit", + target_os = "ios", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] + sin6_len: mem::size_of::() as u8, + sin6_family: AddressFamily::Inet6 as sa_family_t, + sin6_port: addr.port().to_be(), // network byte order + sin6_addr: Ipv6Addr::from_std(addr.ip()).0, + sin6_flowinfo: addr.flowinfo(), // host byte order + sin6_scope_id: addr.scope_id(), // host byte order + .. unsafe { mem::zeroed() } + }) + } + } + } + + #[allow(clippy::needless_update)] // It isn't needless on all OSes + pub fn new(ip: IpAddr, port: u16) -> InetAddr { + match ip { + IpAddr::V4(ref ip) => { + InetAddr::V4(libc::sockaddr_in { + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: port.to_be(), + sin_addr: ip.0, + .. unsafe { mem::zeroed() } + }) + } + IpAddr::V6(ref ip) => { + InetAddr::V6(libc::sockaddr_in6 { + sin6_family: AddressFamily::Inet6 as sa_family_t, + sin6_port: port.to_be(), + sin6_addr: ip.0, + .. unsafe { mem::zeroed() } + }) + } + } + } + /// Gets the IP address associated with this socket address. + pub const fn ip(&self) -> IpAddr { + match *self { + InetAddr::V4(ref sa) => IpAddr::V4(Ipv4Addr(sa.sin_addr)), + InetAddr::V6(ref sa) => IpAddr::V6(Ipv6Addr(sa.sin6_addr)), + } + } + + /// Gets the port number associated with this socket address + pub const fn port(&self) -> u16 { + match *self { + InetAddr::V6(ref sa) => u16::from_be(sa.sin6_port), + InetAddr::V4(ref sa) => u16::from_be(sa.sin_port), + } + } + + pub fn to_std(&self) -> net::SocketAddr { + match *self { + InetAddr::V4(ref sa) => net::SocketAddr::V4( + net::SocketAddrV4::new( + Ipv4Addr(sa.sin_addr).to_std(), + self.port())), + InetAddr::V6(ref sa) => net::SocketAddr::V6( + net::SocketAddrV6::new( + Ipv6Addr(sa.sin6_addr).to_std(), + self.port(), + sa.sin6_flowinfo, + sa.sin6_scope_id)), + } + } + + #[deprecated(since = "0.23.0", note = "use .to_string() instead")] + pub fn to_str(&self) -> String { + format!("{}", self) + } +} + +#[allow(deprecated)] +impl fmt::Display for InetAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + InetAddr::V4(_) => write!(f, "{}:{}", self.ip(), self.port()), + InetAddr::V6(_) => write!(f, "[{}]:{}", self.ip(), self.port()), + } + } +} + +/* + * + * ===== IpAddr ===== + * + */ +#[allow(missing_docs)] // Since they're all deprecated anyway +#[allow(deprecated)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[deprecated( + since = "0.24.0", + note = "Use std::net::IpAddr instead" +)] +pub enum IpAddr { + V4(Ipv4Addr), + V6(Ipv6Addr), +} + +#[allow(deprecated)] +#[allow(missing_docs)] // Since they're all deprecated anyway +impl IpAddr { + /// Create a new IpAddr that contains an IPv4 address. + /// + /// The result will represent the IP address a.b.c.d + pub const fn new_v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(a, b, c, d)) + } + + /// Create a new IpAddr that contains an IPv6 address. + /// + /// The result will represent the IP address a:b:c:d:e:f + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub const fn new_v6(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> IpAddr { + IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h)) + } + + pub fn from_std(std: &net::IpAddr) -> IpAddr { + match *std { + net::IpAddr::V4(ref std) => IpAddr::V4(Ipv4Addr::from_std(std)), + net::IpAddr::V6(ref std) => IpAddr::V6(Ipv6Addr::from_std(std)), + } + } + + pub const fn to_std(&self) -> net::IpAddr { + match *self { + IpAddr::V4(ref ip) => net::IpAddr::V4(ip.to_std()), + IpAddr::V6(ref ip) => net::IpAddr::V6(ip.to_std()), + } + } +} + +#[allow(deprecated)] +impl fmt::Display for IpAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + IpAddr::V4(ref v4) => v4.fmt(f), + IpAddr::V6(ref v6) => v6.fmt(f) + } + } +} + +/* + * + * ===== Ipv4Addr ===== + * + */ + +#[deprecated( + since = "0.24.0", + note = "Use std::net::Ipv4Addr instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct Ipv4Addr(pub libc::in_addr); + +#[allow(deprecated)] +#[allow(missing_docs)] // Since they're all deprecated anyway +impl Ipv4Addr { + #[allow(clippy::identity_op)] // More readable this way + pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { + let ip = (((a as u32) << 24) | + ((b as u32) << 16) | + ((c as u32) << 8) | + ((d as u32) << 0)).to_be(); + + Ipv4Addr(libc::in_addr { s_addr: ip }) + } + + // Use pass by reference for symmetry with Ipv6Addr::from_std + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn from_std(std: &net::Ipv4Addr) -> Ipv4Addr { + let bits = std.octets(); + Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) + } + + pub const fn any() -> Ipv4Addr { + Ipv4Addr(libc::in_addr { s_addr: libc::INADDR_ANY }) + } + + pub const fn octets(self) -> [u8; 4] { + let bits = u32::from_be(self.0.s_addr); + [(bits >> 24) as u8, (bits >> 16) as u8, (bits >> 8) as u8, bits as u8] + } + + pub const fn to_std(self) -> net::Ipv4Addr { + let bits = self.octets(); + net::Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) + } +} + +#[allow(deprecated)] +impl fmt::Display for Ipv4Addr { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let octets = self.octets(); + write!(fmt, "{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]) + } +} + +/* + * + * ===== Ipv6Addr ===== + * + */ + +#[deprecated( + since = "0.24.0", + note = "Use std::net::Ipv6Addr instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct Ipv6Addr(pub libc::in6_addr); + +// Note that IPv6 addresses are stored in big endian order on all architectures. +// See https://tools.ietf.org/html/rfc1700 or consult your favorite search +// engine. + +macro_rules! to_u8_array { + ($($num:ident),*) => { + [ $(($num>>8) as u8, ($num&0xff) as u8,)* ] + } +} + +macro_rules! to_u16_array { + ($slf:ident, $($first:expr, $second:expr),*) => { + [$( (($slf.0.s6_addr[$first] as u16) << 8) + $slf.0.s6_addr[$second] as u16,)*] + } +} + +#[allow(deprecated)] +#[allow(missing_docs)] // Since they're all deprecated anyway +impl Ipv6Addr { + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { + Ipv6Addr(libc::in6_addr{s6_addr: to_u8_array!(a,b,c,d,e,f,g,h)}) + } + + pub fn from_std(std: &net::Ipv6Addr) -> Ipv6Addr { + let s = std.segments(); + Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) + } + + /// Return the eight 16-bit segments that make up this address + pub const fn segments(&self) -> [u16; 8] { + to_u16_array!(self, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) + } + + pub const fn to_std(&self) -> net::Ipv6Addr { + let s = self.segments(); + net::Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) + } +} + +#[allow(deprecated)] +impl fmt::Display for Ipv6Addr { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + self.to_std().fmt(fmt) + } +} +} + +/// A wrapper around `sockaddr_un`. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct UnixAddr { + // INVARIANT: sun & sun_len are valid as defined by docs for from_raw_parts + sun: libc::sockaddr_un, + /// The length of the valid part of `sun`, including the sun_family field + /// but excluding any trailing nul. + // On the BSDs, this field is built into sun + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + sun_len: u8, +} + +// linux man page unix(7) says there are 3 kinds of unix socket: +// pathname: addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1 +// unnamed: addrlen = sizeof(sa_family_t) +// abstract: addren > sizeof(sa_family_t), name = sun_path[..(addrlen - sizeof(sa_family_t))] +// +// what we call path_len = addrlen - offsetof(struct sockaddr_un, sun_path) +#[derive(PartialEq, Eq, Hash)] +enum UnixAddrKind<'a> { + Pathname(&'a Path), + Unnamed, + #[cfg(any(target_os = "android", target_os = "linux"))] + Abstract(&'a [u8]), +} +impl<'a> UnixAddrKind<'a> { + /// Safety: sun & sun_len must be valid + unsafe fn get(sun: &'a libc::sockaddr_un, sun_len: u8) -> Self { + assert!(sun_len as usize >= offset_of!(libc::sockaddr_un, sun_path)); + let path_len = + sun_len as usize - offset_of!(libc::sockaddr_un, sun_path); + if path_len == 0 { + return Self::Unnamed; + } + #[cfg(any(target_os = "android", target_os = "linux"))] + if sun.sun_path[0] == 0 { + let name = slice::from_raw_parts( + sun.sun_path.as_ptr().add(1) as *const u8, + path_len - 1, + ); + return Self::Abstract(name); + } + let pathname = + slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, path_len); + if pathname.last() == Some(&0) { + // A trailing NUL is not considered part of the path, and it does + // not need to be included in the addrlen passed to functions like + // bind(). However, Linux adds a trailing NUL, even if one was not + // originally present, when returning addrs from functions like + // getsockname() (the BSDs do not do that). So we need to filter + // out any trailing NUL here, so sockaddrs can round-trip through + // the kernel and still compare equal. + Self::Pathname(Path::new(OsStr::from_bytes( + &pathname[0..pathname.len() - 1], + ))) + } else { + Self::Pathname(Path::new(OsStr::from_bytes(pathname))) + } + } +} + +impl UnixAddr { + /// Create a new sockaddr_un representing a filesystem path. + pub fn new(path: &P) -> Result { + path.with_nix_path(|cstr| unsafe { + let mut ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + ..mem::zeroed() + }; + + let bytes = cstr.to_bytes(); + + if bytes.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); + } + + let sun_len = (bytes.len() + + offset_of!(libc::sockaddr_un, sun_path)) + .try_into() + .unwrap(); + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + { + ret.sun_len = sun_len; + } + ptr::copy_nonoverlapping( + bytes.as_ptr(), + ret.sun_path.as_mut_ptr() as *mut u8, + bytes.len(), + ); + + Ok(UnixAddr::from_raw_parts(ret, sun_len)) + })? + } + + /// Create a new `sockaddr_un` representing an address in the "abstract namespace". + /// + /// The leading nul byte for the abstract namespace is automatically added; + /// thus the input `path` is expected to be the bare name, not NUL-prefixed. + /// This is a Linux-specific extension, primarily used to allow chrooted + /// processes to communicate with processes having a different filesystem view. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_abstract(path: &[u8]) -> Result { + unsafe { + let mut ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + ..mem::zeroed() + }; + + if path.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); + } + let sun_len = + (path.len() + 1 + offset_of!(libc::sockaddr_un, sun_path)) + .try_into() + .unwrap(); + + // Abstract addresses are represented by sun_path[0] == + // b'\0', so copy starting one byte in. + ptr::copy_nonoverlapping( + path.as_ptr(), + ret.sun_path.as_mut_ptr().offset(1) as *mut u8, + path.len(), + ); + + Ok(UnixAddr::from_raw_parts(ret, sun_len)) + } + } + + /// Create a new `sockaddr_un` representing an "unnamed" unix socket address. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_unnamed() -> UnixAddr { + let ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + .. unsafe { mem::zeroed() } + }; + + let sun_len: u8 = offset_of!(libc::sockaddr_un, sun_path).try_into().unwrap(); + + unsafe { UnixAddr::from_raw_parts(ret, sun_len) } + } + + /// Create a UnixAddr from a raw `sockaddr_un` struct and a size. `sun_len` + /// is the size of the valid portion of the struct, excluding any trailing + /// NUL. + /// + /// # Safety + /// This pair of sockaddr_un & sun_len must be a valid unix addr, which + /// means: + /// - sun_len >= offset_of(sockaddr_un, sun_path) + /// - sun_len <= sockaddr_un.sun_path.len() - offset_of(sockaddr_un, sun_path) + /// - if this is a unix addr with a pathname, sun.sun_path is a + /// fs path, not necessarily nul-terminated. + pub(crate) unsafe fn from_raw_parts( + sun: libc::sockaddr_un, + sun_len: u8, + ) -> UnixAddr { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + UnixAddr { sun, sun_len } + } else { + assert_eq!(sun_len, sun.sun_len); + UnixAddr {sun} + } + } + } + + fn kind(&self) -> UnixAddrKind<'_> { + // SAFETY: our sockaddr is always valid because of the invariant on the struct + unsafe { UnixAddrKind::get(&self.sun, self.sun_len()) } + } + + /// If this address represents a filesystem path, return that path. + pub fn path(&self) -> Option<&Path> { + match self.kind() { + UnixAddrKind::Pathname(path) => Some(path), + _ => None, + } + } + + /// If this address represents an abstract socket, return its name. + /// + /// For abstract sockets only the bare name is returned, without the + /// leading NUL byte. `None` is returned for unnamed or path-backed sockets. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn as_abstract(&self) -> Option<&[u8]> { + match self.kind() { + UnixAddrKind::Abstract(name) => Some(name), + _ => None, + } + } + + /// Check if this address is an "unnamed" unix socket address. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[inline] + pub fn is_unnamed(&self) -> bool { + matches!(self.kind(), UnixAddrKind::Unnamed) + } + + /// Returns the addrlen of this socket - `offsetof(struct sockaddr_un, sun_path)` + #[inline] + pub fn path_len(&self) -> usize { + self.sun_len() as usize - offset_of!(libc::sockaddr_un, sun_path) + } + /// Returns a pointer to the raw `sockaddr_un` struct + #[inline] + pub fn as_ptr(&self) -> *const libc::sockaddr_un { + &self.sun + } + /// Returns a mutable pointer to the raw `sockaddr_un` struct + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut libc::sockaddr_un { + &mut self.sun + } + + fn sun_len(&self) -> u8 { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + self.sun_len + } else { + self.sun.sun_len + } + } + } +} + +impl private::SockaddrLikePriv for UnixAddr {} +impl SockaddrLike for UnixAddr { + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + fn len(&self) -> libc::socklen_t { + self.sun_len.into() + } + + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option, + ) -> Option + where + Self: Sized, + { + if let Some(l) = len { + if (l as usize) < offset_of!(libc::sockaddr_un, sun_path) + || l > u8::MAX as libc::socklen_t + { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_UNIX { + return None; + } + let mut su: libc::sockaddr_un = mem::zeroed(); + let sup = &mut su as *mut libc::sockaddr_un as *mut u8; + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] { + let su_len = len.unwrap_or( + mem::size_of::() as libc::socklen_t + ); + } else { + let su_len = len.unwrap_or((*addr).sa_len as libc::socklen_t); + } + }; + ptr::copy(addr as *const u8, sup, su_len as usize); + Some(Self::from_raw_parts(su, su_len as u8)) + } + + fn size() -> libc::socklen_t + where + Self: Sized, + { + mem::size_of::() as libc::socklen_t + } +} + +impl AsRef for UnixAddr { + fn as_ref(&self) -> &libc::sockaddr_un { + &self.sun + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn fmt_abstract(abs: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + use fmt::Write; + f.write_str("@\"")?; + for &b in abs { + use fmt::Display; + char::from(b).escape_default().fmt(f)?; + } + f.write_char('"')?; + Ok(()) +} + +impl fmt::Display for UnixAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.kind() { + UnixAddrKind::Pathname(path) => path.display().fmt(f), + UnixAddrKind::Unnamed => f.pad(""), + #[cfg(any(target_os = "android", target_os = "linux"))] + UnixAddrKind::Abstract(name) => fmt_abstract(name, f), + } + } +} + +impl PartialEq for UnixAddr { + fn eq(&self, other: &UnixAddr) -> bool { + self.kind() == other.kind() + } +} + +impl Eq for UnixAddr {} + +impl Hash for UnixAddr { + fn hash(&self, s: &mut H) { + self.kind().hash(s) + } +} + +/// Anything that, in C, can be cast back and forth to `sockaddr`. +/// +/// Most implementors also implement `AsRef` to access their +/// inner type read-only. +#[allow(clippy::len_without_is_empty)] +pub trait SockaddrLike: private::SockaddrLikePriv { + /// Returns a raw pointer to the inner structure. Useful for FFI. + fn as_ptr(&self) -> *const libc::sockaddr { + self as *const Self as *const libc::sockaddr + } + + /// Unsafe constructor from a variable length source + /// + /// Some C APIs from provide `len`, and others do not. If it's provided it + /// will be validated. If not, it will be guessed based on the family. + /// + /// # Arguments + /// + /// - `addr`: raw pointer to something that can be cast to a + /// `libc::sockaddr`. For example, `libc::sockaddr_in`, + /// `libc::sockaddr_in6`, etc. + /// - `len`: For fixed-width types like `sockaddr_in`, it will be + /// validated if present and ignored if not. For variable-width + /// types it is required and must be the total length of valid + /// data. For example, if `addr` points to a + /// named `sockaddr_un`, then `len` must be the length of the + /// structure up to but not including the trailing NUL. + /// + /// # Safety + /// + /// `addr` must be valid for the specific type of sockaddr. `len`, if + /// present, must not exceed the length of valid data in `addr`. + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option, + ) -> Option + where + Self: Sized; + + /// Return the address family of this socket + /// + /// # Examples + /// One common use is to match on the family of a union type, like this: + /// ``` + /// # use nix::sys::socket::*; + /// let fd = socket(AddressFamily::Inet, SockType::Stream, + /// SockFlag::empty(), None).unwrap(); + /// let ss: SockaddrStorage = getsockname(fd).unwrap(); + /// match ss.family().unwrap() { + /// AddressFamily::Inet => println!("{}", ss.as_sockaddr_in().unwrap()), + /// AddressFamily::Inet6 => println!("{}", ss.as_sockaddr_in6().unwrap()), + /// _ => println!("Unexpected address family") + /// } + /// ``` + fn family(&self) -> Option { + // Safe since all implementors have a sa_family field at the same + // address, and they're all repr(C) + AddressFamily::from_i32(unsafe { + (*(self as *const Self as *const libc::sockaddr)).sa_family as i32 + }) + } + + cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] { + /// Return the length of valid data in the sockaddr structure. + /// + /// For fixed-size sockaddrs, this should be the size of the + /// structure. But for variable-sized types like [`UnixAddr`] it + /// may be less. + fn len(&self) -> libc::socklen_t { + // Safe since all implementors have a sa_len field at the same + // address, and they're all repr(transparent). + // Robust for all implementors. + unsafe { + (*(self as *const Self as *const libc::sockaddr)).sa_len + }.into() + } + } else { + /// Return the length of valid data in the sockaddr structure. + /// + /// For fixed-size sockaddrs, this should be the size of the + /// structure. But for variable-sized types like [`UnixAddr`] it + /// may be less. + fn len(&self) -> libc::socklen_t { + // No robust default implementation is possible without an + // sa_len field. Implementors with a variable size must + // override this method. + mem::size_of_val(self) as libc::socklen_t + } + } + } + + /// Return the available space in the structure + fn size() -> libc::socklen_t + where + Self: Sized, + { + mem::size_of::() as libc::socklen_t + } +} + +impl private::SockaddrLikePriv for () { + fn as_mut_ptr(&mut self) -> *mut libc::sockaddr { + ptr::null_mut() + } +} + +/// `()` can be used in place of a real Sockaddr when no address is expected, +/// for example for a field of `Option where S: SockaddrLike`. +// If this RFC ever stabilizes, then ! will be a better choice. +// https://github.com/rust-lang/rust/issues/35121 +impl SockaddrLike for () { + fn as_ptr(&self) -> *const libc::sockaddr { + ptr::null() + } + + unsafe fn from_raw( + _: *const libc::sockaddr, + _: Option, + ) -> Option + where + Self: Sized, + { + None + } + + fn family(&self) -> Option { + None + } + + fn len(&self) -> libc::socklen_t { + 0 + } +} + +/// An IPv4 socket address +// This is identical to net::SocketAddrV4. But the standard library +// doesn't allow direct access to the libc fields, which we need. So we +// reimplement it here. +#[cfg(feature = "net")] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SockaddrIn(libc::sockaddr_in); + +#[cfg(feature = "net")] +impl SockaddrIn { + /// Returns the IP address associated with this socket address, in native + /// endian. + pub const fn ip(&self) -> libc::in_addr_t { + u32::from_be(self.0.sin_addr.s_addr) + } + + /// Creates a new socket address from IPv4 octets and a port number. + pub fn new(a: u8, b: u8, c: u8, d: u8, port: u16) -> Self { + Self(libc::sockaddr_in { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" + ))] + sin_len: Self::size() as u8, + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: u16::to_be(port), + sin_addr: libc::in_addr { + s_addr: u32::from_ne_bytes([a, b, c, d]), + }, + sin_zero: unsafe { mem::zeroed() }, + }) + } + + /// Returns the port number associated with this socket address, in native + /// endian. + pub const fn port(&self) -> u16 { + u16::from_be(self.0.sin_port) + } +} + +#[cfg(feature = "net")] +impl private::SockaddrLikePriv for SockaddrIn {} +#[cfg(feature = "net")] +impl SockaddrLike for SockaddrIn { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option, + ) -> Option + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_INET { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } +} + +#[cfg(feature = "net")] +impl AsRef for SockaddrIn { + fn as_ref(&self) -> &libc::sockaddr_in { + &self.0 + } +} + +#[cfg(feature = "net")] +impl fmt::Display for SockaddrIn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let ne = u32::from_be(self.0.sin_addr.s_addr); + let port = u16::from_be(self.0.sin_port); + write!( + f, + "{}.{}.{}.{}:{}", + ne >> 24, + (ne >> 16) & 0xFF, + (ne >> 8) & 0xFF, + ne & 0xFF, + port + ) + } +} + +#[cfg(feature = "net")] +impl From for SockaddrIn { + fn from(addr: net::SocketAddrV4) -> Self { + Self(libc::sockaddr_in { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "hermit", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + sin_len: mem::size_of::() as u8, + sin_family: AddressFamily::Inet as sa_family_t, + sin_port: addr.port().to_be(), // network byte order + sin_addr: ipv4addr_to_libc(*addr.ip()), + ..unsafe { mem::zeroed() } + }) + } +} + +#[cfg(feature = "net")] +impl From for net::SocketAddrV4 { + fn from(addr: SockaddrIn) -> Self { + net::SocketAddrV4::new( + net::Ipv4Addr::from(addr.0.sin_addr.s_addr.to_ne_bytes()), + u16::from_be(addr.0.sin_port), + ) + } +} + +#[cfg(feature = "net")] +impl std::str::FromStr for SockaddrIn { + type Err = net::AddrParseError; + + fn from_str(s: &str) -> std::result::Result { + net::SocketAddrV4::from_str(s).map(SockaddrIn::from) + } +} + +/// An IPv6 socket address +#[cfg(feature = "net")] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct SockaddrIn6(libc::sockaddr_in6); + +#[cfg(feature = "net")] +impl SockaddrIn6 { + /// Returns the flow information associated with this address. + pub const fn flowinfo(&self) -> u32 { + self.0.sin6_flowinfo + } + + /// Returns the IP address associated with this socket address. + pub fn ip(&self) -> net::Ipv6Addr { + net::Ipv6Addr::from(self.0.sin6_addr.s6_addr) + } + + /// Returns the port number associated with this socket address, in native + /// endian. + pub const fn port(&self) -> u16 { + u16::from_be(self.0.sin6_port) + } + + /// Returns the scope ID associated with this address. + pub const fn scope_id(&self) -> u32 { + self.0.sin6_scope_id + } +} + +#[cfg(feature = "net")] +impl private::SockaddrLikePriv for SockaddrIn6 {} +#[cfg(feature = "net")] +impl SockaddrLike for SockaddrIn6 { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option, + ) -> Option + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_INET6 { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } +} + +#[cfg(feature = "net")] +impl AsRef for SockaddrIn6 { + fn as_ref(&self) -> &libc::sockaddr_in6 { + &self.0 + } +} + +#[cfg(feature = "net")] +impl fmt::Display for SockaddrIn6 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // These things are really hard to display properly. Easier to let std + // do it. + let std = net::SocketAddrV6::new( + self.ip(), + self.port(), + self.flowinfo(), + self.scope_id(), + ); + std.fmt(f) + } +} + +#[cfg(feature = "net")] +impl From for SockaddrIn6 { + fn from(addr: net::SocketAddrV6) -> Self { + #[allow(clippy::needless_update)] // It isn't needless on Illumos + Self(libc::sockaddr_in6 { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "hermit", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + sin6_len: mem::size_of::() as u8, + sin6_family: AddressFamily::Inet6 as sa_family_t, + sin6_port: addr.port().to_be(), // network byte order + sin6_addr: ipv6addr_to_libc(addr.ip()), + sin6_flowinfo: addr.flowinfo(), // host byte order + sin6_scope_id: addr.scope_id(), // host byte order + ..unsafe { mem::zeroed() } + }) + } +} + +#[cfg(feature = "net")] +impl From for net::SocketAddrV6 { + fn from(addr: SockaddrIn6) -> Self { + net::SocketAddrV6::new( + net::Ipv6Addr::from(addr.0.sin6_addr.s6_addr), + u16::from_be(addr.0.sin6_port), + addr.0.sin6_flowinfo, + addr.0.sin6_scope_id, + ) + } +} + +#[cfg(feature = "net")] +impl std::str::FromStr for SockaddrIn6 { + type Err = net::AddrParseError; + + fn from_str(s: &str) -> std::result::Result { + net::SocketAddrV6::from_str(s).map(SockaddrIn6::from) + } +} + +/// A container for any sockaddr type +/// +/// Just like C's `sockaddr_storage`, this type is large enough to hold any type +/// of sockaddr. It can be used as an argument with functions like +/// [`bind`](super::bind) and [`getsockname`](super::getsockname). Though it is +/// a union, it can be safely accessed through the `as_*` methods. +/// +/// # Example +/// ``` +/// # use nix::sys::socket::*; +/// # use std::str::FromStr; +/// let localhost = SockaddrIn::from_str("127.0.0.1:8081").unwrap(); +/// let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), +/// None).unwrap(); +/// bind(fd, &localhost).expect("bind"); +/// let ss: SockaddrStorage = getsockname(fd).expect("getsockname"); +/// assert_eq!(&localhost, ss.as_sockaddr_in().unwrap()); +/// ``` +#[derive(Clone, Copy, Eq)] +#[repr(C)] +pub union SockaddrStorage { + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + alg: AlgAddr, + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + dl: LinkAddr, + #[cfg(any(target_os = "android", target_os = "linux"))] + nl: NetlinkAddr, + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] + sctl: SysControlAddr, + #[cfg(feature = "net")] + sin: SockaddrIn, + #[cfg(feature = "net")] + sin6: SockaddrIn6, + ss: libc::sockaddr_storage, + su: UnixAddr, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + vsock: VsockAddr, +} +impl private::SockaddrLikePriv for SockaddrStorage {} +impl SockaddrLike for SockaddrStorage { + unsafe fn from_raw( + addr: *const libc::sockaddr, + l: Option, + ) -> Option + where + Self: Sized, + { + if addr.is_null() { + return None; + } + if let Some(len) = l { + let ulen = len as usize; + if ulen < offset_of!(libc::sockaddr, sa_data) + || ulen > mem::size_of::() + { + None + } else { + let mut ss: libc::sockaddr_storage = mem::zeroed(); + let ssp = &mut ss as *mut libc::sockaddr_storage as *mut u8; + ptr::copy(addr as *const u8, ssp, len as usize); + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + if i32::from(ss.ss_family) == libc::AF_UNIX { + // Safe because we UnixAddr is strictly smaller than + // SockaddrStorage, and we just initialized the structure. + (*(&mut ss as *mut libc::sockaddr_storage as *mut UnixAddr)).sun_len = len as u8; + } + Some(Self { ss }) + } + } else { + // If length is not available and addr is of a fixed-length type, + // copy it. If addr is of a variable length type and len is not + // available, then there's nothing we can do. + match (*addr).sa_family as i32 { + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => { + AlgAddr::from_raw(addr, l).map(|alg| Self { alg }) + } + #[cfg(feature = "net")] + libc::AF_INET => { + SockaddrIn::from_raw(addr, l).map(|sin| Self { sin }) + } + #[cfg(feature = "net")] + libc::AF_INET6 => { + SockaddrIn6::from_raw(addr, l).map(|sin6| Self { sin6 }) + } + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + libc::AF_LINK => { + LinkAddr::from_raw(addr, l).map(|dl| Self { dl }) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => { + NetlinkAddr::from_raw(addr, l).map(|nl| Self { nl }) + } + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg(feature = "net")] + libc::AF_PACKET => { + LinkAddr::from_raw(addr, l).map(|dl| Self { dl }) + } + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + libc::AF_SYSTEM => { + SysControlAddr::from_raw(addr, l).map(|sctl| Self { sctl }) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => { + VsockAddr::from_raw(addr, l).map(|vsock| Self { vsock }) + } + _ => None, + } + } + } + + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + fn len(&self) -> libc::socklen_t { + match self.as_unix_addr() { + // The UnixAddr type knows its own length + Some(ua) => ua.len(), + // For all else, we're just a boring SockaddrStorage + None => mem::size_of_val(self) as libc::socklen_t + } + } +} + +macro_rules! accessors { + ( + $fname:ident, + $fname_mut:ident, + $sockty:ty, + $family:expr, + $libc_ty:ty, + $field:ident) => { + /// Safely and falliably downcast to an immutable reference + pub fn $fname(&self) -> Option<&$sockty> { + if self.family() == Some($family) + && self.len() >= mem::size_of::<$libc_ty>() as libc::socklen_t + { + // Safe because family and len are validated + Some(unsafe { &self.$field }) + } else { + None + } + } + + /// Safely and falliably downcast to a mutable reference + pub fn $fname_mut(&mut self) -> Option<&mut $sockty> { + if self.family() == Some($family) + && self.len() >= mem::size_of::<$libc_ty>() as libc::socklen_t + { + // Safe because family and len are validated + Some(unsafe { &mut self.$field }) + } else { + None + } + } + }; +} + +impl SockaddrStorage { + /// Downcast to an immutable `[UnixAddr]` reference. + pub fn as_unix_addr(&self) -> Option<&UnixAddr> { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + let p = unsafe{ &self.ss as *const libc::sockaddr_storage }; + // Safe because UnixAddr is strictly smaller than + // sockaddr_storage, and we're fully initialized + let len = unsafe { + (*(p as *const UnixAddr )).sun_len as usize + }; + } else { + let len = self.len() as usize; + } + } + // Sanity checks + if self.family() != Some(AddressFamily::Unix) || + len < offset_of!(libc::sockaddr_un, sun_path) || + len > mem::size_of::() { + None + } else { + Some(unsafe{&self.su}) + } + } + + /// Downcast to a mutable `[UnixAddr]` reference. + pub fn as_unix_addr_mut(&mut self) -> Option<&mut UnixAddr> { + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux" + ))] + { + let p = unsafe{ &self.ss as *const libc::sockaddr_storage }; + // Safe because UnixAddr is strictly smaller than + // sockaddr_storage, and we're fully initialized + let len = unsafe { + (*(p as *const UnixAddr )).sun_len as usize + }; + } else { + let len = self.len() as usize; + } + } + // Sanity checks + if self.family() != Some(AddressFamily::Unix) || + len < offset_of!(libc::sockaddr_un, sun_path) || + len > mem::size_of::() { + None + } else { + Some(unsafe{&mut self.su}) + } + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + accessors! {as_alg_addr, as_alg_addr_mut, AlgAddr, + AddressFamily::Alg, libc::sockaddr_alg, alg} + + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg(feature = "net")] + accessors! { + as_link_addr, as_link_addr_mut, LinkAddr, + AddressFamily::Packet, libc::sockaddr_ll, dl} + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + accessors! { + as_link_addr, as_link_addr_mut, LinkAddr, + AddressFamily::Link, libc::sockaddr_dl, dl} + + #[cfg(feature = "net")] + accessors! { + as_sockaddr_in, as_sockaddr_in_mut, SockaddrIn, + AddressFamily::Inet, libc::sockaddr_in, sin} + + #[cfg(feature = "net")] + accessors! { + as_sockaddr_in6, as_sockaddr_in6_mut, SockaddrIn6, + AddressFamily::Inet6, libc::sockaddr_in6, sin6} + + #[cfg(any(target_os = "android", target_os = "linux"))] + accessors! {as_netlink_addr, as_netlink_addr_mut, NetlinkAddr, + AddressFamily::Netlink, libc::sockaddr_nl, nl} + + #[cfg(all(feature = "ioctl", any(target_os = "ios", target_os = "macos")))] + #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] + accessors! {as_sys_control_addr, as_sys_control_addr_mut, SysControlAddr, + AddressFamily::System, libc::sockaddr_ctl, sctl} + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + accessors! {as_vsock_addr, as_vsock_addr_mut, VsockAddr, + AddressFamily::Vsock, libc::sockaddr_vm, vsock} +} + +impl fmt::Debug for SockaddrStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SockaddrStorage") + // Safe because sockaddr_storage has the least specific + // field types + .field("ss", unsafe { &self.ss }) + .finish() + } +} + +impl fmt::Display for SockaddrStorage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + unsafe { + match self.ss.ss_family as i32 { + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => self.alg.fmt(f), + #[cfg(feature = "net")] + libc::AF_INET => self.sin.fmt(f), + #[cfg(feature = "net")] + libc::AF_INET6 => self.sin6.fmt(f), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + libc::AF_LINK => self.dl.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => self.nl.fmt(f), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "fuchsia" + ))] + #[cfg(feature = "net")] + libc::AF_PACKET => self.dl.fmt(f), + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(feature = "ioctl")] + libc::AF_SYSTEM => self.sctl.fmt(f), + libc::AF_UNIX => self.su.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => self.vsock.fmt(f), + _ => "

".fmt(f), + } + } + } +} + +#[cfg(feature = "net")] +impl From for SockaddrStorage { + fn from(s: net::SocketAddrV4) -> Self { + unsafe { + let mut ss: Self = mem::zeroed(); + ss.sin = SockaddrIn::from(s); + ss + } + } +} + +#[cfg(feature = "net")] +impl From for SockaddrStorage { + fn from(s: net::SocketAddrV6) -> Self { + unsafe { + let mut ss: Self = mem::zeroed(); + ss.sin6 = SockaddrIn6::from(s); + ss + } + } +} + +#[cfg(feature = "net")] +impl From for SockaddrStorage { + fn from(s: net::SocketAddr) -> Self { + match s { + net::SocketAddr::V4(sa4) => Self::from(sa4), + net::SocketAddr::V6(sa6) => Self::from(sa6), + } + } +} + +impl Hash for SockaddrStorage { + fn hash(&self, s: &mut H) { + unsafe { + match self.ss.ss_family as i32 { + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => self.alg.hash(s), + #[cfg(feature = "net")] + libc::AF_INET => self.sin.hash(s), + #[cfg(feature = "net")] + libc::AF_INET6 => self.sin6.hash(s), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + libc::AF_LINK => self.dl.hash(s), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => self.nl.hash(s), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "fuchsia" + ))] + #[cfg(feature = "net")] + libc::AF_PACKET => self.dl.hash(s), + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(feature = "ioctl")] + libc::AF_SYSTEM => self.sctl.hash(s), + libc::AF_UNIX => self.su.hash(s), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => self.vsock.hash(s), + _ => self.ss.hash(s), + } + } + } +} + +impl PartialEq for SockaddrStorage { + fn eq(&self, other: &Self) -> bool { + unsafe { + match (self.ss.ss_family as i32, other.ss.ss_family as i32) { + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::AF_ALG, libc::AF_ALG) => self.alg == other.alg, + #[cfg(feature = "net")] + (libc::AF_INET, libc::AF_INET) => self.sin == other.sin, + #[cfg(feature = "net")] + (libc::AF_INET6, libc::AF_INET6) => self.sin6 == other.sin6, + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + (libc::AF_LINK, libc::AF_LINK) => self.dl == other.dl, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::AF_NETLINK, libc::AF_NETLINK) => self.nl == other.nl, + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg(feature = "net")] + (libc::AF_PACKET, libc::AF_PACKET) => self.dl == other.dl, + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(feature = "ioctl")] + (libc::AF_SYSTEM, libc::AF_SYSTEM) => self.sctl == other.sctl, + (libc::AF_UNIX, libc::AF_UNIX) => self.su == other.su, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::AF_VSOCK, libc::AF_VSOCK) => self.vsock == other.vsock, + _ => false, + } + } + } +} + +mod private { + pub trait SockaddrLikePriv { + /// Returns a mutable raw pointer to the inner structure. + /// + /// # Safety + /// + /// This method is technically safe, but modifying the inner structure's + /// `family` or `len` fields may result in violating Nix's invariants. + /// It is best to use this method only with foreign functions that do + /// not change the sockaddr type. + fn as_mut_ptr(&mut self) -> *mut libc::sockaddr { + self as *mut Self as *mut libc::sockaddr + } + } +} + +/// Represents a socket address +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[deprecated( + since = "0.24.0", + note = "use SockaddrLike or SockaddrStorage instead" +)] +#[allow(missing_docs)] // Since they're all deprecated anyway +#[allow(deprecated)] +#[non_exhaustive] +pub enum SockAddr { + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Inet(InetAddr), + Unix(UnixAddr), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Netlink(NetlinkAddr), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Alg(AlgAddr), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] + SysControl(SysControlAddr), + /// Datalink address (MAC) + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Link(LinkAddr), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + Vsock(VsockAddr), +} + +#[allow(missing_docs)] // Since they're all deprecated anyway +#[allow(deprecated)] +impl SockAddr { + feature! { + #![feature = "net"] + pub fn new_inet(addr: InetAddr) -> SockAddr { + SockAddr::Inet(addr) + } + } + + pub fn new_unix(path: &P) -> Result { + Ok(SockAddr::Unix(UnixAddr::new(path)?)) + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_netlink(pid: u32, groups: u32) -> SockAddr { + SockAddr::Netlink(NetlinkAddr::new(pid, groups)) + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_alg(alg_type: &str, alg_name: &str) -> SockAddr { + SockAddr::Alg(AlgAddr::new(alg_type, alg_name)) + } + + feature! { + #![feature = "ioctl"] + #[cfg(any(target_os = "ios", target_os = "macos"))] + pub fn new_sys_control(sockfd: RawFd, name: &str, unit: u32) -> Result { + SysControlAddr::from_name(sockfd, name, unit).map(SockAddr::SysControl) + } + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn new_vsock(cid: u32, port: u32) -> SockAddr { + SockAddr::Vsock(VsockAddr::new(cid, port)) + } + + pub fn family(&self) -> AddressFamily { + match *self { + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V4(..)) => AddressFamily::Inet, + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V6(..)) => AddressFamily::Inet6, + SockAddr::Unix(..) => AddressFamily::Unix, + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Netlink(..) => AddressFamily::Netlink, + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Alg(..) => AddressFamily::Alg, + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + SockAddr::SysControl(..) => AddressFamily::System, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + SockAddr::Link(..) => AddressFamily::Packet, + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + SockAddr::Link(..) => AddressFamily::Link, + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Vsock(..) => AddressFamily::Vsock, + } + } + + #[deprecated(since = "0.23.0", note = "use .to_string() instead")] + pub fn to_str(&self) -> String { + format!("{}", self) + } + + /// Creates a `SockAddr` struct from libc's sockaddr. + /// + /// Supports only the following address families: Unix, Inet (v4 & v6), Netlink and System. + /// Returns None for unsupported families. + /// + /// # Safety + /// + /// unsafe because it takes a raw pointer as argument. The caller must + /// ensure that the pointer is valid. + #[cfg(not(target_os = "fuchsia"))] + #[cfg(feature = "net")] + pub(crate) unsafe fn from_libc_sockaddr( + addr: *const libc::sockaddr, + ) -> Option { + if addr.is_null() { + None + } else { + match AddressFamily::from_i32(i32::from((*addr).sa_family)) { + Some(AddressFamily::Unix) => None, + #[cfg(feature = "net")] + Some(AddressFamily::Inet) => Some(SockAddr::Inet( + InetAddr::V4(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(feature = "net")] + Some(AddressFamily::Inet6) => Some(SockAddr::Inet( + InetAddr::V6(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(any(target_os = "android", target_os = "linux"))] + Some(AddressFamily::Netlink) => Some(SockAddr::Netlink( + NetlinkAddr(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + Some(AddressFamily::System) => Some(SockAddr::SysControl( + SysControlAddr(ptr::read_unaligned(addr as *const _)), + )), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + Some(AddressFamily::Packet) => Some(SockAddr::Link(LinkAddr( + ptr::read_unaligned(addr as *const _), + ))), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + Some(AddressFamily::Link) => { + let ether_addr = + LinkAddr(ptr::read_unaligned(addr as *const _)); + if ether_addr.is_empty() { + None + } else { + Some(SockAddr::Link(ether_addr)) + } + } + #[cfg(any(target_os = "android", target_os = "linux"))] + Some(AddressFamily::Vsock) => Some(SockAddr::Vsock(VsockAddr( + ptr::read_unaligned(addr as *const _), + ))), + // Other address families are currently not supported and simply yield a None + // entry instead of a proper conversion to a `SockAddr`. + Some(_) | None => None, + } + } + } + + /// Conversion from nix's SockAddr type to the underlying libc sockaddr type. + /// + /// This is useful for interfacing with other libc functions that don't yet have nix wrappers. + /// Returns a reference to the underlying data type (as a sockaddr reference) along + /// with the size of the actual data type. sockaddr is commonly used as a proxy for + /// a superclass as C doesn't support inheritance, so many functions that take + /// a sockaddr * need to take the size of the underlying type as well and then internally cast it back. + pub fn as_ffi_pair(&self) -> (&libc::sockaddr, libc::socklen_t) { + match *self { + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V4(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_in + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + #[cfg(feature = "net")] + SockAddr::Inet(InetAddr::V6(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_in6 + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + SockAddr::Unix(ref unix_addr) => ( + // This cast is always allowed in C + unsafe { + &*(&unix_addr.sun as *const libc::sockaddr_un + as *const libc::sockaddr) + }, + unix_addr.sun_len() as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Netlink(NetlinkAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_nl as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Alg(AlgAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_alg as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + SockAddr::SysControl(SysControlAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_ctl as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + SockAddr::Link(LinkAddr(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_ll + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + SockAddr::Link(LinkAddr(ref addr)) => ( + // This cast is always allowed in C + unsafe { + &*(addr as *const libc::sockaddr_dl + as *const libc::sockaddr) + }, + mem::size_of_val(addr) as libc::socklen_t, + ), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Vsock(VsockAddr(ref sa)) => ( + // This cast is always allowed in C + unsafe { + &*(sa as *const libc::sockaddr_vm as *const libc::sockaddr) + }, + mem::size_of_val(sa) as libc::socklen_t, + ), + } + } +} + +#[allow(deprecated)] +impl fmt::Display for SockAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "net")] + SockAddr::Inet(ref inet) => inet.fmt(f), + SockAddr::Unix(ref unix) => unix.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Netlink(ref nl) => nl.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Alg(ref nl) => nl.fmt(f), + #[cfg(all( + feature = "ioctl", + any(target_os = "ios", target_os = "macos") + ))] + SockAddr::SysControl(ref sc) => sc.fmt(f), + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd" + ))] + #[cfg(feature = "net")] + SockAddr::Link(ref ether_addr) => ether_addr.fmt(f), + #[cfg(any(target_os = "android", target_os = "linux"))] + SockAddr::Vsock(ref svm) => svm.fmt(f), + } + } +} + +#[cfg(not(target_os = "fuchsia"))] +#[cfg(feature = "net")] +#[allow(deprecated)] +impl private::SockaddrLikePriv for SockAddr {} +#[cfg(not(target_os = "fuchsia"))] +#[cfg(feature = "net")] +#[allow(deprecated)] +impl SockaddrLike for SockAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + _len: Option, + ) -> Option { + Self::from_libc_sockaddr(addr) + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub mod netlink { + use super::*; + use crate::sys::socket::addr::AddressFamily; + use libc::{sa_family_t, sockaddr_nl}; + use std::{fmt, mem}; + + /// Address for the Linux kernel user interface device. + /// + /// # References + /// + /// [netlink(7)](https://man7.org/linux/man-pages/man7/netlink.7.html) + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct NetlinkAddr(pub(in super::super) sockaddr_nl); + + impl NetlinkAddr { + /// Construct a new socket address from its port ID and multicast groups + /// mask. + pub fn new(pid: u32, groups: u32) -> NetlinkAddr { + let mut addr: sockaddr_nl = unsafe { mem::zeroed() }; + addr.nl_family = AddressFamily::Netlink as sa_family_t; + addr.nl_pid = pid; + addr.nl_groups = groups; + + NetlinkAddr(addr) + } + + /// Return the socket's port ID. + pub const fn pid(&self) -> u32 { + self.0.nl_pid + } + + /// Return the socket's multicast groups mask + pub const fn groups(&self) -> u32 { + self.0.nl_groups + } + } + + impl private::SockaddrLikePriv for NetlinkAddr {} + impl SockaddrLike for NetlinkAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option, + ) -> Option + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_NETLINK { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef for NetlinkAddr { + fn as_ref(&self) -> &libc::sockaddr_nl { + &self.0 + } + } + + impl fmt::Display for NetlinkAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "pid: {} groups: {}", self.pid(), self.groups()) + } + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub mod alg { + use super::*; + use libc::{c_char, sockaddr_alg, AF_ALG}; + use std::ffi::CStr; + use std::hash::{Hash, Hasher}; + use std::{fmt, mem, str}; + + /// Socket address for the Linux kernel crypto API + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct AlgAddr(pub(in super::super) sockaddr_alg); + + impl private::SockaddrLikePriv for AlgAddr {} + impl SockaddrLike for AlgAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + l: Option, + ) -> Option + where + Self: Sized, + { + if let Some(l) = l { + if l != mem::size_of::() as libc::socklen_t + { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_ALG { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef for AlgAddr { + fn as_ref(&self) -> &libc::sockaddr_alg { + &self.0 + } + } + + // , PartialEq, Eq, Debug, Hash + impl PartialEq for AlgAddr { + fn eq(&self, other: &Self) -> bool { + let (inner, other) = (self.0, other.0); + ( + inner.salg_family, + &inner.salg_type[..], + inner.salg_feat, + inner.salg_mask, + &inner.salg_name[..], + ) == ( + other.salg_family, + &other.salg_type[..], + other.salg_feat, + other.salg_mask, + &other.salg_name[..], + ) + } + } + + impl Eq for AlgAddr {} + + impl Hash for AlgAddr { + fn hash(&self, s: &mut H) { + let inner = self.0; + ( + inner.salg_family, + &inner.salg_type[..], + inner.salg_feat, + inner.salg_mask, + &inner.salg_name[..], + ) + .hash(s); + } + } + + impl AlgAddr { + /// Construct an `AF_ALG` socket from its cipher name and type. + pub fn new(alg_type: &str, alg_name: &str) -> AlgAddr { + let mut addr: sockaddr_alg = unsafe { mem::zeroed() }; + addr.salg_family = AF_ALG as u16; + addr.salg_type[..alg_type.len()] + .copy_from_slice(alg_type.to_string().as_bytes()); + addr.salg_name[..alg_name.len()] + .copy_from_slice(alg_name.to_string().as_bytes()); + + AlgAddr(addr) + } + + /// Return the socket's cipher type, for example `hash` or `aead`. + pub fn alg_type(&self) -> &CStr { + unsafe { + CStr::from_ptr(self.0.salg_type.as_ptr() as *const c_char) + } + } + + /// Return the socket's cipher name, for example `sha1`. + pub fn alg_name(&self) -> &CStr { + unsafe { + CStr::from_ptr(self.0.salg_name.as_ptr() as *const c_char) + } + } + } + + impl fmt::Display for AlgAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "type: {} alg: {}", + self.alg_name().to_string_lossy(), + self.alg_type().to_string_lossy() + ) + } + } + + impl fmt::Debug for AlgAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } +} + +feature! { +#![feature = "ioctl"] +#[cfg(any(target_os = "ios", target_os = "macos"))] +pub mod sys_control { + use crate::sys::socket::addr::AddressFamily; + use libc::{self, c_uchar}; + use std::{fmt, mem, ptr}; + use std::os::unix::io::RawFd; + use crate::{Errno, Result}; + use super::{private, SockaddrLike}; + + // FIXME: Move type into `libc` + #[repr(C)] + #[derive(Clone, Copy)] + #[allow(missing_debug_implementations)] + pub struct ctl_ioc_info { + pub ctl_id: u32, + pub ctl_name: [c_uchar; MAX_KCTL_NAME], + } + + const CTL_IOC_MAGIC: u8 = b'N'; + const CTL_IOC_INFO: u8 = 3; + const MAX_KCTL_NAME: usize = 96; + + ioctl_readwrite!(ctl_info, CTL_IOC_MAGIC, CTL_IOC_INFO, ctl_ioc_info); + + /// Apple system control socket + /// + /// # References + /// + /// + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct SysControlAddr(pub(in super::super) libc::sockaddr_ctl); + + impl private::SockaddrLikePriv for SysControlAddr {} + impl SockaddrLike for SysControlAddr { + unsafe fn from_raw(addr: *const libc::sockaddr, len: Option) + -> Option where Self: Sized + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_SYSTEM { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef for SysControlAddr { + fn as_ref(&self) -> &libc::sockaddr_ctl { + &self.0 + } + } + + impl SysControlAddr { + /// Construct a new `SysControlAddr` from its kernel unique identifier + /// and unit number. + pub const fn new(id: u32, unit: u32) -> SysControlAddr { + let addr = libc::sockaddr_ctl { + sc_len: mem::size_of::() as c_uchar, + sc_family: AddressFamily::System as c_uchar, + ss_sysaddr: libc::AF_SYS_CONTROL as u16, + sc_id: id, + sc_unit: unit, + sc_reserved: [0; 5] + }; + + SysControlAddr(addr) + } + + /// Construct a new `SysControlAddr` from its human readable name and + /// unit number. + pub fn from_name(sockfd: RawFd, name: &str, unit: u32) -> Result { + if name.len() > MAX_KCTL_NAME { + return Err(Errno::ENAMETOOLONG); + } + + let mut ctl_name = [0; MAX_KCTL_NAME]; + ctl_name[..name.len()].clone_from_slice(name.as_bytes()); + let mut info = ctl_ioc_info { ctl_id: 0, ctl_name }; + + unsafe { ctl_info(sockfd, &mut info)?; } + + Ok(SysControlAddr::new(info.ctl_id, unit)) + } + + /// Return the kernel unique identifier + pub const fn id(&self) -> u32 { + self.0.sc_id + } + + /// Return the kernel controller private unit number. + pub const fn unit(&self) -> u32 { + self.0.sc_unit + } + } + + impl fmt::Display for SysControlAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } + } +} +} + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "fuchsia"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod datalink { + feature! { + #![feature = "net"] + use super::{fmt, mem, private, ptr, SockaddrLike}; + + /// Hardware Address + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct LinkAddr(pub(in super::super) libc::sockaddr_ll); + + impl LinkAddr { + /// Physical-layer protocol + pub fn protocol(&self) -> u16 { + self.0.sll_protocol + } + + /// Interface number + pub fn ifindex(&self) -> usize { + self.0.sll_ifindex as usize + } + + /// ARP hardware type + pub fn hatype(&self) -> u16 { + self.0.sll_hatype + } + + /// Packet type + pub fn pkttype(&self) -> u8 { + self.0.sll_pkttype + } + + /// Length of MAC address + pub fn halen(&self) -> usize { + self.0.sll_halen as usize + } + + /// Physical-layer address (MAC) + // Returns an Option just for cross-platform compatibility + pub fn addr(&self) -> Option<[u8; 6]> { + Some([ + self.0.sll_addr[0], + self.0.sll_addr[1], + self.0.sll_addr[2], + self.0.sll_addr[3], + self.0.sll_addr[4], + self.0.sll_addr[5], + ]) + } + } + + impl fmt::Display for LinkAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(addr) = self.addr() { + write!(f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5]) + } else { + Ok(()) + } + } + } + impl private::SockaddrLikePriv for LinkAddr {} + impl SockaddrLike for LinkAddr { + unsafe fn from_raw(addr: *const libc::sockaddr, + len: Option) + -> Option where Self: Sized + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_PACKET { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef for LinkAddr { + fn as_ref(&self) -> &libc::sockaddr_ll { + &self.0 + } + } + + } +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "illumos", + target_os = "netbsd", + target_os = "haiku", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +mod datalink { + feature! { + #![feature = "net"] + use super::{fmt, mem, private, ptr, SockaddrLike}; + + /// Hardware Address + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(transparent)] + pub struct LinkAddr(pub(in super::super) libc::sockaddr_dl); + + impl LinkAddr { + /// interface index, if != 0, system given index for interface + #[cfg(not(target_os = "haiku"))] + pub fn ifindex(&self) -> usize { + self.0.sdl_index as usize + } + + /// Datalink type + #[cfg(not(target_os = "haiku"))] + pub fn datalink_type(&self) -> u8 { + self.0.sdl_type + } + + /// MAC address start position + pub fn nlen(&self) -> usize { + self.0.sdl_nlen as usize + } + + /// link level address length + pub fn alen(&self) -> usize { + self.0.sdl_alen as usize + } + + /// link layer selector length + #[cfg(not(target_os = "haiku"))] + pub fn slen(&self) -> usize { + self.0.sdl_slen as usize + } + + /// if link level address length == 0, + /// or `sdl_data` not be larger. + pub fn is_empty(&self) -> bool { + let nlen = self.nlen(); + let alen = self.alen(); + let data_len = self.0.sdl_data.len(); + + alen == 0 || nlen + alen >= data_len + } + + /// Physical-layer address (MAC) + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn addr(&self) -> Option<[u8; 6]> { + let nlen = self.nlen(); + let data = self.0.sdl_data; + + if self.is_empty() { + None + } else { + Some([ + data[nlen] as u8, + data[nlen + 1] as u8, + data[nlen + 2] as u8, + data[nlen + 3] as u8, + data[nlen + 4] as u8, + data[nlen + 5] as u8, + ]) + } + } + } + + impl fmt::Display for LinkAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(addr) = self.addr() { + write!(f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5]) + } else { + Ok(()) + } + } + } + impl private::SockaddrLikePriv for LinkAddr {} + impl SockaddrLike for LinkAddr { + unsafe fn from_raw(addr: *const libc::sockaddr, + len: Option) + -> Option where Self: Sized + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_LINK { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef for LinkAddr { + fn as_ref(&self) -> &libc::sockaddr_dl { + &self.0 + } + } + + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub mod vsock { + use super::*; + use crate::sys::socket::addr::AddressFamily; + use libc::{sa_family_t, sockaddr_vm}; + use std::hash::{Hash, Hasher}; + use std::{fmt, mem}; + + /// Socket address for VMWare VSockets protocol + /// + /// # References + /// + /// [vsock(7)](https://man7.org/linux/man-pages/man7/vsock.7.html) + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct VsockAddr(pub(in super::super) sockaddr_vm); + + impl private::SockaddrLikePriv for VsockAddr {} + impl SockaddrLike for VsockAddr { + unsafe fn from_raw( + addr: *const libc::sockaddr, + len: Option, + ) -> Option + where + Self: Sized, + { + if let Some(l) = len { + if l != mem::size_of::() as libc::socklen_t { + return None; + } + } + if (*addr).sa_family as i32 != libc::AF_VSOCK { + return None; + } + Some(Self(ptr::read_unaligned(addr as *const _))) + } + } + + impl AsRef for VsockAddr { + fn as_ref(&self) -> &libc::sockaddr_vm { + &self.0 + } + } + + impl PartialEq for VsockAddr { + fn eq(&self, other: &Self) -> bool { + let (inner, other) = (self.0, other.0); + (inner.svm_family, inner.svm_cid, inner.svm_port) + == (other.svm_family, other.svm_cid, other.svm_port) + } + } + + impl Eq for VsockAddr {} + + impl Hash for VsockAddr { + fn hash(&self, s: &mut H) { + let inner = self.0; + (inner.svm_family, inner.svm_cid, inner.svm_port).hash(s); + } + } + + /// VSOCK Address + /// + /// The address for AF_VSOCK socket is defined as a combination of a + /// 32-bit Context Identifier (CID) and a 32-bit port number. + impl VsockAddr { + /// Construct a `VsockAddr` from its raw fields. + pub fn new(cid: u32, port: u32) -> VsockAddr { + let mut addr: sockaddr_vm = unsafe { mem::zeroed() }; + addr.svm_family = AddressFamily::Vsock as sa_family_t; + addr.svm_cid = cid; + addr.svm_port = port; + + VsockAddr(addr) + } + + /// Context Identifier (CID) + pub fn cid(&self) -> u32 { + self.0.svm_cid + } + + /// Port number + pub fn port(&self) -> u32 { + self.0.svm_port + } + } + + impl fmt::Display for VsockAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "cid: {} port: {}", self.cid(), self.port()) + } + } + + impl fmt::Debug for VsockAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod types { + use super::*; + + #[test] + fn test_ipv4addr_to_libc() { + let s = std::net::Ipv4Addr::new(1, 2, 3, 4); + let l = ipv4addr_to_libc(s); + assert_eq!(l.s_addr, u32::to_be(0x01020304)); + } + + #[test] + fn test_ipv6addr_to_libc() { + let s = std::net::Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8); + let l = ipv6addr_to_libc(&s); + assert_eq!( + l.s6_addr, + [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8] + ); + } + } + + mod link { + #![allow(clippy::cast_ptr_alignment)] + + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "illumos" + ))] + use super::super::super::socklen_t; + use super::*; + + /// Don't panic when trying to display an empty datalink address + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[test] + fn test_datalink_display() { + use super::super::LinkAddr; + use std::mem; + + let la = LinkAddr(libc::sockaddr_dl { + sdl_len: 56, + sdl_family: 18, + sdl_index: 5, + sdl_type: 24, + sdl_nlen: 3, + sdl_alen: 0, + sdl_slen: 0, + ..unsafe { mem::zeroed() } + }); + format!("{}", la); + } + + #[cfg(all( + any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ), + target_endian = "little" + ))] + #[test] + fn linux_loopback() { + #[repr(align(2))] + struct Raw([u8; 20]); + + let bytes = Raw([ + 17u8, 0, 0, 0, 1, 0, 0, 0, 4, 3, 0, 6, 1, 2, 3, 4, 5, 6, 0, 0, + ]); + let sa = bytes.0.as_ptr() as *const libc::sockaddr; + let len = None; + let sock_addr = + unsafe { SockaddrStorage::from_raw(sa, len) }.unwrap(); + assert_eq!(sock_addr.family(), Some(AddressFamily::Packet)); + match sock_addr.as_link_addr() { + Some(dl) => assert_eq!(dl.addr(), Some([1, 2, 3, 4, 5, 6])), + None => panic!("Can't unwrap sockaddr storage"), + } + } + + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[test] + fn macos_loopback() { + let bytes = + [20i8, 18, 1, 0, 24, 3, 0, 0, 108, 111, 48, 0, 0, 0, 0, 0]; + let sa = bytes.as_ptr() as *const libc::sockaddr; + let len = Some(bytes.len() as socklen_t); + let sock_addr = + unsafe { SockaddrStorage::from_raw(sa, len) }.unwrap(); + assert_eq!(sock_addr.family(), Some(AddressFamily::Link)); + match sock_addr.as_link_addr() { + Some(dl) => { + assert!(dl.addr().is_none()); + } + None => panic!("Can't unwrap sockaddr storage"), + } + } + + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[test] + fn macos_tap() { + let bytes = [ + 20i8, 18, 7, 0, 6, 3, 6, 0, 101, 110, 48, 24, 101, -112, -35, + 76, -80, + ]; + let ptr = bytes.as_ptr(); + let sa = ptr as *const libc::sockaddr; + let len = Some(bytes.len() as socklen_t); + + let sock_addr = + unsafe { SockaddrStorage::from_raw(sa, len).unwrap() }; + assert_eq!(sock_addr.family(), Some(AddressFamily::Link)); + match sock_addr.as_link_addr() { + Some(dl) => { + assert_eq!(dl.addr(), Some([24u8, 101, 144, 221, 76, 176])) + } + None => panic!("Can't unwrap sockaddr storage"), + } + } + + #[cfg(target_os = "illumos")] + #[test] + fn illumos_tap() { + let bytes = [25u8, 0, 0, 0, 6, 0, 6, 0, 24, 101, 144, 221, 76, 176]; + let ptr = bytes.as_ptr(); + let sa = ptr as *const libc::sockaddr; + let len = Some(bytes.len() as socklen_t); + let _sock_addr = unsafe { SockaddrStorage::from_raw(sa, len) }; + + assert!(_sock_addr.is_some()); + + let sock_addr = _sock_addr.unwrap(); + + assert_eq!(sock_addr.family().unwrap(), AddressFamily::Link); + + assert_eq!( + sock_addr.as_link_addr().unwrap().addr(), + Some([24u8, 101, 144, 221, 76, 176]) + ); + } + + #[test] + fn size() { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd", + target_os = "haiku" + ))] + let l = mem::size_of::(); + #[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_os = "linux" + ))] + let l = mem::size_of::(); + assert_eq!(LinkAddr::size() as usize, l); + } + } + + mod sockaddr_in { + use super::*; + use std::str::FromStr; + + #[test] + fn display() { + let s = "127.0.0.1:8080"; + let addr = SockaddrIn::from_str(s).unwrap(); + assert_eq!(s, format!("{}", addr)); + } + + #[test] + fn size() { + assert_eq!( + mem::size_of::(), + SockaddrIn::size() as usize + ); + } + } + + mod sockaddr_in6 { + use super::*; + use std::str::FromStr; + + #[test] + fn display() { + let s = "[1234:5678:90ab:cdef::1111:2222]:8080"; + let addr = SockaddrIn6::from_str(s).unwrap(); + assert_eq!(s, format!("{}", addr)); + } + + #[test] + fn size() { + assert_eq!( + mem::size_of::(), + SockaddrIn6::size() as usize + ); + } + + #[test] + // Ensure that we can convert to-and-from std::net variants without change. + fn to_and_from() { + let s = "[1234:5678:90ab:cdef::1111:2222]:8080"; + let mut nix_sin6 = SockaddrIn6::from_str(s).unwrap(); + nix_sin6.0.sin6_flowinfo = 0x12345678; + nix_sin6.0.sin6_scope_id = 0x9abcdef0; + + let std_sin6 : std::net::SocketAddrV6 = nix_sin6.into(); + assert_eq!(nix_sin6, std_sin6.into()); + } + } + + mod sockaddr_storage { + use super::*; + + #[test] + fn from_sockaddr_un_named() { + let ua = UnixAddr::new("/var/run/mysock").unwrap(); + let ptr = ua.as_ptr() as *const libc::sockaddr; + let ss = unsafe { + SockaddrStorage::from_raw(ptr, Some(ua.len())) + }.unwrap(); + assert_eq!(ss.len(), ua.len()); + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn from_sockaddr_un_abstract_named() { + let name = String::from("nix\0abstract\0test"); + let ua = UnixAddr::new_abstract(name.as_bytes()).unwrap(); + let ptr = ua.as_ptr() as *const libc::sockaddr; + let ss = unsafe { + SockaddrStorage::from_raw(ptr, Some(ua.len())) + }.unwrap(); + assert_eq!(ss.len(), ua.len()); + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn from_sockaddr_un_abstract_unnamed() { + let ua = UnixAddr::new_unnamed(); + let ptr = ua.as_ptr() as *const libc::sockaddr; + let ss = unsafe { + SockaddrStorage::from_raw(ptr, Some(ua.len())) + }.unwrap(); + assert_eq!(ss.len(), ua.len()); + } + } + + mod unixaddr { + use super::*; + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn abstract_sun_path() { + let name = String::from("nix\0abstract\0test"); + let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap(); + + let sun_path1 = + unsafe { &(*addr.as_ptr()).sun_path[..addr.path_len()] }; + let sun_path2 = [ + 0, 110, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, + 116, 101, 115, 116, + ]; + assert_eq!(sun_path1, sun_path2); + } + + #[test] + fn size() { + assert_eq!( + mem::size_of::(), + UnixAddr::size() as usize + ); + } + } +} diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs new file mode 100644 index 0000000..8513b6f --- /dev/null +++ b/src/sys/socket/mod.rs @@ -0,0 +1,2487 @@ +//! Socket interface functions +//! +//! [Further reading](https://man7.org/linux/man-pages/man7/socket.7.html) +#[cfg(target_os = "linux")] +#[cfg(feature = "uio")] +use crate::sys::time::TimeSpec; +#[cfg(feature = "uio")] +use crate::sys::time::TimeVal; +use crate::{errno::Errno, Result}; +use cfg_if::cfg_if; +use libc::{ + self, c_int, c_void, iovec, size_t, socklen_t, CMSG_DATA, CMSG_FIRSTHDR, + CMSG_LEN, CMSG_NXTHDR, +}; +use std::convert::{TryFrom, TryInto}; +use std::io::{IoSlice, IoSliceMut}; +#[cfg(feature = "net")] +use std::net; +use std::os::unix::io::RawFd; +use std::{mem, ptr, slice}; + +#[deny(missing_docs)] +mod addr; +#[deny(missing_docs)] +pub mod sockopt; + +/* + * + * ===== Re-exports ===== + * + */ + +pub use self::addr::{SockaddrLike, SockaddrStorage}; + +#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] +#[allow(deprecated)] +pub use self::addr::{AddressFamily, SockAddr, UnixAddr}; +#[cfg(any(target_os = "illumos", target_os = "solaris"))] +#[allow(deprecated)] +pub use self::addr::{AddressFamily, SockAddr, UnixAddr}; +#[allow(deprecated)] +#[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" +)))] +#[cfg(feature = "net")] +pub use self::addr::{ + InetAddr, IpAddr, Ipv4Addr, Ipv6Addr, LinkAddr, SockaddrIn, SockaddrIn6, +}; +#[allow(deprecated)] +#[cfg(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" +))] +#[cfg(feature = "net")] +pub use self::addr::{ + InetAddr, IpAddr, Ipv4Addr, Ipv6Addr, SockaddrIn, SockaddrIn6, +}; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use crate::sys::socket::addr::alg::AlgAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use crate::sys::socket::addr::netlink::NetlinkAddr; +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(feature = "ioctl")] +pub use crate::sys::socket::addr::sys_control::SysControlAddr; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use crate::sys::socket::addr::vsock::VsockAddr; + +#[cfg(feature = "uio")] +pub use libc::{cmsghdr, msghdr}; +pub use libc::{sa_family_t, sockaddr, sockaddr_storage, sockaddr_un}; +#[cfg(feature = "net")] +pub use libc::{sockaddr_in, sockaddr_in6}; + +// Needed by the cmsg_space macro +#[doc(hidden)] +pub use libc::{c_uint, CMSG_SPACE}; + +#[cfg(feature = "net")] +use crate::sys::socket::addr::{ipv4addr_to_libc, ipv6addr_to_libc}; + +/// These constants are used to specify the communication semantics +/// when creating a socket with [`socket()`](fn.socket.html) +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(i32)] +#[non_exhaustive] +pub enum SockType { + /// Provides sequenced, reliable, two-way, connection- + /// based byte streams. An out-of-band data transmission + /// mechanism may be supported. + Stream = libc::SOCK_STREAM, + /// Supports datagrams (connectionless, unreliable + /// messages of a fixed maximum length). + Datagram = libc::SOCK_DGRAM, + /// Provides a sequenced, reliable, two-way connection- + /// based data transmission path for datagrams of fixed + /// maximum length; a consumer is required to read an + /// entire packet with each input system call. + SeqPacket = libc::SOCK_SEQPACKET, + /// Provides raw network protocol access. + Raw = libc::SOCK_RAW, + /// Provides a reliable datagram layer that does not + /// guarantee ordering. + #[cfg(not(any(target_os = "haiku")))] + Rdm = libc::SOCK_RDM, +} +// The TryFrom impl could've been derived using libc_enum!. But for +// backwards-compatibility with Nix-0.25.0 we manually implement it, so as to +// keep the old variant names. +impl TryFrom for SockType { + type Error = crate::Error; + + fn try_from(x: i32) -> Result { + match x { + libc::SOCK_STREAM => Ok(Self::Stream), + libc::SOCK_DGRAM => Ok(Self::Datagram), + libc::SOCK_SEQPACKET => Ok(Self::SeqPacket), + libc::SOCK_RAW => Ok(Self::Raw), + #[cfg(not(any(target_os = "haiku")))] + libc::SOCK_RDM => Ok(Self::Rdm), + _ => Err(Errno::EINVAL) + } + } +} + +/// Constants used in [`socket`](fn.socket.html) and [`socketpair`](fn.socketpair.html) +/// to specify the protocol to use. +#[repr(i32)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum SockProtocol { + /// TCP protocol ([ip(7)](https://man7.org/linux/man-pages/man7/ip.7.html)) + Tcp = libc::IPPROTO_TCP, + /// UDP protocol ([ip(7)](https://man7.org/linux/man-pages/man7/ip.7.html)) + Udp = libc::IPPROTO_UDP, + /// Raw sockets ([raw(7)](https://man7.org/linux/man-pages/man7/raw.7.html)) + Raw = libc::IPPROTO_RAW, + /// Allows applications and other KEXTs to be notified when certain kernel events occur + /// ([ref](https://developer.apple.com/library/content/documentation/Darwin/Conceptual/NKEConceptual/control/control.html)) + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + KextEvent = libc::SYSPROTO_EVENT, + /// Allows applications to configure and control a KEXT + /// ([ref](https://developer.apple.com/library/content/documentation/Darwin/Conceptual/NKEConceptual/control/control.html)) + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + KextControl = libc::SYSPROTO_CONTROL, + /// Receives routing and link updates and may be used to modify the routing tables (both IPv4 and IPv6), IP addresses, link + // parameters, neighbor setups, queueing disciplines, traffic classes and packet classifiers + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkRoute = libc::NETLINK_ROUTE, + /// Reserved for user-mode socket protocols + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkUserSock = libc::NETLINK_USERSOCK, + /// Query information about sockets of various protocol families from the kernel + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkSockDiag = libc::NETLINK_SOCK_DIAG, + /// SELinux event notifications. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkSELinux = libc::NETLINK_SELINUX, + /// Open-iSCSI + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkISCSI = libc::NETLINK_ISCSI, + /// Auditing + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkAudit = libc::NETLINK_AUDIT, + /// Access to FIB lookup from user space + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkFIBLookup = libc::NETLINK_FIB_LOOKUP, + /// Netfilter subsystem + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkNetFilter = libc::NETLINK_NETFILTER, + /// SCSI Transports + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkSCSITransport = libc::NETLINK_SCSITRANSPORT, + /// Infiniband RDMA + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkRDMA = libc::NETLINK_RDMA, + /// Transport IPv6 packets from netfilter to user space. Used by ip6_queue kernel module. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkIPv6Firewall = libc::NETLINK_IP6_FW, + /// DECnet routing messages + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkDECNetRoutingMessage = libc::NETLINK_DNRTMSG, + /// Kernel messages to user space + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkKObjectUEvent = libc::NETLINK_KOBJECT_UEVENT, + /// Netlink interface to request information about ciphers registered with the kernel crypto API as well as allow + /// configuration of the kernel crypto API. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NetlinkCrypto = libc::NETLINK_CRYPTO, + /// Non-DIX type protocol number defined for the Ethernet IEEE 802.3 interface that allows packets of all protocols + /// defined in the interface to be received. + /// ([ref](https://man7.org/linux/man-pages/man7/packet.7.html)) + // The protocol number is fed into the socket syscall in network byte order. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + EthAll = libc::ETH_P_ALL.to_be(), +} + +#[cfg(any(target_os = "linux"))] +libc_bitflags! { + /// Configuration flags for `SO_TIMESTAMPING` interface + /// + /// For use with [`Timestamping`][sockopt::Timestamping]. + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + pub struct TimestampingFlag: c_uint { + /// Report any software timestamps when available. + SOF_TIMESTAMPING_SOFTWARE; + /// Report hardware timestamps as generated by SOF_TIMESTAMPING_TX_HARDWARE when available. + SOF_TIMESTAMPING_RAW_HARDWARE; + /// Collect transmiting timestamps as reported by hardware + SOF_TIMESTAMPING_TX_HARDWARE; + /// Collect transmiting timestamps as reported by software + SOF_TIMESTAMPING_TX_SOFTWARE; + /// Collect receiving timestamps as reported by hardware + SOF_TIMESTAMPING_RX_HARDWARE; + /// Collect receiving timestamps as reported by software + SOF_TIMESTAMPING_RX_SOFTWARE; + } +} + +libc_bitflags! { + /// Additional socket options + pub struct SockFlag: c_int { + /// Set non-blocking mode on the new socket + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_NONBLOCK; + /// Set close-on-exec on the new descriptor + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_CLOEXEC; + /// Return `EPIPE` instead of raising `SIGPIPE` + #[cfg(target_os = "netbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_NOSIGPIPE; + /// For domains `AF_INET(6)`, only allow `connect(2)`, `sendto(2)`, or `sendmsg(2)` + /// to the DNS port (typically 53) + #[cfg(target_os = "openbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + SOCK_DNS; + } +} + +libc_bitflags! { + /// Flags for send/recv and their relatives + pub struct MsgFlags: c_int { + /// Sends or requests out-of-band data on sockets that support this notion + /// (e.g., of type [`Stream`](enum.SockType.html)); the underlying protocol must also + /// support out-of-band data. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_OOB; + /// Peeks at an incoming message. The data is treated as unread and the next + /// [`recv()`](fn.recv.html) + /// or similar function shall still return this data. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_PEEK; + /// Receive operation blocks until the full amount of data can be + /// returned. The function may return smaller amount of data if a signal + /// is caught, an error or disconnect occurs. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_WAITALL; + /// Enables nonblocking operation; if the operation would block, + /// `EAGAIN` or `EWOULDBLOCK` is returned. This provides similar + /// behavior to setting the `O_NONBLOCK` flag + /// (via the [`fcntl`](../../fcntl/fn.fcntl.html) + /// `F_SETFL` operation), but differs in that `MSG_DONTWAIT` is a per- + /// call option, whereas `O_NONBLOCK` is a setting on the open file + /// description (see [open(2)](https://man7.org/linux/man-pages/man2/open.2.html)), + /// which will affect all threads in + /// the calling process and as well as other processes that hold + /// file descriptors referring to the same open file description. + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_DONTWAIT; + /// Receive flags: Control Data was discarded (buffer too small) + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_CTRUNC; + /// For raw ([`Packet`](addr/enum.AddressFamily.html)), Internet datagram + /// (since Linux 2.4.27/2.6.8), + /// netlink (since Linux 2.6.22) and UNIX datagram (since Linux 3.4) + /// sockets: return the real length of the packet or datagram, even + /// when it was longer than the passed buffer. Not implemented for UNIX + /// domain ([unix(7)](https://linux.die.net/man/7/unix)) sockets. + /// + /// For use with Internet stream sockets, see [tcp(7)](https://linux.die.net/man/7/tcp). + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_TRUNC; + /// Terminates a record (when this notion is supported, as for + /// sockets of type [`SeqPacket`](enum.SockType.html)). + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_EOR; + /// This flag specifies that queued errors should be received from + /// the socket error queue. (For more details, see + /// [recvfrom(2)](https://linux.die.net/man/2/recvfrom)) + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_ERRQUEUE; + /// Set the `close-on-exec` flag for the file descriptor received via a UNIX domain + /// file descriptor using the `SCM_RIGHTS` operation (described in + /// [unix(7)](https://linux.die.net/man/7/unix)). + /// This flag is useful for the same reasons as the `O_CLOEXEC` flag of + /// [open(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html). + /// + /// Only used in [`recvmsg`](fn.recvmsg.html) function. + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_CMSG_CLOEXEC; + /// Requests not to send `SIGPIPE` errors when the other end breaks the connection. + /// (For more details, see [send(2)](https://linux.die.net/man/2/send)). + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + MSG_NOSIGNAL; + } +} + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + /// Unix credentials of the sending process. + /// + /// This struct is used with the `SO_PEERCRED` ancillary message + /// and the `SCM_CREDENTIALS` control message for UNIX sockets. + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct UnixCredentials(libc::ucred); + + impl UnixCredentials { + /// Creates a new instance with the credentials of the current process + pub fn new() -> Self { + // Safe because these FFI functions are inherently safe + unsafe { + UnixCredentials(libc::ucred { + pid: libc::getpid(), + uid: libc::getuid(), + gid: libc::getgid() + }) + } + } + + /// Returns the process identifier + pub fn pid(&self) -> libc::pid_t { + self.0.pid + } + + /// Returns the user identifier + pub fn uid(&self) -> libc::uid_t { + self.0.uid + } + + /// Returns the group identifier + pub fn gid(&self) -> libc::gid_t { + self.0.gid + } + } + + impl Default for UnixCredentials { + fn default() -> Self { + Self::new() + } + } + + impl From for UnixCredentials { + fn from(cred: libc::ucred) -> Self { + UnixCredentials(cred) + } + } + + impl From for libc::ucred { + fn from(uc: UnixCredentials) -> Self { + uc.0 + } + } + } else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] { + /// Unix credentials of the sending process. + /// + /// This struct is used with the `SCM_CREDS` ancillary message for UNIX sockets. + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct UnixCredentials(libc::cmsgcred); + + impl UnixCredentials { + /// Returns the process identifier + pub fn pid(&self) -> libc::pid_t { + self.0.cmcred_pid + } + + /// Returns the real user identifier + pub fn uid(&self) -> libc::uid_t { + self.0.cmcred_uid + } + + /// Returns the effective user identifier + pub fn euid(&self) -> libc::uid_t { + self.0.cmcred_euid + } + + /// Returns the real group identifier + pub fn gid(&self) -> libc::gid_t { + self.0.cmcred_gid + } + + /// Returns a list group identifiers (the first one being the effective GID) + pub fn groups(&self) -> &[libc::gid_t] { + unsafe { + slice::from_raw_parts( + self.0.cmcred_groups.as_ptr() as *const libc::gid_t, + self.0.cmcred_ngroups as _ + ) + } + } + } + + impl From for UnixCredentials { + fn from(cred: libc::cmsgcred) -> Self { + UnixCredentials(cred) + } + } + } +} + +cfg_if! { + if #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" + ))] { + /// Return type of [`LocalPeerCred`](crate::sys::socket::sockopt::LocalPeerCred) + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct XuCred(libc::xucred); + + impl XuCred { + /// Structure layout version + pub fn version(&self) -> u32 { + self.0.cr_version + } + + /// Effective user ID + pub fn uid(&self) -> libc::uid_t { + self.0.cr_uid + } + + /// Returns a list of group identifiers (the first one being the + /// effective GID) + pub fn groups(&self) -> &[libc::gid_t] { + &self.0.cr_groups + } + } + } +} + +feature! { +#![feature = "net"] +/// Request for multicast socket operations +/// +/// This is a wrapper type around `ip_mreq`. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct IpMembershipRequest(libc::ip_mreq); + +impl IpMembershipRequest { + /// Instantiate a new `IpMembershipRequest` + /// + /// If `interface` is `None`, then `Ipv4Addr::any()` will be used for the interface. + pub fn new(group: net::Ipv4Addr, interface: Option) + -> Self + { + let imr_addr = match interface { + None => net::Ipv4Addr::UNSPECIFIED, + Some(addr) => addr + }; + IpMembershipRequest(libc::ip_mreq { + imr_multiaddr: ipv4addr_to_libc(group), + imr_interface: ipv4addr_to_libc(imr_addr) + }) + } +} + +/// Request for ipv6 multicast socket operations +/// +/// This is a wrapper type around `ipv6_mreq`. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Ipv6MembershipRequest(libc::ipv6_mreq); + +impl Ipv6MembershipRequest { + /// Instantiate a new `Ipv6MembershipRequest` + pub const fn new(group: net::Ipv6Addr) -> Self { + Ipv6MembershipRequest(libc::ipv6_mreq { + ipv6mr_multiaddr: ipv6addr_to_libc(&group), + ipv6mr_interface: 0, + }) + } +} +} + +feature! { +#![feature = "uio"] + +/// Create a buffer large enough for storing some control messages as returned +/// by [`recvmsg`](fn.recvmsg.html). +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate nix; +/// # use nix::sys::time::TimeVal; +/// # use std::os::unix::io::RawFd; +/// # fn main() { +/// // Create a buffer for a `ControlMessageOwned::ScmTimestamp` message +/// let _ = cmsg_space!(TimeVal); +/// // Create a buffer big enough for a `ControlMessageOwned::ScmRights` message +/// // with two file descriptors +/// let _ = cmsg_space!([RawFd; 2]); +/// // Create a buffer big enough for a `ControlMessageOwned::ScmRights` message +/// // and a `ControlMessageOwned::ScmTimestamp` message +/// let _ = cmsg_space!(RawFd, TimeVal); +/// # } +/// ``` +// Unfortunately, CMSG_SPACE isn't a const_fn, or else we could return a +// stack-allocated array. +#[macro_export] +macro_rules! cmsg_space { + ( $( $x:ty ),* ) => { + { + let mut space = 0; + $( + // CMSG_SPACE is always safe + space += unsafe { + $crate::sys::socket::CMSG_SPACE(::std::mem::size_of::<$x>() as $crate::sys::socket::c_uint) + } as usize; + )* + Vec::::with_capacity(space) + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// Contains outcome of sending or receiving a message +/// +/// Use [`cmsgs`][RecvMsg::cmsgs] to access all the control messages present, and +/// [`iovs`][RecvMsg::iovs`] to access underlying io slices. +pub struct RecvMsg<'a, 's, S> { + pub bytes: usize, + cmsghdr: Option<&'a cmsghdr>, + pub address: Option, + pub flags: MsgFlags, + iobufs: std::marker::PhantomData<& 's()>, + mhdr: msghdr, +} + +impl<'a, S> RecvMsg<'a, '_, S> { + /// Iterate over the valid control messages pointed to by this + /// msghdr. + pub fn cmsgs(&self) -> CmsgIterator { + CmsgIterator { + cmsghdr: self.cmsghdr, + mhdr: &self.mhdr + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CmsgIterator<'a> { + /// Control message buffer to decode from. Must adhere to cmsg alignment. + cmsghdr: Option<&'a cmsghdr>, + mhdr: &'a msghdr +} + +impl<'a> Iterator for CmsgIterator<'a> { + type Item = ControlMessageOwned; + + fn next(&mut self) -> Option { + match self.cmsghdr { + None => None, // No more messages + Some(hdr) => { + // Get the data. + // Safe if cmsghdr points to valid data returned by recvmsg(2) + let cm = unsafe { Some(ControlMessageOwned::decode_from(hdr))}; + // Advance the internal pointer. Safe if mhdr and cmsghdr point + // to valid data returned by recvmsg(2) + self.cmsghdr = unsafe { + let p = CMSG_NXTHDR(self.mhdr as *const _, hdr as *const _); + p.as_ref() + }; + cm + } + } + } +} + +/// A type-safe wrapper around a single control message, as used with +/// [`recvmsg`](#fn.recvmsg). +/// +/// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) +// Nix version 0.13.0 and earlier used ControlMessage for both recvmsg and +// sendmsg. However, on some platforms the messages returned by recvmsg may be +// unaligned. ControlMessageOwned takes those messages by copy, obviating any +// alignment issues. +// +// See https://github.com/nix-rust/nix/issues/999 +#[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum ControlMessageOwned { + /// Received version of [`ControlMessage::ScmRights`] + ScmRights(Vec), + /// Received version of [`ControlMessage::ScmCredentials`] + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCredentials(UnixCredentials), + /// Received version of [`ControlMessage::ScmCreds`] + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCreds(UnixCredentials), + /// A message of type `SCM_TIMESTAMP`, containing the time the + /// packet was received by the kernel. + /// + /// See the kernel's explanation in "SO_TIMESTAMP" of + /// [networking/timestamping](https://www.kernel.org/doc/Documentation/networking/timestamping.txt). + /// + /// # Examples + /// + /// ``` + /// # #[macro_use] extern crate nix; + /// # use nix::sys::socket::*; + /// # use nix::sys::time::*; + /// # use std::io::{IoSlice, IoSliceMut}; + /// # use std::time::*; + /// # use std::str::FromStr; + /// # fn main() { + /// // Set up + /// let message = "Ohayō!".as_bytes(); + /// let in_socket = socket( + /// AddressFamily::Inet, + /// SockType::Datagram, + /// SockFlag::empty(), + /// None).unwrap(); + /// setsockopt(in_socket, sockopt::ReceiveTimestamp, &true).unwrap(); + /// let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + /// bind(in_socket, &localhost).unwrap(); + /// let address: SockaddrIn = getsockname(in_socket).unwrap(); + /// // Get initial time + /// let time0 = SystemTime::now(); + /// // Send the message + /// let iov = [IoSlice::new(message)]; + /// let flags = MsgFlags::empty(); + /// let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + /// assert_eq!(message.len(), l); + /// // Receive the message + /// let mut buffer = vec![0u8; message.len()]; + /// let mut cmsgspace = cmsg_space!(TimeVal); + /// let mut iov = [IoSliceMut::new(&mut buffer)]; + /// let r = recvmsg::(in_socket, &mut iov, Some(&mut cmsgspace), flags) + /// .unwrap(); + /// let rtime = match r.cmsgs().next() { + /// Some(ControlMessageOwned::ScmTimestamp(rtime)) => rtime, + /// Some(_) => panic!("Unexpected control message"), + /// None => panic!("No control message") + /// }; + /// // Check the final time + /// let time1 = SystemTime::now(); + /// // the packet's received timestamp should lie in-between the two system + /// // times, unless the system clock was adjusted in the meantime. + /// let rduration = Duration::new(rtime.tv_sec() as u64, + /// rtime.tv_usec() as u32 * 1000); + /// assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); + /// assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); + /// // Close socket + /// nix::unistd::close(in_socket).unwrap(); + /// # } + /// ``` + ScmTimestamp(TimeVal), + /// A set of nanosecond resolution timestamps + /// + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + #[cfg(all(target_os = "linux"))] + ScmTimestampsns(Timestamps), + /// Nanoseconds resolution timestamp + /// + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + #[cfg(all(target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmTimestampns(TimeSpec), + #[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4PacketInfo(libc::in_pktinfo), + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "openbsd", + target_os = "netbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6PacketInfo(libc::in6_pktinfo), + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvIf(libc::sockaddr_dl), + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvDstAddr(libc::in_addr), + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4OrigDstAddr(libc::sockaddr_in), + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6OrigDstAddr(libc::sockaddr_in6), + + /// UDP Generic Receive Offload (GRO) allows receiving multiple UDP + /// packets from a single sender. + /// Fixed-size payloads are following one by one in a receive buffer. + /// This Control Message indicates the size of all smaller packets, + /// except, maybe, the last one. + /// + /// `UdpGroSegment` socket option should be enabled on a socket + /// to allow receiving GRO packets. + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + UdpGroSegments(u16), + + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value + /// ancilliary msg (cmsg) should be attached to recieved + /// skbs indicating the number of packets dropped by the + /// socket between the last recieved packet and this + /// received packet. + /// + /// `RxqOvfl` socket option should be enabled on a socket + /// to allow receiving the drop counter. + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RxqOvfl(u32), + + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvErr(libc::sock_extended_err, Option), + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6RecvErr(libc::sock_extended_err, Option), + + /// Catch-all variant for unimplemented cmsg types. + #[doc(hidden)] + Unknown(UnknownCmsg), +} + +/// For representing packet timestamps via `SO_TIMESTAMPING` interface +#[cfg(all(target_os = "linux"))] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Timestamps { + /// software based timestamp, usually one containing data + pub system: TimeSpec, + /// legacy timestamp, usually empty + pub hw_trans: TimeSpec, + /// hardware based timestamp + pub hw_raw: TimeSpec, +} + +impl ControlMessageOwned { + /// Decodes a `ControlMessageOwned` from raw bytes. + /// + /// This is only safe to call if the data is correct for the message type + /// specified in the header. Normally, the kernel ensures that this is the + /// case. "Correct" in this case includes correct length, alignment and + /// actual content. + // Clippy complains about the pointer alignment of `p`, not understanding + // that it's being fed to a function that can handle that. + #[allow(clippy::cast_ptr_alignment)] + unsafe fn decode_from(header: &cmsghdr) -> ControlMessageOwned + { + let p = CMSG_DATA(header); + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + let len = header as *const _ as usize + header.cmsg_len as usize + - p as usize; + match (header.cmsg_level, header.cmsg_type) { + (libc::SOL_SOCKET, libc::SCM_RIGHTS) => { + let n = len / mem::size_of::(); + let mut fds = Vec::with_capacity(n); + for i in 0..n { + let fdp = (p as *const RawFd).add(i); + fds.push(ptr::read_unaligned(fdp)); + } + ControlMessageOwned::ScmRights(fds) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_CREDENTIALS) => { + let cred: libc::ucred = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmCredentials(cred.into()) + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + (libc::SOL_SOCKET, libc::SCM_CREDS) => { + let cred: libc::cmsgcred = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmCreds(cred.into()) + } + #[cfg(not(target_os = "haiku"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => { + let tv: libc::timeval = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmTimestamp(TimeVal::from(tv)) + }, + #[cfg(all(target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => { + let ts: libc::timespec = ptr::read_unaligned(p as *const _); + ControlMessageOwned::ScmTimestampns(TimeSpec::from(ts)) + } + #[cfg(all(target_os = "linux"))] + (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { + let tp = p as *const libc::timespec; + let ts: libc::timespec = ptr::read_unaligned(tp); + let system = TimeSpec::from(ts); + let ts: libc::timespec = ptr::read_unaligned(tp.add(1)); + let hw_trans = TimeSpec::from(ts); + let ts: libc::timespec = ptr::read_unaligned(tp.add(2)); + let hw_raw = TimeSpec::from(ts); + let timestamping = Timestamps { system, hw_trans, hw_raw }; + ControlMessageOwned::ScmTimestampsns(timestamping) + } + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos" + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => { + let info = ptr::read_unaligned(p as *const libc::in6_pktinfo); + ControlMessageOwned::Ipv6PacketInfo(info) + } + #[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_PKTINFO) => { + let info = ptr::read_unaligned(p as *const libc::in_pktinfo); + ControlMessageOwned::Ipv4PacketInfo(info) + } + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVIF) => { + let dl = ptr::read_unaligned(p as *const libc::sockaddr_dl); + ControlMessageOwned::Ipv4RecvIf(dl) + }, + #[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::in_addr); + ControlMessageOwned::Ipv4RecvDstAddr(dl) + }, + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_ORIGDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::sockaddr_in); + ControlMessageOwned::Ipv4OrigDstAddr(dl) + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + (libc::SOL_UDP, libc::UDP_GRO) => { + let gso_size: u16 = ptr::read_unaligned(p as *const _); + ControlMessageOwned::UdpGroSegments(gso_size) + }, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + (libc::SOL_SOCKET, libc::SO_RXQ_OVFL) => { + let drop_counter = ptr::read_unaligned(p as *const u32); + ControlMessageOwned::RxqOvfl(drop_counter) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVERR) => { + let (err, addr) = Self::recv_err_helper::(p, len); + ControlMessageOwned::Ipv4RecvErr(err, addr) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => { + let (err, addr) = Self::recv_err_helper::(p, len); + ControlMessageOwned::Ipv6RecvErr(err, addr) + }, + #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_ORIGDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::sockaddr_in6); + ControlMessageOwned::Ipv6OrigDstAddr(dl) + }, + (_, _) => { + let sl = slice::from_raw_parts(p, len); + let ucmsg = UnknownCmsg(*header, Vec::::from(sl)); + ControlMessageOwned::Unknown(ucmsg) + } + } + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + #[allow(clippy::cast_ptr_alignment)] // False positive + unsafe fn recv_err_helper(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option) { + let ee = p as *const libc::sock_extended_err; + let err = ptr::read_unaligned(ee); + + // For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len] + // CMSG_DATA buffer. For local errors, there is no address included in the control + // message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to + // validate that the address object is in-bounds before we attempt to copy it. + let addrp = libc::SO_EE_OFFENDER(ee) as *const T; + + if addrp.offset(1) as usize - (p as usize) > len { + (err, None) + } else { + (err, Some(ptr::read_unaligned(addrp))) + } + } +} + +/// A type-safe zero-copy wrapper around a single control message, as used wih +/// [`sendmsg`](#fn.sendmsg). More types may be added to this enum; do not +/// exhaustively pattern-match it. +/// +/// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum ControlMessage<'a> { + /// A message of type `SCM_RIGHTS`, containing an array of file + /// descriptors passed between processes. + /// + /// See the description in the "Ancillary messages" section of the + /// [unix(7) man page](https://man7.org/linux/man-pages/man7/unix.7.html). + /// + /// Using multiple `ScmRights` messages for a single `sendmsg` call isn't + /// recommended since it causes platform-dependent behaviour: It might + /// swallow all but the first `ScmRights` message or fail with `EINVAL`. + /// Instead, you can put all fds to be passed into a single `ScmRights` + /// message. + ScmRights(&'a [RawFd]), + /// A message of type `SCM_CREDENTIALS`, containing the pid, uid and gid of + /// a process connected to the socket. + /// + /// This is similar to the socket option `SO_PEERCRED`, but requires a + /// process to explicitly send its credentials. A process running as root is + /// allowed to specify any credentials, while credentials sent by other + /// processes are verified by the kernel. + /// + /// For further information, please refer to the + /// [`unix(7)`](https://man7.org/linux/man-pages/man7/unix.7.html) man page. + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCredentials(&'a UnixCredentials), + /// A message of type `SCM_CREDS`, containing the pid, uid, euid, gid and groups of + /// a process connected to the socket. + /// + /// This is similar to the socket options `LOCAL_CREDS` and `LOCAL_PEERCRED`, but + /// requires a process to explicitly send its credentials. + /// + /// Credentials are always overwritten by the kernel, so this variant does have + /// any data, unlike the receive-side + /// [`ControlMessageOwned::ScmCreds`]. + /// + /// For further information, please refer to the + /// [`unix(4)`](https://www.freebsd.org/cgi/man.cgi?query=unix) man page. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ScmCreds, + + /// Set IV for `AF_ALG` crypto API. + /// + /// For further information, please refer to the + /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) + #[cfg(any( + target_os = "android", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AlgSetIv(&'a [u8]), + /// Set crypto operation for `AF_ALG` crypto API. It may be one of + /// `ALG_OP_ENCRYPT` or `ALG_OP_DECRYPT` + /// + /// For further information, please refer to the + /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) + #[cfg(any( + target_os = "android", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AlgSetOp(&'a libc::c_int), + /// Set the length of associated authentication data (AAD) (applicable only to AEAD algorithms) + /// for `AF_ALG` crypto API. + /// + /// For further information, please refer to the + /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) + #[cfg(any( + target_os = "android", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AlgSetAeadAssoclen(&'a u32), + + /// UDP GSO makes it possible for applications to generate network packets + /// for a virtual MTU much greater than the real one. + /// The length of the send data no longer matches the expected length on + /// the wire. + /// The size of the datagram payload as it should appear on the wire may be + /// passed through this control message. + /// Send buffer should consist of multiple fixed-size wire payloads + /// following one by one, and the last, possibly smaller one. + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + UdpGsoSegments(&'a u16), + + /// Configure the sending addressing and interface for v4 + /// + /// For further information, please refer to the + /// [`ip(7)`](https://man7.org/linux/man-pages/man7/ip.7.html) man page. + #[cfg(any(target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4PacketInfo(&'a libc::in_pktinfo), + + /// Configure the sending addressing and interface for v6 + /// + /// For further information, please refer to the + /// [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html) man page. + #[cfg(any(target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "freebsd", + target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6PacketInfo(&'a libc::in6_pktinfo), + + /// Configure the IPv4 source address with `IP_SENDSRCADDR`. + #[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "dragonfly", + ))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4SendSrcAddr(&'a libc::in_addr), + + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value + /// ancilliary msg (cmsg) should be attached to recieved + /// skbs indicating the number of packets dropped by the + /// socket between the last recieved packet and this + /// received packet. + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RxqOvfl(&'a u32), + + /// Configure the transmission time of packets. + /// + /// For further information, please refer to the + /// [`tc-etf(8)`](https://man7.org/linux/man-pages/man8/tc-etf.8.html) man + /// page. + #[cfg(target_os = "linux")] + TxTime(&'a u64), +} + +// An opaque structure used to prevent cmsghdr from being a public type +#[doc(hidden)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UnknownCmsg(cmsghdr, Vec); + +impl<'a> ControlMessage<'a> { + /// The value of CMSG_SPACE on this message. + /// Safe because CMSG_SPACE is always safe + fn space(&self) -> usize { + unsafe{CMSG_SPACE(self.len() as libc::c_uint) as usize} + } + + /// The value of CMSG_LEN on this message. + /// Safe because CMSG_LEN is always safe + #[cfg(any(target_os = "android", + all(target_os = "linux", not(target_env = "musl"))))] + fn cmsg_len(&self) -> usize { + unsafe{CMSG_LEN(self.len() as libc::c_uint) as usize} + } + + #[cfg(not(any(target_os = "android", + all(target_os = "linux", not(target_env = "musl")))))] + fn cmsg_len(&self) -> libc::c_uint { + unsafe{CMSG_LEN(self.len() as libc::c_uint)} + } + + /// Return a reference to the payload data as a byte pointer + fn copy_to_cmsg_data(&self, cmsg_data: *mut u8) { + let data_ptr = match *self { + ControlMessage::ScmRights(fds) => { + fds as *const _ as *const u8 + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(creds) => { + &creds.0 as *const libc::ucred as *const u8 + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => { + // The kernel overwrites the data, we just zero it + // to make sure it's not uninitialized memory + unsafe { ptr::write_bytes(cmsg_data, 0, self.len()) }; + return + } + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(iv) => { + #[allow(deprecated)] // https://github.com/rust-lang/libc/issues/1501 + let af_alg_iv = libc::af_alg_iv { + ivlen: iv.len() as u32, + iv: [0u8; 0], + }; + + let size = mem::size_of_val(&af_alg_iv); + + unsafe { + ptr::copy_nonoverlapping( + &af_alg_iv as *const _ as *const u8, + cmsg_data, + size, + ); + ptr::copy_nonoverlapping( + iv.as_ptr(), + cmsg_data.add(size), + iv.len() + ); + }; + + return + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetOp(op) => { + op as *const _ as *const u8 + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetAeadAssoclen(len) => { + len as *const _ as *const u8 + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(gso_size) => { + gso_size as *const _ as *const u8 + }, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(info) => info as *const _ as *const u8, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(info) => info as *const _ as *const u8, + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(addr) => addr as *const _ as *const u8, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(drop_count) => { + drop_count as *const _ as *const u8 + }, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(tx_time) => { + tx_time as *const _ as *const u8 + }, + }; + unsafe { + ptr::copy_nonoverlapping( + data_ptr, + cmsg_data, + self.len() + ) + }; + } + + /// The size of the payload, excluding its cmsghdr + fn len(&self) -> usize { + match *self { + ControlMessage::ScmRights(fds) => { + mem::size_of_val(fds) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(creds) => { + mem::size_of_val(creds) + } + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => { + mem::size_of::() + } + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(iv) => { + mem::size_of::<&[u8]>() + iv.len() + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetOp(op) => { + mem::size_of_val(op) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetAeadAssoclen(len) => { + mem::size_of_val(len) + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(gso_size) => { + mem::size_of_val(gso_size) + }, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(info) => mem::size_of_val(info), + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(info) => mem::size_of_val(info), + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(addr) => mem::size_of_val(addr), + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(drop_count) => { + mem::size_of_val(drop_count) + }, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(tx_time) => { + mem::size_of_val(tx_time) + }, + } + } + + /// Returns the value to put into the `cmsg_level` field of the header. + fn cmsg_level(&self) -> libc::c_int { + match *self { + ControlMessage::ScmRights(_) => libc::SOL_SOCKET, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(_) => libc::SOL_SOCKET, + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => libc::SOL_SOCKET, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(_) | ControlMessage::AlgSetOp(_) | + ControlMessage::AlgSetAeadAssoclen(_) => libc::SOL_ALG, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(_) => libc::SOL_UDP, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(_) => libc::IPPROTO_IP, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(_) => libc::IPPROTO_IPV6, + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(_) => libc::IPPROTO_IP, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(_) => libc::SOL_SOCKET, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(_) => libc::SOL_SOCKET, + } + } + + /// Returns the value to put into the `cmsg_type` field of the header. + fn cmsg_type(&self) -> libc::c_int { + match *self { + ControlMessage::ScmRights(_) => libc::SCM_RIGHTS, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::ScmCredentials(_) => libc::SCM_CREDENTIALS, + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessage::ScmCreds => libc::SCM_CREDS, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetIv(_) => { + libc::ALG_SET_IV + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetOp(_) => { + libc::ALG_SET_OP + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessage::AlgSetAeadAssoclen(_) => { + libc::ALG_SET_AEAD_ASSOCLEN + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + ControlMessage::UdpGsoSegments(_) => { + libc::UDP_SEGMENT + }, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "android", + target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv4PacketInfo(_) => libc::IP_PKTINFO, + #[cfg(any(target_os = "linux", target_os = "macos", + target_os = "netbsd", target_os = "freebsd", + target_os = "android", target_os = "ios",))] + #[cfg(feature = "net")] + ControlMessage::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO, + #[cfg(any(target_os = "netbsd", target_os = "freebsd", + target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4SendSrcAddr(_) => libc::IP_SENDSRCADDR, + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + ControlMessage::RxqOvfl(_) => { + libc::SO_RXQ_OVFL + }, + #[cfg(target_os = "linux")] + ControlMessage::TxTime(_) => { + libc::SCM_TXTIME + }, + } + } + + // Unsafe: cmsg must point to a valid cmsghdr with enough space to + // encode self. + unsafe fn encode_into(&self, cmsg: *mut cmsghdr) { + (*cmsg).cmsg_level = self.cmsg_level(); + (*cmsg).cmsg_type = self.cmsg_type(); + (*cmsg).cmsg_len = self.cmsg_len(); + self.copy_to_cmsg_data(CMSG_DATA(cmsg)); + } +} + + +/// Send data in scatter-gather vectors to a socket, possibly accompanied +/// by ancillary data. Optionally direct the message at the given address, +/// as with sendto. +/// +/// Allocates if cmsgs is nonempty. +/// +/// # Examples +/// When not directing to any specific address, use `()` for the generic type +/// ``` +/// # use nix::sys::socket::*; +/// # use nix::unistd::pipe; +/// # use std::io::IoSlice; +/// let (fd1, fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None, +/// SockFlag::empty()) +/// .unwrap(); +/// let (r, w) = pipe().unwrap(); +/// +/// let iov = [IoSlice::new(b"hello")]; +/// let fds = [r]; +/// let cmsg = ControlMessage::ScmRights(&fds); +/// sendmsg::<()>(fd1, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(); +/// ``` +/// When directing to a specific address, the generic type will be inferred. +/// ``` +/// # use nix::sys::socket::*; +/// # use nix::unistd::pipe; +/// # use std::io::IoSlice; +/// # use std::str::FromStr; +/// let localhost = SockaddrIn::from_str("1.2.3.4:8080").unwrap(); +/// let fd = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), +/// None).unwrap(); +/// let (r, w) = pipe().unwrap(); +/// +/// let iov = [IoSlice::new(b"hello")]; +/// let fds = [r]; +/// let cmsg = ControlMessage::ScmRights(&fds); +/// sendmsg(fd, &iov, &[cmsg], MsgFlags::empty(), Some(&localhost)).unwrap(); +/// ``` +pub fn sendmsg(fd: RawFd, iov: &[IoSlice<'_>], cmsgs: &[ControlMessage], + flags: MsgFlags, addr: Option<&S>) -> Result + where S: SockaddrLike +{ + let capacity = cmsgs.iter().map(|c| c.space()).sum(); + + // First size the buffer needed to hold the cmsgs. It must be zeroed, + // because subsequent code will not clear the padding bytes. + let mut cmsg_buffer = vec![0u8; capacity]; + + let mhdr = pack_mhdr_to_send(&mut cmsg_buffer[..], iov, cmsgs, addr); + + let ret = unsafe { libc::sendmsg(fd, &mhdr, flags.bits()) }; + + Errno::result(ret).map(|r| r as usize) +} + + +/// An extension of `sendmsg` that allows the caller to transmit multiple +/// messages on a socket using a single system call. This has performance +/// benefits for some applications. +/// +/// Allocations are performed for cmsgs and to build `msghdr` buffer +/// +/// # Arguments +/// +/// * `fd`: Socket file descriptor +/// * `data`: Struct that implements `IntoIterator` with `SendMmsgData` items +/// * `flags`: Optional flags passed directly to the operating system. +/// +/// # Returns +/// `Vec` with numbers of sent bytes on each sent message. +/// +/// # References +/// [`sendmsg`](fn.sendmsg.html) +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +pub fn sendmmsg<'a, XS, AS, C, I, S>( + fd: RawFd, + data: &'a mut MultiHeaders, + slices: XS, + // one address per group of slices + addrs: AS, + // shared across all the messages + cmsgs: C, + flags: MsgFlags +) -> crate::Result> + where + XS: IntoIterator, + AS: AsRef<[Option]>, + I: AsRef<[IoSlice<'a>]> + 'a, + C: AsRef<[ControlMessage<'a>]> + 'a, + S: SockaddrLike + 'a +{ + + let mut count = 0; + + + for (i, ((slice, addr), mmsghdr)) in slices.into_iter().zip(addrs.as_ref()).zip(data.items.iter_mut() ).enumerate() { + let mut p = &mut mmsghdr.msg_hdr; + p.msg_iov = slice.as_ref().as_ptr() as *mut libc::iovec; + p.msg_iovlen = slice.as_ref().len() as _; + + p.msg_namelen = addr.as_ref().map_or(0, S::len); + p.msg_name = addr.as_ref().map_or(ptr::null(), S::as_ptr) as _; + + // Encode each cmsg. This must happen after initializing the header because + // CMSG_NEXT_HDR and friends read the msg_control and msg_controllen fields. + // CMSG_FIRSTHDR is always safe + let mut pmhdr: *mut cmsghdr = unsafe { CMSG_FIRSTHDR(p) }; + for cmsg in cmsgs.as_ref() { + assert_ne!(pmhdr, ptr::null_mut()); + // Safe because we know that pmhdr is valid, and we initialized it with + // sufficient space + unsafe { cmsg.encode_into(pmhdr) }; + // Safe because mhdr is valid + pmhdr = unsafe { CMSG_NXTHDR(p, pmhdr) }; + } + + count = i+1; + } + + let sent = Errno::result(unsafe { + libc::sendmmsg( + fd, + data.items.as_mut_ptr(), + count as _, + flags.bits() as _ + ) + })? as usize; + + Ok(MultiResults { + rmm: data, + current_index: 0, + received: sent + }) + +} + + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +#[derive(Debug)] +/// Preallocated structures needed for [`recvmmsg`] and [`sendmmsg`] functions +pub struct MultiHeaders { + // preallocated boxed slice of mmsghdr + items: Box<[libc::mmsghdr]>, + addresses: Box<[mem::MaybeUninit]>, + // while we are not using it directly - this is used to store control messages + // and we retain pointers to them inside items array + #[allow(dead_code)] + cmsg_buffers: Option>, + msg_controllen: usize, +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +impl MultiHeaders { + /// Preallocate structure used by [`recvmmsg`] and [`sendmmsg`] takes number of headers to preallocate + /// + /// `cmsg_buffer` should be created with [`cmsg_space!`] if needed + pub fn preallocate(num_slices: usize, cmsg_buffer: Option>) -> Self + where + S: Copy + SockaddrLike, + { + // we will be storing pointers to addresses inside mhdr - convert it into boxed + // slice so it can'be changed later by pushing anything into self.addresses + let mut addresses = vec![std::mem::MaybeUninit::uninit(); num_slices].into_boxed_slice(); + + let msg_controllen = cmsg_buffer.as_ref().map_or(0, |v| v.capacity()); + + // we'll need a cmsg_buffer for each slice, we preallocate a vector and split + // it into "slices" parts + let cmsg_buffers = + cmsg_buffer.map(|v| vec![0u8; v.capacity() * num_slices].into_boxed_slice()); + + let items = addresses + .iter_mut() + .enumerate() + .map(|(ix, address)| { + let (ptr, cap) = match &cmsg_buffers { + Some(v) => ((&v[ix * msg_controllen] as *const u8), msg_controllen), + None => (std::ptr::null(), 0), + }; + let msg_hdr = unsafe { pack_mhdr_to_receive(std::ptr::null(), 0, ptr, cap, address.as_mut_ptr()) }; + libc::mmsghdr { + msg_hdr, + msg_len: 0, + } + }) + .collect::>(); + + Self { + items: items.into_boxed_slice(), + addresses, + cmsg_buffers, + msg_controllen, + } + } +} + +/// An extension of recvmsg that allows the caller to receive multiple messages from a socket using a single system call. +/// +/// This has performance benefits for some applications. +/// +/// This method performs no allocations. +/// +/// Returns an iterator producing [`RecvMsg`], one per received messages. Each `RecvMsg` can produce +/// iterators over [`IoSlice`] with [`iovs`][RecvMsg::iovs`] and +/// `ControlMessageOwned` with [`cmsgs`][RecvMsg::cmsgs]. +/// +/// # Bugs (in underlying implementation, at least in Linux) +/// The timeout argument does not work as intended. The timeout is checked only after the receipt +/// of each datagram, so that if up to `vlen`-1 datagrams are received before the timeout expires, +/// but then no further datagrams are received, the call will block forever. +/// +/// If an error occurs after at least one message has been received, the call succeeds, and returns +/// the number of messages received. The error code is expected to be returned on a subsequent +/// call to recvmmsg(). In the current implementation, however, the error code can be +/// overwritten in the meantime by an unrelated network event on a socket, for example an +/// incoming ICMP packet. + +// On aarch64 linux using recvmmsg and trying to get hardware/kernel timestamps might not +// always produce the desired results - see https://github.com/nix-rust/nix/pull/1744 for more +// details + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +pub fn recvmmsg<'a, XS, S, I>( + fd: RawFd, + data: &'a mut MultiHeaders, + slices: XS, + flags: MsgFlags, + mut timeout: Option, +) -> crate::Result> +where + XS: IntoIterator, + I: AsRef<[IoSliceMut<'a>]> + 'a, +{ + let mut count = 0; + for (i, (slice, mmsghdr)) in slices.into_iter().zip(data.items.iter_mut()).enumerate() { + let mut p = &mut mmsghdr.msg_hdr; + p.msg_iov = slice.as_ref().as_ptr() as *mut libc::iovec; + p.msg_iovlen = slice.as_ref().len() as _; + count = i + 1; + } + + let timeout_ptr = timeout + .as_mut() + .map_or_else(std::ptr::null_mut, |t| t as *mut _ as *mut libc::timespec); + + let received = Errno::result(unsafe { + libc::recvmmsg( + fd, + data.items.as_mut_ptr(), + count as _, + flags.bits() as _, + timeout_ptr, + ) + })? as usize; + + Ok(MultiResults { + rmm: data, + current_index: 0, + received, + }) +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +#[derive(Debug)] +/// Iterator over results of [`recvmmsg`]/[`sendmmsg`] +/// +/// +pub struct MultiResults<'a, S> { + // preallocated structures + rmm: &'a MultiHeaders, + current_index: usize, + received: usize, +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", +))] +impl<'a, S> Iterator for MultiResults<'a, S> +where + S: Copy + SockaddrLike, +{ + type Item = RecvMsg<'a, 'a, S>; + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn next(&mut self) -> Option { + if self.current_index >= self.received { + return None; + } + let mmsghdr = self.rmm.items[self.current_index]; + + // as long as we are not reading past the index writen by recvmmsg - address + // will be initialized + let address = unsafe { self.rmm.addresses[self.current_index].assume_init() }; + + self.current_index += 1; + Some(unsafe { + read_mhdr( + mmsghdr.msg_hdr, + mmsghdr.msg_len as isize, + self.rmm.msg_controllen, + address, + ) + }) + } +} + +impl<'a, S> RecvMsg<'_, 'a, S> { + /// Iterate over the filled io slices pointed by this msghdr + pub fn iovs(&self) -> IoSliceIterator<'a> { + IoSliceIterator { + index: 0, + remaining: self.bytes, + slices: unsafe { + // safe for as long as mgdr is properly initialized and references are valid. + // for multi messages API we initialize it with an empty + // slice and replace with a concrete buffer + // for single message API we hold a lifetime reference to ioslices + std::slice::from_raw_parts(self.mhdr.msg_iov as *const _, self.mhdr.msg_iovlen as _) + }, + } + } +} + +#[derive(Debug)] +pub struct IoSliceIterator<'a> { + index: usize, + remaining: usize, + slices: &'a [IoSlice<'a>], +} + +impl<'a> Iterator for IoSliceIterator<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if self.index >= self.slices.len() { + return None; + } + let slice = &self.slices[self.index][..self.remaining.min(self.slices[self.index].len())]; + self.remaining -= slice.len(); + self.index += 1; + if slice.is_empty() { + return None; + } + + Some(slice) + } +} + +// test contains both recvmmsg and timestaping which is linux only +// there are existing tests for recvmmsg only in tests/ +#[cfg(target_os = "linux")] +#[cfg(test)] +mod test { + use crate::sys::socket::{AddressFamily, ControlMessageOwned}; + use crate::*; + use std::str::FromStr; + + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recvmm2() -> crate::Result<()> { + use crate::sys::socket::{ + sendmsg, setsockopt, socket, sockopt::Timestamping, MsgFlags, SockFlag, SockType, + SockaddrIn, TimestampingFlag, + }; + use std::io::{IoSlice, IoSliceMut}; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + )?; + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::SOCK_NONBLOCK, + None, + )?; + + crate::sys::socket::bind(rsock, &sock_addr)?; + + setsockopt(rsock, Timestamping, &TimestampingFlag::all())?; + + let sbuf = (0..400).map(|i| i as u8).collect::>(); + + let mut recv_buf = vec![0; 1024]; + + let mut recv_iovs = Vec::new(); + let mut pkt_iovs = Vec::new(); + + for (ix, chunk) in recv_buf.chunks_mut(256).enumerate() { + pkt_iovs.push(IoSliceMut::new(chunk)); + if ix % 2 == 1 { + recv_iovs.push(pkt_iovs); + pkt_iovs = Vec::new(); + } + } + drop(pkt_iovs); + + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + + let cmsg = cmsg_space!(crate::sys::socket::Timestamps); + sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap(); + + let mut data = super::MultiHeaders::<()>::preallocate(recv_iovs.len(), Some(cmsg)); + + let t = sys::time::TimeSpec::from_duration(std::time::Duration::from_secs(10)); + + let recv = super::recvmmsg(rsock, &mut data, recv_iovs.iter(), flags, Some(t))?; + + for rmsg in recv { + #[cfg(not(any(qemu, target_arch = "aarch64")))] + let mut saw_time = false; + let mut recvd = 0; + for cmsg in rmsg.cmsgs() { + if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg { + let ts = timestamps.system; + + let sys_time = + crate::time::clock_gettime(crate::time::ClockId::CLOCK_REALTIME)?; + let diff = if ts > sys_time { + ts - sys_time + } else { + sys_time - ts + }; + assert!(std::time::Duration::from(diff).as_secs() < 60); + #[cfg(not(any(qemu, target_arch = "aarch64")))] + { + saw_time = true; + } + } + } + + #[cfg(not(any(qemu, target_arch = "aarch64")))] + assert!(saw_time); + + for iov in rmsg.iovs() { + recvd += iov.len(); + } + assert_eq!(recvd, 400); + } + + Ok(()) + } +} +unsafe fn read_mhdr<'a, 'i, S>( + mhdr: msghdr, + r: isize, + msg_controllen: usize, + address: S, +) -> RecvMsg<'a, 'i, S> + where S: SockaddrLike +{ + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + let cmsghdr = { + if mhdr.msg_controllen > 0 { + debug_assert!(!mhdr.msg_control.is_null()); + debug_assert!(msg_controllen >= mhdr.msg_controllen as usize); + CMSG_FIRSTHDR(&mhdr as *const msghdr) + } else { + ptr::null() + }.as_ref() + }; + + RecvMsg { + bytes: r as usize, + cmsghdr, + address: Some(address), + flags: MsgFlags::from_bits_truncate(mhdr.msg_flags), + mhdr, + iobufs: std::marker::PhantomData, + } +} + +/// Pack pointers to various structures into into msghdr +/// +/// # Safety +/// `iov_buffer` and `iov_buffer_len` must point to a slice +/// of `IoSliceMut` and number of available elements or be a null pointer and 0 +/// +/// `cmsg_buffer` and `cmsg_capacity` must point to a byte buffer used +/// to store control headers later or be a null pointer and 0 if control +/// headers are not used +/// +/// Buffers must remain valid for the whole lifetime of msghdr +unsafe fn pack_mhdr_to_receive( + iov_buffer: *const IoSliceMut, + iov_buffer_len: usize, + cmsg_buffer: *const u8, + cmsg_capacity: usize, + address: *mut S, +) -> msghdr + where + S: SockaddrLike +{ + // Musl's msghdr has private fields, so this is the only way to + // initialize it. + let mut mhdr = mem::MaybeUninit::::zeroed(); + let p = mhdr.as_mut_ptr(); + (*p).msg_name = (*address).as_mut_ptr() as *mut c_void; + (*p).msg_namelen = S::size(); + (*p).msg_iov = iov_buffer as *mut iovec; + (*p).msg_iovlen = iov_buffer_len as _; + (*p).msg_control = cmsg_buffer as *mut c_void; + (*p).msg_controllen = cmsg_capacity as _; + (*p).msg_flags = 0; + mhdr.assume_init() +} + +fn pack_mhdr_to_send<'a, I, C, S>( + cmsg_buffer: &mut [u8], + iov: I, + cmsgs: C, + addr: Option<&S> +) -> msghdr + where + I: AsRef<[IoSlice<'a>]>, + C: AsRef<[ControlMessage<'a>]>, + S: SockaddrLike + 'a +{ + let capacity = cmsg_buffer.len(); + + // The message header must be initialized before the individual cmsgs. + let cmsg_ptr = if capacity > 0 { + cmsg_buffer.as_ptr() as *mut c_void + } else { + ptr::null_mut() + }; + + let mhdr = unsafe { + // Musl's msghdr has private fields, so this is the only way to + // initialize it. + let mut mhdr = mem::MaybeUninit::::zeroed(); + let p = mhdr.as_mut_ptr(); + (*p).msg_name = addr.map(S::as_ptr).unwrap_or(ptr::null()) as *mut _; + (*p).msg_namelen = addr.map(S::len).unwrap_or(0); + // transmute iov into a mutable pointer. sendmsg doesn't really mutate + // the buffer, but the standard says that it takes a mutable pointer + (*p).msg_iov = iov.as_ref().as_ptr() as *mut _; + (*p).msg_iovlen = iov.as_ref().len() as _; + (*p).msg_control = cmsg_ptr; + (*p).msg_controllen = capacity as _; + (*p).msg_flags = 0; + mhdr.assume_init() + }; + + // Encode each cmsg. This must happen after initializing the header because + // CMSG_NEXT_HDR and friends read the msg_control and msg_controllen fields. + // CMSG_FIRSTHDR is always safe + let mut pmhdr: *mut cmsghdr = unsafe { CMSG_FIRSTHDR(&mhdr as *const msghdr) }; + for cmsg in cmsgs.as_ref() { + assert_ne!(pmhdr, ptr::null_mut()); + // Safe because we know that pmhdr is valid, and we initialized it with + // sufficient space + unsafe { cmsg.encode_into(pmhdr) }; + // Safe because mhdr is valid + pmhdr = unsafe { CMSG_NXTHDR(&mhdr as *const msghdr, pmhdr) }; + } + + mhdr +} + +/// Receive message in scatter-gather vectors from a socket, and +/// optionally receive ancillary data into the provided buffer. +/// If no ancillary data is desired, use () as the type parameter. +/// +/// # Arguments +/// +/// * `fd`: Socket file descriptor +/// * `iov`: Scatter-gather list of buffers to receive the message +/// * `cmsg_buffer`: Space to receive ancillary data. Should be created by +/// [`cmsg_space!`](../../macro.cmsg_space.html) +/// * `flags`: Optional flags passed directly to the operating system. +/// +/// # References +/// [recvmsg(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html) +pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'inner>], + mut cmsg_buffer: Option<&'a mut Vec>, + flags: MsgFlags) -> Result> + where S: SockaddrLike + 'a, + 'inner: 'outer +{ + let mut address = mem::MaybeUninit::uninit(); + + let (msg_control, msg_controllen) = cmsg_buffer.as_mut() + .map(|v| (v.as_mut_ptr(), v.capacity())) + .unwrap_or((ptr::null_mut(), 0)); + let mut mhdr = unsafe { + pack_mhdr_to_receive(iov.as_ref().as_ptr(), iov.len(), msg_control, msg_controllen, address.as_mut_ptr()) + }; + + let ret = unsafe { libc::recvmsg(fd, &mut mhdr, flags.bits()) }; + + let r = Errno::result(ret)?; + + Ok(unsafe { read_mhdr(mhdr, r, msg_controllen, address.assume_init()) }) +} +} + +/// Create an endpoint for communication +/// +/// The `protocol` specifies a particular protocol to be used with the +/// socket. Normally only a single protocol exists to support a +/// particular socket type within a given protocol family, in which case +/// protocol can be specified as `None`. However, it is possible that many +/// protocols may exist, in which case a particular protocol must be +/// specified in this manner. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html) +pub fn socket>>( + domain: AddressFamily, + ty: SockType, + flags: SockFlag, + protocol: T, +) -> Result { + let protocol = match protocol.into() { + None => 0, + Some(p) => p as c_int, + }; + + // SockFlags are usually embedded into `ty`, but we don't do that in `nix` because it's a + // little easier to understand by separating it out. So we have to merge these bitfields + // here. + let mut ty = ty as c_int; + ty |= flags.bits(); + + let res = unsafe { libc::socket(domain as c_int, ty, protocol) }; + + Errno::result(res) +} + +/// Create a pair of connected sockets +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html) +pub fn socketpair>>( + domain: AddressFamily, + ty: SockType, + protocol: T, + flags: SockFlag, +) -> Result<(RawFd, RawFd)> { + let protocol = match protocol.into() { + None => 0, + Some(p) => p as c_int, + }; + + // SockFlags are usually embedded into `ty`, but we don't do that in `nix` because it's a + // little easier to understand by separating it out. So we have to merge these bitfields + // here. + let mut ty = ty as c_int; + ty |= flags.bits(); + + let mut fds = [-1, -1]; + + let res = unsafe { + libc::socketpair(domain as c_int, ty, protocol, fds.as_mut_ptr()) + }; + Errno::result(res)?; + + Ok((fds[0], fds[1])) +} + +/// Listen for connections on a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html) +pub fn listen(sockfd: RawFd, backlog: usize) -> Result<()> { + let res = unsafe { libc::listen(sockfd, backlog as c_int) }; + + Errno::result(res).map(drop) +} + +/// Bind a name to a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html) +pub fn bind(fd: RawFd, addr: &dyn SockaddrLike) -> Result<()> { + let res = unsafe { libc::bind(fd, addr.as_ptr(), addr.len()) }; + + Errno::result(res).map(drop) +} + +/// Accept a connection on a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html) +pub fn accept(sockfd: RawFd) -> Result { + let res = unsafe { libc::accept(sockfd, ptr::null_mut(), ptr::null_mut()) }; + + Errno::result(res) +} + +/// Accept a connection on a socket +/// +/// [Further reading](https://man7.org/linux/man-pages/man2/accept.2.html) +#[cfg(any( + all( + target_os = "android", + any( + target_arch = "aarch64", + target_arch = "x86", + target_arch = "x86_64" + ) + ), + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" +))] +pub fn accept4(sockfd: RawFd, flags: SockFlag) -> Result { + let res = unsafe { + libc::accept4(sockfd, ptr::null_mut(), ptr::null_mut(), flags.bits()) + }; + + Errno::result(res) +} + +/// Initiate a connection on a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html) +pub fn connect(fd: RawFd, addr: &dyn SockaddrLike) -> Result<()> { + let res = unsafe { libc::connect(fd, addr.as_ptr(), addr.len()) }; + + Errno::result(res).map(drop) +} + +/// Receive data from a connection-oriented socket. Returns the number of +/// bytes read +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html) +pub fn recv(sockfd: RawFd, buf: &mut [u8], flags: MsgFlags) -> Result { + unsafe { + let ret = libc::recv( + sockfd, + buf.as_ptr() as *mut c_void, + buf.len() as size_t, + flags.bits(), + ); + + Errno::result(ret).map(|r| r as usize) + } +} + +/// Receive data from a connectionless or connection-oriented socket. Returns +/// the number of bytes read and, for connectionless sockets, the socket +/// address of the sender. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html) +pub fn recvfrom( + sockfd: RawFd, + buf: &mut [u8], +) -> Result<(usize, Option)> { + unsafe { + let mut addr = mem::MaybeUninit::::uninit(); + let mut len = mem::size_of_val(&addr) as socklen_t; + + let ret = Errno::result(libc::recvfrom( + sockfd, + buf.as_ptr() as *mut c_void, + buf.len() as size_t, + 0, + addr.as_mut_ptr() as *mut libc::sockaddr, + &mut len as *mut socklen_t, + ))? as usize; + + Ok(( + ret, + T::from_raw( + addr.assume_init().as_ptr() as *const libc::sockaddr, + Some(len), + ), + )) + } +} + +/// Send a message to a socket +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html) +pub fn sendto( + fd: RawFd, + buf: &[u8], + addr: &dyn SockaddrLike, + flags: MsgFlags, +) -> Result { + let ret = unsafe { + libc::sendto( + fd, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + flags.bits(), + addr.as_ptr(), + addr.len(), + ) + }; + + Errno::result(ret).map(|r| r as usize) +} + +/// Send data to a connection-oriented socket. Returns the number of bytes read +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html) +pub fn send(fd: RawFd, buf: &[u8], flags: MsgFlags) -> Result { + let ret = unsafe { + libc::send( + fd, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + flags.bits(), + ) + }; + + Errno::result(ret).map(|r| r as usize) +} + +/* + * + * ===== Socket Options ===== + * + */ + +/// Represents a socket option that can be retrieved. +pub trait GetSockOpt: Copy { + type Val; + + /// Look up the value of this socket option on the given socket. + fn get(&self, fd: RawFd) -> Result; +} + +/// Represents a socket option that can be set. +pub trait SetSockOpt: Clone { + type Val; + + /// Set the value of this socket option on the given socket. + fn set(&self, fd: RawFd, val: &Self::Val) -> Result<()>; +} + +/// Get the current value for the requested socket option +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html) +pub fn getsockopt(fd: RawFd, opt: O) -> Result { + opt.get(fd) +} + +/// Sets the value for the requested socket option +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html) +/// +/// # Examples +/// +/// ``` +/// use nix::sys::socket::setsockopt; +/// use nix::sys::socket::sockopt::KeepAlive; +/// use std::net::TcpListener; +/// use std::os::unix::io::AsRawFd; +/// +/// let listener = TcpListener::bind("0.0.0.0:0").unwrap(); +/// let fd = listener.as_raw_fd(); +/// let res = setsockopt(fd, KeepAlive, &true); +/// assert!(res.is_ok()); +/// ``` +pub fn setsockopt( + fd: RawFd, + opt: O, + val: &O::Val, +) -> Result<()> { + opt.set(fd, val) +} + +/// Get the address of the peer connected to the socket `fd`. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html) +pub fn getpeername(fd: RawFd) -> Result { + unsafe { + let mut addr = mem::MaybeUninit::::uninit(); + let mut len = T::size(); + + let ret = libc::getpeername( + fd, + addr.as_mut_ptr() as *mut libc::sockaddr, + &mut len, + ); + + Errno::result(ret)?; + + T::from_raw(addr.assume_init().as_ptr(), Some(len)).ok_or(Errno::EINVAL) + } +} + +/// Get the current address to which the socket `fd` is bound. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html) +pub fn getsockname(fd: RawFd) -> Result { + unsafe { + let mut addr = mem::MaybeUninit::::uninit(); + let mut len = T::size(); + + let ret = libc::getsockname( + fd, + addr.as_mut_ptr() as *mut libc::sockaddr, + &mut len, + ); + + Errno::result(ret)?; + + T::from_raw(addr.assume_init().as_ptr(), Some(len)).ok_or(Errno::EINVAL) + } +} + +/// Return the appropriate `SockAddr` type from a `sockaddr_storage` of a +/// certain size. +/// +/// In C this would usually be done by casting. The `len` argument +/// should be the number of bytes in the `sockaddr_storage` that are actually +/// allocated and valid. It must be at least as large as all the useful parts +/// of the structure. Note that in the case of a `sockaddr_un`, `len` need not +/// include the terminating null. +#[deprecated( + since = "0.24.0", + note = "use SockaddrLike or SockaddrStorage instead" +)] +#[allow(deprecated)] +pub fn sockaddr_storage_to_addr( + addr: &sockaddr_storage, + len: usize, +) -> Result { + assert!(len <= mem::size_of::()); + if len < mem::size_of_val(&addr.ss_family) { + return Err(Errno::ENOTCONN); + } + + match c_int::from(addr.ss_family) { + #[cfg(feature = "net")] + libc::AF_INET => { + assert!(len >= mem::size_of::()); + let sin = unsafe { + *(addr as *const sockaddr_storage as *const sockaddr_in) + }; + Ok(SockAddr::Inet(InetAddr::V4(sin))) + } + #[cfg(feature = "net")] + libc::AF_INET6 => { + assert!(len >= mem::size_of::()); + let sin6 = unsafe { *(addr as *const _ as *const sockaddr_in6) }; + Ok(SockAddr::Inet(InetAddr::V6(sin6))) + } + libc::AF_UNIX => unsafe { + let sun = *(addr as *const _ as *const sockaddr_un); + let sun_len = len.try_into().unwrap(); + Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, sun_len))) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(feature = "net")] + libc::AF_PACKET => { + use libc::sockaddr_ll; + // Don't assert anything about the size. + // Apparently the Linux kernel can return smaller sizes when + // the value in the last element of sockaddr_ll (`sll_addr`) is + // smaller than the declared size of that field + let sll = unsafe { *(addr as *const _ as *const sockaddr_ll) }; + Ok(SockAddr::Link(LinkAddr(sll))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => { + use libc::sockaddr_nl; + let snl = unsafe { *(addr as *const _ as *const sockaddr_nl) }; + Ok(SockAddr::Netlink(NetlinkAddr(snl))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_ALG => { + use libc::sockaddr_alg; + let salg = unsafe { *(addr as *const _ as *const sockaddr_alg) }; + Ok(SockAddr::Alg(AlgAddr(salg))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_VSOCK => { + use libc::sockaddr_vm; + let svm = unsafe { *(addr as *const _ as *const sockaddr_vm) }; + Ok(SockAddr::Vsock(VsockAddr(svm))) + } + af => panic!("unexpected address family {}", af), + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Shutdown { + /// Further receptions will be disallowed. + Read, + /// Further transmissions will be disallowed. + Write, + /// Further receptions and transmissions will be disallowed. + Both, +} + +/// Shut down part of a full-duplex connection. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html) +pub fn shutdown(df: RawFd, how: Shutdown) -> Result<()> { + unsafe { + use libc::shutdown; + + let how = match how { + Shutdown::Read => libc::SHUT_RD, + Shutdown::Write => libc::SHUT_WR, + Shutdown::Both => libc::SHUT_RDWR, + }; + + Errno::result(shutdown(df, how)).map(drop) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn can_use_cmsg_space() { + let _ = cmsg_space!(u8); + } +} diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs new file mode 100644 index 0000000..06e9ee4 --- /dev/null +++ b/src/sys/socket/sockopt.rs @@ -0,0 +1,1422 @@ +//! Socket options as used by `setsockopt` and `getsockopt`. +use super::{GetSockOpt, SetSockOpt}; +use crate::errno::Errno; +use crate::sys::time::TimeVal; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int, c_void, socklen_t}; +use std::ffi::{OsStr, OsString}; +use std::{ + convert::TryFrom, + mem::{self, MaybeUninit} +}; +#[cfg(target_family = "unix")] +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::RawFd; + +// Constants +// TCP_CA_NAME_MAX isn't defined in user space include files +#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +const TCP_CA_NAME_MAX: usize = 16; + +/// Helper for implementing `SetSockOpt` for a given socket option. See +/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html). +/// +/// This macro aims to help implementing `SetSockOpt` for different socket options that accept +/// different kinds of data to be used with `setsockopt`. +/// +/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option +/// you are implementing represents a simple type. +/// +/// # Arguments +/// +/// * `$name:ident`: name of the type you want to implement `SetSockOpt` for. +/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* +/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), +/// and more. Please refer to your system manual for more options. Will be passed as the second +/// argument (`level`) to the `setsockopt` call. +/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, +/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) +/// to the `setsockopt` call. +/// * Type of the value that you are going to set. +/// * Type that implements the `Set` trait for the type from the previous item (like `SetBool` for +/// `bool`, `SetUsize` for `usize`, etc.). +macro_rules! setsockopt_impl { + ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { + impl SetSockOpt for $name { + type Val = $ty; + + fn set(&self, fd: RawFd, val: &$ty) -> Result<()> { + unsafe { + let setter: $setter = Set::new(val); + + let res = libc::setsockopt( + fd, + $level, + $flag, + setter.ffi_ptr(), + setter.ffi_len(), + ); + Errno::result(res).map(drop) + } + } + } + }; +} + +/// Helper for implementing `GetSockOpt` for a given socket option. See +/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html). +/// +/// This macro aims to help implementing `GetSockOpt` for different socket options that accept +/// different kinds of data to be use with `getsockopt`. +/// +/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option +/// you are implementing represents a simple type. +/// +/// # Arguments +/// +/// * Name of the type you want to implement `GetSockOpt` for. +/// * Socket layer, or a `protocol level`: could be *raw sockets* (`lic::SOL_SOCKET`), *ip +/// protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), and more. Please refer +/// to your system manual for more options. Will be passed as the second argument (`level`) to +/// the `getsockopt` call. +/// * A flag to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, +/// `libc::SO_ORIGINAL_DST` and others. Will be passed as the third argument (`option_name`) to +/// the `getsockopt` call. +/// * Type of the value that you are going to get. +/// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for +/// `bool`, `GetUsize` for `usize`, etc.). +macro_rules! getsockopt_impl { + ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { + impl GetSockOpt for $name { + type Val = $ty; + + fn get(&self, fd: RawFd) -> Result<$ty> { + unsafe { + let mut getter: $getter = Get::uninit(); + + let res = libc::getsockopt( + fd, + $level, + $flag, + getter.ffi_ptr(), + getter.ffi_len(), + ); + Errno::result(res)?; + + match <$ty>::try_from(getter.assume_init()) { + Err(_) => Err(Errno::EINVAL), + Ok(r) => Ok(r) + } + } + } + } + }; +} + +/// Helper to generate the sockopt accessors. See +/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html) and +/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html). +/// +/// This macro aims to help implementing `GetSockOpt` and `SetSockOpt` for different socket options +/// that accept different kinds of data to be use with `getsockopt` and `setsockopt` respectively. +/// +/// Basically this macro wraps up the [`getsockopt_impl!`](macro.getsockopt_impl.html) and +/// [`setsockopt_impl!`](macro.setsockopt_impl.html) macros. +/// +/// # Arguments +/// +/// * `GetOnly`, `SetOnly` or `Both`: whether you want to implement only getter, only setter or +/// both of them. +/// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for. +/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* +/// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), +/// and more. Please refer to your system manual for more options. Will be passed as the second +/// argument (`level`) to the `getsockopt`/`setsockopt` call. +/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, +/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) +/// to the `setsockopt`/`getsockopt` call. +/// * `$ty:ty`: type of the value that will be get/set. +/// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`. +/// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`. +// Some targets don't use all rules. +#[allow(unknown_lints)] +#[allow(unused_macro_rules)] +macro_rules! sockopt_impl { + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, bool, GetBool); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, GetU8); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, usize, GetUsize); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, bool, SetBool); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, SetU8); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, usize, SetUsize); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, bool, GetBool, SetBool); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, u8, GetU8, SetU8); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, usize, GetUsize, SetUsize); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, + OsString<$array:ty>) => + { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, OsString, GetOsString<$array>, + SetOsString); + }; + + /* + * Matchers with generic getter types must be placed at the end, so + * they'll only match _after_ specialized matchers fail + */ + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, $ty, GetStruct<$ty>); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty, + $getter:ty) => + { + $(#[$attr])* + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct $name; + + getsockopt_impl!($name, $level, $flag, $ty, $getter); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, $ty, SetStruct<$ty>); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty, + $setter:ty) => + { + $(#[$attr])* + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct $name; + + setsockopt_impl!($name, $level, $flag, $ty, $setter); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty, + $getter:ty, $setter:ty) => + { + $(#[$attr])* + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct $name; + + setsockopt_impl!($name, $level, $flag, $ty, $setter); + getsockopt_impl!($name, $level, $flag, $ty, $getter); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, $ty, GetStruct<$ty>, + SetStruct<$ty>); + }; +} + +/* + * + * ===== Define sockopts ===== + * + */ + +sockopt_impl!( + /// Enables local address reuse + ReuseAddr, + Both, + libc::SOL_SOCKET, + libc::SO_REUSEADDR, + bool +); +#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] +sockopt_impl!( + /// Permits multiple AF_INET or AF_INET6 sockets to be bound to an + /// identical socket address. + ReusePort, + Both, + libc::SOL_SOCKET, + libc::SO_REUSEPORT, + bool +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Under most circumstances, TCP sends data when it is presented; when + /// outstanding data has not yet been acknowledged, it gathers small amounts + /// of output to be sent in a single packet once an acknowledgement is + /// received. For a small number of clients, such as window systems that + /// send a stream of mouse events which receive no replies, this + /// packetization may cause significant delays. The boolean option + /// TCP_NODELAY defeats this algorithm. + TcpNoDelay, + Both, + libc::IPPROTO_TCP, + libc::TCP_NODELAY, + bool +); +sockopt_impl!( + /// When enabled, a close(2) or shutdown(2) will not return until all + /// queued messages for the socket have been successfully sent or the + /// linger timeout has been reached. + Linger, + Both, + libc::SOL_SOCKET, + libc::SO_LINGER, + libc::linger +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Join a multicast group + IpAddMembership, + SetOnly, + libc::IPPROTO_IP, + libc::IP_ADD_MEMBERSHIP, + super::IpMembershipRequest +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Leave a multicast group. + IpDropMembership, + SetOnly, + libc::IPPROTO_IP, + libc::IP_DROP_MEMBERSHIP, + super::IpMembershipRequest +); +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Join an IPv6 multicast group. + Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_ADD_MEMBERSHIP, super::Ipv6MembershipRequest); + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Leave an IPv6 multicast group. + Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, super::Ipv6MembershipRequest); + } else if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] { + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Join an IPv6 multicast group. + Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6, + libc::IPV6_JOIN_GROUP, super::Ipv6MembershipRequest); + #[cfg(feature = "net")] + sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Leave an IPv6 multicast group. + Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, + libc::IPV6_LEAVE_GROUP, super::Ipv6MembershipRequest); + } +} +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or read the time-to-live value of outgoing multicast packets for + /// this socket. + IpMulticastTtl, + Both, + libc::IPPROTO_IP, + libc::IP_MULTICAST_TTL, + u8 +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or read a boolean integer argument that determines whether sent + /// multicast packets should be looped back to the local sockets. + IpMulticastLoop, + Both, + libc::IPPROTO_IP, + libc::IP_MULTICAST_LOOP, + bool +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set the protocol-defined priority for all packets to be + /// sent on this socket + Priority, + Both, + libc::SOL_SOCKET, + libc::SO_PRIORITY, + libc::c_int +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or receive the Type-Of-Service (TOS) field that is + /// sent with every IP packet originating from this socket + IpTos, + Both, + libc::IPPROTO_IP, + libc::IP_TOS, + libc::c_int +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Traffic class associated with outgoing packets + Ipv6TClass, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_TCLASS, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// If enabled, this boolean option allows binding to an IP address that + /// is nonlocal or does not (yet) exist. + IpFreebind, + Both, + libc::IPPROTO_IP, + libc::IP_FREEBIND, + bool +); +sockopt_impl!( + /// Specify the receiving timeout until reporting an error. + ReceiveTimeout, + Both, + libc::SOL_SOCKET, + libc::SO_RCVTIMEO, + TimeVal +); +sockopt_impl!( + /// Specify the sending timeout until reporting an error. + SendTimeout, + Both, + libc::SOL_SOCKET, + libc::SO_SNDTIMEO, + TimeVal +); +sockopt_impl!( + /// Set or get the broadcast flag. + Broadcast, + Both, + libc::SOL_SOCKET, + libc::SO_BROADCAST, + bool +); +sockopt_impl!( + /// If this option is enabled, out-of-band data is directly placed into + /// the receive data stream. + OobInline, + Both, + libc::SOL_SOCKET, + libc::SO_OOBINLINE, + bool +); +sockopt_impl!( + /// Get and clear the pending socket error. + SocketError, + GetOnly, + libc::SOL_SOCKET, + libc::SO_ERROR, + i32 +); +sockopt_impl!( + /// Set or get the don't route flag. + DontRoute, + Both, + libc::SOL_SOCKET, + libc::SO_DONTROUTE, + bool +); +sockopt_impl!( + /// Enable sending of keep-alive messages on connection-oriented sockets. + KeepAlive, + Both, + libc::SOL_SOCKET, + libc::SO_KEEPALIVE, + bool +); +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" +))] +sockopt_impl!( + /// Get the credentials of the peer process of a connected unix domain + /// socket. + LocalPeerCred, + GetOnly, + 0, + libc::LOCAL_PEERCRED, + super::XuCred +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Return the credentials of the foreign process connected to this socket. + PeerCredentials, + GetOnly, + libc::SOL_SOCKET, + libc::SO_PEERCRED, + super::UnixCredentials +); +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Specify the amount of time, in seconds, that the connection must be idle + /// before keepalive probes (if enabled) are sent. + TcpKeepAlive, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPALIVE, + u32 +); +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The time (in seconds) the connection needs to remain idle before TCP + /// starts sending keepalive probes + TcpKeepIdle, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPIDLE, + u32 +); +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + sockopt_impl!( + /// The maximum segment size for outgoing TCP packets. + TcpMaxSeg, Both, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32); + } else { + sockopt_impl!( + /// The maximum segment size for outgoing TCP packets. + TcpMaxSeg, GetOnly, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32); + } +} +#[cfg(not(any(target_os = "openbsd", target_os = "haiku")))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The maximum number of keepalive probes TCP should send before + /// dropping the connection. + TcpKeepCount, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPCNT, + u32 +); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!( + #[allow(missing_docs)] + // Not documented by Linux! + TcpRepair, + Both, + libc::IPPROTO_TCP, + libc::TCP_REPAIR, + u32 +); +#[cfg(not(any(target_os = "openbsd", target_os = "haiku")))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The time (in seconds) between individual keepalive probes. + TcpKeepInterval, + Both, + libc::IPPROTO_TCP, + libc::TCP_KEEPINTVL, + u32 +); +#[cfg(any(target_os = "fuchsia", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Specifies the maximum amount of time in milliseconds that transmitted + /// data may remain unacknowledged before TCP will forcibly close the + /// corresponding connection + TcpUserTimeout, + Both, + libc::IPPROTO_TCP, + libc::TCP_USER_TIMEOUT, + u32 +); +sockopt_impl!( + /// Sets or gets the maximum socket receive buffer in bytes. + RcvBuf, + Both, + libc::SOL_SOCKET, + libc::SO_RCVBUF, + usize +); +sockopt_impl!( + /// Sets or gets the maximum socket send buffer in bytes. + SndBuf, + Both, + libc::SOL_SOCKET, + libc::SO_SNDBUF, + usize +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can + /// perform the same task as `SO_RCVBUF`, but the `rmem_max limit` can be + /// overridden. + RcvBufForce, + SetOnly, + libc::SOL_SOCKET, + libc::SO_RCVBUFFORCE, + usize +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can + /// perform the same task as `SO_SNDBUF`, but the `wmem_max` limit can be + /// overridden. + SndBufForce, + SetOnly, + libc::SOL_SOCKET, + libc::SO_SNDBUFFORCE, + usize +); +sockopt_impl!( + /// Gets the socket type as an integer. + SockType, + GetOnly, + libc::SOL_SOCKET, + libc::SO_TYPE, + super::SockType, + GetStruct +); +sockopt_impl!( + /// Returns a value indicating whether or not this socket has been marked to + /// accept connections with `listen(2)`. + AcceptConn, + GetOnly, + libc::SOL_SOCKET, + libc::SO_ACCEPTCONN, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Bind this socket to a particular device like “eth0”. + BindToDevice, + Both, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + OsString<[u8; libc::IFNAMSIZ]> +); +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[allow(missing_docs)] + // Not documented by Linux! + OriginalDst, + GetOnly, + libc::SOL_IP, + libc::SO_ORIGINAL_DST, + libc::sockaddr_in +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + #[allow(missing_docs)] + // Not documented by Linux! + Ip6tOriginalDst, + GetOnly, + libc::SOL_IPV6, + libc::IP6T_SO_ORIGINAL_DST, + libc::sockaddr_in6 +); +#[cfg(any(target_os = "linux"))] +sockopt_impl!( + /// Specifies exact type of timestamping information collected by the kernel + /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) + Timestamping, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMPING, + super::TimestampingFlag +); +#[cfg(not(target_os = "haiku"))] +sockopt_impl!( + /// Enable or disable the receiving of the `SO_TIMESTAMP` control message. + ReceiveTimestamp, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMP, + bool +); +#[cfg(all(target_os = "linux"))] +sockopt_impl!( + /// Enable or disable the receiving of the `SO_TIMESTAMPNS` control message. + ReceiveTimestampns, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMPNS, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Setting this boolean option enables transparent proxying on this socket. + IpTransparent, + Both, + libc::SOL_IP, + libc::IP_TRANSPARENT, + bool +); +#[cfg(target_os = "openbsd")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Allows the socket to be bound to addresses which are not local to the + /// machine, so it can be used to make a transparent proxy. + BindAny, + Both, + libc::SOL_SOCKET, + libc::SO_BINDANY, + bool +); +#[cfg(target_os = "freebsd")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Can `bind(2)` to any address, even one not bound to any available + /// network interface in the system. + BindAny, + Both, + libc::IPPROTO_IP, + libc::IP_BINDANY, + bool +); +#[cfg(target_os = "linux")] +sockopt_impl!( + /// Set the mark for each packet sent through this socket (similar to the + /// netfilter MARK target but socket-based). + Mark, + Both, + libc::SOL_SOCKET, + libc::SO_MARK, + u32 +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Enable or disable the receiving of the `SCM_CREDENTIALS` control + /// message. + PassCred, + Both, + libc::SOL_SOCKET, + libc::SO_PASSCRED, + bool +); +#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// This option allows the caller to set the TCP congestion control + /// algorithm to be used, on a per-socket basis. + TcpCongestion, + Both, + libc::IPPROTO_TCP, + libc::TCP_CONGESTION, + OsString<[u8; TCP_CA_NAME_MAX]> +); +#[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Pass an `IP_PKTINFO` ancillary message that contains a pktinfo + /// structure that supplies some information about the incoming packet. + Ipv4PacketInfo, + Both, + libc::IPPROTO_IP, + libc::IP_PKTINFO, + bool +); +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set delivery of the `IPV6_PKTINFO` control message on incoming + /// datagrams. + Ipv6RecvPacketInfo, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVPKTINFO, + bool +); +#[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call returns a `struct sockaddr_dl` corresponding to + /// the interface on which the packet was received. + Ipv4RecvIf, + Both, + libc::IPPROTO_IP, + libc::IP_RECVIF, + bool +); +#[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv4RecvDstAddr, + Both, + libc::IPPROTO_IP, + libc::IP_RECVDSTADDR, + bool +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv4OrigDstAddr, + Both, + libc::IPPROTO_IP, + libc::IP_ORIGDSTADDR, + bool +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[allow(missing_docs)] + // Not documented by Linux! + UdpGsoSegment, + Both, + libc::SOL_UDP, + libc::UDP_SEGMENT, + libc::c_int +); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + #[allow(missing_docs)] + // Not documented by Linux! + UdpGroSegment, + Both, + libc::IPPROTO_UDP, + libc::UDP_GRO, + bool +); +#[cfg(target_os = "linux")] +sockopt_impl!( + /// Configures the behavior of time-based transmission of packets, for use + /// with the `TxTime` control message. + TxTime, + Both, + libc::SOL_SOCKET, + libc::SO_TXTIME, + libc::sock_txtime +); +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +sockopt_impl!( + /// Indicates that an unsigned 32-bit value ancillary message (cmsg) should + /// be attached to received skbs indicating the number of packets dropped by + /// the socket since its creation. + RxqOvfl, + Both, + libc::SOL_SOCKET, + libc::SO_RXQ_OVFL, + libc::c_int +); +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The socket is restricted to sending and receiving IPv6 packets only. + Ipv6V6Only, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_V6ONLY, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Enable extended reliable error message passing. + Ipv4RecvErr, + Both, + libc::IPPROTO_IP, + libc::IP_RECVERR, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Control receiving of asynchronous error options. + Ipv6RecvErr, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVERR, + bool +); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!( + /// Fetch the current system-estimated Path MTU. + IpMtu, + GetOnly, + libc::IPPROTO_IP, + libc::IP_MTU, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +sockopt_impl!( + /// Set or retrieve the current time-to-live field that is used in every + /// packet sent from this socket. + Ipv4Ttl, + Both, + libc::IPPROTO_IP, + libc::IP_TTL, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +sockopt_impl!( + /// Set the unicast hop limit for the socket. + Ipv6Ttl, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_UNICAST_HOPS, + libc::c_int +); +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv6OrigDstAddr, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_ORIGDSTADDR, + bool +); +#[cfg(any(target_os = "ios", target_os = "macos"))] +sockopt_impl!( + /// Set "don't fragment packet" flag on the IP packet. + IpDontFrag, + Both, + libc::IPPROTO_IP, + libc::IP_DONTFRAG, + bool +); +#[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", +))] +sockopt_impl!( + /// Set "don't fragment packet" flag on the IPv6 packet. + Ipv6DontFrag, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_DONTFRAG, + bool +); + +#[allow(missing_docs)] +// Not documented by Linux! +#[cfg(any(target_os = "android", target_os = "linux"))] +#[derive(Copy, Clone, Debug)] +pub struct AlgSetAeadAuthSize; + +// ALG_SET_AEAD_AUTH_SIZE read the length from passed `option_len` +// See https://elixir.bootlin.com/linux/v4.4/source/crypto/af_alg.c#L222 +#[cfg(any(target_os = "android", target_os = "linux"))] +impl SetSockOpt for AlgSetAeadAuthSize { + type Val = usize; + + fn set(&self, fd: RawFd, val: &usize) -> Result<()> { + unsafe { + let res = libc::setsockopt( + fd, + libc::SOL_ALG, + libc::ALG_SET_AEAD_AUTHSIZE, + ::std::ptr::null(), + *val as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +#[allow(missing_docs)] +// Not documented by Linux! +#[cfg(any(target_os = "android", target_os = "linux"))] +#[derive(Clone, Debug)] +pub struct AlgSetKey(::std::marker::PhantomData); + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl Default for AlgSetKey { + fn default() -> Self { + AlgSetKey(Default::default()) + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl SetSockOpt for AlgSetKey +where + T: AsRef<[u8]> + Clone, +{ + type Val = T; + + fn set(&self, fd: RawFd, val: &T) -> Result<()> { + unsafe { + let res = libc::setsockopt( + fd, + libc::SOL_ALG, + libc::ALG_SET_KEY, + val.as_ref().as_ptr() as *const _, + val.as_ref().len() as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +/* + * + * ===== Accessor helpers ===== + * + */ + +/// Helper trait that describes what is expected from a `GetSockOpt` getter. +trait Get { + /// Returns an uninitialized value. + fn uninit() -> Self; + /// Returns a pointer to the stored value. This pointer will be passed to the system's + /// `getsockopt` call (`man 3p getsockopt`, argument `option_value`). + fn ffi_ptr(&mut self) -> *mut c_void; + /// Returns length of the stored value. This pointer will be passed to the system's + /// `getsockopt` call (`man 3p getsockopt`, argument `option_len`). + fn ffi_len(&mut self) -> *mut socklen_t; + /// Returns the hopefully initialized inner value. + unsafe fn assume_init(self) -> T; +} + +/// Helper trait that describes what is expected from a `SetSockOpt` setter. +trait Set<'a, T> { + /// Initialize the setter with a given value. + fn new(val: &'a T) -> Self; + /// Returns a pointer to the stored value. This pointer will be passed to the system's + /// `setsockopt` call (`man 3p setsockopt`, argument `option_value`). + fn ffi_ptr(&self) -> *const c_void; + /// Returns length of the stored value. This pointer will be passed to the system's + /// `setsockopt` call (`man 3p setsockopt`, argument `option_len`). + fn ffi_len(&self) -> socklen_t; +} + +/// Getter for an arbitrary `struct`. +struct GetStruct { + len: socklen_t, + val: MaybeUninit, +} + +impl Get for GetStruct { + fn uninit() -> Self { + GetStruct { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> T { + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); + self.val.assume_init() + } +} + +/// Setter for an arbitrary `struct`. +struct SetStruct<'a, T: 'static> { + ptr: &'a T, +} + +impl<'a, T> Set<'a, T> for SetStruct<'a, T> { + fn new(ptr: &'a T) -> SetStruct<'a, T> { + SetStruct { ptr } + } + + fn ffi_ptr(&self) -> *const c_void { + self.ptr as *const T as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::() as socklen_t + } +} + +/// Getter for a boolean value. +struct GetBool { + len: socklen_t, + val: MaybeUninit, +} + +impl Get for GetBool { + fn uninit() -> Self { + GetBool { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> bool { + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); + self.val.assume_init() != 0 + } +} + +/// Setter for a boolean value. +struct SetBool { + val: c_int, +} + +impl<'a> Set<'a, bool> for SetBool { + fn new(val: &'a bool) -> SetBool { + SetBool { + val: i32::from(*val), + } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const c_int as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::() as socklen_t + } +} + +/// Getter for an `u8` value. +struct GetU8 { + len: socklen_t, + val: MaybeUninit, +} + +impl Get for GetU8 { + fn uninit() -> Self { + GetU8 { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> u8 { + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); + self.val.assume_init() + } +} + +/// Setter for an `u8` value. +struct SetU8 { + val: u8, +} + +impl<'a> Set<'a, u8> for SetU8 { + fn new(val: &'a u8) -> SetU8 { + SetU8 { val: *val } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const u8 as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::() as socklen_t + } +} + +/// Getter for an `usize` value. +struct GetUsize { + len: socklen_t, + val: MaybeUninit, +} + +impl Get for GetUsize { + fn uninit() -> Self { + GetUsize { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> usize { + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); + self.val.assume_init() as usize + } +} + +/// Setter for an `usize` value. +struct SetUsize { + val: c_int, +} + +impl<'a> Set<'a, usize> for SetUsize { + fn new(val: &'a usize) -> SetUsize { + SetUsize { val: *val as c_int } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const c_int as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of::() as socklen_t + } +} + +/// Getter for a `OsString` value. +struct GetOsString> { + len: socklen_t, + val: MaybeUninit, +} + +impl> Get for GetOsString { + fn uninit() -> Self { + GetOsString { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr() as *mut c_void + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> OsString { + let len = self.len as usize; + let mut v = self.val.assume_init(); + OsStr::from_bytes(&v.as_mut()[0..len]).to_owned() + } +} + +/// Setter for a `OsString` value. +struct SetOsString<'a> { + val: &'a OsStr, +} + +impl<'a> Set<'a, OsString> for SetOsString<'a> { + fn new(val: &'a OsString) -> SetOsString { + SetOsString { + val: val.as_os_str(), + } + } + + fn ffi_ptr(&self) -> *const c_void { + self.val.as_bytes().as_ptr() as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + self.val.len() as socklen_t + } +} + +#[cfg(test)] +mod test { + #[cfg(any(target_os = "android", target_os = "linux"))] + #[test] + fn can_get_peercred_on_unix_socket() { + use super::super::*; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let a_cred = getsockopt(a, super::PeerCredentials).unwrap(); + let b_cred = getsockopt(b, super::PeerCredentials).unwrap(); + assert_eq!(a_cred, b_cred); + assert_ne!(a_cred.pid(), 0); + } + + #[test] + fn is_socket_type_unix() { + use super::super::*; + use crate::unistd::close; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let a_type = getsockopt(a, super::SockType).unwrap(); + assert_eq!(a_type, SockType::Stream); + close(a).unwrap(); + close(b).unwrap(); + } + + #[test] + fn is_socket_type_dgram() { + use super::super::*; + use crate::unistd::close; + + let s = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + let s_type = getsockopt(s, super::SockType).unwrap(); + assert_eq!(s_type, SockType::Datagram); + close(s).unwrap(); + } + + #[cfg(any(target_os = "freebsd", target_os = "linux"))] + #[test] + fn can_get_listen_on_tcp_socket() { + use super::super::*; + use crate::unistd::close; + + let s = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + let s_listening = getsockopt(s, super::AcceptConn).unwrap(); + assert!(!s_listening); + listen(s, 10).unwrap(); + let s_listening2 = getsockopt(s, super::AcceptConn).unwrap(); + assert!(s_listening2); + close(s).unwrap(); + } +} diff --git a/src/sys/stat.rs b/src/sys/stat.rs new file mode 100644 index 0000000..78203bf --- /dev/null +++ b/src/sys/stat.rs @@ -0,0 +1,480 @@ +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +pub use libc::c_uint; +#[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly" +))] +pub use libc::c_ulong; +pub use libc::stat as FileStat; +pub use libc::{dev_t, mode_t}; + +#[cfg(not(target_os = "redox"))] +use crate::fcntl::{at_rawfd, AtFlags}; +use crate::sys::time::{TimeSpec, TimeVal}; +use crate::{errno::Errno, NixPath, Result}; +use std::mem; +use std::os::unix::io::RawFd; + +libc_bitflags!( + /// "File type" flags for `mknod` and related functions. + pub struct SFlag: mode_t { + S_IFIFO; + S_IFCHR; + S_IFDIR; + S_IFBLK; + S_IFREG; + S_IFLNK; + S_IFSOCK; + S_IFMT; + } +); + +libc_bitflags! { + /// "File mode / permissions" flags. + pub struct Mode: mode_t { + /// Read, write and execute for owner. + S_IRWXU; + /// Read for owner. + S_IRUSR; + /// Write for owner. + S_IWUSR; + /// Execute for owner. + S_IXUSR; + /// Read write and execute for group. + S_IRWXG; + /// Read fr group. + S_IRGRP; + /// Write for group. + S_IWGRP; + /// Execute for group. + S_IXGRP; + /// Read, write and execute for other. + S_IRWXO; + /// Read for other. + S_IROTH; + /// Write for other. + S_IWOTH; + /// Execute for other. + S_IXOTH; + /// Set user id on execution. + S_ISUID as mode_t; + /// Set group id on execution. + S_ISGID as mode_t; + S_ISVTX as mode_t; + } +} + +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +pub type type_of_file_flag = c_uint; +#[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly" +))] +pub type type_of_file_flag = c_ulong; + +#[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "ios" +))] +libc_bitflags! { + /// File flags. + #[cfg_attr(docsrs, doc(cfg(all())))] + pub struct FileFlag: type_of_file_flag { + /// The file may only be appended to. + SF_APPEND; + /// The file has been archived. + SF_ARCHIVED; + #[cfg(any(target_os = "dragonfly"))] + SF_CACHE; + /// The file may not be changed. + SF_IMMUTABLE; + /// Indicates a WAPBL journal file. + #[cfg(any(target_os = "netbsd"))] + SF_LOG; + /// Do not retain history for file + #[cfg(any(target_os = "dragonfly"))] + SF_NOHISTORY; + /// The file may not be renamed or deleted. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + SF_NOUNLINK; + /// Mask of superuser changeable flags + SF_SETTABLE; + /// Snapshot is invalid. + #[cfg(any(target_os = "netbsd"))] + SF_SNAPINVAL; + /// The file is a snapshot file. + #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] + SF_SNAPSHOT; + #[cfg(any(target_os = "dragonfly"))] + SF_XLINK; + /// The file may only be appended to. + UF_APPEND; + /// The file needs to be archived. + #[cfg(any(target_os = "freebsd"))] + UF_ARCHIVE; + #[cfg(any(target_os = "dragonfly"))] + UF_CACHE; + /// File is compressed at the file system level. + #[cfg(any(target_os = "macos", target_os = "ios"))] + UF_COMPRESSED; + /// The file may be hidden from directory listings at the application's + /// discretion. + #[cfg(any( + target_os = "freebsd", + target_os = "macos", + target_os = "ios", + ))] + UF_HIDDEN; + /// The file may not be changed. + UF_IMMUTABLE; + /// Do not dump the file. + UF_NODUMP; + #[cfg(any(target_os = "dragonfly"))] + UF_NOHISTORY; + /// The file may not be renamed or deleted. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + UF_NOUNLINK; + /// The file is offline, or has the Windows and CIFS + /// `FILE_ATTRIBUTE_OFFLINE` attribute. + #[cfg(any(target_os = "freebsd"))] + UF_OFFLINE; + /// The directory is opaque when viewed through a union stack. + UF_OPAQUE; + /// The file is read only, and may not be written or appended. + #[cfg(any(target_os = "freebsd"))] + UF_READONLY; + /// The file contains a Windows reparse point. + #[cfg(any(target_os = "freebsd"))] + UF_REPARSE; + /// Mask of owner changeable flags. + UF_SETTABLE; + /// The file has the Windows `FILE_ATTRIBUTE_SPARSE_FILE` attribute. + #[cfg(any(target_os = "freebsd"))] + UF_SPARSE; + /// The file has the DOS, Windows and CIFS `FILE_ATTRIBUTE_SYSTEM` + /// attribute. + #[cfg(any(target_os = "freebsd"))] + UF_SYSTEM; + /// File renames and deletes are tracked. + #[cfg(any(target_os = "macos", target_os = "ios"))] + UF_TRACKED; + #[cfg(any(target_os = "dragonfly"))] + UF_XLINK; + } +} + +/// Create a special or ordinary file, by pathname. +pub fn mknod( + path: &P, + kind: SFlag, + perm: Mode, + dev: dev_t, +) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mknod(cstr.as_ptr(), kind.bits | perm.bits() as mode_t, dev) + })?; + + Errno::result(res).map(drop) +} + +/// Create a special or ordinary file, relative to a given directory. +#[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "redox", + target_os = "haiku" +)))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn mknodat( + dirfd: RawFd, + path: &P, + kind: SFlag, + perm: Mode, + dev: dev_t, +) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mknodat( + dirfd, + cstr.as_ptr(), + kind.bits | perm.bits() as mode_t, + dev, + ) + })?; + + Errno::result(res).map(drop) +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub const fn major(dev: dev_t) -> u64 { + ((dev >> 32) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff) +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub const fn minor(dev: dev_t) -> u64 { + ((dev >> 12) & 0xffff_ff00) | ((dev) & 0x0000_00ff) +} + +#[cfg(target_os = "linux")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub const fn makedev(major: u64, minor: u64) -> dev_t { + ((major & 0xffff_f000) << 32) + | ((major & 0x0000_0fff) << 8) + | ((minor & 0xffff_ff00) << 12) + | (minor & 0x0000_00ff) +} + +pub fn umask(mode: Mode) -> Mode { + let prev = unsafe { libc::umask(mode.bits() as mode_t) }; + Mode::from_bits(prev).expect("[BUG] umask returned invalid Mode") +} + +pub fn stat(path: &P) -> Result { + let mut dst = mem::MaybeUninit::uninit(); + let res = path.with_nix_path(|cstr| unsafe { + libc::stat(cstr.as_ptr(), dst.as_mut_ptr()) + })?; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +pub fn lstat(path: &P) -> Result { + let mut dst = mem::MaybeUninit::uninit(); + let res = path.with_nix_path(|cstr| unsafe { + libc::lstat(cstr.as_ptr(), dst.as_mut_ptr()) + })?; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +pub fn fstat(fd: RawFd) -> Result { + let mut dst = mem::MaybeUninit::uninit(); + let res = unsafe { libc::fstat(fd, dst.as_mut_ptr()) }; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn fstatat( + dirfd: RawFd, + pathname: &P, + f: AtFlags, +) -> Result { + let mut dst = mem::MaybeUninit::uninit(); + let res = pathname.with_nix_path(|cstr| unsafe { + libc::fstatat( + dirfd, + cstr.as_ptr(), + dst.as_mut_ptr(), + f.bits() as libc::c_int, + ) + })?; + + Errno::result(res)?; + + Ok(unsafe { dst.assume_init() }) +} + +/// Change the file permission bits of the file specified by a file descriptor. +/// +/// # References +/// +/// [fchmod(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html). +pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> { + let res = unsafe { libc::fchmod(fd, mode.bits() as mode_t) }; + + Errno::result(res).map(drop) +} + +/// Flags for `fchmodat` function. +#[derive(Clone, Copy, Debug)] +pub enum FchmodatFlags { + FollowSymlink, + NoFollowSymlink, +} + +/// Change the file permission bits. +/// +/// The file to be changed is determined relative to the directory associated +/// with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. +/// +/// If `flag` is `FchmodatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// then the mode of the symbolic link is changed. +/// +/// `fchmodat(None, path, mode, FchmodatFlags::FollowSymlink)` is identical to +/// a call `libc::chmod(path, mode)`. That's why `chmod` is unimplemented +/// in the `nix` crate. +/// +/// # References +/// +/// [fchmodat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html). +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn fchmodat( + dirfd: Option, + path: &P, + mode: Mode, + flag: FchmodatFlags, +) -> Result<()> { + let atflag = match flag { + FchmodatFlags::FollowSymlink => AtFlags::empty(), + FchmodatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + let res = path.with_nix_path(|cstr| unsafe { + libc::fchmodat( + at_rawfd(dirfd), + cstr.as_ptr(), + mode.bits() as mode_t, + atflag.bits() as libc::c_int, + ) + })?; + + Errno::result(res).map(drop) +} + +/// Change the access and modification times of a file. +/// +/// `utimes(path, times)` is identical to +/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)`. The former +/// is a deprecated API so prefer using the latter if the platforms you care +/// about support it. +/// +/// # References +/// +/// [utimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html). +pub fn utimes( + path: &P, + atime: &TimeVal, + mtime: &TimeVal, +) -> Result<()> { + let times: [libc::timeval; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::utimes(cstr.as_ptr(), ×[0]) + })?; + + Errno::result(res).map(drop) +} + +/// Change the access and modification times of a file without following symlinks. +/// +/// `lutimes(path, times)` is identical to +/// `utimensat(None, path, times, UtimensatFlags::NoFollowSymlink)`. The former +/// is a deprecated API so prefer using the latter if the platforms you care +/// about support it. +/// +/// # References +/// +/// [lutimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lutimes.html). +#[cfg(any( + target_os = "linux", + target_os = "haiku", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn lutimes( + path: &P, + atime: &TimeVal, + mtime: &TimeVal, +) -> Result<()> { + let times: [libc::timeval; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::lutimes(cstr.as_ptr(), ×[0]) + })?; + + Errno::result(res).map(drop) +} + +/// Change the access and modification times of the file specified by a file descriptor. +/// +/// # References +/// +/// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html). +#[inline] +pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> { + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = unsafe { libc::futimens(fd, ×[0]) }; + + Errno::result(res).map(drop) +} + +/// Flags for `utimensat` function. +// TODO: replace with fcntl::AtFlags +#[derive(Clone, Copy, Debug)] +pub enum UtimensatFlags { + FollowSymlink, + NoFollowSymlink, +} + +/// Change the access and modification times of a file. +/// +/// The file to be changed is determined relative to the directory associated +/// with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. +/// +/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// then the mode of the symbolic link is changed. +/// +/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)` is identical to +/// `utimes(path, times)`. The latter is a deprecated API so prefer using the +/// former if the platforms you care about support it. +/// +/// # References +/// +/// [utimensat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html). +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn utimensat( + dirfd: Option, + path: &P, + atime: &TimeSpec, + mtime: &TimeSpec, + flag: UtimensatFlags, +) -> Result<()> { + let atflag = match flag { + UtimensatFlags::FollowSymlink => AtFlags::empty(), + UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::utimensat( + at_rawfd(dirfd), + cstr.as_ptr(), + ×[0], + atflag.bits() as libc::c_int, + ) + })?; + + Errno::result(res).map(drop) +} + +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn mkdirat( + fd: RawFd, + path: &P, + mode: Mode, +) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mkdirat(fd, cstr.as_ptr(), mode.bits() as mode_t) + })?; + + Errno::result(res).map(drop) +} diff --git a/src/sys/statfs.rs b/src/sys/statfs.rs new file mode 100644 index 0000000..9be8ca6 --- /dev/null +++ b/src/sys/statfs.rs @@ -0,0 +1,853 @@ +//! Get filesystem statistics, non-portably +//! +//! See [`statvfs`](crate::sys::statvfs) for a portable alternative. +#[cfg(not(any(target_os = "linux", target_os = "android")))] +use std::ffi::CStr; +use std::fmt::{self, Debug}; +use std::mem; +use std::os::unix::io::AsRawFd; + +use cfg_if::cfg_if; + +#[cfg(all( + feature = "mount", + any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ) +))] +use crate::mount::MntFlags; +#[cfg(target_os = "linux")] +use crate::sys::statvfs::FsFlags; +use crate::{errno::Errno, NixPath, Result}; + +/// Identifies a mounted file system +#[cfg(target_os = "android")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub type fsid_t = libc::__fsid_t; +/// Identifies a mounted file system +#[cfg(not(target_os = "android"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub type fsid_t = libc::fsid_t; + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] { + type type_of_statfs = libc::statfs64; + const LIBC_FSTATFS: unsafe extern fn + (fd: libc::c_int, buf: *mut type_of_statfs) -> libc::c_int + = libc::fstatfs64; + const LIBC_STATFS: unsafe extern fn + (path: *const libc::c_char, buf: *mut type_of_statfs) -> libc::c_int + = libc::statfs64; + } else { + type type_of_statfs = libc::statfs; + const LIBC_FSTATFS: unsafe extern fn + (fd: libc::c_int, buf: *mut type_of_statfs) -> libc::c_int + = libc::fstatfs; + const LIBC_STATFS: unsafe extern fn + (path: *const libc::c_char, buf: *mut type_of_statfs) -> libc::c_int + = libc::statfs; + } +} + +/// Describes a mounted file system +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Statfs(type_of_statfs); + +#[cfg(target_os = "freebsd")] +type fs_type_t = u32; +#[cfg(target_os = "android")] +type fs_type_t = libc::c_ulong; +#[cfg(all(target_os = "linux", target_arch = "s390x"))] +type fs_type_t = libc::c_uint; +#[cfg(all(target_os = "linux", target_env = "musl"))] +type fs_type_t = libc::c_ulong; +#[cfg(all(target_os = "linux", target_env = "uclibc"))] +type fs_type_t = libc::c_int; +#[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) +))] +type fs_type_t = libc::__fsword_t; + +/// Describes the file system type as known by the operating system. +#[cfg(any( + target_os = "freebsd", + target_os = "android", + all(target_os = "linux", target_arch = "s390x"), + all(target_os = "linux", target_env = "musl"), + all( + target_os = "linux", + not(any(target_arch = "s390x", target_env = "musl")) + ), +))] +#[derive(Eq, Copy, Clone, PartialEq, Debug)] +pub struct FsType(pub fs_type_t); + +// These constants are defined without documentation in the Linux headers, so we +// can't very well document them here. +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const ADFS_SUPER_MAGIC: FsType = + FsType(libc::ADFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const AFFS_SUPER_MAGIC: FsType = + FsType(libc::AFFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const AFS_SUPER_MAGIC: FsType = FsType(libc::AFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const AUTOFS_SUPER_MAGIC: FsType = + FsType(libc::AUTOFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const BPF_FS_MAGIC: FsType = FsType(libc::BPF_FS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const BTRFS_SUPER_MAGIC: FsType = + FsType(libc::BTRFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CGROUP2_SUPER_MAGIC: FsType = + FsType(libc::CGROUP2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CGROUP_SUPER_MAGIC: FsType = + FsType(libc::CGROUP_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CODA_SUPER_MAGIC: FsType = + FsType(libc::CODA_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const CRAMFS_MAGIC: FsType = FsType(libc::CRAMFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const DEBUGFS_MAGIC: FsType = FsType(libc::DEBUGFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const DEVPTS_SUPER_MAGIC: FsType = + FsType(libc::DEVPTS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const ECRYPTFS_SUPER_MAGIC: FsType = + FsType(libc::ECRYPTFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EFS_SUPER_MAGIC: FsType = FsType(libc::EFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EXT2_SUPER_MAGIC: FsType = + FsType(libc::EXT2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EXT3_SUPER_MAGIC: FsType = + FsType(libc::EXT3_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const EXT4_SUPER_MAGIC: FsType = + FsType(libc::EXT4_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const F2FS_SUPER_MAGIC: FsType = + FsType(libc::F2FS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const FUSE_SUPER_MAGIC: FsType = + FsType(libc::FUSE_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const FUTEXFS_SUPER_MAGIC: FsType = + FsType(libc::FUTEXFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const HOSTFS_SUPER_MAGIC: FsType = + FsType(libc::HOSTFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const HPFS_SUPER_MAGIC: FsType = + FsType(libc::HPFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const HUGETLBFS_MAGIC: FsType = FsType(libc::HUGETLBFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const ISOFS_SUPER_MAGIC: FsType = + FsType(libc::ISOFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const JFFS2_SUPER_MAGIC: FsType = + FsType(libc::JFFS2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX2_SUPER_MAGIC2: FsType = + FsType(libc::MINIX2_SUPER_MAGIC2 as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX2_SUPER_MAGIC: FsType = + FsType(libc::MINIX2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX3_SUPER_MAGIC: FsType = + FsType(libc::MINIX3_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX_SUPER_MAGIC2: FsType = + FsType(libc::MINIX_SUPER_MAGIC2 as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MINIX_SUPER_MAGIC: FsType = + FsType(libc::MINIX_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const MSDOS_SUPER_MAGIC: FsType = + FsType(libc::MSDOS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NCP_SUPER_MAGIC: FsType = FsType(libc::NCP_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NFS_SUPER_MAGIC: FsType = FsType(libc::NFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NILFS_SUPER_MAGIC: FsType = + FsType(libc::NILFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const OCFS2_SUPER_MAGIC: FsType = + FsType(libc::OCFS2_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const OPENPROM_SUPER_MAGIC: FsType = + FsType(libc::OPENPROM_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const OVERLAYFS_SUPER_MAGIC: FsType = + FsType(libc::OVERLAYFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const PROC_SUPER_MAGIC: FsType = + FsType(libc::PROC_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const QNX4_SUPER_MAGIC: FsType = + FsType(libc::QNX4_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const QNX6_SUPER_MAGIC: FsType = + FsType(libc::QNX6_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const RDTGROUP_SUPER_MAGIC: FsType = + FsType(libc::RDTGROUP_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const REISERFS_SUPER_MAGIC: FsType = + FsType(libc::REISERFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SECURITYFS_MAGIC: FsType = + FsType(libc::SECURITYFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SELINUX_MAGIC: FsType = FsType(libc::SELINUX_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SMACK_MAGIC: FsType = FsType(libc::SMACK_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SMB_SUPER_MAGIC: FsType = FsType(libc::SMB_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const SYSFS_MAGIC: FsType = FsType(libc::SYSFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const TMPFS_MAGIC: FsType = FsType(libc::TMPFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const TRACEFS_MAGIC: FsType = FsType(libc::TRACEFS_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const UDF_SUPER_MAGIC: FsType = FsType(libc::UDF_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const USBDEVICE_SUPER_MAGIC: FsType = + FsType(libc::USBDEVICE_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const XENFS_SUPER_MAGIC: FsType = + FsType(libc::XENFS_SUPER_MAGIC as fs_type_t); +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(missing_docs)] +pub const NSFS_MAGIC: FsType = FsType(libc::NSFS_MAGIC as fs_type_t); +#[cfg(all( + any(target_os = "linux", target_os = "android"), + not(target_env = "musl") +))] +#[allow(missing_docs)] +pub const XFS_SUPER_MAGIC: FsType = FsType(libc::XFS_SUPER_MAGIC as fs_type_t); + +impl Statfs { + /// Magic code defining system type + #[cfg(not(any( + target_os = "openbsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos" + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn filesystem_type(&self) -> FsType { + FsType(self.0.f_type) + } + + /// Magic code defining system type + #[cfg(not(any(target_os = "linux", target_os = "android")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn filesystem_type_name(&self) -> &str { + let c_str = unsafe { CStr::from_ptr(self.0.f_fstypename.as_ptr()) }; + c_str.to_str().unwrap() + } + + /// Optimal transfer block size + #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> i32 { + self.0.f_iosize + } + + /// Optimal transfer block size + #[cfg(target_os = "openbsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> u32 { + self.0.f_iosize + } + + /// Optimal transfer block size + #[cfg(all(target_os = "linux", target_arch = "s390x"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> u32 { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(any( + target_os = "android", + all(target_os = "linux", target_env = "musl") + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::__fsword_t { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::c_int { + self.0.f_bsize + } + + /// Optimal transfer block size + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> libc::c_long { + self.0.f_iosize + } + + /// Optimal transfer block size + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn optimal_transfer_size(&self) -> u64 { + self.0.f_iosize + } + + /// Size of a block + #[cfg(any(target_os = "ios", target_os = "macos", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> u32 { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_arch = "s390x"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> u32 { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_env = "musl"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_int { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::__fsword_t { + self.0.f_bsize + } + + /// Size of a block + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> u64 { + self.0.f_bsize + } + + /// Size of a block + #[cfg(target_os = "android")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Size of a block + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn block_size(&self) -> libc::c_long { + self.0.f_bsize + } + + /// Get the mount flags + #[cfg(all( + feature = "mount", + any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(clippy::unnecessary_cast)] // Not unnecessary on all arches + pub fn flags(&self) -> MntFlags { + MntFlags::from_bits_truncate(self.0.f_flags as i32) + } + + /// Get the mount flags + // The f_flags field exists on Android and Fuchsia too, but without man + // pages I can't tell if it can be cast to FsFlags. + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn flags(&self) -> FsFlags { + FsFlags::from_bits_truncate(self.0.f_flags as libc::c_ulong) + } + + /// Maximum length of filenames + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> u32 { + self.0.f_namemax + } + + /// Maximum length of filenames + #[cfg(all(target_os = "linux", target_arch = "s390x"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> u32 { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(all(target_os = "linux", target_env = "musl"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::c_ulong { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(all(target_os = "linux", target_env = "uclibc"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::c_int { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "s390x", + target_env = "musl", + target_env = "uclibc" + )) + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::__fsword_t { + self.0.f_namelen + } + + /// Maximum length of filenames + #[cfg(target_os = "android")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn maximum_name_length(&self) -> libc::c_ulong { + self.0.f_namelen + } + + /// Total data blocks in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> u64 { + self.0.f_blocks + } + + /// Total data blocks in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> libc::c_long { + self.0.f_blocks + } + + /// Total data blocks in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> u32 { + self.0.f_blocks + } + + /// Free blocks in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> u64 { + self.0.f_bfree + } + + /// Free blocks in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> libc::c_long { + self.0.f_bfree + } + + /// Free blocks in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> u32 { + self.0.f_bfree + } + + /// Free blocks available to unprivileged user + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "fuchsia", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> u64 { + self.0.f_bavail + } + + /// Free blocks available to unprivileged user + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> libc::c_long { + self.0.f_bavail + } + + /// Free blocks available to unprivileged user + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> i64 { + self.0.f_bavail + } + + /// Free blocks available to unprivileged user + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> u32 { + self.0.f_bavail + } + + /// Total file nodes in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> u64 { + self.0.f_files + } + + /// Total file nodes in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> libc::c_long { + self.0.f_files + } + + /// Total file nodes in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> u32 { + self.0.f_files + } + + /// Free file nodes in filesystem + #[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "android", + target_os = "fuchsia", + target_os = "openbsd", + target_os = "linux", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> u64 { + self.0.f_ffree + } + + /// Free file nodes in filesystem + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> libc::c_long { + self.0.f_ffree + } + + /// Free file nodes in filesystem + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> i64 { + self.0.f_ffree + } + + /// Free file nodes in filesystem + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> u32 { + self.0.f_ffree + } + + /// Filesystem ID + pub fn filesystem_id(&self) -> fsid_t { + self.0.f_fsid + } +} + +impl Debug for Statfs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut ds = f.debug_struct("Statfs"); + ds.field("optimal_transfer_size", &self.optimal_transfer_size()); + ds.field("block_size", &self.block_size()); + ds.field("blocks", &self.blocks()); + ds.field("blocks_free", &self.blocks_free()); + ds.field("blocks_available", &self.blocks_available()); + ds.field("files", &self.files()); + ds.field("files_free", &self.files_free()); + ds.field("filesystem_id", &self.filesystem_id()); + #[cfg(all( + feature = "mount", + any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ) + ))] + ds.field("flags", &self.flags()); + ds.finish() + } +} + +/// Describes a mounted file system. +/// +/// The result is OS-dependent. For a portable alternative, see +/// [`statvfs`](crate::sys::statvfs::statvfs). +/// +/// # Arguments +/// +/// `path` - Path to any file within the file system to describe +pub fn statfs(path: &P) -> Result { + unsafe { + let mut stat = mem::MaybeUninit::::uninit(); + let res = path.with_nix_path(|path| { + LIBC_STATFS(path.as_ptr(), stat.as_mut_ptr()) + })?; + Errno::result(res).map(|_| Statfs(stat.assume_init())) + } +} + +/// Describes a mounted file system. +/// +/// The result is OS-dependent. For a portable alternative, see +/// [`fstatvfs`](crate::sys::statvfs::fstatvfs). +/// +/// # Arguments +/// +/// `fd` - File descriptor of any open file within the file system to describe +pub fn fstatfs(fd: &T) -> Result { + unsafe { + let mut stat = mem::MaybeUninit::::uninit(); + Errno::result(LIBC_FSTATFS(fd.as_raw_fd(), stat.as_mut_ptr())) + .map(|_| Statfs(stat.assume_init())) + } +} + +#[cfg(test)] +mod test { + use std::fs::File; + + use crate::sys::statfs::*; + use crate::sys::statvfs::*; + use std::path::Path; + + #[test] + fn statfs_call() { + check_statfs("/tmp"); + check_statfs("/dev"); + check_statfs("/run"); + check_statfs("/"); + } + + #[test] + fn fstatfs_call() { + check_fstatfs("/tmp"); + check_fstatfs("/dev"); + check_fstatfs("/run"); + check_fstatfs("/"); + } + + fn check_fstatfs(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()).unwrap(); + let file = File::open(path).unwrap(); + let fs = fstatfs(&file).unwrap(); + assert_fs_equals(fs, vfs); + } + + fn check_statfs(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()).unwrap(); + let fs = statfs(path.as_bytes()).unwrap(); + assert_fs_equals(fs, vfs); + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { + assert_eq!(fs.files() as u64, vfs.files() as u64); + assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); + assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); + } + + // This test is ignored because files_free/blocks_free can change after statvfs call and before + // statfs call. + #[test] + #[ignore] + fn statfs_call_strict() { + check_statfs_strict("/tmp"); + check_statfs_strict("/dev"); + check_statfs_strict("/run"); + check_statfs_strict("/"); + } + + // This test is ignored because files_free/blocks_free can change after statvfs call and before + // fstatfs call. + #[test] + #[ignore] + fn fstatfs_call_strict() { + check_fstatfs_strict("/tmp"); + check_fstatfs_strict("/dev"); + check_fstatfs_strict("/run"); + check_fstatfs_strict("/"); + } + + fn check_fstatfs_strict(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()); + let file = File::open(path).unwrap(); + let fs = fstatfs(&file); + assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) + } + + fn check_statfs_strict(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()); + let fs = statfs(path.as_bytes()); + assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) { + assert_eq!(fs.files_free() as u64, vfs.files_free() as u64); + assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64); + assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64); + assert_eq!(fs.files() as u64, vfs.files() as u64); + assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); + assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); + } +} diff --git a/src/sys/statvfs.rs b/src/sys/statvfs.rs new file mode 100644 index 0000000..8de369f --- /dev/null +++ b/src/sys/statvfs.rs @@ -0,0 +1,173 @@ +//! Get filesystem statistics +//! +//! See [the man pages](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html) +//! for more details. +use std::mem; +use std::os::unix::io::AsRawFd; + +use libc::{self, c_ulong}; + +use crate::{errno::Errno, NixPath, Result}; + +#[cfg(not(target_os = "redox"))] +libc_bitflags!( + /// File system mount Flags + #[repr(C)] + #[derive(Default)] + pub struct FsFlags: c_ulong { + /// Read Only + #[cfg(not(target_os = "haiku"))] + ST_RDONLY; + /// Do not allow the set-uid bits to have an effect + #[cfg(not(target_os = "haiku"))] + ST_NOSUID; + /// Do not interpret character or block-special devices + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NODEV; + /// Do not allow execution of binaries on the filesystem + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NOEXEC; + /// All IO should be done synchronously + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_SYNCHRONOUS; + /// Allow mandatory locks on the filesystem + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_MANDLOCK; + /// Write on file/directory/symlink + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_WRITE; + /// Append-only file + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_APPEND; + /// Immutable file + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_IMMUTABLE; + /// Do not update access times on files + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NOATIME; + /// Do not update access times on files + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_NODIRATIME; + /// Update access time relative to modify/change time + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_env = "musl"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ST_RELATIME; + } +); + +/// Wrapper around the POSIX `statvfs` struct +/// +/// For more information see the [`statvfs(3)` man pages](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_statvfs.h.html). +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Statvfs(libc::statvfs); + +impl Statvfs { + /// get the file system block size + pub fn block_size(&self) -> c_ulong { + self.0.f_bsize + } + + /// Get the fundamental file system block size + pub fn fragment_size(&self) -> c_ulong { + self.0.f_frsize + } + + /// Get the number of blocks. + /// + /// Units are in units of `fragment_size()` + pub fn blocks(&self) -> libc::fsblkcnt_t { + self.0.f_blocks + } + + /// Get the number of free blocks in the file system + pub fn blocks_free(&self) -> libc::fsblkcnt_t { + self.0.f_bfree + } + + /// Get the number of free blocks for unprivileged users + pub fn blocks_available(&self) -> libc::fsblkcnt_t { + self.0.f_bavail + } + + /// Get the total number of file inodes + pub fn files(&self) -> libc::fsfilcnt_t { + self.0.f_files + } + + /// Get the number of free file inodes + pub fn files_free(&self) -> libc::fsfilcnt_t { + self.0.f_ffree + } + + /// Get the number of free file inodes for unprivileged users + pub fn files_available(&self) -> libc::fsfilcnt_t { + self.0.f_favail + } + + /// Get the file system id + pub fn filesystem_id(&self) -> c_ulong { + self.0.f_fsid + } + + /// Get the mount flags + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn flags(&self) -> FsFlags { + FsFlags::from_bits_truncate(self.0.f_flag) + } + + /// Get the maximum filename length + pub fn name_max(&self) -> c_ulong { + self.0.f_namemax + } +} + +/// Return a `Statvfs` object with information about the `path` +pub fn statvfs(path: &P) -> Result { + unsafe { + Errno::clear(); + let mut stat = mem::MaybeUninit::::uninit(); + let res = path.with_nix_path(|path| { + libc::statvfs(path.as_ptr(), stat.as_mut_ptr()) + })?; + + Errno::result(res).map(|_| Statvfs(stat.assume_init())) + } +} + +/// Return a `Statvfs` object with information about `fd` +pub fn fstatvfs(fd: &T) -> Result { + unsafe { + Errno::clear(); + let mut stat = mem::MaybeUninit::::uninit(); + Errno::result(libc::fstatvfs(fd.as_raw_fd(), stat.as_mut_ptr())) + .map(|_| Statvfs(stat.assume_init())) + } +} + +#[cfg(test)] +mod test { + use crate::sys::statvfs::*; + use std::fs::File; + + #[test] + fn statvfs_call() { + statvfs(&b"/"[..]).unwrap(); + } + + #[test] + fn fstatvfs_call() { + let root = File::open("/").unwrap(); + fstatvfs(&root).unwrap(); + } +} diff --git a/src/sys/sysinfo.rs b/src/sys/sysinfo.rs new file mode 100644 index 0000000..e8aa00b --- /dev/null +++ b/src/sys/sysinfo.rs @@ -0,0 +1,83 @@ +use libc::{self, SI_LOAD_SHIFT}; +use std::time::Duration; +use std::{cmp, mem}; + +use crate::errno::Errno; +use crate::Result; + +/// System info structure returned by `sysinfo`. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct SysInfo(libc::sysinfo); + +// The fields are c_ulong on 32-bit linux, u64 on 64-bit linux; x32's ulong is u32 +#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] +type mem_blocks_t = u64; +#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] +type mem_blocks_t = libc::c_ulong; + +impl SysInfo { + /// Returns the load average tuple. + /// + /// The returned values represent the load average over time intervals of + /// 1, 5, and 15 minutes, respectively. + pub fn load_average(&self) -> (f64, f64, f64) { + ( + self.0.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, + self.0.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, + self.0.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, + ) + } + + /// Returns the time since system boot. + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn uptime(&self) -> Duration { + // Truncate negative values to 0 + Duration::from_secs(cmp::max(self.0.uptime, 0) as u64) + } + + /// Current number of processes. + pub fn process_count(&self) -> u16 { + self.0.procs + } + + /// Returns the amount of swap memory in Bytes. + pub fn swap_total(&self) -> u64 { + self.scale_mem(self.0.totalswap) + } + + /// Returns the amount of unused swap memory in Bytes. + pub fn swap_free(&self) -> u64 { + self.scale_mem(self.0.freeswap) + } + + /// Returns the total amount of installed RAM in Bytes. + pub fn ram_total(&self) -> u64 { + self.scale_mem(self.0.totalram) + } + + /// Returns the amount of completely unused RAM in Bytes. + /// + /// "Unused" in this context means that the RAM in neither actively used by + /// programs, nor by the operating system as disk cache or buffer. It is + /// "wasted" RAM since it currently serves no purpose. + pub fn ram_unused(&self) -> u64 { + self.scale_mem(self.0.freeram) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn scale_mem(&self, units: mem_blocks_t) -> u64 { + units as u64 * self.0.mem_unit as u64 + } +} + +/// Returns system information. +/// +/// [See `sysinfo(2)`](https://man7.org/linux/man-pages/man2/sysinfo.2.html). +pub fn sysinfo() -> Result { + let mut info = mem::MaybeUninit::uninit(); + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + Errno::result(res).map(|_| unsafe { SysInfo(info.assume_init()) }) +} diff --git a/src/sys/termios.rs b/src/sys/termios.rs new file mode 100644 index 0000000..fba2cd8 --- /dev/null +++ b/src/sys/termios.rs @@ -0,0 +1,1227 @@ +//! An interface for controlling asynchronous communication ports +//! +//! This interface provides a safe wrapper around the termios subsystem defined by POSIX. The +//! underlying types are all implemented in libc for most platforms and either wrapped in safer +//! types here or exported directly. +//! +//! If you are unfamiliar with the `termios` API, you should first read the +//! [API documentation](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/termios.h.html) and +//! then come back to understand how `nix` safely wraps it. +//! +//! It should be noted that this API incurs some runtime overhead above the base `libc` definitions. +//! As this interface is not used with high-bandwidth information, this should be fine in most +//! cases. The primary cost when using this API is that the `Termios` datatype here duplicates the +//! standard fields of the underlying `termios` struct and uses safe type wrappers for those fields. +//! This means that when crossing the FFI interface to the underlying C library, data is first +//! copied into the underlying `termios` struct, then the operation is done, and the data is copied +//! back (with additional sanity checking) into the safe wrapper types. The `termios` struct is +//! relatively small across all platforms (on the order of 32-64 bytes). +//! +//! The following examples highlight some of the API use cases such that users coming from using C +//! or reading the standard documentation will understand how to use the safe API exposed here. +//! +//! Example disabling processing of the end-of-file control character: +//! +//! ``` +//! # use self::nix::sys::termios::SpecialCharacterIndices::VEOF; +//! # use self::nix::sys::termios::{_POSIX_VDISABLE, Termios}; +//! # let mut termios: Termios = unsafe { std::mem::zeroed() }; +//! termios.control_chars[VEOF as usize] = _POSIX_VDISABLE; +//! ``` +//! +//! The flags within `Termios` are defined as bitfields using the `bitflags` crate. This provides +//! an interface for working with bitfields that is similar to working with the raw unsigned +//! integer types but offers type safety because of the internal checking that values will always +//! be a valid combination of the defined flags. +//! +//! An example showing some of the basic operations for interacting with the control flags: +//! +//! ``` +//! # use self::nix::sys::termios::{ControlFlags, Termios}; +//! # let mut termios: Termios = unsafe { std::mem::zeroed() }; +//! termios.control_flags & ControlFlags::CSIZE == ControlFlags::CS5; +//! termios.control_flags |= ControlFlags::CS5; +//! ``` +//! +//! # Baud rates +//! +//! This API is not consistent across platforms when it comes to `BaudRate`: Android and Linux both +//! only support the rates specified by the `BaudRate` enum through their termios API while the BSDs +//! support arbitrary baud rates as the values of the `BaudRate` enum constants are the same integer +//! value of the constant (`B9600` == `9600`). Therefore the `nix::termios` API uses the following +//! conventions: +//! +//! * `cfgetispeed()` - Returns `u32` on BSDs, `BaudRate` on Android/Linux +//! * `cfgetospeed()` - Returns `u32` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetispeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetospeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! * `cfsetspeed()` - Takes `u32` or `BaudRate` on BSDs, `BaudRate` on Android/Linux +//! +//! The most common use case of specifying a baud rate using the enum will work the same across +//! platforms: +//! +//! ```rust +//! # use nix::sys::termios::{BaudRate, cfsetispeed, cfsetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! cfsetispeed(&mut t, BaudRate::B9600).unwrap(); +//! cfsetospeed(&mut t, BaudRate::B9600).unwrap(); +//! cfsetspeed(&mut t, BaudRate::B9600).unwrap(); +//! # } +//! ``` +//! +//! Additionally round-tripping baud rates is consistent across platforms: +//! +//! ```rust +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetispeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, BaudRate::B9600).unwrap(); +//! let speed = cfgetispeed(&t); +//! assert_eq!(speed, cfgetospeed(&t)); +//! cfsetispeed(&mut t, speed).unwrap(); +//! # } +//! ``` +//! +//! On non-BSDs, `cfgetispeed()` and `cfgetospeed()` both return a `BaudRate`: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust,ignore" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust" +)] +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, BaudRate::B9600); +//! assert_eq!(cfgetispeed(&t), BaudRate::B9600); +//! assert_eq!(cfgetospeed(&t), BaudRate::B9600); +//! # } +//! ``` +//! +//! But on the BSDs, `cfgetispeed()` and `cfgetospeed()` both return `u32`s: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust,ignore" +)] +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, 9600u32); +//! assert_eq!(cfgetispeed(&t), 9600u32); +//! assert_eq!(cfgetospeed(&t), 9600u32); +//! # } +//! ``` +//! +//! It's trivial to convert from a `BaudRate` to a `u32` on BSDs: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust,ignore" +)] +//! # use nix::sys::termios::{BaudRate, cfgetispeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! # cfsetspeed(&mut t, 9600u32); +//! assert_eq!(cfgetispeed(&t), BaudRate::B9600.into()); +//! assert_eq!(u32::from(BaudRate::B9600), 9600u32); +//! # } +//! ``` +//! +//! And on BSDs you can specify arbitrary baud rates (**note** this depends on hardware support) +//! by specifying baud rates directly using `u32`s: +//! +#![cfg_attr( + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ), + doc = " ```rust" +)] +#![cfg_attr( + not(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + )), + doc = " ```rust,ignore" +)] +//! # use nix::sys::termios::{cfsetispeed, cfsetospeed, cfsetspeed, Termios}; +//! # fn main() { +//! # let mut t: Termios = unsafe { std::mem::zeroed() }; +//! cfsetispeed(&mut t, 9600u32); +//! cfsetospeed(&mut t, 9600u32); +//! cfsetspeed(&mut t, 9600u32); +//! # } +//! ``` +use crate::errno::Errno; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int, tcflag_t}; +use std::cell::{Ref, RefCell}; +use std::convert::From; +use std::mem; +use std::os::unix::io::RawFd; + +#[cfg(feature = "process")] +use crate::unistd::Pid; + +/// Stores settings for the termios API +/// +/// This is a wrapper around the `libc::termios` struct that provides a safe interface for the +/// standard fields. The only safe way to obtain an instance of this struct is to extract it from +/// an open port using `tcgetattr()`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Termios { + inner: RefCell, + /// Input mode flags (see `termios.c_iflag` documentation) + pub input_flags: InputFlags, + /// Output mode flags (see `termios.c_oflag` documentation) + pub output_flags: OutputFlags, + /// Control mode flags (see `termios.c_cflag` documentation) + pub control_flags: ControlFlags, + /// Local mode flags (see `termios.c_lflag` documentation) + pub local_flags: LocalFlags, + /// Control characters (see `termios.c_cc` documentation) + pub control_chars: [libc::cc_t; NCCS], + /// Line discipline (see `termios.c_line` documentation) + #[cfg(any(target_os = "linux", target_os = "android",))] + pub line_discipline: libc::cc_t, + /// Line discipline (see `termios.c_line` documentation) + #[cfg(target_os = "haiku")] + pub line_discipline: libc::c_char, +} + +impl Termios { + /// Exposes an immutable reference to the underlying `libc::termios` data structure. + /// + /// This is not part of `nix`'s public API because it requires additional work to maintain type + /// safety. + pub(crate) fn get_libc_termios(&self) -> Ref { + { + let mut termios = self.inner.borrow_mut(); + termios.c_iflag = self.input_flags.bits(); + termios.c_oflag = self.output_flags.bits(); + termios.c_cflag = self.control_flags.bits(); + termios.c_lflag = self.local_flags.bits(); + termios.c_cc = self.control_chars; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + { + termios.c_line = self.line_discipline; + } + } + self.inner.borrow() + } + + /// Exposes the inner `libc::termios` datastore within `Termios`. + /// + /// This is unsafe because if this is used to modify the inner `libc::termios` struct, it will + /// not automatically update the safe wrapper type around it. In this case it should also be + /// paired with a call to `update_wrapper()` so that the wrapper-type and internal + /// representation stay consistent. + pub(crate) unsafe fn get_libc_termios_mut(&mut self) -> *mut libc::termios { + { + let mut termios = self.inner.borrow_mut(); + termios.c_iflag = self.input_flags.bits(); + termios.c_oflag = self.output_flags.bits(); + termios.c_cflag = self.control_flags.bits(); + termios.c_lflag = self.local_flags.bits(); + termios.c_cc = self.control_chars; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + { + termios.c_line = self.line_discipline; + } + } + self.inner.as_ptr() + } + + /// Updates the wrapper values from the internal `libc::termios` data structure. + pub(crate) fn update_wrapper(&mut self) { + let termios = *self.inner.borrow_mut(); + self.input_flags = InputFlags::from_bits_truncate(termios.c_iflag); + self.output_flags = OutputFlags::from_bits_truncate(termios.c_oflag); + self.control_flags = ControlFlags::from_bits_truncate(termios.c_cflag); + self.local_flags = LocalFlags::from_bits_truncate(termios.c_lflag); + self.control_chars = termios.c_cc; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + { + self.line_discipline = termios.c_line; + } + } +} + +impl From for Termios { + fn from(termios: libc::termios) -> Self { + Termios { + inner: RefCell::new(termios), + input_flags: InputFlags::from_bits_truncate(termios.c_iflag), + output_flags: OutputFlags::from_bits_truncate(termios.c_oflag), + control_flags: ControlFlags::from_bits_truncate(termios.c_cflag), + local_flags: LocalFlags::from_bits_truncate(termios.c_lflag), + control_chars: termios.c_cc, + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "haiku", + ))] + line_discipline: termios.c_line, + } + } +} + +impl From for libc::termios { + fn from(termios: Termios) -> Self { + termios.inner.into_inner() + } +} + +libc_enum! { + /// Baud rates supported by the system. + /// + /// For the BSDs, arbitrary baud rates can be specified by using `u32`s directly instead of this + /// enum. + /// + /// B0 is special and will disable the port. + #[cfg_attr(all(any(target_os = "haiku"), target_pointer_width = "64"), repr(u8))] + #[cfg_attr(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64"), repr(u64))] + #[cfg_attr(not(all(any(target_os = "ios", target_os = "macos", target_os = "haiku"), target_pointer_width = "64")), repr(u32))] + #[non_exhaustive] + pub enum BaudRate { + B0, + B50, + B75, + B110, + B134, + B150, + B200, + B300, + B600, + B1200, + B1800, + B2400, + B4800, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B7200, + B9600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B14400, + B19200, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B28800, + B38400, + B57600, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B76800, + B115200, + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B153600, + B230400, + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B307200, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B460800, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B576000, + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B921600, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B1000000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B1152000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B1500000, + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B2000000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B2500000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B3000000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B3500000, + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + B4000000, + } + impl TryFrom +} + +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +impl From for u32 { + fn from(b: BaudRate) -> u32 { + b as u32 + } +} + +#[cfg(target_os = "haiku")] +impl From for u8 { + fn from(b: BaudRate) -> u8 { + b as u8 + } +} + +// TODO: Add TCSASOFT, which will require treating this as a bitfield. +libc_enum! { + /// Specify when a port configuration change should occur. + /// + /// Used as an argument to `tcsetattr()` + #[repr(i32)] + #[non_exhaustive] + pub enum SetArg { + /// The change will occur immediately + TCSANOW, + /// The change occurs after all output has been written + TCSADRAIN, + /// Same as `TCSADRAIN`, but will also flush the input buffer + TCSAFLUSH, + } +} + +libc_enum! { + /// Specify a combination of the input and output buffers to flush + /// + /// Used as an argument to `tcflush()`. + #[repr(i32)] + #[non_exhaustive] + pub enum FlushArg { + /// Flush data that was received but not read + TCIFLUSH, + /// Flush data written but not transmitted + TCOFLUSH, + /// Flush both received data not read and written data not transmitted + TCIOFLUSH, + } +} + +libc_enum! { + /// Specify how transmission flow should be altered + /// + /// Used as an argument to `tcflow()`. + #[repr(i32)] + #[non_exhaustive] + pub enum FlowArg { + /// Suspend transmission + TCOOFF, + /// Resume transmission + TCOON, + /// Transmit a STOP character, which should disable a connected terminal device + TCIOFF, + /// Transmit a START character, which should re-enable a connected terminal device + TCION, + } +} + +// TODO: Make this usable directly as a slice index. +#[cfg(not(target_os = "haiku"))] +libc_enum! { + /// Indices into the `termios.c_cc` array for special characters. + #[repr(usize)] + #[non_exhaustive] + pub enum SpecialCharacterIndices { + VDISCARD, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VDSUSP, + VEOF, + VEOL, + VEOL2, + VERASE, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VERASE2, + VINTR, + VKILL, + VLNEXT, + #[cfg(not(any(all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VMIN, + VQUIT, + VREPRINT, + VSTART, + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VSTATUS, + VSTOP, + VSUSP, + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + VSWTC, + #[cfg(any(target_os = "haiku", target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VSWTCH, + #[cfg(not(any(all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VTIME, + VWERASE, + #[cfg(target_os = "dragonfly")] + #[cfg_attr(docsrs, doc(cfg(all())))] + VCHECKPT, + } +} + +#[cfg(any( + all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", + target_os = "solaris" +))] +impl SpecialCharacterIndices { + pub const VMIN: SpecialCharacterIndices = SpecialCharacterIndices::VEOF; + pub const VTIME: SpecialCharacterIndices = SpecialCharacterIndices::VEOL; +} + +pub use libc::NCCS; +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub use libc::_POSIX_VDISABLE; + +libc_bitflags! { + /// Flags for configuring the input mode of a terminal + pub struct InputFlags: tcflag_t { + IGNBRK; + BRKINT; + IGNPAR; + PARMRK; + INPCK; + ISTRIP; + INLCR; + IGNCR; + ICRNL; + IXON; + IXOFF; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IXANY; + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IMAXBEL; + #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IUTF8; + } +} + +libc_bitflags! { + /// Flags for configuring the output mode of a terminal + pub struct OutputFlags: tcflag_t { + OPOST; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "linux", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OLCUC; + ONLCR; + OCRNL as tcflag_t; + ONOCR as tcflag_t; + ONLRET as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OFILL as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OFDEL as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NL0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NL1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR2 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CR3 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB2 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TAB3 as tcflag_t; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + XTABS; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BS0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BS1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VT0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VT1 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FF0 as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FF1 as tcflag_t; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + OXTABS; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ONOEOT as tcflag_t; + + // Bitmasks for use with OutputFlags to select specific settings + // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 + // is resolved. + + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NLDLY as tcflag_t; // FIXME: Datatype needs to be corrected in libc for mac + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CRDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TABDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BSDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + VTDLY as tcflag_t; + #[cfg(any(target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FFDLY as tcflag_t; + } +} + +libc_bitflags! { + /// Flags for setting the control mode of a terminal + pub struct ControlFlags: tcflag_t { + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CIGNORE; + CS5; + CS6; + CS7; + CS8; + CSTOPB; + CREAD; + PARENB; + PARODD; + HUPCL; + CLOCAL; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CRTSCTS; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CBAUD; + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "mips"))))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CMSPAR; + #[cfg(any(target_os = "android", + all(target_os = "linux", + not(any(target_arch = "powerpc", target_arch = "powerpc64")))))] + CIBAUD; + #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CBAUDEX; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MDMBUF; + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CHWFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CCTS_OFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CRTS_IFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CDTR_IFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CDSR_OFLOW; + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + CCAR_OFLOW; + + // Bitmasks for use with ControlFlags to select specific settings + // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 + // is resolved. + + CSIZE; + } +} + +libc_bitflags! { + /// Flags for setting any local modes + pub struct LocalFlags: tcflag_t { + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ECHOKE; + ECHOE; + ECHOK; + ECHO; + ECHONL; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ECHOPRT; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ECHOCTL; + ISIG; + ICANON; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ALTWERASE; + IEXTEN; + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + EXTPROC; + TOSTOP; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + FLUSHO; + #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + NOKERNINFO; + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PENDIN; + NOFLSH; + } +} + +cfg_if! { + if #[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] { + /// Get input baud rate (see + /// [cfgetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html)). + /// + /// `cfgetispeed()` extracts the input baud rate from the given `Termios` structure. + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn cfgetispeed(termios: &Termios) -> u32 { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetispeed(&*inner_termios) as u32 } + } + + /// Get output baud rate (see + /// [cfgetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html)). + /// + /// `cfgetospeed()` extracts the output baud rate from the given `Termios` structure. + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + pub fn cfgetospeed(termios: &Termios) -> u32 { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetospeed(&*inner_termios) as u32 } + } + + /// Set input baud rate (see + /// [cfsetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html)). + /// + /// `cfsetispeed()` sets the intput baud rate in the given `Termios` structure. + pub fn cfsetispeed>(termios: &mut Termios, baud: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetispeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set output baud rate (see + /// [cfsetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html)). + /// + /// `cfsetospeed()` sets the output baud rate in the given termios structure. + pub fn cfsetospeed>(termios: &mut Termios, baud: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetospeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set both the input and output baud rates (see + /// [termios(3)](https://www.freebsd.org/cgi/man.cgi?query=cfsetspeed)). + /// + /// `cfsetspeed()` sets the input and output baud rate in the given termios structure. Note that + /// this is part of the 4.4BSD standard and not part of POSIX. + pub fn cfsetspeed>(termios: &mut Termios, baud: T) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetspeed(inner_termios, baud.into() as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + } else { + use std::convert::TryInto; + + /// Get input baud rate (see + /// [cfgetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html)). + /// + /// `cfgetispeed()` extracts the input baud rate from the given `Termios` structure. + pub fn cfgetispeed(termios: &Termios) -> BaudRate { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetispeed(&*inner_termios) }.try_into().unwrap() + } + + /// Get output baud rate (see + /// [cfgetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html)). + /// + /// `cfgetospeed()` extracts the output baud rate from the given `Termios` structure. + pub fn cfgetospeed(termios: &Termios) -> BaudRate { + let inner_termios = termios.get_libc_termios(); + unsafe { libc::cfgetospeed(&*inner_termios) }.try_into().unwrap() + } + + /// Set input baud rate (see + /// [cfsetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html)). + /// + /// `cfsetispeed()` sets the intput baud rate in the given `Termios` structure. + pub fn cfsetispeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetispeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set output baud rate (see + /// [cfsetospeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html)). + /// + /// `cfsetospeed()` sets the output baud rate in the given `Termios` structure. + pub fn cfsetospeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetospeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + + /// Set both the input and output baud rates (see + /// [termios(3)](https://www.freebsd.org/cgi/man.cgi?query=cfsetspeed)). + /// + /// `cfsetspeed()` sets the input and output baud rate in the given `Termios` structure. Note that + /// this is part of the 4.4BSD standard and not part of POSIX. + #[cfg(not(target_os = "haiku"))] + pub fn cfsetspeed(termios: &mut Termios, baud: BaudRate) -> Result<()> { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + let res = unsafe { libc::cfsetspeed(inner_termios, baud as libc::speed_t) }; + termios.update_wrapper(); + Errno::result(res).map(drop) + } + } +} + +/// Configures the port to something like the "raw" mode of the old Version 7 terminal driver (see +/// [termios(3)](https://man7.org/linux/man-pages/man3/termios.3.html)). +/// +/// `cfmakeraw()` configures the termios structure such that input is available character-by- +/// character, echoing is disabled, and all special input and output processing is disabled. Note +/// that this is a non-standard function, but is available on Linux and BSDs. +pub fn cfmakeraw(termios: &mut Termios) { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + unsafe { + libc::cfmakeraw(inner_termios); + } + termios.update_wrapper(); +} + +/// Configures the port to "sane" mode (like the configuration of a newly created terminal) (see +/// [tcsetattr(3)](https://www.freebsd.org/cgi/man.cgi?query=tcsetattr)). +/// +/// Note that this is a non-standard function, available on FreeBSD. +#[cfg(target_os = "freebsd")] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn cfmakesane(termios: &mut Termios) { + let inner_termios = unsafe { termios.get_libc_termios_mut() }; + unsafe { + libc::cfmakesane(inner_termios); + } + termios.update_wrapper(); +} + +/// Return the configuration of a port +/// [tcgetattr(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html)). +/// +/// `tcgetattr()` returns a `Termios` structure with the current configuration for a port. Modifying +/// this structure *will not* reconfigure the port, instead the modifications should be done to +/// the `Termios` structure and then the port should be reconfigured using `tcsetattr()`. +pub fn tcgetattr(fd: RawFd) -> Result { + let mut termios = mem::MaybeUninit::uninit(); + + let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; + + Errno::result(res)?; + + unsafe { Ok(termios.assume_init().into()) } +} + +/// Set the configuration for a terminal (see +/// [tcsetattr(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html)). +/// +/// `tcsetattr()` reconfigures the given port based on a given `Termios` structure. This change +/// takes affect at a time specified by `actions`. Note that this function may return success if +/// *any* of the parameters were successfully set, not only if all were set successfully. +pub fn tcsetattr(fd: RawFd, actions: SetArg, termios: &Termios) -> Result<()> { + let inner_termios = termios.get_libc_termios(); + Errno::result(unsafe { + libc::tcsetattr(fd, actions as c_int, &*inner_termios) + }) + .map(drop) +} + +/// Block until all output data is written (see +/// [tcdrain(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html)). +pub fn tcdrain(fd: RawFd) -> Result<()> { + Errno::result(unsafe { libc::tcdrain(fd) }).map(drop) +} + +/// Suspend or resume the transmission or reception of data (see +/// [tcflow(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html)). +/// +/// `tcflow()` suspends of resumes the transmission or reception of data for the given port +/// depending on the value of `action`. +pub fn tcflow(fd: RawFd, action: FlowArg) -> Result<()> { + Errno::result(unsafe { libc::tcflow(fd, action as c_int) }).map(drop) +} + +/// Discard data in the output or input queue (see +/// [tcflush(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html)). +/// +/// `tcflush()` will discard data for a terminal port in the input queue, output queue, or both +/// depending on the value of `action`. +pub fn tcflush(fd: RawFd, action: FlushArg) -> Result<()> { + Errno::result(unsafe { libc::tcflush(fd, action as c_int) }).map(drop) +} + +/// Send a break for a specific duration (see +/// [tcsendbreak(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html)). +/// +/// When using asynchronous data transmission `tcsendbreak()` will transmit a continuous stream +/// of zero-valued bits for an implementation-defined duration. +pub fn tcsendbreak(fd: RawFd, duration: c_int) -> Result<()> { + Errno::result(unsafe { libc::tcsendbreak(fd, duration) }).map(drop) +} + +feature! { +#![feature = "process"] +/// Get the session controlled by the given terminal (see +/// [tcgetsid(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html)). +pub fn tcgetsid(fd: RawFd) -> Result { + let res = unsafe { libc::tcgetsid(fd) }; + + Errno::result(res).map(Pid::from_raw) +} +} + +#[cfg(test)] +mod test { + use super::*; + use std::convert::TryFrom; + + #[test] + fn try_from() { + assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0)); + #[cfg(not(target_os = "haiku"))] + BaudRate::try_from(999999999).expect_err("assertion failed"); + #[cfg(target_os = "haiku")] + BaudRate::try_from(99).expect_err("assertion failed"); + } +} diff --git a/src/sys/time.rs b/src/sys/time.rs new file mode 100644 index 0000000..0042c45 --- /dev/null +++ b/src/sys/time.rs @@ -0,0 +1,811 @@ +#[cfg_attr(target_env = "musl", allow(deprecated))] +// https://github.com/rust-lang/libc/issues/1848 +pub use libc::{suseconds_t, time_t}; +use libc::{timespec, timeval}; +use std::convert::From; +use std::time::Duration; +use std::{cmp, fmt, ops}; + +const fn zero_init_timespec() -> timespec { + // `std::mem::MaybeUninit::zeroed()` is not yet a const fn + // (https://github.com/rust-lang/rust/issues/91850) so we will instead initialize an array of + // the appropriate size to zero and then transmute it to a timespec value. + unsafe { std::mem::transmute([0u8; std::mem::size_of::()]) } +} + +#[cfg(any( + all(feature = "time", any(target_os = "android", target_os = "linux")), + all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" + ) +))] +pub(crate) mod timer { + use crate::sys::time::{zero_init_timespec, TimeSpec}; + use bitflags::bitflags; + + #[derive(Debug, Clone, Copy)] + pub(crate) struct TimerSpec(libc::itimerspec); + + impl TimerSpec { + pub const fn none() -> Self { + Self(libc::itimerspec { + it_interval: zero_init_timespec(), + it_value: zero_init_timespec(), + }) + } + } + + impl AsMut for TimerSpec { + fn as_mut(&mut self) -> &mut libc::itimerspec { + &mut self.0 + } + } + + impl AsRef for TimerSpec { + fn as_ref(&self) -> &libc::itimerspec { + &self.0 + } + } + + impl From for TimerSpec { + fn from(expiration: Expiration) -> TimerSpec { + match expiration { + Expiration::OneShot(t) => TimerSpec(libc::itimerspec { + it_interval: zero_init_timespec(), + it_value: *t.as_ref(), + }), + Expiration::IntervalDelayed(start, interval) => { + TimerSpec(libc::itimerspec { + it_interval: *interval.as_ref(), + it_value: *start.as_ref(), + }) + } + Expiration::Interval(t) => TimerSpec(libc::itimerspec { + it_interval: *t.as_ref(), + it_value: *t.as_ref(), + }), + } + } + } + + /// An enumeration allowing the definition of the expiration time of an alarm, + /// recurring or not. + #[derive(Debug, Clone, Copy, Eq, PartialEq)] + pub enum Expiration { + /// Alarm will trigger once after the time given in `TimeSpec` + OneShot(TimeSpec), + /// Alarm will trigger after a specified delay and then every interval of + /// time. + IntervalDelayed(TimeSpec, TimeSpec), + /// Alarm will trigger every specified interval of time. + Interval(TimeSpec), + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + bitflags! { + /// Flags that are used for arming the timer. + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; + } + } + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "illumos" + ))] + bitflags! { + /// Flags that are used for arming the timer. + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TIMER_ABSTIME; + } + } + + impl From for Expiration { + fn from(timerspec: TimerSpec) -> Expiration { + match timerspec { + TimerSpec(libc::itimerspec { + it_interval: + libc::timespec { + tv_sec: 0, + tv_nsec: 0, + .. + }, + it_value: ts, + }) => Expiration::OneShot(ts.into()), + TimerSpec(libc::itimerspec { + it_interval: int_ts, + it_value: val_ts, + }) => { + if (int_ts.tv_sec == val_ts.tv_sec) + && (int_ts.tv_nsec == val_ts.tv_nsec) + { + Expiration::Interval(int_ts.into()) + } else { + Expiration::IntervalDelayed( + val_ts.into(), + int_ts.into(), + ) + } + } + } + } + } +} + +pub trait TimeValLike: Sized { + #[inline] + fn zero() -> Self { + Self::seconds(0) + } + + #[inline] + fn hours(hours: i64) -> Self { + let secs = hours + .checked_mul(SECS_PER_HOUR) + .expect("TimeValLike::hours ouf of bounds"); + Self::seconds(secs) + } + + #[inline] + fn minutes(minutes: i64) -> Self { + let secs = minutes + .checked_mul(SECS_PER_MINUTE) + .expect("TimeValLike::minutes out of bounds"); + Self::seconds(secs) + } + + fn seconds(seconds: i64) -> Self; + fn milliseconds(milliseconds: i64) -> Self; + fn microseconds(microseconds: i64) -> Self; + fn nanoseconds(nanoseconds: i64) -> Self; + + #[inline] + fn num_hours(&self) -> i64 { + self.num_seconds() / 3600 + } + + #[inline] + fn num_minutes(&self) -> i64 { + self.num_seconds() / 60 + } + + fn num_seconds(&self) -> i64; + fn num_milliseconds(&self) -> i64; + fn num_microseconds(&self) -> i64; + fn num_nanoseconds(&self) -> i64; +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct TimeSpec(timespec); + +const NANOS_PER_SEC: i64 = 1_000_000_000; +const SECS_PER_MINUTE: i64 = 60; +const SECS_PER_HOUR: i64 = 3600; + +#[cfg(target_pointer_width = "64")] +const TS_MAX_SECONDS: i64 = (i64::MAX / NANOS_PER_SEC) - 1; + +#[cfg(target_pointer_width = "32")] +const TS_MAX_SECONDS: i64 = isize::MAX as i64; + +const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS; + +// x32 compatibility +// See https://sourceware.org/bugzilla/show_bug.cgi?id=16437 +#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] +type timespec_tv_nsec_t = i64; +#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] +type timespec_tv_nsec_t = libc::c_long; + +impl From for TimeSpec { + fn from(ts: timespec) -> Self { + Self(ts) + } +} + +impl From for TimeSpec { + fn from(duration: Duration) -> Self { + Self::from_duration(duration) + } +} + +impl From for Duration { + fn from(timespec: TimeSpec) -> Self { + Duration::new(timespec.0.tv_sec as u64, timespec.0.tv_nsec as u32) + } +} + +impl AsRef for TimeSpec { + fn as_ref(&self) -> ×pec { + &self.0 + } +} + +impl AsMut for TimeSpec { + fn as_mut(&mut self) -> &mut timespec { + &mut self.0 + } +} + +impl Ord for TimeSpec { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_nsec must always be within [0, 1_000_000_000) + fn cmp(&self, other: &TimeSpec) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_nsec().cmp(&other.tv_nsec()) + } else { + self.tv_sec().cmp(&other.tv_sec()) + } + } +} + +impl PartialOrd for TimeSpec { + fn partial_cmp(&self, other: &TimeSpec) -> Option { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeSpec { + #[inline] + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + fn seconds(seconds: i64) -> TimeSpec { + assert!( + (TS_MIN_SECONDS..=TS_MAX_SECONDS).contains(&seconds), + "TimeSpec out of bounds; seconds={}", + seconds + ); + let mut ts = zero_init_timespec(); + ts.tv_sec = seconds as time_t; + TimeSpec(ts) + } + + #[inline] + fn milliseconds(milliseconds: i64) -> TimeSpec { + let nanoseconds = milliseconds + .checked_mul(1_000_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of microseconds. + #[inline] + fn microseconds(microseconds: i64) -> TimeSpec { + let nanoseconds = microseconds + .checked_mul(1_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of nanoseconds. + #[inline] + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + fn nanoseconds(nanoseconds: i64) -> TimeSpec { + let (secs, nanos) = div_mod_floor_64(nanoseconds, NANOS_PER_SEC); + assert!( + (TS_MIN_SECONDS..=TS_MAX_SECONDS).contains(&secs), + "TimeSpec out of bounds" + ); + let mut ts = zero_init_timespec(); + ts.tv_sec = secs as time_t; + ts.tv_nsec = nanos as timespec_tv_nsec_t; + TimeSpec(ts) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + (self.tv_sec() + 1) as i64 + } else { + self.tv_sec() as i64 + } + } + + fn num_milliseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000_000 + } + + fn num_microseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000 + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_nanoseconds(&self) -> i64 { + let secs = self.num_seconds() * 1_000_000_000; + let nsec = self.nanos_mod_sec(); + secs + nsec as i64 + } +} + +impl TimeSpec { + /// Construct a new `TimeSpec` from its components + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn new(seconds: time_t, nanoseconds: timespec_tv_nsec_t) -> Self { + let mut ts = zero_init_timespec(); + ts.tv_sec = seconds; + ts.tv_nsec = nanoseconds; + Self(ts) + } + + fn nanos_mod_sec(&self) -> timespec_tv_nsec_t { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + self.tv_nsec() - NANOS_PER_SEC as timespec_tv_nsec_t + } else { + self.tv_nsec() + } + } + + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub const fn tv_nsec(&self) -> timespec_tv_nsec_t { + self.0.tv_nsec + } + + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + pub const fn from_duration(duration: Duration) -> Self { + let mut ts = zero_init_timespec(); + ts.tv_sec = duration.as_secs() as time_t; + ts.tv_nsec = duration.subsec_nanos() as timespec_tv_nsec_t; + TimeSpec(ts) + } + + pub const fn from_timespec(timespec: timespec) -> Self { + Self(timespec) + } +} + +impl ops::Neg for TimeSpec { + type Output = TimeSpec; + + fn neg(self) -> TimeSpec { + TimeSpec::nanoseconds(-self.num_nanoseconds()) + } +} + +impl ops::Add for TimeSpec { + type Output = TimeSpec; + + fn add(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds(self.num_nanoseconds() + rhs.num_nanoseconds()) + } +} + +impl ops::Sub for TimeSpec { + type Output = TimeSpec; + + fn sub(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds(self.num_nanoseconds() - rhs.num_nanoseconds()) + } +} + +impl ops::Mul for TimeSpec { + type Output = TimeSpec; + + fn mul(self, rhs: i32) -> TimeSpec { + let usec = self + .num_nanoseconds() + .checked_mul(i64::from(rhs)) + .expect("TimeSpec multiply out of bounds"); + + TimeSpec::nanoseconds(usec) + } +} + +impl ops::Div for TimeSpec { + type Output = TimeSpec; + + fn div(self, rhs: i32) -> TimeSpec { + let usec = self.num_nanoseconds() / i64::from(rhs); + TimeSpec::nanoseconds(usec) + } +} + +impl fmt::Display for TimeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (abs, sign) = if self.tv_sec() < 0 { + (-*self, "-") + } else { + (*self, "") + }; + + let sec = abs.tv_sec(); + + write!(f, "{}", sign)?; + + if abs.tv_nsec() == 0 { + if abs.tv_sec() == 1 { + write!(f, "{} second", sec)?; + } else { + write!(f, "{} seconds", sec)?; + } + } else if abs.tv_nsec() % 1_000_000 == 0 { + write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)?; + } else if abs.tv_nsec() % 1_000 == 0 { + write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)?; + } else { + write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())?; + } + + Ok(()) + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct TimeVal(timeval); + +const MICROS_PER_SEC: i64 = 1_000_000; + +#[cfg(target_pointer_width = "64")] +const TV_MAX_SECONDS: i64 = (i64::MAX / MICROS_PER_SEC) - 1; + +#[cfg(target_pointer_width = "32")] +const TV_MAX_SECONDS: i64 = isize::MAX as i64; + +const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS; + +impl AsRef for TimeVal { + fn as_ref(&self) -> &timeval { + &self.0 + } +} + +impl AsMut for TimeVal { + fn as_mut(&mut self) -> &mut timeval { + &mut self.0 + } +} + +impl Ord for TimeVal { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_usec must always be within [0, 1_000_000) + fn cmp(&self, other: &TimeVal) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_usec().cmp(&other.tv_usec()) + } else { + self.tv_sec().cmp(&other.tv_sec()) + } + } +} + +impl PartialOrd for TimeVal { + fn partial_cmp(&self, other: &TimeVal) -> Option { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeVal { + #[inline] + fn seconds(seconds: i64) -> TimeVal { + assert!( + (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&seconds), + "TimeVal out of bounds; seconds={}", + seconds + ); + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + TimeVal(timeval { + tv_sec: seconds as time_t, + tv_usec: 0, + }) + } + + #[inline] + fn milliseconds(milliseconds: i64) -> TimeVal { + let microseconds = milliseconds + .checked_mul(1_000) + .expect("TimeVal::milliseconds out of bounds"); + + TimeVal::microseconds(microseconds) + } + + /// Makes a new `TimeVal` with given number of microseconds. + #[inline] + fn microseconds(microseconds: i64) -> TimeVal { + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + assert!( + (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&secs), + "TimeVal out of bounds" + ); + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + TimeVal(timeval { + tv_sec: secs as time_t, + tv_usec: micros as suseconds_t, + }) + } + + /// Makes a new `TimeVal` with given number of nanoseconds. Some precision + /// will be lost + #[inline] + fn nanoseconds(nanoseconds: i64) -> TimeVal { + let microseconds = nanoseconds / 1000; + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + assert!( + (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&secs), + "TimeVal out of bounds" + ); + #[cfg_attr(target_env = "musl", allow(deprecated))] + // https://github.com/rust-lang/libc/issues/1848 + TimeVal(timeval { + tv_sec: secs as time_t, + tv_usec: micros as suseconds_t, + }) + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_usec() > 0 { + (self.tv_sec() + 1) as i64 + } else { + self.tv_sec() as i64 + } + } + + fn num_milliseconds(&self) -> i64 { + self.num_microseconds() / 1_000 + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + fn num_microseconds(&self) -> i64 { + let secs = self.num_seconds() * 1_000_000; + let usec = self.micros_mod_sec(); + secs + usec as i64 + } + + fn num_nanoseconds(&self) -> i64 { + self.num_microseconds() * 1_000 + } +} + +impl TimeVal { + /// Construct a new `TimeVal` from its components + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn new(seconds: time_t, microseconds: suseconds_t) -> Self { + Self(timeval { + tv_sec: seconds, + tv_usec: microseconds, + }) + } + + fn micros_mod_sec(&self) -> suseconds_t { + if self.tv_sec() < 0 && self.tv_usec() > 0 { + self.tv_usec() - MICROS_PER_SEC as suseconds_t + } else { + self.tv_usec() + } + } + + #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + pub const fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub const fn tv_usec(&self) -> suseconds_t { + self.0.tv_usec + } +} + +impl ops::Neg for TimeVal { + type Output = TimeVal; + + fn neg(self) -> TimeVal { + TimeVal::microseconds(-self.num_microseconds()) + } +} + +impl ops::Add for TimeVal { + type Output = TimeVal; + + fn add(self, rhs: TimeVal) -> TimeVal { + TimeVal::microseconds(self.num_microseconds() + rhs.num_microseconds()) + } +} + +impl ops::Sub for TimeVal { + type Output = TimeVal; + + fn sub(self, rhs: TimeVal) -> TimeVal { + TimeVal::microseconds(self.num_microseconds() - rhs.num_microseconds()) + } +} + +impl ops::Mul for TimeVal { + type Output = TimeVal; + + fn mul(self, rhs: i32) -> TimeVal { + let usec = self + .num_microseconds() + .checked_mul(i64::from(rhs)) + .expect("TimeVal multiply out of bounds"); + + TimeVal::microseconds(usec) + } +} + +impl ops::Div for TimeVal { + type Output = TimeVal; + + fn div(self, rhs: i32) -> TimeVal { + let usec = self.num_microseconds() / i64::from(rhs); + TimeVal::microseconds(usec) + } +} + +impl fmt::Display for TimeVal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (abs, sign) = if self.tv_sec() < 0 { + (-*self, "-") + } else { + (*self, "") + }; + + let sec = abs.tv_sec(); + + write!(f, "{}", sign)?; + + if abs.tv_usec() == 0 { + if abs.tv_sec() == 1 { + write!(f, "{} second", sec)?; + } else { + write!(f, "{} seconds", sec)?; + } + } else if abs.tv_usec() % 1000 == 0 { + write!(f, "{}.{:03} seconds", sec, abs.tv_usec() / 1000)?; + } else { + write!(f, "{}.{:06} seconds", sec, abs.tv_usec())?; + } + + Ok(()) + } +} + +impl From for TimeVal { + fn from(tv: timeval) -> Self { + TimeVal(tv) + } +} + +#[inline] +fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { + (div_floor_64(this, other), mod_floor_64(this, other)) +} + +#[inline] +fn div_floor_64(this: i64, other: i64) -> i64 { + match div_rem_64(this, other) { + (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, + (d, _) => d, + } +} + +#[inline] +fn mod_floor_64(this: i64, other: i64) -> i64 { + match this % other { + r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, + r => r, + } +} + +#[inline] +fn div_rem_64(this: i64, other: i64) -> (i64, i64) { + (this / other, this % other) +} + +#[cfg(test)] +mod test { + use super::{TimeSpec, TimeVal, TimeValLike}; + use std::time::Duration; + + #[test] + pub fn test_timespec() { + assert_ne!(TimeSpec::seconds(1), TimeSpec::zero()); + assert_eq!( + TimeSpec::seconds(1) + TimeSpec::seconds(2), + TimeSpec::seconds(3) + ); + assert_eq!( + TimeSpec::minutes(3) + TimeSpec::seconds(2), + TimeSpec::seconds(182) + ); + } + + #[test] + pub fn test_timespec_from() { + let duration = Duration::new(123, 123_456_789); + let timespec = TimeSpec::nanoseconds(123_123_456_789); + + assert_eq!(TimeSpec::from(duration), timespec); + assert_eq!(Duration::from(timespec), duration); + } + + #[test] + pub fn test_timespec_neg() { + let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); + let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); + + assert_eq!(a, -b); + } + + #[test] + pub fn test_timespec_ord() { + assert_eq!(TimeSpec::seconds(1), TimeSpec::nanoseconds(1_000_000_000)); + assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001)); + assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999)); + assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999)); + assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001)); + } + + #[test] + pub fn test_timespec_fmt() { + assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); + assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!( + TimeSpec::nanoseconds(42).to_string(), + "0.000000042 seconds" + ); + assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); + } + + #[test] + pub fn test_timeval() { + assert_ne!(TimeVal::seconds(1), TimeVal::zero()); + assert_eq!( + TimeVal::seconds(1) + TimeVal::seconds(2), + TimeVal::seconds(3) + ); + assert_eq!( + TimeVal::minutes(3) + TimeVal::seconds(2), + TimeVal::seconds(182) + ); + } + + #[test] + pub fn test_timeval_ord() { + assert_eq!(TimeVal::seconds(1), TimeVal::microseconds(1_000_000)); + assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001)); + assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999)); + assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999)); + assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001)); + } + + #[test] + pub fn test_timeval_neg() { + let a = TimeVal::seconds(1) + TimeVal::microseconds(123); + let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); + + assert_eq!(a, -b); + } + + #[test] + pub fn test_timeval_fmt() { + assert_eq!(TimeVal::zero().to_string(), "0 seconds"); + assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); + assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); + } +} diff --git a/src/sys/timer.rs b/src/sys/timer.rs new file mode 100644 index 0000000..e1a3405 --- /dev/null +++ b/src/sys/timer.rs @@ -0,0 +1,187 @@ +//! Timer API via signals. +//! +//! Timer is a POSIX API to create timers and get expiration notifications +//! through queued Unix signals, for the current process. This is similar to +//! Linux's timerfd mechanism, except that API is specific to Linux and makes +//! use of file polling. +//! +//! For more documentation, please read [timer_create](https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html). +//! +//! # Examples +//! +//! Create an interval timer that signals SIGALARM every 250 milliseconds. +//! +//! ```no_run +//! use nix::sys::signal::{self, SigEvent, SigHandler, SigevNotify, Signal}; +//! use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; +//! use nix::time::ClockId; +//! use std::convert::TryFrom; +//! use std::sync::atomic::{AtomicU64, Ordering}; +//! use std::thread::yield_now; +//! use std::time::Duration; +//! +//! const SIG: Signal = Signal::SIGALRM; +//! static ALARMS: AtomicU64 = AtomicU64::new(0); +//! +//! extern "C" fn handle_alarm(signal: libc::c_int) { +//! let signal = Signal::try_from(signal).unwrap(); +//! if signal == SIG { +//! ALARMS.fetch_add(1, Ordering::Relaxed); +//! } +//! } +//! +//! fn main() { +//! let clockid = ClockId::CLOCK_MONOTONIC; +//! let sigevent = SigEvent::new(SigevNotify::SigevSignal { +//! signal: SIG, +//! si_value: 0, +//! }); +//! +//! let mut timer = Timer::new(clockid, sigevent).unwrap(); +//! let expiration = Expiration::Interval(Duration::from_millis(250).into()); +//! let flags = TimerSetTimeFlags::empty(); +//! timer.set(expiration, flags).expect("could not set timer"); +//! +//! let handler = SigHandler::Handler(handle_alarm); +//! unsafe { signal::signal(SIG, handler) }.unwrap(); +//! +//! loop { +//! let alarms = ALARMS.load(Ordering::Relaxed); +//! if alarms >= 10 { +//! println!("total alarms handled: {}", alarms); +//! break; +//! } +//! yield_now() +//! } +//! } +//! ``` +use crate::sys::signal::SigEvent; +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; +use crate::time::ClockId; +use crate::{errno::Errno, Result}; +use core::mem; + +/// A Unix signal per-process timer. +#[derive(Debug)] +#[repr(transparent)] +pub struct Timer(libc::timer_t); + +impl Timer { + /// Creates a new timer based on the clock defined by `clockid`. The details + /// of the signal and its handler are defined by the passed `sigevent`. + #[doc(alias("timer_create"))] + pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result { + let mut timer_id: mem::MaybeUninit = + mem::MaybeUninit::uninit(); + Errno::result(unsafe { + libc::timer_create( + clockid.as_raw(), + sigevent.as_mut_ptr(), + timer_id.as_mut_ptr(), + ) + }) + .map(|_| { + // SAFETY: libc::timer_create is responsible for initializing + // timer_id. + unsafe { Self(timer_id.assume_init()) } + }) + } + + /// Set a new alarm on the timer. + /// + /// # Types of alarm + /// + /// There are 3 types of alarms you can set: + /// + /// - one shot: the alarm will trigger once after the specified amount of + /// time. + /// Example: I want an alarm to go off in 60s and then disable itself. + /// + /// - interval: the alarm will trigger every specified interval of time. + /// Example: I want an alarm to go off every 60s. The alarm will first + /// go off 60s after I set it and every 60s after that. The alarm will + /// not disable itself. + /// + /// - interval delayed: the alarm will trigger after a certain amount of + /// time and then trigger at a specified interval. + /// Example: I want an alarm to go off every 60s but only start in 1h. + /// The alarm will first trigger 1h after I set it and then every 60s + /// after that. The alarm will not disable itself. + /// + /// # Relative vs absolute alarm + /// + /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass + /// to the `Expiration` you want is relative. If however you want an alarm + /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. + /// Then the one shot TimeSpec and the delay TimeSpec of the delayed + /// interval are going to be interpreted as absolute. + /// + /// # Disabling alarms + /// + /// Note: Only one alarm can be set for any given timer. Setting a new alarm + /// actually removes the previous one. + /// + /// Note: Setting a one shot alarm with a 0s TimeSpec disable the alarm + /// altogether. + #[doc(alias("timer_settime"))] + pub fn set( + &mut self, + expiration: Expiration, + flags: TimerSetTimeFlags, + ) -> Result<()> { + let timerspec: TimerSpec = expiration.into(); + Errno::result(unsafe { + libc::timer_settime( + self.0, + flags.bits(), + timerspec.as_ref(), + core::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Get the parameters for the alarm currently set, if any. + #[doc(alias("timer_gettime"))] + pub fn get(&self) -> Result> { + let mut timerspec = TimerSpec::none(); + Errno::result(unsafe { + libc::timer_gettime(self.0, timerspec.as_mut()) + }) + .map(|_| { + if timerspec.as_ref().it_interval.tv_sec == 0 + && timerspec.as_ref().it_interval.tv_nsec == 0 + && timerspec.as_ref().it_value.tv_sec == 0 + && timerspec.as_ref().it_value.tv_nsec == 0 + { + None + } else { + Some(timerspec.into()) + } + }) + } + + /// Return the number of timers that have overrun + /// + /// Each timer is able to queue one signal to the process at a time, meaning + /// if the signal is not handled before the next expiration the timer has + /// 'overrun'. This function returns how many times that has happened to + /// this timer, up to `libc::DELAYTIMER_MAX`. If more than the maximum + /// number of overruns have happened the return is capped to the maximum. + #[doc(alias("timer_getoverrun"))] + pub fn overruns(&self) -> i32 { + unsafe { libc::timer_getoverrun(self.0) } + } +} + +impl Drop for Timer { + fn drop(&mut self) { + if !std::thread::panicking() { + let result = Errno::result(unsafe { libc::timer_delete(self.0) }); + if let Err(Errno::EINVAL) = result { + panic!("close of Timer encountered EINVAL"); + } + } + } +} diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs new file mode 100644 index 0000000..a35fc92 --- /dev/null +++ b/src/sys/timerfd.rs @@ -0,0 +1,214 @@ +//! Timer API via file descriptors. +//! +//! Timer FD is a Linux-only API to create timers and get expiration +//! notifications through file descriptors. +//! +//! For more documentation, please read [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html). +//! +//! # Examples +//! +//! Create a new one-shot timer that expires after 1 second. +//! ``` +//! # use std::os::unix::io::AsRawFd; +//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags, +//! # Expiration}; +//! # use nix::sys::time::{TimeSpec, TimeValLike}; +//! # use nix::unistd::read; +//! # +//! // We create a new monotonic timer. +//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()) +//! .unwrap(); +//! +//! // We set a new one-shot timer in 1 seconds. +//! timer.set( +//! Expiration::OneShot(TimeSpec::seconds(1)), +//! TimerSetTimeFlags::empty() +//! ).unwrap(); +//! +//! // We wait for the timer to expire. +//! timer.wait().unwrap(); +//! ``` +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; +use crate::unistd::read; +use crate::{errno::Errno, Result}; +use libc::c_int; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; + +/// A timerfd instance. This is also a file descriptor, you can feed it to +/// other interfaces consuming file descriptors, epoll for example. +#[derive(Debug)] +pub struct TimerFd { + fd: RawFd, +} + +impl AsRawFd for TimerFd { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl FromRawFd for TimerFd { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + TimerFd { fd } + } +} + +libc_enum! { + /// The type of the clock used to mark the progress of the timer. For more + /// details on each kind of clock, please refer to [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html). + #[repr(i32)] + #[non_exhaustive] + pub enum ClockId { + /// A settable system-wide real-time clock. + CLOCK_REALTIME, + /// A non-settable monotonically increasing clock. + /// + /// Does not change after system startup. + /// Does not measure time while the system is suspended. + CLOCK_MONOTONIC, + /// Like `CLOCK_MONOTONIC`, except that `CLOCK_BOOTTIME` includes the time + /// that the system was suspended. + CLOCK_BOOTTIME, + /// Like `CLOCK_REALTIME`, but will wake the system if it is suspended. + CLOCK_REALTIME_ALARM, + /// Like `CLOCK_BOOTTIME`, but will wake the system if it is suspended. + CLOCK_BOOTTIME_ALARM, + } +} + +libc_bitflags! { + /// Additional flags to change the behaviour of the file descriptor at the + /// time of creation. + pub struct TimerFlags: c_int { + /// Set the `O_NONBLOCK` flag on the open file description referred to by the new file descriptor. + TFD_NONBLOCK; + /// Set the `FD_CLOEXEC` flag on the file descriptor. + TFD_CLOEXEC; + } +} + +impl TimerFd { + /// Creates a new timer based on the clock defined by `clockid`. The + /// underlying fd can be assigned specific flags with `flags` (CLOEXEC, + /// NONBLOCK). The underlying fd will be closed on drop. + #[doc(alias("timerfd_create"))] + pub fn new(clockid: ClockId, flags: TimerFlags) -> Result { + Errno::result(unsafe { + libc::timerfd_create(clockid as i32, flags.bits()) + }) + .map(|fd| Self { fd }) + } + + /// Sets a new alarm on the timer. + /// + /// # Types of alarm + /// + /// There are 3 types of alarms you can set: + /// + /// - one shot: the alarm will trigger once after the specified amount of + /// time. + /// Example: I want an alarm to go off in 60s and then disable itself. + /// + /// - interval: the alarm will trigger every specified interval of time. + /// Example: I want an alarm to go off every 60s. The alarm will first + /// go off 60s after I set it and every 60s after that. The alarm will + /// not disable itself. + /// + /// - interval delayed: the alarm will trigger after a certain amount of + /// time and then trigger at a specified interval. + /// Example: I want an alarm to go off every 60s but only start in 1h. + /// The alarm will first trigger 1h after I set it and then every 60s + /// after that. The alarm will not disable itself. + /// + /// # Relative vs absolute alarm + /// + /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass + /// to the `Expiration` you want is relative. If however you want an alarm + /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. + /// Then the one shot TimeSpec and the delay TimeSpec of the delayed + /// interval are going to be interpreted as absolute. + /// + /// # Disabling alarms + /// + /// Note: Only one alarm can be set for any given timer. Setting a new alarm + /// actually removes the previous one. + /// + /// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm + /// altogether. + #[doc(alias("timerfd_settime"))] + pub fn set( + &self, + expiration: Expiration, + flags: TimerSetTimeFlags, + ) -> Result<()> { + let timerspec: TimerSpec = expiration.into(); + Errno::result(unsafe { + libc::timerfd_settime( + self.fd, + flags.bits(), + timerspec.as_ref(), + std::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Get the parameters for the alarm currently set, if any. + #[doc(alias("timerfd_gettime"))] + pub fn get(&self) -> Result> { + let mut timerspec = TimerSpec::none(); + Errno::result(unsafe { + libc::timerfd_gettime(self.fd, timerspec.as_mut()) + }) + .map(|_| { + if timerspec.as_ref().it_interval.tv_sec == 0 + && timerspec.as_ref().it_interval.tv_nsec == 0 + && timerspec.as_ref().it_value.tv_sec == 0 + && timerspec.as_ref().it_value.tv_nsec == 0 + { + None + } else { + Some(timerspec.into()) + } + }) + } + + /// Remove the alarm if any is set. + #[doc(alias("timerfd_settime"))] + pub fn unset(&self) -> Result<()> { + Errno::result(unsafe { + libc::timerfd_settime( + self.fd, + TimerSetTimeFlags::empty().bits(), + TimerSpec::none().as_ref(), + std::ptr::null_mut(), + ) + }) + .map(drop) + } + + /// Wait for the configured alarm to expire. + /// + /// Note: If the alarm is unset, then you will wait forever. + pub fn wait(&self) -> Result<()> { + while let Err(e) = read(self.fd, &mut [0u8; 8]) { + if e != Errno::EINTR { + return Err(e); + } + } + + Ok(()) + } +} + +impl Drop for TimerFd { + fn drop(&mut self) { + if !std::thread::panicking() { + let result = Errno::result(unsafe { libc::close(self.fd) }); + if let Err(Errno::EBADF) = result { + panic!("close of TimerFd encountered EBADF"); + } + } + } +} diff --git a/src/sys/uio.rs b/src/sys/uio.rs new file mode 100644 index 0000000..b31c306 --- /dev/null +++ b/src/sys/uio.rs @@ -0,0 +1,291 @@ +//! Vectored I/O + +use crate::errno::Errno; +use crate::Result; +use libc::{self, c_int, c_void, off_t, size_t}; +use std::io::{IoSlice, IoSliceMut}; +use std::marker::PhantomData; +use std::os::unix::io::RawFd; + +/// Low-level vectored write to a raw file descriptor +/// +/// See also [writev(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/writev.html) +pub fn writev(fd: RawFd, iov: &[IoSlice<'_>]) -> Result { + // SAFETY: to quote the documentation for `IoSlice`: + // + // [IoSlice] is semantically a wrapper around a &[u8], but is + // guaranteed to be ABI compatible with the iovec type on Unix + // platforms. + // + // Because it is ABI compatible, a pointer cast here is valid + let res = unsafe { + libc::writev(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Low-level vectored read from a raw file descriptor +/// +/// See also [readv(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html) +pub fn readv(fd: RawFd, iov: &mut [IoSliceMut<'_>]) -> Result { + // SAFETY: same as in writev(), IoSliceMut is ABI-compatible with iovec + let res = unsafe { + libc::readv(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Write to `fd` at `offset` from buffers in `iov`. +/// +/// Buffers in `iov` will be written in order until all buffers have been written +/// or an error occurs. The file offset is not changed. +/// +/// See also: [`writev`](fn.writev.html) and [`pwrite`](fn.pwrite.html) +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn pwritev(fd: RawFd, iov: &[IoSlice<'_>], offset: off_t) -> Result { + #[cfg(target_env = "uclibc")] + let offset = offset as libc::off64_t; // uclibc doesn't use off_t + + // SAFETY: same as in writev() + let res = unsafe { + libc::pwritev( + fd, + iov.as_ptr() as *const libc::iovec, + iov.len() as c_int, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Read from `fd` at `offset` filling buffers in `iov`. +/// +/// Buffers in `iov` will be filled in order until all buffers have been filled, +/// no more bytes are available, or an error occurs. The file offset is not +/// changed. +/// +/// See also: [`readv`](fn.readv.html) and [`pread`](fn.pread.html) +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn preadv( + fd: RawFd, + iov: &mut [IoSliceMut<'_>], + offset: off_t, +) -> Result { + #[cfg(target_env = "uclibc")] + let offset = offset as libc::off64_t; // uclibc doesn't use off_t + + // SAFETY: same as in readv() + let res = unsafe { + libc::preadv( + fd, + iov.as_ptr() as *const libc::iovec, + iov.len() as c_int, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Low-level write to a file, with specified offset. +/// +/// See also [pwrite(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html) +// TODO: move to unistd +pub fn pwrite(fd: RawFd, buf: &[u8], offset: off_t) -> Result { + let res = unsafe { + libc::pwrite( + fd, + buf.as_ptr() as *const c_void, + buf.len() as size_t, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Low-level read from a file, with specified offset. +/// +/// See also [pread(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html) +// TODO: move to unistd +pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result { + let res = unsafe { + libc::pread( + fd, + buf.as_mut_ptr() as *mut c_void, + buf.len() as size_t, + offset, + ) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// A slice of memory in a remote process, starting at address `base` +/// and consisting of `len` bytes. +/// +/// This is the same underlying C structure as `IoSlice`, +/// except that it refers to memory in some other process, and is +/// therefore not represented in Rust by an actual slice as `IoSlice` is. It +/// is used with [`process_vm_readv`](fn.process_vm_readv.html) +/// and [`process_vm_writev`](fn.process_vm_writev.html). +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct RemoteIoVec { + /// The starting address of this slice (`iov_base`). + pub base: usize, + /// The number of bytes in this slice (`iov_len`). + pub len: usize, +} + +/// A vector of buffers. +/// +/// Vectored I/O methods like [`writev`] and [`readv`] use this structure for +/// both reading and writing. Each `IoVec` specifies the base address and +/// length of an area in memory. +#[deprecated( + since = "0.24.0", + note = "`IoVec` is no longer used in the public interface, use `IoSlice` or `IoSliceMut` instead" +)] +#[repr(transparent)] +#[allow(renamed_and_removed_lints)] +#[allow(clippy::unknown_clippy_lints)] +// Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/8867 +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct IoVec(pub(crate) libc::iovec, PhantomData); + +#[allow(deprecated)] +impl IoVec { + /// View the `IoVec` as a Rust slice. + #[deprecated( + since = "0.24.0", + note = "Use the `Deref` impl of `IoSlice` or `IoSliceMut` instead" + )] + #[inline] + pub fn as_slice(&self) -> &[u8] { + use std::slice; + + unsafe { + slice::from_raw_parts(self.0.iov_base as *const u8, self.0.iov_len) + } + } +} + +#[allow(deprecated)] +impl<'a> IoVec<&'a [u8]> { + /// Create an `IoVec` from a Rust slice. + #[deprecated(since = "0.24.0", note = "Use `IoSlice::new` instead")] + pub fn from_slice(buf: &'a [u8]) -> IoVec<&'a [u8]> { + IoVec( + libc::iovec { + iov_base: buf.as_ptr() as *mut c_void, + iov_len: buf.len() as size_t, + }, + PhantomData, + ) + } +} + +#[allow(deprecated)] +impl<'a> IoVec<&'a mut [u8]> { + /// Create an `IoVec` from a mutable Rust slice. + #[deprecated(since = "0.24.0", note = "Use `IoSliceMut::new` instead")] + pub fn from_mut_slice(buf: &'a mut [u8]) -> IoVec<&'a mut [u8]> { + IoVec( + libc::iovec { + iov_base: buf.as_ptr() as *mut c_void, + iov_len: buf.len() as size_t, + }, + PhantomData, + ) + } +} + +// The only reason IoVec isn't automatically Send+Sync is because libc::iovec +// contains raw pointers. +#[allow(deprecated)] +unsafe impl Send for IoVec where T: Send {} +#[allow(deprecated)] +unsafe impl Sync for IoVec where T: Sync {} + +feature! { +#![feature = "process"] + +/// Write data directly to another process's virtual memory +/// (see [`process_vm_writev`(2)]). +/// +/// `local_iov` is a list of [`IoSlice`]s containing the data to be written, +/// and `remote_iov` is a list of [`RemoteIoVec`]s identifying where the +/// data should be written in the target process. On success, returns the +/// number of bytes written, which will always be a whole +/// number of `remote_iov` chunks. +/// +/// This requires the same permissions as debugging the process using +/// [ptrace]: you must either be a privileged process (with +/// `CAP_SYS_PTRACE`), or you must be running as the same user as the +/// target process and the OS must have unprivileged debugging enabled. +/// +/// This function is only available on Linux and Android(SDK23+). +/// +/// [`process_vm_writev`(2)]: https://man7.org/linux/man-pages/man2/process_vm_writev.2.html +/// [ptrace]: ../ptrace/index.html +/// [`IoSlice`]: https://doc.rust-lang.org/std/io/struct.IoSlice.html +/// [`RemoteIoVec`]: struct.RemoteIoVec.html +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +pub fn process_vm_writev( + pid: crate::unistd::Pid, + local_iov: &[IoSlice<'_>], + remote_iov: &[RemoteIoVec]) -> Result +{ + let res = unsafe { + libc::process_vm_writev(pid.into(), + local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, + remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Read data directly from another process's virtual memory +/// (see [`process_vm_readv`(2)]). +/// +/// `local_iov` is a list of [`IoSliceMut`]s containing the buffer to copy +/// data into, and `remote_iov` is a list of [`RemoteIoVec`]s identifying +/// where the source data is in the target process. On success, +/// returns the number of bytes written, which will always be a whole +/// number of `remote_iov` chunks. +/// +/// This requires the same permissions as debugging the process using +/// [`ptrace`]: you must either be a privileged process (with +/// `CAP_SYS_PTRACE`), or you must be running as the same user as the +/// target process and the OS must have unprivileged debugging enabled. +/// +/// This function is only available on Linux and Android(SDK23+). +/// +/// [`process_vm_readv`(2)]: https://man7.org/linux/man-pages/man2/process_vm_readv.2.html +/// [`ptrace`]: ../ptrace/index.html +/// [`IoSliceMut`]: https://doc.rust-lang.org/std/io/struct.IoSliceMut.html +/// [`RemoteIoVec`]: struct.RemoteIoVec.html +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +pub fn process_vm_readv( + pid: crate::unistd::Pid, + local_iov: &mut [IoSliceMut<'_>], + remote_iov: &[RemoteIoVec]) -> Result +{ + let res = unsafe { + libc::process_vm_readv(pid.into(), + local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, + remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + }; + + Errno::result(res).map(|r| r as usize) +} +} diff --git a/src/sys/utsname.rs b/src/sys/utsname.rs new file mode 100644 index 0000000..b48ed9f --- /dev/null +++ b/src/sys/utsname.rs @@ -0,0 +1,85 @@ +//! Get system identification +use crate::{Errno, Result}; +use libc::c_char; +use std::ffi::OsStr; +use std::mem; +use std::os::unix::ffi::OsStrExt; + +/// Describes the running system. Return type of [`uname`]. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct UtsName(libc::utsname); + +impl UtsName { + /// Name of the operating system implementation. + pub fn sysname(&self) -> &OsStr { + cast_and_trim(&self.0.sysname) + } + + /// Network name of this machine. + pub fn nodename(&self) -> &OsStr { + cast_and_trim(&self.0.nodename) + } + + /// Release level of the operating system. + pub fn release(&self) -> &OsStr { + cast_and_trim(&self.0.release) + } + + /// Version level of the operating system. + pub fn version(&self) -> &OsStr { + cast_and_trim(&self.0.version) + } + + /// Machine hardware platform. + pub fn machine(&self) -> &OsStr { + cast_and_trim(&self.0.machine) + } + + /// NIS or YP domain name of this machine. + #[cfg(any(target_os = "android", target_os = "linux"))] + pub fn domainname(&self) -> &OsStr { + cast_and_trim(&self.0.domainname) + } +} + +/// Get system identification +pub fn uname() -> Result { + unsafe { + let mut ret = mem::MaybeUninit::zeroed(); + Errno::result(libc::uname(ret.as_mut_ptr()))?; + Ok(UtsName(ret.assume_init())) + } +} + +fn cast_and_trim(slice: &[c_char]) -> &OsStr { + let length = slice + .iter() + .position(|&byte| byte == 0) + .unwrap_or(slice.len()); + let bytes = + unsafe { std::slice::from_raw_parts(slice.as_ptr().cast(), length) }; + + OsStr::from_bytes(bytes) +} + +#[cfg(test)] +mod test { + #[cfg(target_os = "linux")] + #[test] + pub fn test_uname_linux() { + assert_eq!(super::uname().unwrap().sysname(), "Linux"); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + #[test] + pub fn test_uname_darwin() { + assert_eq!(super::uname().unwrap().sysname(), "Darwin"); + } + + #[cfg(target_os = "freebsd")] + #[test] + pub fn test_uname_freebsd() { + assert_eq!(super::uname().unwrap().sysname(), "FreeBSD"); + } +} diff --git a/src/sys/wait.rs b/src/sys/wait.rs new file mode 100644 index 0000000..b6524e8 --- /dev/null +++ b/src/sys/wait.rs @@ -0,0 +1,388 @@ +//! Wait for a process to change status +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::unistd::Pid; +use crate::Result; +use cfg_if::cfg_if; +use libc::{self, c_int}; +use std::convert::TryFrom; +#[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_env = "uclibc")), +))] +use std::os::unix::io::RawFd; + +libc_bitflags!( + /// Controls the behavior of [`waitpid`]. + pub struct WaitPidFlag: c_int { + /// Do not block when there are no processes wishing to report status. + WNOHANG; + /// Report the status of selected processes which are stopped due to a + /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN), + /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU), + /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or + /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal. + WUNTRACED; + /// Report the status of selected processes which have terminated. + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + WEXITED; + /// Report the status of selected processes that have continued from a + /// job control stop by receiving a + /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal. + WCONTINUED; + /// An alias for WUNTRACED. + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + WSTOPPED; + /// Don't reap, just poll status. + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "redox", + target_os = "macos", + target_os = "netbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + WNOWAIT; + /// Don't wait on children of other threads in this group + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + __WNOTHREAD; + /// Wait on all children, regardless of type + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + __WALL; + /// Wait for "clone" children only. + #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + __WCLONE; + } +); + +/// Possible return values from `wait()` or `waitpid()`. +/// +/// Each status (other than `StillAlive`) describes a state transition +/// in a child process `Pid`, such as the process exiting or stopping, +/// plus additional data about the transition if any. +/// +/// Note that there are two Linux-specific enum variants, `PtraceEvent` +/// and `PtraceSyscall`. Portable code should avoid exhaustively +/// matching on `WaitStatus`. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum WaitStatus { + /// The process exited normally (as with `exit()` or returning from + /// `main`) with the given exit code. This case matches the C macro + /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`. + Exited(Pid, i32), + /// The process was killed by the given signal. The third field + /// indicates whether the signal generated a core dump. This case + /// matches the C macro `WIFSIGNALED(status)`; the last two fields + /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`. + Signaled(Pid, Signal, bool), + /// The process is alive, but was stopped by the given signal. This + /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This + /// case matches the C macro `WIFSTOPPED(status)`; the second field + /// is `WSTOPSIG(status)`. + Stopped(Pid, Signal), + /// The traced process was stopped by a `PTRACE_EVENT_*` event. See + /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All + /// currently-defined events use `SIGTRAP` as the signal; the third + /// field is the `PTRACE_EVENT_*` value of the event. + /// + /// [`nix::sys::ptrace`]: ../ptrace/index.html + /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html + #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PtraceEvent(Pid, Signal, c_int), + /// The traced process was stopped by execution of a system call, + /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for + /// more information. + /// + /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html + #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PtraceSyscall(Pid), + /// The process was previously stopped but has resumed execution + /// after receiving a `SIGCONT` signal. This is only reported if + /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C + /// macro `WIFCONTINUED(status)`. + Continued(Pid), + /// There are currently no state changes to report in any awaited + /// child process. This is only returned if `WaitPidFlag::WNOHANG` + /// was used (otherwise `wait()` or `waitpid()` would block until + /// there was something to report). + StillAlive, +} + +impl WaitStatus { + /// Extracts the PID from the WaitStatus unless it equals StillAlive. + pub fn pid(&self) -> Option { + use self::WaitStatus::*; + match *self { + Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => { + Some(p) + } + StillAlive => None, + #[cfg(any(target_os = "android", target_os = "linux"))] + PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p), + } + } +} + +fn exited(status: i32) -> bool { + libc::WIFEXITED(status) +} + +fn exit_status(status: i32) -> i32 { + libc::WEXITSTATUS(status) +} + +fn signaled(status: i32) -> bool { + libc::WIFSIGNALED(status) +} + +fn term_signal(status: i32) -> Result { + Signal::try_from(libc::WTERMSIG(status)) +} + +fn dumped_core(status: i32) -> bool { + libc::WCOREDUMP(status) +} + +fn stopped(status: i32) -> bool { + libc::WIFSTOPPED(status) +} + +fn stop_signal(status: i32) -> Result { + Signal::try_from(libc::WSTOPSIG(status)) +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn syscall_stop(status: i32) -> bool { + // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect + // of delivering SIGTRAP | 0x80 as the signal number for syscall + // stops. This allows easily distinguishing syscall stops from + // genuine SIGTRAP signals. + libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn stop_additional(status: i32) -> c_int { + (status >> 16) as c_int +} + +fn continued(status: i32) -> bool { + libc::WIFCONTINUED(status) +} + +impl WaitStatus { + /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus` + /// + /// # Errors + /// + /// Returns an `Error` corresponding to `EINVAL` for invalid status values. + /// + /// # Examples + /// + /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`: + /// + /// ``` + /// use nix::sys::wait::WaitStatus; + /// use nix::sys::signal::Signal; + /// let pid = nix::unistd::Pid::from_raw(1); + /// let status = WaitStatus::from_raw(pid, 0x0002); + /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false))); + /// ``` + pub fn from_raw(pid: Pid, status: i32) -> Result { + Ok(if exited(status) { + WaitStatus::Exited(pid, exit_status(status)) + } else if signaled(status) { + WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status)) + } else if stopped(status) { + cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + fn decode_stopped(pid: Pid, status: i32) -> Result { + let status_additional = stop_additional(status); + Ok(if syscall_stop(status) { + WaitStatus::PtraceSyscall(pid) + } else if status_additional == 0 { + WaitStatus::Stopped(pid, stop_signal(status)?) + } else { + WaitStatus::PtraceEvent(pid, stop_signal(status)?, + stop_additional(status)) + }) + } + } else { + fn decode_stopped(pid: Pid, status: i32) -> Result { + Ok(WaitStatus::Stopped(pid, stop_signal(status)?)) + } + } + } + return decode_stopped(pid, status); + } else { + assert!(continued(status)); + WaitStatus::Continued(pid) + }) + } + + /// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus` + /// + /// # Errors + /// + /// Returns an `Error` corresponding to `EINVAL` for invalid values. + /// + /// # Safety + /// + /// siginfo_t is actually a union, not all fields may be initialized. + /// The functions si_pid() and si_status() must be valid to call on + /// the passed siginfo_t. + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), + ))] + unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result { + let si_pid = siginfo.si_pid(); + if si_pid == 0 { + return Ok(WaitStatus::StillAlive); + } + + assert_eq!(siginfo.si_signo, libc::SIGCHLD); + + let pid = Pid::from_raw(si_pid); + let si_status = siginfo.si_status(); + + let status = match siginfo.si_code { + libc::CLD_EXITED => WaitStatus::Exited(pid, si_status), + libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled( + pid, + Signal::try_from(si_status)?, + siginfo.si_code == libc::CLD_DUMPED, + ), + libc::CLD_STOPPED => { + WaitStatus::Stopped(pid, Signal::try_from(si_status)?) + } + libc::CLD_CONTINUED => WaitStatus::Continued(pid), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::CLD_TRAPPED => { + if si_status == libc::SIGTRAP | 0x80 { + WaitStatus::PtraceSyscall(pid) + } else { + WaitStatus::PtraceEvent( + pid, + Signal::try_from(si_status & 0xff)?, + (si_status >> 8) as c_int, + ) + } + } + _ => return Err(Errno::EINVAL), + }; + + Ok(status) + } +} + +/// Wait for a process to change status +/// +/// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html) +pub fn waitpid>>( + pid: P, + options: Option, +) -> Result { + use self::WaitStatus::*; + + let mut status: i32 = 0; + + let option_bits = match options { + Some(bits) => bits.bits(), + None => 0, + }; + + let res = unsafe { + libc::waitpid( + pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(), + &mut status as *mut c_int, + option_bits, + ) + }; + + match Errno::result(res)? { + 0 => Ok(StillAlive), + res => WaitStatus::from_raw(Pid::from_raw(res), status), + } +} + +/// Wait for any child process to change status or a signal is received. +/// +/// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html) +pub fn wait() -> Result { + waitpid(None, None) +} + +/// The ID argument for `waitid` +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Id { + /// Wait for any child + All, + /// Wait for the child whose process ID matches the given PID + Pid(Pid), + /// Wait for the child whose process group ID matches the given PID + /// + /// If the PID is zero, the caller's process group is used since Linux 5.4. + PGid(Pid), + /// Wait for the child referred to by the given PID file descriptor + #[cfg(any(target_os = "android", target_os = "linux"))] + PIDFd(RawFd), +} + +/// Wait for a process to change status +/// +/// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html) +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +pub fn waitid(id: Id, flags: WaitPidFlag) -> Result { + let (idtype, idval) = match id { + Id::All => (libc::P_ALL, 0), + Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t), + Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), + #[cfg(any(target_os = "android", target_os = "linux"))] + Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t), + }; + + let siginfo = unsafe { + // Memory is zeroed rather than uninitialized, as not all platforms + // initialize the memory in the StillAlive case + let mut siginfo: libc::siginfo_t = std::mem::zeroed(); + Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?; + siginfo + }; + + unsafe { WaitStatus::from_siginfo(&siginfo) } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..2e03c46 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,283 @@ +use crate::sys::time::TimeSpec; +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +#[cfg(feature = "process")] +use crate::unistd::Pid; +use crate::{Errno, Result}; +use libc::{self, clockid_t}; +use std::mem::MaybeUninit; + +/// Clock identifier +/// +/// Newtype pattern around `clockid_t` (which is just alias). It prevents bugs caused by +/// accidentally passing wrong value. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ClockId(clockid_t); + +impl ClockId { + /// Creates `ClockId` from raw `clockid_t` + pub const fn from_raw(clk_id: clockid_t) -> Self { + ClockId(clk_id) + } + + feature! { + #![feature = "process"] + /// Returns `ClockId` of a `pid` CPU-time clock + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn pid_cpu_clock_id(pid: Pid) -> Result { + clock_getcpuclockid(pid) + } + } + + /// Returns resolution of the clock id + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn res(self) -> Result { + clock_getres(self) + } + + /// Returns the current time on the clock id + pub fn now(self) -> Result { + clock_gettime(self) + } + + /// Sets time to `timespec` on the clock id + #[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "redox", + target_os = "hermit", + )))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn set_time(self, timespec: TimeSpec) -> Result<()> { + clock_settime(self, timespec) + } + + /// Gets the raw `clockid_t` wrapped by `self` + pub const fn as_raw(self) -> clockid_t { + self.0 + } + + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_BOOTTIME: ClockId = ClockId(libc::CLOCK_BOOTTIME); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_BOOTTIME_ALARM: ClockId = + ClockId(libc::CLOCK_BOOTTIME_ALARM); + pub const CLOCK_MONOTONIC: ClockId = ClockId(libc::CLOCK_MONOTONIC); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_MONOTONIC_COARSE: ClockId = + ClockId(libc::CLOCK_MONOTONIC_COARSE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_MONOTONIC_FAST: ClockId = + ClockId(libc::CLOCK_MONOTONIC_FAST); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_MONOTONIC_PRECISE: ClockId = + ClockId(libc::CLOCK_MONOTONIC_PRECISE); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_MONOTONIC_RAW: ClockId = ClockId(libc::CLOCK_MONOTONIC_RAW); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "redox", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_PROCESS_CPUTIME_ID: ClockId = + ClockId(libc::CLOCK_PROCESS_CPUTIME_ID); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_PROF: ClockId = ClockId(libc::CLOCK_PROF); + pub const CLOCK_REALTIME: ClockId = ClockId(libc::CLOCK_REALTIME); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_REALTIME_ALARM: ClockId = + ClockId(libc::CLOCK_REALTIME_ALARM); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_REALTIME_COARSE: ClockId = + ClockId(libc::CLOCK_REALTIME_COARSE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_REALTIME_FAST: ClockId = ClockId(libc::CLOCK_REALTIME_FAST); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_REALTIME_PRECISE: ClockId = + ClockId(libc::CLOCK_REALTIME_PRECISE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_SECOND: ClockId = ClockId(libc::CLOCK_SECOND); + #[cfg(any( + target_os = "emscripten", + target_os = "fuchsia", + all(target_os = "linux", target_env = "musl") + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_SGI_CYCLE: ClockId = ClockId(libc::CLOCK_SGI_CYCLE); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_TAI: ClockId = ClockId(libc::CLOCK_TAI); + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux" + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_THREAD_CPUTIME_ID: ClockId = + ClockId(libc::CLOCK_THREAD_CPUTIME_ID); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_UPTIME: ClockId = ClockId(libc::CLOCK_UPTIME); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_UPTIME_FAST: ClockId = ClockId(libc::CLOCK_UPTIME_FAST); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_UPTIME_PRECISE: ClockId = + ClockId(libc::CLOCK_UPTIME_PRECISE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub const CLOCK_VIRTUAL: ClockId = ClockId(libc::CLOCK_VIRTUAL); +} + +impl From for clockid_t { + fn from(clock_id: ClockId) -> Self { + clock_id.as_raw() + } +} + +impl From for ClockId { + fn from(clk_id: clockid_t) -> Self { + ClockId::from_raw(clk_id) + } +} + +impl std::fmt::Display for ClockId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +/// Get the resolution of the specified clock, (see +/// [clock_getres(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_getres.html)). +#[cfg(not(target_os = "redox"))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn clock_getres(clock_id: ClockId) -> Result { + let mut c_time: MaybeUninit = MaybeUninit::uninit(); + let ret = + unsafe { libc::clock_getres(clock_id.as_raw(), c_time.as_mut_ptr()) }; + Errno::result(ret)?; + let res = unsafe { c_time.assume_init() }; + Ok(TimeSpec::from(res)) +} + +/// Get the time of the specified clock, (see +/// [clock_gettime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_gettime.html)). +pub fn clock_gettime(clock_id: ClockId) -> Result { + let mut c_time: MaybeUninit = MaybeUninit::uninit(); + let ret = + unsafe { libc::clock_gettime(clock_id.as_raw(), c_time.as_mut_ptr()) }; + Errno::result(ret)?; + let res = unsafe { c_time.assume_init() }; + Ok(TimeSpec::from(res)) +} + +/// Set the time of the specified clock, (see +/// [clock_settime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_settime.html)). +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "redox", + target_os = "hermit", +)))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn clock_settime(clock_id: ClockId, timespec: TimeSpec) -> Result<()> { + let ret = + unsafe { libc::clock_settime(clock_id.as_raw(), timespec.as_ref()) }; + Errno::result(ret).map(drop) +} + +/// Get the clock id of the specified process id, (see +/// [clock_getcpuclockid(3)](https://pubs.opengroup.org/onlinepubs/009695399/functions/clock_getcpuclockid.html)). +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +#[cfg(feature = "process")] +#[cfg_attr(docsrs, doc(cfg(feature = "process")))] +pub fn clock_getcpuclockid(pid: Pid) -> Result { + let mut clk_id: MaybeUninit = MaybeUninit::uninit(); + let ret = + unsafe { libc::clock_getcpuclockid(pid.into(), clk_id.as_mut_ptr()) }; + if ret == 0 { + let res = unsafe { clk_id.assume_init() }; + Ok(ClockId::from(res)) + } else { + Err(Errno::from_i32(ret)) + } +} diff --git a/src/ucontext.rs b/src/ucontext.rs new file mode 100644 index 0000000..b2a39f7 --- /dev/null +++ b/src/ucontext.rs @@ -0,0 +1,47 @@ +#[cfg(not(target_env = "musl"))] +use crate::errno::Errno; +use crate::sys::signal::SigSet; +#[cfg(not(target_env = "musl"))] +use crate::Result; +#[cfg(not(target_env = "musl"))] +use std::mem; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct UContext { + context: libc::ucontext_t, +} + +impl UContext { + #[cfg(not(target_env = "musl"))] + pub fn get() -> Result { + let mut context = mem::MaybeUninit::::uninit(); + let res = unsafe { libc::getcontext(context.as_mut_ptr()) }; + Errno::result(res).map(|_| unsafe { + UContext { + context: context.assume_init(), + } + }) + } + + #[cfg(not(target_env = "musl"))] + pub fn set(&self) -> Result<()> { + let res = unsafe { + libc::setcontext(&self.context as *const libc::ucontext_t) + }; + Errno::result(res).map(drop) + } + + pub fn sigmask_mut(&mut self) -> &mut SigSet { + unsafe { + &mut *(&mut self.context.uc_sigmask as *mut libc::sigset_t + as *mut SigSet) + } + } + + pub fn sigmask(&self) -> &SigSet { + unsafe { + &*(&self.context.uc_sigmask as *const libc::sigset_t + as *const SigSet) + } + } +} diff --git a/src/unistd.rs b/src/unistd.rs new file mode 100644 index 0000000..ca07b34 --- /dev/null +++ b/src/unistd.rs @@ -0,0 +1,3383 @@ +//! Safe wrappers around functions found in libc "unistd.h" header + +use crate::errno::{self, Errno}; +#[cfg(not(target_os = "redox"))] +#[cfg(feature = "fs")] +use crate::fcntl::{at_rawfd, AtFlags}; +#[cfg(feature = "fs")] +use crate::fcntl::{fcntl, FcntlArg::F_SETFD, FdFlag, OFlag}; +#[cfg(all( + feature = "fs", + any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "ios" + ) +))] +use crate::sys::stat::FileFlag; +#[cfg(feature = "fs")] +use crate::sys::stat::Mode; +use crate::{Error, NixPath, Result}; +#[cfg(not(target_os = "redox"))] +use cfg_if::cfg_if; +use libc::{ + self, c_char, c_int, c_long, c_uint, c_void, gid_t, mode_t, off_t, pid_t, + size_t, uid_t, PATH_MAX, +}; +use std::convert::Infallible; +use std::ffi::{CStr, OsString}; +#[cfg(not(target_os = "redox"))] +use std::ffi::{CString, OsStr}; +#[cfg(not(target_os = "redox"))] +use std::os::unix::ffi::OsStrExt; +use std::os::unix::ffi::OsStringExt; +use std::os::unix::io::RawFd; +use std::path::PathBuf; +use std::{fmt, mem, ptr}; + +feature! { + #![feature = "fs"] + #[cfg(any(target_os = "android", target_os = "linux"))] + pub use self::pivot_root::*; +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +pub use self::setres::*; + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +pub use self::getres::*; + +feature! { +#![feature = "user"] + +/// User identifier +/// +/// Newtype pattern around `uid_t` (which is just alias). It prevents bugs caused by accidentally +/// passing wrong value. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Uid(uid_t); + +impl Uid { + /// Creates `Uid` from raw `uid_t`. + pub const fn from_raw(uid: uid_t) -> Self { + Uid(uid) + } + + /// Returns Uid of calling process. This is practically a more Rusty alias for `getuid`. + #[doc(alias("getuid"))] + pub fn current() -> Self { + getuid() + } + + /// Returns effective Uid of calling process. This is practically a more Rusty alias for `geteuid`. + #[doc(alias("geteuid"))] + pub fn effective() -> Self { + geteuid() + } + + /// Returns true if the `Uid` represents privileged user - root. (If it equals zero.) + pub const fn is_root(self) -> bool { + self.0 == ROOT.0 + } + + /// Get the raw `uid_t` wrapped by `self`. + pub const fn as_raw(self) -> uid_t { + self.0 + } +} + +impl From for uid_t { + fn from(uid: Uid) -> Self { + uid.0 + } +} + +impl From for Uid { + fn from(uid: uid_t) -> Self { + Uid(uid) + } +} + +impl fmt::Display for Uid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +/// Constant for UID = 0 +pub const ROOT: Uid = Uid(0); + +/// Group identifier +/// +/// Newtype pattern around `gid_t` (which is just alias). It prevents bugs caused by accidentally +/// passing wrong value. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Gid(gid_t); + +impl Gid { + /// Creates `Gid` from raw `gid_t`. + pub const fn from_raw(gid: gid_t) -> Self { + Gid(gid) + } + + /// Returns Gid of calling process. This is practically a more Rusty alias for `getgid`. + #[doc(alias("getgid"))] + pub fn current() -> Self { + getgid() + } + + /// Returns effective Gid of calling process. This is practically a more Rusty alias for `getegid`. + #[doc(alias("getegid"))] + pub fn effective() -> Self { + getegid() + } + + /// Get the raw `gid_t` wrapped by `self`. + pub const fn as_raw(self) -> gid_t { + self.0 + } +} + +impl From for gid_t { + fn from(gid: Gid) -> Self { + gid.0 + } +} + +impl From for Gid { + fn from(gid: gid_t) -> Self { + Gid(gid) + } +} + +impl fmt::Display for Gid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} +} + +feature! { +#![feature = "process"] +/// Process identifier +/// +/// Newtype pattern around `pid_t` (which is just alias). It prevents bugs caused by accidentally +/// passing wrong value. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Pid(pid_t); + +impl Pid { + /// Creates `Pid` from raw `pid_t`. + pub const fn from_raw(pid: pid_t) -> Self { + Pid(pid) + } + + /// Returns PID of calling process + #[doc(alias("getpid"))] + pub fn this() -> Self { + getpid() + } + + /// Returns PID of parent of calling process + #[doc(alias("getppid"))] + pub fn parent() -> Self { + getppid() + } + + /// Get the raw `pid_t` wrapped by `self`. + pub const fn as_raw(self) -> pid_t { + self.0 + } +} + +impl From for pid_t { + fn from(pid: Pid) -> Self { + pid.0 + } +} + +impl fmt::Display for Pid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + + +/// Represents the successful result of calling `fork` +/// +/// When `fork` is called, the process continues execution in the parent process +/// and in the new child. This return type can be examined to determine whether +/// you are now executing in the parent process or in the child. +#[derive(Clone, Copy, Debug)] +pub enum ForkResult { + Parent { child: Pid }, + Child, +} + +impl ForkResult { + + /// Return `true` if this is the child process of the `fork()` + #[inline] + pub fn is_child(self) -> bool { + matches!(self, ForkResult::Child) + } + + /// Returns `true` if this is the parent process of the `fork()` + #[inline] + pub fn is_parent(self) -> bool { + !self.is_child() + } +} + +/// Create a new child process duplicating the parent process ([see +/// fork(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html)). +/// +/// After successfully calling the fork system call, a second process will +/// be created which is identical to the original except for the pid and the +/// return value of this function. As an example: +/// +/// ``` +/// use nix::{sys::wait::waitpid,unistd::{fork, ForkResult, write}}; +/// +/// match unsafe{fork()} { +/// Ok(ForkResult::Parent { child, .. }) => { +/// println!("Continuing execution in parent process, new child has pid: {}", child); +/// waitpid(child, None).unwrap(); +/// } +/// Ok(ForkResult::Child) => { +/// // Unsafe to use `println!` (or `unwrap`) here. See Safety. +/// write(libc::STDOUT_FILENO, "I'm a new child process\n".as_bytes()).ok(); +/// unsafe { libc::_exit(0) }; +/// } +/// Err(_) => println!("Fork failed"), +/// } +/// ``` +/// +/// This will print something like the following (order nondeterministic). The +/// thing to note is that you end up with two processes continuing execution +/// immediately after the fork call but with different match arms. +/// +/// ```text +/// Continuing execution in parent process, new child has pid: 1234 +/// I'm a new child process +/// ``` +/// +/// # Safety +/// +/// In a multithreaded program, only [async-signal-safe] functions like `pause` +/// and `_exit` may be called by the child (the parent isn't restricted). Note +/// that memory allocation may **not** be async-signal-safe and thus must be +/// prevented. +/// +/// Those functions are only a small subset of your operating system's API, so +/// special care must be taken to only invoke code you can control and audit. +/// +/// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html +#[inline] +pub unsafe fn fork() -> Result { + use self::ForkResult::*; + let res = libc::fork(); + + Errno::result(res).map(|res| match res { + 0 => Child, + res => Parent { child: Pid(res) }, + }) +} + +/// Get the pid of this process (see +/// [getpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpid.html)). +/// +/// Since you are running code, there is always a pid to return, so there +/// is no error case that needs to be handled. +#[inline] +pub fn getpid() -> Pid { + Pid(unsafe { libc::getpid() }) +} + +/// Get the pid of this processes' parent (see +/// [getpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getppid.html)). +/// +/// There is always a parent pid to return, so there is no error case that needs +/// to be handled. +#[inline] +pub fn getppid() -> Pid { + Pid(unsafe { libc::getppid() }) // no error handling, according to man page: "These functions are always successful." +} + +/// Set a process group ID (see +/// [setpgid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgid.html)). +/// +/// Set the process group id (PGID) of a particular process. If a pid of zero +/// is specified, then the pid of the calling process is used. Process groups +/// may be used to group together a set of processes in order for the OS to +/// apply some operations across the group. +/// +/// `setsid()` may be used to create a new process group. +#[inline] +pub fn setpgid(pid: Pid, pgid: Pid) -> Result<()> { + let res = unsafe { libc::setpgid(pid.into(), pgid.into()) }; + Errno::result(res).map(drop) +} +#[inline] +pub fn getpgid(pid: Option) -> Result { + let res = unsafe { libc::getpgid(pid.unwrap_or(Pid(0)).into()) }; + Errno::result(res).map(Pid) +} + +/// Create new session and set process group id (see +/// [setsid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsid.html)). +#[inline] +pub fn setsid() -> Result { + Errno::result(unsafe { libc::setsid() }).map(Pid) +} + +/// Get the process group ID of a session leader +/// [getsid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsid.html). +/// +/// Obtain the process group ID of the process that is the session leader of the process specified +/// by pid. If pid is zero, it specifies the calling process. +#[inline] +#[cfg(not(target_os = "redox"))] +pub fn getsid(pid: Option) -> Result { + let res = unsafe { libc::getsid(pid.unwrap_or(Pid(0)).into()) }; + Errno::result(res).map(Pid) +} +} + +feature! { +#![all(feature = "process", feature = "term")] +/// Get the terminal foreground process group (see +/// [tcgetpgrp(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetpgrp.html)). +/// +/// Get the group process id (GPID) of the foreground process group on the +/// terminal associated to file descriptor (FD). +#[inline] +pub fn tcgetpgrp(fd: c_int) -> Result { + let res = unsafe { libc::tcgetpgrp(fd) }; + Errno::result(res).map(Pid) +} +/// Set the terminal foreground process group (see +/// [tcgetpgrp(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html)). +/// +/// Get the group process id (PGID) to the foreground process group on the +/// terminal associated to file descriptor (FD). +#[inline] +pub fn tcsetpgrp(fd: c_int, pgrp: Pid) -> Result<()> { + let res = unsafe { libc::tcsetpgrp(fd, pgrp.into()) }; + Errno::result(res).map(drop) +} +} + +feature! { +#![feature = "process"] +/// Get the group id of the calling process (see +///[getpgrp(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgrp.html)). +/// +/// Get the process group id (PGID) of the calling process. +/// According to the man page it is always successful. +#[inline] +pub fn getpgrp() -> Pid { + Pid(unsafe { libc::getpgrp() }) +} + +/// Get the caller's thread ID (see +/// [gettid(2)](https://man7.org/linux/man-pages/man2/gettid.2.html). +/// +/// This function is only available on Linux based systems. In a single +/// threaded process, the main thread will have the same ID as the process. In +/// a multithreaded process, each thread will have a unique thread id but the +/// same process ID. +/// +/// No error handling is required as a thread id should always exist for any +/// process, even if threads are not being used. +#[cfg(any(target_os = "linux", target_os = "android"))] +#[inline] +pub fn gettid() -> Pid { + Pid(unsafe { libc::syscall(libc::SYS_gettid) as pid_t }) +} +} + +feature! { +#![feature = "fs"] +/// Create a copy of the specified file descriptor (see +/// [dup(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html)). +/// +/// The new file descriptor will have a new index but refer to the same +/// resource as the old file descriptor and the old and new file descriptors may +/// be used interchangeably. The new and old file descriptor share the same +/// underlying resource, offset, and file status flags. The actual index used +/// for the file descriptor will be the lowest fd index that is available. +/// +/// The two file descriptors do not share file descriptor flags (e.g. `OFlag::FD_CLOEXEC`). +#[inline] +pub fn dup(oldfd: RawFd) -> Result { + let res = unsafe { libc::dup(oldfd) }; + + Errno::result(res) +} + +/// Create a copy of the specified file descriptor using the specified fd (see +/// [dup(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html)). +/// +/// This function behaves similar to `dup()` except that it will try to use the +/// specified fd instead of allocating a new one. See the man pages for more +/// detail on the exact behavior of this function. +#[inline] +pub fn dup2(oldfd: RawFd, newfd: RawFd) -> Result { + let res = unsafe { libc::dup2(oldfd, newfd) }; + + Errno::result(res) +} + +/// Create a new copy of the specified file descriptor using the specified fd +/// and flags (see [dup(2)](https://man7.org/linux/man-pages/man2/dup.2.html)). +/// +/// This function behaves similar to `dup2()` but allows for flags to be +/// specified. +pub fn dup3(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result { + dup3_polyfill(oldfd, newfd, flags) +} + +#[inline] +fn dup3_polyfill(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result { + if oldfd == newfd { + return Err(Errno::EINVAL); + } + + let fd = dup2(oldfd, newfd)?; + + if flags.contains(OFlag::O_CLOEXEC) { + if let Err(e) = fcntl(fd, F_SETFD(FdFlag::FD_CLOEXEC)) { + let _ = close(fd); + return Err(e); + } + } + + Ok(fd) +} + +/// Change the current working directory of the calling process (see +/// [chdir(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html)). +/// +/// This function may fail in a number of different scenarios. See the man +/// pages for additional details on possible failure cases. +#[inline] +pub fn chdir(path: &P) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { libc::chdir(cstr.as_ptr()) } + })?; + + Errno::result(res).map(drop) +} + +/// Change the current working directory of the process to the one +/// given as an open file descriptor (see +/// [fchdir(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchdir.html)). +/// +/// This function may fail in a number of different scenarios. See the man +/// pages for additional details on possible failure cases. +#[inline] +#[cfg(not(target_os = "fuchsia"))] +pub fn fchdir(dirfd: RawFd) -> Result<()> { + let res = unsafe { libc::fchdir(dirfd) }; + + Errno::result(res).map(drop) +} + +/// Creates new directory `path` with access rights `mode`. (see [mkdir(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html)) +/// +/// # Errors +/// +/// There are several situations where mkdir might fail: +/// +/// - current user has insufficient rights in the parent directory +/// - the path already exists +/// - the path name is too long (longer than `PATH_MAX`, usually 4096 on linux, 1024 on OS X) +/// +/// # Example +/// +/// ```rust +/// use nix::unistd; +/// use nix::sys::stat; +/// use tempfile::tempdir; +/// +/// let tmp_dir1 = tempdir().unwrap(); +/// let tmp_dir2 = tmp_dir1.path().join("new_dir"); +/// +/// // create new directory and give read, write and execute rights to the owner +/// match unistd::mkdir(&tmp_dir2, stat::Mode::S_IRWXU) { +/// Ok(_) => println!("created {:?}", tmp_dir2), +/// Err(err) => println!("Error creating directory: {}", err), +/// } +/// ``` +#[inline] +pub fn mkdir(path: &P, mode: Mode) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { libc::mkdir(cstr.as_ptr(), mode.bits() as mode_t) } + })?; + + Errno::result(res).map(drop) +} + +/// Creates new fifo special file (named pipe) with path `path` and access rights `mode`. +/// +/// # Errors +/// +/// There are several situations where mkfifo might fail: +/// +/// - current user has insufficient rights in the parent directory +/// - the path already exists +/// - the path name is too long (longer than `PATH_MAX`, usually 4096 on linux, 1024 on OS X) +/// +/// For a full list consult +/// [posix specification](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html) +/// +/// # Example +/// +/// ```rust +/// use nix::unistd; +/// use nix::sys::stat; +/// use tempfile::tempdir; +/// +/// let tmp_dir = tempdir().unwrap(); +/// let fifo_path = tmp_dir.path().join("foo.pipe"); +/// +/// // create new fifo and give read, write and execute rights to the owner +/// match unistd::mkfifo(&fifo_path, stat::Mode::S_IRWXU) { +/// Ok(_) => println!("created {:?}", fifo_path), +/// Err(err) => println!("Error creating fifo: {}", err), +/// } +/// ``` +#[inline] +#[cfg(not(target_os = "redox"))] // RedoxFS does not support fifo yet +pub fn mkfifo(path: &P, mode: Mode) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { libc::mkfifo(cstr.as_ptr(), mode.bits() as mode_t) } + })?; + + Errno::result(res).map(drop) +} + +/// Creates new fifo special file (named pipe) with path `path` and access rights `mode`. +/// +/// If `dirfd` has a value, then `path` is relative to directory associated with the file descriptor. +/// +/// If `dirfd` is `None`, then `path` is relative to the current working directory. +/// +/// # References +/// +/// [mkfifoat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifoat.html). +// mkfifoat is not implemented in OSX or android +#[inline] +#[cfg(not(any( + target_os = "macos", target_os = "ios", target_os = "haiku", + target_os = "android", target_os = "redox")))] +pub fn mkfifoat(dirfd: Option, path: &P, mode: Mode) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mkfifoat(at_rawfd(dirfd), cstr.as_ptr(), mode.bits() as mode_t) + })?; + + Errno::result(res).map(drop) +} + +/// Creates a symbolic link at `path2` which points to `path1`. +/// +/// If `dirfd` has a value, then `path2` is relative to directory associated +/// with the file descriptor. +/// +/// If `dirfd` is `None`, then `path2` is relative to the current working +/// directory. This is identical to `libc::symlink(path1, path2)`. +/// +/// See also [symlinkat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html). +#[cfg(not(target_os = "redox"))] +pub fn symlinkat( + path1: &P1, + dirfd: Option, + path2: &P2) -> Result<()> { + let res = + path1.with_nix_path(|path1| { + path2.with_nix_path(|path2| { + unsafe { + libc::symlinkat( + path1.as_ptr(), + dirfd.unwrap_or(libc::AT_FDCWD), + path2.as_ptr() + ) + } + }) + })??; + Errno::result(res).map(drop) +} +} + +// Double the buffer capacity up to limit. In case it already has +// reached the limit, return Errno::ERANGE. +#[cfg(any(feature = "fs", feature = "user"))] +fn reserve_double_buffer_size(buf: &mut Vec, limit: usize) -> Result<()> { + use std::cmp::min; + + if buf.capacity() >= limit { + return Err(Errno::ERANGE); + } + + let capacity = min(buf.capacity() * 2, limit); + buf.reserve(capacity); + + Ok(()) +} + +feature! { +#![feature = "fs"] + +/// Returns the current directory as a `PathBuf` +/// +/// Err is returned if the current user doesn't have the permission to read or search a component +/// of the current path. +/// +/// # Example +/// +/// ```rust +/// use nix::unistd; +/// +/// // assume that we are allowed to get current directory +/// let dir = unistd::getcwd().unwrap(); +/// println!("The current directory is {:?}", dir); +/// ``` +#[inline] +pub fn getcwd() -> Result { + let mut buf = Vec::with_capacity(512); + loop { + unsafe { + let ptr = buf.as_mut_ptr() as *mut c_char; + + // The buffer must be large enough to store the absolute pathname plus + // a terminating null byte, or else null is returned. + // To safely handle this we start with a reasonable size (512 bytes) + // and double the buffer size upon every error + if !libc::getcwd(ptr, buf.capacity()).is_null() { + let len = CStr::from_ptr(buf.as_ptr() as *const c_char).to_bytes().len(); + buf.set_len(len); + buf.shrink_to_fit(); + return Ok(PathBuf::from(OsString::from_vec(buf))); + } else { + let error = Errno::last(); + // ERANGE means buffer was too small to store directory name + if error != Errno::ERANGE { + return Err(error); + } + } + + // Trigger the internal buffer resizing logic. + reserve_double_buffer_size(&mut buf, PATH_MAX as usize)?; + } + } +} +} + +feature! { +#![all(feature = "user", feature = "fs")] + +/// Computes the raw UID and GID values to pass to a `*chown` call. +// The cast is not unnecessary on all platforms. +#[allow(clippy::unnecessary_cast)] +fn chown_raw_ids(owner: Option, group: Option) -> (libc::uid_t, libc::gid_t) { + // According to the POSIX specification, -1 is used to indicate that owner and group + // are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap + // around to get -1. + let uid = owner.map(Into::into) + .unwrap_or_else(|| (0 as uid_t).wrapping_sub(1)); + let gid = group.map(Into::into) + .unwrap_or_else(|| (0 as gid_t).wrapping_sub(1)); + (uid, gid) +} + +/// Change the ownership of the file at `path` to be owned by the specified +/// `owner` (user) and `group` (see +/// [chown(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html)). +/// +/// The owner/group for the provided path name will not be modified if `None` is +/// provided for that argument. Ownership change will be attempted for the path +/// only if `Some` owner/group is provided. +#[inline] +pub fn chown(path: &P, owner: Option, group: Option) -> Result<()> { + let res = path.with_nix_path(|cstr| { + let (uid, gid) = chown_raw_ids(owner, group); + unsafe { libc::chown(cstr.as_ptr(), uid, gid) } + })?; + + Errno::result(res).map(drop) +} + +/// Change the ownership of the file referred to by the open file descriptor `fd` to be owned by +/// the specified `owner` (user) and `group` (see +/// [fchown(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html)). +/// +/// The owner/group for the provided file will not be modified if `None` is +/// provided for that argument. Ownership change will be attempted for the path +/// only if `Some` owner/group is provided. +#[inline] +pub fn fchown(fd: RawFd, owner: Option, group: Option) -> Result<()> { + let (uid, gid) = chown_raw_ids(owner, group); + let res = unsafe { libc::fchown(fd, uid, gid) }; + Errno::result(res).map(drop) +} + +/// Flags for `fchownat` function. +#[derive(Clone, Copy, Debug)] +pub enum FchownatFlags { + FollowSymlink, + NoFollowSymlink, +} + +/// Change the ownership of the file at `path` to be owned by the specified +/// `owner` (user) and `group`. +/// +/// The owner/group for the provided path name will not be modified if `None` is +/// provided for that argument. Ownership change will be attempted for the path +/// only if `Some` owner/group is provided. +/// +/// The file to be changed is determined relative to the directory associated +/// with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. +/// +/// If `flag` is `FchownatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// then the mode of the symbolic link is changed. +/// +/// `fchownat(None, path, owner, group, FchownatFlags::NoFollowSymlink)` is identical to +/// a call `libc::lchown(path, owner, group)`. That's why `lchown` is unimplemented in +/// the `nix` crate. +/// +/// # References +/// +/// [fchownat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html). +#[cfg(not(target_os = "redox"))] +pub fn fchownat( + dirfd: Option, + path: &P, + owner: Option, + group: Option, + flag: FchownatFlags, +) -> Result<()> { + let atflag = + match flag { + FchownatFlags::FollowSymlink => AtFlags::empty(), + FchownatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + let res = path.with_nix_path(|cstr| unsafe { + let (uid, gid) = chown_raw_ids(owner, group); + libc::fchownat(at_rawfd(dirfd), cstr.as_ptr(), uid, gid, + atflag.bits() as libc::c_int) + })?; + + Errno::result(res).map(drop) +} +} + +feature! { +#![feature = "process"] +fn to_exec_array>(args: &[S]) -> Vec<*const c_char> { + use std::iter::once; + args.iter() + .map(|s| s.as_ref().as_ptr()) + .chain(once(ptr::null())) + .collect() +} + +/// Replace the current process image with a new one (see +/// [exec(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html)). +/// +/// See the `::nix::unistd::execve` system call for additional details. `execv` +/// performs the same action but does not allow for customization of the +/// environment for the new process. +#[inline] +pub fn execv>(path: &CStr, argv: &[S]) -> Result { + let args_p = to_exec_array(argv); + + unsafe { + libc::execv(path.as_ptr(), args_p.as_ptr()) + }; + + Err(Errno::last()) +} + + +/// Replace the current process image with a new one (see +/// [execve(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html)). +/// +/// The execve system call allows for another process to be "called" which will +/// replace the current process image. That is, this process becomes the new +/// command that is run. On success, this function will not return. Instead, +/// the new program will run until it exits. +/// +/// `::nix::unistd::execv` and `::nix::unistd::execve` take as arguments a slice +/// of `::std::ffi::CString`s for `args` and `env` (for `execve`). Each element +/// in the `args` list is an argument to the new process. Each element in the +/// `env` list should be a string in the form "key=value". +#[inline] +pub fn execve, SE: AsRef>(path: &CStr, args: &[SA], env: &[SE]) -> Result { + let args_p = to_exec_array(args); + let env_p = to_exec_array(env); + + unsafe { + libc::execve(path.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) + }; + + Err(Errno::last()) +} + +/// Replace the current process image with a new one and replicate shell `PATH` +/// searching behavior (see +/// [exec(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html)). +/// +/// See `::nix::unistd::execve` for additional details. `execvp` behaves the +/// same as execv except that it will examine the `PATH` environment variables +/// for file names not specified with a leading slash. For example, `execv` +/// would not work if "bash" was specified for the path argument, but `execvp` +/// would assuming that a bash executable was on the system `PATH`. +#[inline] +pub fn execvp>(filename: &CStr, args: &[S]) -> Result { + let args_p = to_exec_array(args); + + unsafe { + libc::execvp(filename.as_ptr(), args_p.as_ptr()) + }; + + Err(Errno::last()) +} + +/// Replace the current process image with a new one and replicate shell `PATH` +/// searching behavior (see +/// [`execvpe(3)`](https://man7.org/linux/man-pages/man3/exec.3.html)). +/// +/// This functions like a combination of `execvp(2)` and `execve(2)` to pass an +/// environment and have a search path. See these two for additional +/// information. +#[cfg(any(target_os = "haiku", + target_os = "linux", + target_os = "openbsd"))] +pub fn execvpe, SE: AsRef>(filename: &CStr, args: &[SA], env: &[SE]) -> Result { + let args_p = to_exec_array(args); + let env_p = to_exec_array(env); + + unsafe { + libc::execvpe(filename.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) + }; + + Err(Errno::last()) +} + +/// Replace the current process image with a new one (see +/// [fexecve(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html)). +/// +/// The `fexecve` function allows for another process to be "called" which will +/// replace the current process image. That is, this process becomes the new +/// command that is run. On success, this function will not return. Instead, +/// the new program will run until it exits. +/// +/// This function is similar to `execve`, except that the program to be executed +/// is referenced as a file descriptor instead of a path. +#[cfg(any(target_os = "android", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd"))] +#[inline] +pub fn fexecve ,SE: AsRef>(fd: RawFd, args: &[SA], env: &[SE]) -> Result { + let args_p = to_exec_array(args); + let env_p = to_exec_array(env); + + unsafe { + libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr()) + }; + + Err(Errno::last()) +} + +/// Execute program relative to a directory file descriptor (see +/// [execveat(2)](https://man7.org/linux/man-pages/man2/execveat.2.html)). +/// +/// The `execveat` function allows for another process to be "called" which will +/// replace the current process image. That is, this process becomes the new +/// command that is run. On success, this function will not return. Instead, +/// the new program will run until it exits. +/// +/// This function is similar to `execve`, except that the program to be executed +/// is referenced as a file descriptor to the base directory plus a path. +#[cfg(any(target_os = "android", target_os = "linux"))] +#[inline] +pub fn execveat,SE: AsRef>(dirfd: RawFd, pathname: &CStr, args: &[SA], + env: &[SE], flags: super::fcntl::AtFlags) -> Result { + let args_p = to_exec_array(args); + let env_p = to_exec_array(env); + + unsafe { + libc::syscall(libc::SYS_execveat, dirfd, pathname.as_ptr(), + args_p.as_ptr(), env_p.as_ptr(), flags); + }; + + Err(Errno::last()) +} + +/// Daemonize this process by detaching from the controlling terminal (see +/// [daemon(3)](https://man7.org/linux/man-pages/man3/daemon.3.html)). +/// +/// When a process is launched it is typically associated with a parent and it, +/// in turn, by its controlling terminal/process. In order for a process to run +/// in the "background" it must daemonize itself by detaching itself. Under +/// posix, this is done by doing the following: +/// +/// 1. Parent process (this one) forks +/// 2. Parent process exits +/// 3. Child process continues to run. +/// +/// `nochdir`: +/// +/// * `nochdir = true`: The current working directory after daemonizing will +/// be the current working directory. +/// * `nochdir = false`: The current working directory after daemonizing will +/// be the root direcory, `/`. +/// +/// `noclose`: +/// +/// * `noclose = true`: The process' current stdin, stdout, and stderr file +/// descriptors will remain identical after daemonizing. +/// * `noclose = false`: The process' stdin, stdout, and stderr will point to +/// `/dev/null` after daemonizing. +#[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] +pub fn daemon(nochdir: bool, noclose: bool) -> Result<()> { + let res = unsafe { libc::daemon(nochdir as c_int, noclose as c_int) }; + Errno::result(res).map(drop) +} +} + +feature! { +#![feature = "hostname"] + +/// Set the system host name (see +/// [sethostname(2)](https://man7.org/linux/man-pages/man2/gethostname.2.html)). +/// +/// Given a name, attempt to update the system host name to the given string. +/// On some systems, the host name is limited to as few as 64 bytes. An error +/// will be returned if the name is not valid or the current process does not +/// have permissions to update the host name. +#[cfg(not(target_os = "redox"))] +pub fn sethostname>(name: S) -> Result<()> { + // Handle some differences in type of the len arg across platforms. + cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "solaris", ))] { + type sethostname_len_t = c_int; + } else { + type sethostname_len_t = size_t; + } + } + let ptr = name.as_ref().as_bytes().as_ptr() as *const c_char; + let len = name.as_ref().len() as sethostname_len_t; + + let res = unsafe { libc::sethostname(ptr, len) }; + Errno::result(res).map(drop) +} + +/// Get the host name and store it in an internally allocated buffer, returning an +/// `OsString` on success (see +/// [gethostname(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html)). +/// +/// This function call attempts to get the host name for the running system and +/// store it in an internal buffer, returning it as an `OsString` if successful. +/// +/// ```no_run +/// use nix::unistd; +/// +/// let hostname = unistd::gethostname().expect("Failed getting hostname"); +/// let hostname = hostname.into_string().expect("Hostname wasn't valid UTF-8"); +/// println!("Hostname: {}", hostname); +/// ``` +pub fn gethostname() -> Result { + // The capacity is the max length of a hostname plus the NUL terminator. + let mut buffer: Vec = Vec::with_capacity(256); + let ptr = buffer.as_mut_ptr() as *mut c_char; + let len = buffer.capacity() as size_t; + + let res = unsafe { libc::gethostname(ptr, len) }; + Errno::result(res).map(|_| { + unsafe { + buffer.as_mut_ptr().wrapping_add(len - 1).write(0); // ensure always null-terminated + let len = CStr::from_ptr(buffer.as_ptr() as *const c_char).len(); + buffer.set_len(len); + } + OsString::from_vec(buffer) + }) +} +} + +/// Close a raw file descriptor +/// +/// Be aware that many Rust types implicitly close-on-drop, including +/// `std::fs::File`. Explicitly closing them with this method too can result in +/// a double-close condition, which can cause confusing `EBADF` errors in +/// seemingly unrelated code. Caveat programmer. See also +/// [close(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html). +/// +/// # Examples +/// +/// ```no_run +/// use std::os::unix::io::AsRawFd; +/// use nix::unistd::close; +/// +/// let f = tempfile::tempfile().unwrap(); +/// close(f.as_raw_fd()).unwrap(); // Bad! f will also close on drop! +/// ``` +/// +/// ```rust +/// use std::os::unix::io::IntoRawFd; +/// use nix::unistd::close; +/// +/// let f = tempfile::tempfile().unwrap(); +/// close(f.into_raw_fd()).unwrap(); // Good. into_raw_fd consumes f +/// ``` +pub fn close(fd: RawFd) -> Result<()> { + let res = unsafe { libc::close(fd) }; + Errno::result(res).map(drop) +} + +/// Read from a raw file descriptor. +/// +/// See also [read(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html) +pub fn read(fd: RawFd, buf: &mut [u8]) -> Result { + let res = unsafe { + libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len() as size_t) + }; + + Errno::result(res).map(|r| r as usize) +} + +/// Write to a raw file descriptor. +/// +/// See also [write(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html) +pub fn write(fd: RawFd, buf: &[u8]) -> Result { + let res = unsafe { + libc::write(fd, buf.as_ptr() as *const c_void, buf.len() as size_t) + }; + + Errno::result(res).map(|r| r as usize) +} + +feature! { +#![feature = "fs"] + +/// Directive that tells [`lseek`] and [`lseek64`] what the offset is relative to. +/// +/// [`lseek`]: ./fn.lseek.html +/// [`lseek64`]: ./fn.lseek64.html +#[repr(i32)] +#[derive(Clone, Copy, Debug)] +pub enum Whence { + /// Specify an offset relative to the start of the file. + SeekSet = libc::SEEK_SET, + /// Specify an offset relative to the current file location. + SeekCur = libc::SEEK_CUR, + /// Specify an offset relative to the end of the file. + SeekEnd = libc::SEEK_END, + /// Specify an offset relative to the next location in the file greater than or + /// equal to offset that contains some data. If offset points to + /// some data, then the file offset is set to offset. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "solaris"))] + SeekData = libc::SEEK_DATA, + /// Specify an offset relative to the next hole in the file greater than + /// or equal to offset. If offset points into the middle of a hole, then + /// the file offset should be set to offset. If there is no hole past offset, + /// then the file offset should be adjusted to the end of the file (i.e., there + /// is an implicit hole at the end of any file). + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "solaris"))] + SeekHole = libc::SEEK_HOLE +} + +/// Move the read/write file offset. +/// +/// See also [lseek(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html) +pub fn lseek(fd: RawFd, offset: off_t, whence: Whence) -> Result { + let res = unsafe { libc::lseek(fd, offset, whence as i32) }; + + Errno::result(res).map(|r| r as off_t) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn lseek64(fd: RawFd, offset: libc::off64_t, whence: Whence) -> Result { + let res = unsafe { libc::lseek64(fd, offset, whence as i32) }; + + Errno::result(res).map(|r| r as libc::off64_t) +} +} + +/// Create an interprocess channel. +/// +/// See also [pipe(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html) +pub fn pipe() -> std::result::Result<(RawFd, RawFd), Error> { + let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); + + let res = unsafe { libc::pipe(fds.as_mut_ptr() as *mut c_int) }; + + Error::result(res)?; + + unsafe { Ok((fds.assume_init()[0], fds.assume_init()[1])) } +} + +feature! { +#![feature = "fs"] +/// Like `pipe`, but allows setting certain file descriptor flags. +/// +/// The following flags are supported, and will be set atomically as the pipe is +/// created: +/// +/// - `O_CLOEXEC`: Set the close-on-exec flag for the new file descriptors. +#[cfg_attr(target_os = "linux", doc = "- `O_DIRECT`: Create a pipe that performs I/O in \"packet\" mode.")] +#[cfg_attr(target_os = "netbsd", doc = "- `O_NOSIGPIPE`: Return `EPIPE` instead of raising `SIGPIPE`.")] +/// - `O_NONBLOCK`: Set the non-blocking flag for the ends of the pipe. +/// +/// See also [pipe(2)](https://man7.org/linux/man-pages/man2/pipe.2.html) +#[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "redox", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] +pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { + let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); + + let res = unsafe { + libc::pipe2(fds.as_mut_ptr() as *mut c_int, flags.bits()) + }; + + Errno::result(res)?; + + unsafe { Ok((fds.assume_init()[0], fds.assume_init()[1])) } +} + +/// Truncate a file to a specified length +/// +/// See also +/// [truncate(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html) +#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +pub fn truncate(path: &P, len: off_t) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { + libc::truncate(cstr.as_ptr(), len) + } + })?; + + Errno::result(res).map(drop) +} + +/// Truncate a file to a specified length +/// +/// See also +/// [ftruncate(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html) +pub fn ftruncate(fd: RawFd, len: off_t) -> Result<()> { + Errno::result(unsafe { libc::ftruncate(fd, len) }).map(drop) +} + +pub fn isatty(fd: RawFd) -> Result { + unsafe { + // ENOTTY means `fd` is a valid file descriptor, but not a TTY, so + // we return `Ok(false)` + if libc::isatty(fd) == 1 { + Ok(true) + } else { + match Errno::last() { + Errno::ENOTTY => Ok(false), + err => Err(err), + } + } + } +} + +/// Flags for `linkat` function. +#[derive(Clone, Copy, Debug)] +pub enum LinkatFlags { + SymlinkFollow, + NoSymlinkFollow, +} + +/// Link one file to another file +/// +/// Creates a new link (directory entry) at `newpath` for the existing file at `oldpath`. In the +/// case of a relative `oldpath`, the path is interpreted relative to the directory associated +/// with file descriptor `olddirfd` instead of the current working directory and similiarly for +/// `newpath` and file descriptor `newdirfd`. In case `flag` is LinkatFlags::SymlinkFollow and +/// `oldpath` names a symoblic link, a new link for the target of the symbolic link is created. +/// If either `olddirfd` or `newdirfd` is `None`, `AT_FDCWD` is used respectively where `oldpath` +/// and/or `newpath` is then interpreted relative to the current working directory of the calling +/// process. If either `oldpath` or `newpath` is absolute, then `dirfd` is ignored. +/// +/// # References +/// See also [linkat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html) +#[cfg(not(target_os = "redox"))] // RedoxFS does not support symlinks yet +pub fn linkat( + olddirfd: Option, + oldpath: &P, + newdirfd: Option, + newpath: &P, + flag: LinkatFlags, +) -> Result<()> { + + let atflag = + match flag { + LinkatFlags::SymlinkFollow => AtFlags::AT_SYMLINK_FOLLOW, + LinkatFlags::NoSymlinkFollow => AtFlags::empty(), + }; + + let res = + oldpath.with_nix_path(|oldcstr| { + newpath.with_nix_path(|newcstr| { + unsafe { + libc::linkat( + at_rawfd(olddirfd), + oldcstr.as_ptr(), + at_rawfd(newdirfd), + newcstr.as_ptr(), + atflag.bits() as libc::c_int + ) + } + }) + })??; + Errno::result(res).map(drop) +} + + +/// Remove a directory entry +/// +/// See also [unlink(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html) +pub fn unlink(path: &P) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { + libc::unlink(cstr.as_ptr()) + } + })?; + Errno::result(res).map(drop) +} + +/// Flags for `unlinkat` function. +#[derive(Clone, Copy, Debug)] +pub enum UnlinkatFlags { + RemoveDir, + NoRemoveDir, +} + +/// Remove a directory entry +/// +/// In the case of a relative path, the directory entry to be removed is determined relative to +/// the directory associated with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. In the case of an absolute `path` `dirfd` is ignored. If `flag` is +/// `UnlinkatFlags::RemoveDir` then removal of the directory entry specified by `dirfd` and `path` +/// is performed. +/// +/// # References +/// See also [unlinkat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html) +#[cfg(not(target_os = "redox"))] +pub fn unlinkat( + dirfd: Option, + path: &P, + flag: UnlinkatFlags, +) -> Result<()> { + let atflag = + match flag { + UnlinkatFlags::RemoveDir => AtFlags::AT_REMOVEDIR, + UnlinkatFlags::NoRemoveDir => AtFlags::empty(), + }; + let res = path.with_nix_path(|cstr| { + unsafe { + libc::unlinkat(at_rawfd(dirfd), cstr.as_ptr(), atflag.bits() as libc::c_int) + } + })?; + Errno::result(res).map(drop) +} + + +#[inline] +#[cfg(not(target_os = "fuchsia"))] +pub fn chroot(path: &P) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { libc::chroot(cstr.as_ptr()) } + })?; + + Errno::result(res).map(drop) +} + +/// Commit filesystem caches to disk +/// +/// See also [sync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html) +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" +))] +pub fn sync() { + unsafe { libc::sync() }; +} + +/// Commit filesystem caches containing file referred to by the open file +/// descriptor `fd` to disk +/// +/// See also [syncfs(2)](https://man7.org/linux/man-pages/man2/sync.2.html) +#[cfg(target_os = "linux")] +pub fn syncfs(fd: RawFd) -> Result<()> { + let res = unsafe { libc::syncfs(fd) }; + + Errno::result(res).map(drop) +} + +/// Synchronize changes to a file +/// +/// See also [fsync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html) +#[inline] +pub fn fsync(fd: RawFd) -> Result<()> { + let res = unsafe { libc::fsync(fd) }; + + Errno::result(res).map(drop) +} + +/// Synchronize the data of a file +/// +/// See also +/// [fdatasync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html) +#[cfg(any(target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos", + target_os = "solaris"))] +#[inline] +pub fn fdatasync(fd: RawFd) -> Result<()> { + let res = unsafe { libc::fdatasync(fd) }; + + Errno::result(res).map(drop) +} +} + +feature! { +#![feature = "user"] + +/// Get a real user ID +/// +/// See also [getuid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getuid.html) +// POSIX requires that getuid is always successful, so no need to check return +// value or errno. +#[inline] +pub fn getuid() -> Uid { + Uid(unsafe { libc::getuid() }) +} + +/// Get the effective user ID +/// +/// See also [geteuid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/geteuid.html) +// POSIX requires that geteuid is always successful, so no need to check return +// value or errno. +#[inline] +pub fn geteuid() -> Uid { + Uid(unsafe { libc::geteuid() }) +} + +/// Get the real group ID +/// +/// See also [getgid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgid.html) +// POSIX requires that getgid is always successful, so no need to check return +// value or errno. +#[inline] +pub fn getgid() -> Gid { + Gid(unsafe { libc::getgid() }) +} + +/// Get the effective group ID +/// +/// See also [getegid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getegid.html) +// POSIX requires that getegid is always successful, so no need to check return +// value or errno. +#[inline] +pub fn getegid() -> Gid { + Gid(unsafe { libc::getegid() }) +} + +/// Set the effective user ID +/// +/// See also [seteuid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/seteuid.html) +#[inline] +pub fn seteuid(euid: Uid) -> Result<()> { + let res = unsafe { libc::seteuid(euid.into()) }; + + Errno::result(res).map(drop) +} + +/// Set the effective group ID +/// +/// See also [setegid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setegid.html) +#[inline] +pub fn setegid(egid: Gid) -> Result<()> { + let res = unsafe { libc::setegid(egid.into()) }; + + Errno::result(res).map(drop) +} + +/// Set the user ID +/// +/// See also [setuid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setuid.html) +#[inline] +pub fn setuid(uid: Uid) -> Result<()> { + let res = unsafe { libc::setuid(uid.into()) }; + + Errno::result(res).map(drop) +} + +/// Set the group ID +/// +/// See also [setgid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setgid.html) +#[inline] +pub fn setgid(gid: Gid) -> Result<()> { + let res = unsafe { libc::setgid(gid.into()) }; + + Errno::result(res).map(drop) +} +} + +feature! { +#![all(feature = "fs", feature = "user")] +/// Set the user identity used for filesystem checks per-thread. +/// On both success and failure, this call returns the previous filesystem user +/// ID of the caller. +/// +/// See also [setfsuid(2)](https://man7.org/linux/man-pages/man2/setfsuid.2.html) +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn setfsuid(uid: Uid) -> Uid { + let prev_fsuid = unsafe { libc::setfsuid(uid.into()) }; + Uid::from_raw(prev_fsuid as uid_t) +} + +/// Set the group identity used for filesystem checks per-thread. +/// On both success and failure, this call returns the previous filesystem group +/// ID of the caller. +/// +/// See also [setfsgid(2)](https://man7.org/linux/man-pages/man2/setfsgid.2.html) +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn setfsgid(gid: Gid) -> Gid { + let prev_fsgid = unsafe { libc::setfsgid(gid.into()) }; + Gid::from_raw(prev_fsgid as gid_t) +} +} + +feature! { +#![feature = "user"] + +/// Get the list of supplementary group IDs of the calling process. +/// +/// [Further reading](https://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html) +/// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, checking group membership should be achieved via communication +/// with the `opendirectoryd` service. +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +pub fn getgroups() -> Result> { + // First get the maximum number of groups. The value returned + // shall always be greater than or equal to one and less than or + // equal to the value of {NGROUPS_MAX} + 1. + let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { + Ok(Some(n)) => (n + 1) as usize, + Ok(None) | Err(_) => ::max_value(), + }; + + // Next, get the number of groups so we can size our Vec + let ngroups = unsafe { libc::getgroups(0, ptr::null_mut()) }; + + // If there are no supplementary groups, return early. + // This prevents a potential buffer over-read if the number of groups + // increases from zero before the next call. It would return the total + // number of groups beyond the capacity of the buffer. + if ngroups == 0 { + return Ok(Vec::new()); + } + + // Now actually get the groups. We try multiple times in case the number of + // groups has changed since the first call to getgroups() and the buffer is + // now too small. + let mut groups = Vec::::with_capacity(Errno::result(ngroups)? as usize); + loop { + // FIXME: On the platforms we currently support, the `Gid` struct has + // the same representation in memory as a bare `gid_t`. This is not + // necessarily the case on all Rust platforms, though. See RFC 1785. + let ngroups = unsafe { + libc::getgroups(groups.capacity() as c_int, groups.as_mut_ptr() as *mut gid_t) + }; + + match Errno::result(ngroups) { + Ok(s) => { + unsafe { groups.set_len(s as usize) }; + return Ok(groups); + }, + Err(Errno::EINVAL) => { + // EINVAL indicates that the buffer size was too + // small, resize it up to ngroups_max as limit. + reserve_double_buffer_size(&mut groups, ngroups_max) + .or(Err(Errno::EINVAL))?; + }, + Err(e) => return Err(e) + } + } +} + +/// Set the list of supplementary group IDs for the calling process. +/// +/// [Further reading](https://man7.org/linux/man-pages/man2/getgroups.2.html) +/// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, group membership management should be achieved via communication +/// with the `opendirectoryd` service. +/// +/// # Examples +/// +/// `setgroups` can be used when dropping privileges from the root user to a +/// specific user and group. For example, given the user `www-data` with UID +/// `33` and the group `backup` with the GID `34`, one could switch the user as +/// follows: +/// +/// ```rust,no_run +/// # use std::error::Error; +/// # use nix::unistd::*; +/// # +/// # fn try_main() -> Result<(), Box> { +/// let uid = Uid::from_raw(33); +/// let gid = Gid::from_raw(34); +/// setgroups(&[gid])?; +/// setgid(gid)?; +/// setuid(uid)?; +/// # +/// # Ok(()) +/// # } +/// # +/// # try_main().unwrap(); +/// ``` +#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox", target_os = "haiku")))] +pub fn setgroups(groups: &[Gid]) -> Result<()> { + cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "illumos", + target_os = "openbsd"))] { + type setgroups_ngroups_t = c_int; + } else { + type setgroups_ngroups_t = size_t; + } + } + // FIXME: On the platforms we currently support, the `Gid` struct has the + // same representation in memory as a bare `gid_t`. This is not necessarily + // the case on all Rust platforms, though. See RFC 1785. + let res = unsafe { + libc::setgroups(groups.len() as setgroups_ngroups_t, groups.as_ptr() as *const gid_t) + }; + + Errno::result(res).map(drop) +} + +/// Calculate the supplementary group access list. +/// +/// Gets the group IDs of all groups that `user` is a member of. The additional +/// group `group` is also added to the list. +/// +/// [Further reading](https://man7.org/linux/man-pages/man3/getgrouplist.3.html) +/// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, checking group membership should be achieved via communication +/// with the `opendirectoryd` service. +/// +/// # Errors +/// +/// Although the `getgrouplist()` call does not return any specific +/// errors on any known platforms, this implementation will return a system +/// error of `EINVAL` if the number of groups to be fetched exceeds the +/// `NGROUPS_MAX` sysconf value. This mimics the behaviour of `getgroups()` +/// and `setgroups()`. Additionally, while some implementations will return a +/// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation +/// will only ever return the complete list or else an error. +#[cfg(not(any(target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "redox")))] +pub fn getgrouplist(user: &CStr, group: Gid) -> Result> { + let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { + Ok(Some(n)) => n as c_int, + Ok(None) | Err(_) => ::max_value(), + }; + use std::cmp::min; + let mut groups = Vec::::with_capacity(min(ngroups_max, 8) as usize); + cfg_if! { + if #[cfg(any(target_os = "ios", target_os = "macos"))] { + type getgrouplist_group_t = c_int; + } else { + type getgrouplist_group_t = gid_t; + } + } + let gid: gid_t = group.into(); + loop { + let mut ngroups = groups.capacity() as i32; + let ret = unsafe { + libc::getgrouplist(user.as_ptr(), + gid as getgrouplist_group_t, + groups.as_mut_ptr() as *mut getgrouplist_group_t, + &mut ngroups) + }; + + // BSD systems only return 0 or -1, Linux returns ngroups on success. + if ret >= 0 { + unsafe { groups.set_len(ngroups as usize) }; + return Ok(groups); + } else if ret == -1 { + // Returns -1 if ngroups is too small, but does not set errno. + // BSD systems will still fill the groups buffer with as many + // groups as possible, but Linux manpages do not mention this + // behavior. + reserve_double_buffer_size(&mut groups, ngroups_max as usize) + .map_err(|_| Errno::EINVAL)?; + } + } +} + +/// Initialize the supplementary group access list. +/// +/// Sets the supplementary group IDs for the calling process using all groups +/// that `user` is a member of. The additional group `group` is also added to +/// the list. +/// +/// [Further reading](https://man7.org/linux/man-pages/man3/initgroups.3.html) +/// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, group membership management should be achieved via communication +/// with the `opendirectoryd` service. +/// +/// # Examples +/// +/// `initgroups` can be used when dropping privileges from the root user to +/// another user. For example, given the user `www-data`, we could look up the +/// UID and GID for the user in the system's password database (usually found +/// in `/etc/passwd`). If the `www-data` user's UID and GID were `33` and `33`, +/// respectively, one could switch the user as follows: +/// +/// ```rust,no_run +/// # use std::error::Error; +/// # use std::ffi::CString; +/// # use nix::unistd::*; +/// # +/// # fn try_main() -> Result<(), Box> { +/// let user = CString::new("www-data").unwrap(); +/// let uid = Uid::from_raw(33); +/// let gid = Gid::from_raw(33); +/// initgroups(&user, gid)?; +/// setgid(gid)?; +/// setuid(uid)?; +/// # +/// # Ok(()) +/// # } +/// # +/// # try_main().unwrap(); +/// ``` +#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox", target_os = "haiku")))] +pub fn initgroups(user: &CStr, group: Gid) -> Result<()> { + cfg_if! { + if #[cfg(any(target_os = "ios", target_os = "macos"))] { + type initgroups_group_t = c_int; + } else { + type initgroups_group_t = gid_t; + } + } + let gid: gid_t = group.into(); + let res = unsafe { libc::initgroups(user.as_ptr(), gid as initgroups_group_t) }; + + Errno::result(res).map(drop) +} +} + +feature! { +#![feature = "signal"] + +/// Suspend the thread until a signal is received. +/// +/// See also [pause(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html). +#[inline] +#[cfg(not(target_os = "redox"))] +pub fn pause() { + unsafe { libc::pause() }; +} + +pub mod alarm { + //! Alarm signal scheduling. + //! + //! Scheduling an alarm will trigger a `SIGALRM` signal when the time has + //! elapsed, which has to be caught, because the default action for the + //! signal is to terminate the program. This signal also can't be ignored + //! because the system calls like `pause` will not be interrupted, see the + //! second example below. + //! + //! # Examples + //! + //! Canceling an alarm: + //! + //! ``` + //! use nix::unistd::alarm; + //! + //! // Set an alarm for 60 seconds from now. + //! alarm::set(60); + //! + //! // Cancel the above set alarm, which returns the number of seconds left + //! // of the previously set alarm. + //! assert_eq!(alarm::cancel(), Some(60)); + //! ``` + //! + //! Scheduling an alarm and waiting for the signal: + //! +#![cfg_attr(target_os = "redox", doc = " ```rust,ignore")] +#![cfg_attr(not(target_os = "redox"), doc = " ```rust")] + //! use std::time::{Duration, Instant}; + //! + //! use nix::unistd::{alarm, pause}; + //! use nix::sys::signal::*; + //! + //! // We need to setup an empty signal handler to catch the alarm signal, + //! // otherwise the program will be terminated once the signal is delivered. + //! extern fn signal_handler(_: nix::libc::c_int) { } + //! let sa = SigAction::new( + //! SigHandler::Handler(signal_handler), + //! SaFlags::SA_RESTART, + //! SigSet::empty() + //! ); + //! unsafe { + //! sigaction(Signal::SIGALRM, &sa); + //! } + //! + //! let start = Instant::now(); + //! + //! // Set an alarm for 1 second from now. + //! alarm::set(1); + //! + //! // Pause the process until the alarm signal is received. + //! let mut sigset = SigSet::empty(); + //! sigset.add(Signal::SIGALRM); + //! sigset.wait(); + //! + //! assert!(start.elapsed() >= Duration::from_secs(1)); + //! ``` + //! + //! # References + //! + //! See also [alarm(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/alarm.html). + + /// Schedule an alarm signal. + /// + /// This will cause the system to generate a `SIGALRM` signal for the + /// process after the specified number of seconds have elapsed. + /// + /// Returns the leftover time of a previously set alarm if there was one. + pub fn set(secs: libc::c_uint) -> Option { + assert!(secs != 0, "passing 0 to `alarm::set` is not allowed, to cancel an alarm use `alarm::cancel`"); + alarm(secs) + } + + /// Cancel an previously set alarm signal. + /// + /// Returns the leftover time of a previously set alarm if there was one. + pub fn cancel() -> Option { + alarm(0) + } + + fn alarm(secs: libc::c_uint) -> Option { + match unsafe { libc::alarm(secs) } { + 0 => None, + secs => Some(secs), + } + } +} +} + +/// Suspend execution for an interval of time +/// +/// See also [sleep(2)](https://pubs.opengroup.org/onlinepubs/009695399/functions/sleep.html#tag_03_705_05) +// Per POSIX, does not fail +#[inline] +pub fn sleep(seconds: c_uint) -> c_uint { + unsafe { libc::sleep(seconds) } +} + +feature! { +#![feature = "acct"] + +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +pub mod acct { + use crate::{Result, NixPath}; + use crate::errno::Errno; + use std::ptr; + + /// Enable process accounting + /// + /// See also [acct(2)](https://linux.die.net/man/2/acct) + pub fn enable(filename: &P) -> Result<()> { + let res = filename.with_nix_path(|cstr| { + unsafe { libc::acct(cstr.as_ptr()) } + })?; + + Errno::result(res).map(drop) + } + + /// Disable process accounting + pub fn disable() -> Result<()> { + let res = unsafe { libc::acct(ptr::null()) }; + + Errno::result(res).map(drop) + } +} +} + +feature! { +#![feature = "fs"] +/// Creates a regular file which persists even after process termination +/// +/// * `template`: a path whose 6 rightmost characters must be X, e.g. `/tmp/tmpfile_XXXXXX` +/// * returns: tuple of file descriptor and filename +/// +/// Err is returned either if no temporary filename could be created or the template doesn't +/// end with XXXXXX +/// +/// See also [mkstemp(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkstemp.html) +/// +/// # Example +/// +/// ```rust +/// use nix::unistd; +/// +/// let _ = match unistd::mkstemp("/tmp/tempfile_XXXXXX") { +/// Ok((fd, path)) => { +/// unistd::unlink(path.as_path()).unwrap(); // flag file to be deleted at app termination +/// fd +/// } +/// Err(e) => panic!("mkstemp failed: {}", e) +/// }; +/// // do something with fd +/// ``` +#[inline] +pub fn mkstemp(template: &P) -> Result<(RawFd, PathBuf)> { + let mut path = template.with_nix_path(|path| {path.to_bytes_with_nul().to_owned()})?; + let p = path.as_mut_ptr() as *mut _; + let fd = unsafe { libc::mkstemp(p) }; + let last = path.pop(); // drop the trailing nul + debug_assert!(last == Some(b'\0')); + let pathname = OsString::from_vec(path); + Errno::result(fd)?; + Ok((fd, PathBuf::from(pathname))) +} +} + +feature! { +#![all(feature = "fs", feature = "feature")] + +/// Variable names for `pathconf` +/// +/// Nix uses the same naming convention for these variables as the +/// [getconf(1)](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/getconf.html) utility. +/// That is, `PathconfVar` variables have the same name as the abstract +/// variables shown in the `pathconf(2)` man page. Usually, it's the same as +/// the C variable name without the leading `_PC_`. +/// +/// POSIX 1003.1-2008 standardizes all of these variables, but some OSes choose +/// not to implement variables that cannot change at runtime. +/// +/// # References +/// +/// - [pathconf(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html) +/// - [limits.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) +/// - [unistd.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html) +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(i32)] +#[non_exhaustive] +pub enum PathconfVar { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux", + target_os = "netbsd", target_os = "openbsd", target_os = "redox"))] + /// Minimum number of bits needed to represent, as a signed integer value, + /// the maximum size of a regular file allowed in the specified directory. + #[cfg_attr(docsrs, doc(cfg(all())))] + FILESIZEBITS = libc::_PC_FILESIZEBITS, + /// Maximum number of links to a single file. + LINK_MAX = libc::_PC_LINK_MAX, + /// Maximum number of bytes in a terminal canonical input line. + MAX_CANON = libc::_PC_MAX_CANON, + /// Minimum number of bytes for which space is available in a terminal input + /// queue; therefore, the maximum number of bytes a conforming application + /// may require to be typed as input before reading them. + MAX_INPUT = libc::_PC_MAX_INPUT, + /// Maximum number of bytes in a filename (not including the terminating + /// null of a filename string). + NAME_MAX = libc::_PC_NAME_MAX, + /// Maximum number of bytes the implementation will store as a pathname in a + /// user-supplied buffer of unspecified size, including the terminating null + /// character. Minimum number the implementation will accept as the maximum + /// number of bytes in a pathname. + PATH_MAX = libc::_PC_PATH_MAX, + /// Maximum number of bytes that is guaranteed to be atomic when writing to + /// a pipe. + PIPE_BUF = libc::_PC_PIPE_BUF, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "illumos", + target_os = "linux", target_os = "netbsd", target_os = "openbsd", + target_os = "redox", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Symbolic links can be created. + POSIX2_SYMLINKS = libc::_PC_2_SYMLINKS, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Minimum number of bytes of storage actually allocated for any portion of + /// a file. + POSIX_ALLOC_SIZE_MIN = libc::_PC_ALLOC_SIZE_MIN, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Recommended increment for file transfer sizes between the + /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values. + POSIX_REC_INCR_XFER_SIZE = libc::_PC_REC_INCR_XFER_SIZE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum recommended file transfer size. + POSIX_REC_MAX_XFER_SIZE = libc::_PC_REC_MAX_XFER_SIZE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Minimum recommended file transfer size. + POSIX_REC_MIN_XFER_SIZE = libc::_PC_REC_MIN_XFER_SIZE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "linux", target_os = "openbsd", target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Recommended file transfer buffer alignment. + POSIX_REC_XFER_ALIGN = libc::_PC_REC_XFER_ALIGN, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "illumos", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", target_os = "redox", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum number of bytes in a symbolic link. + SYMLINK_MAX = libc::_PC_SYMLINK_MAX, + /// The use of `chown` and `fchown` is restricted to a process with + /// appropriate privileges, and to changing the group ID of a file only to + /// the effective group ID of the process or to one of its supplementary + /// group IDs. + _POSIX_CHOWN_RESTRICTED = libc::_PC_CHOWN_RESTRICTED, + /// Pathname components longer than {NAME_MAX} generate an error. + _POSIX_NO_TRUNC = libc::_PC_NO_TRUNC, + /// This symbol shall be defined to be the value of a character that shall + /// disable terminal special character handling. + _POSIX_VDISABLE = libc::_PC_VDISABLE, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "illumos", target_os = "linux", target_os = "openbsd", + target_os = "redox", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Asynchronous input or output operations may be performed for the + /// associated file. + _POSIX_ASYNC_IO = libc::_PC_ASYNC_IO, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "illumos", target_os = "linux", target_os = "openbsd", + target_os = "redox", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Prioritized input or output operations may be performed for the + /// associated file. + _POSIX_PRIO_IO = libc::_PC_PRIO_IO, + #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", + target_os = "illumos", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", target_os = "redox", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Synchronized input or output operations may be performed for the + /// associated file. + _POSIX_SYNC_IO = libc::_PC_SYNC_IO, + #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The resolution in nanoseconds for all file timestamps. + _POSIX_TIMESTAMP_RESOLUTION = libc::_PC_TIMESTAMP_RESOLUTION +} + +/// Like `pathconf`, but works with file descriptors instead of paths (see +/// [fpathconf(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html)) +/// +/// # Parameters +/// +/// - `fd`: The file descriptor whose variable should be interrogated +/// - `var`: The pathconf variable to lookup +/// +/// # Returns +/// +/// - `Ok(Some(x))`: the variable's limit (for limit variables) or its +/// implementation level (for option variables). Implementation levels are +/// usually a decimal-coded date, such as 200112 for POSIX 2001.12 +/// - `Ok(None)`: the variable has no limit (for limit variables) or is +/// unsupported (for option variables) +/// - `Err(x)`: an error occurred +pub fn fpathconf(fd: RawFd, var: PathconfVar) -> Result> { + let raw = unsafe { + Errno::clear(); + libc::fpathconf(fd, var as c_int) + }; + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(Errno::last()) + } + } else { + Ok(Some(raw)) + } +} + +/// Get path-dependent configurable system variables (see +/// [pathconf(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html)) +/// +/// Returns the value of a path-dependent configurable system variable. Most +/// supported variables also have associated compile-time constants, but POSIX +/// allows their values to change at runtime. There are generally two types of +/// `pathconf` variables: options and limits. See [pathconf(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pathconf.html) for more details. +/// +/// # Parameters +/// +/// - `path`: Lookup the value of `var` for this file or directory +/// - `var`: The `pathconf` variable to lookup +/// +/// # Returns +/// +/// - `Ok(Some(x))`: the variable's limit (for limit variables) or its +/// implementation level (for option variables). Implementation levels are +/// usually a decimal-coded date, such as 200112 for POSIX 2001.12 +/// - `Ok(None)`: the variable has no limit (for limit variables) or is +/// unsupported (for option variables) +/// - `Err(x)`: an error occurred +pub fn pathconf(path: &P, var: PathconfVar) -> Result> { + let raw = path.with_nix_path(|cstr| { + unsafe { + Errno::clear(); + libc::pathconf(cstr.as_ptr(), var as c_int) + } + })?; + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(Errno::last()) + } + } else { + Ok(Some(raw)) + } +} +} + +feature! { +#![feature = "feature"] + +/// Variable names for `sysconf` +/// +/// Nix uses the same naming convention for these variables as the +/// [getconf(1)](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/getconf.html) utility. +/// That is, `SysconfVar` variables have the same name as the abstract variables +/// shown in the `sysconf(3)` man page. Usually, it's the same as the C +/// variable name without the leading `_SC_`. +/// +/// All of these symbols are standardized by POSIX 1003.1-2008, but haven't been +/// implemented by all platforms. +/// +/// # References +/// +/// - [sysconf(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html) +/// - [unistd.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html) +/// - [limits.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(i32)] +#[non_exhaustive] +pub enum SysconfVar { + /// Maximum number of I/O operations in a single list I/O call supported by + /// the implementation. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AIO_LISTIO_MAX = libc::_SC_AIO_LISTIO_MAX, + /// Maximum number of outstanding asynchronous I/O operations supported by + /// the implementation. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + AIO_MAX = libc::_SC_AIO_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum amount by which a process can decrease its asynchronous I/O + /// priority level from its own scheduling priority. + AIO_PRIO_DELTA_MAX = libc::_SC_AIO_PRIO_DELTA_MAX, + /// Maximum length of argument to the exec functions including environment data. + ARG_MAX = libc::_SC_ARG_MAX, + /// Maximum number of functions that may be registered with `atexit`. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + ATEXIT_MAX = libc::_SC_ATEXIT_MAX, + /// Maximum obase values allowed by the bc utility. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BC_BASE_MAX = libc::_SC_BC_BASE_MAX, + /// Maximum number of elements permitted in an array by the bc utility. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BC_DIM_MAX = libc::_SC_BC_DIM_MAX, + /// Maximum scale value allowed by the bc utility. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BC_SCALE_MAX = libc::_SC_BC_SCALE_MAX, + /// Maximum length of a string constant accepted by the bc utility. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + BC_STRING_MAX = libc::_SC_BC_STRING_MAX, + /// Maximum number of simultaneous processes per real user ID. + CHILD_MAX = libc::_SC_CHILD_MAX, + // The number of clock ticks per second. + CLK_TCK = libc::_SC_CLK_TCK, + /// Maximum number of weights that can be assigned to an entry of the + /// LC_COLLATE order keyword in the locale definition file + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + COLL_WEIGHTS_MAX = libc::_SC_COLL_WEIGHTS_MAX, + /// Maximum number of timer expiration overruns. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + DELAYTIMER_MAX = libc::_SC_DELAYTIMER_MAX, + /// Maximum number of expressions that can be nested within parentheses by + /// the expr utility. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + EXPR_NEST_MAX = libc::_SC_EXPR_NEST_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum length of a host name (not including the terminating null) as + /// returned from the `gethostname` function + HOST_NAME_MAX = libc::_SC_HOST_NAME_MAX, + /// Maximum number of iovec structures that one process has available for + /// use with `readv` or `writev`. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + IOV_MAX = libc::_SC_IOV_MAX, + /// Unless otherwise noted, the maximum length, in bytes, of a utility's + /// input line (either standard input or another file), when the utility is + /// described as processing text files. The length includes room for the + /// trailing newline. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + LINE_MAX = libc::_SC_LINE_MAX, + /// Maximum length of a login name. + #[cfg(not(target_os = "haiku"))] + LOGIN_NAME_MAX = libc::_SC_LOGIN_NAME_MAX, + /// Maximum number of simultaneous supplementary group IDs per process. + NGROUPS_MAX = libc::_SC_NGROUPS_MAX, + /// Initial size of `getgrgid_r` and `getgrnam_r` data buffers + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + GETGR_R_SIZE_MAX = libc::_SC_GETGR_R_SIZE_MAX, + /// Initial size of `getpwuid_r` and `getpwnam_r` data buffers + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + GETPW_R_SIZE_MAX = libc::_SC_GETPW_R_SIZE_MAX, + /// The maximum number of open message queue descriptors a process may hold. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MQ_OPEN_MAX = libc::_SC_MQ_OPEN_MAX, + /// The maximum number of message priorities supported by the implementation. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MQ_PRIO_MAX = libc::_SC_MQ_PRIO_MAX, + /// A value one greater than the maximum value that the system may assign to + /// a newly-created file descriptor. + OPEN_MAX = libc::_SC_OPEN_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Advisory Information option. + _POSIX_ADVISORY_INFO = libc::_SC_ADVISORY_INFO, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports barriers. + _POSIX_BARRIERS = libc::_SC_BARRIERS, + /// The implementation supports asynchronous input and output. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_ASYNCHRONOUS_IO = libc::_SC_ASYNCHRONOUS_IO, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports clock selection. + _POSIX_CLOCK_SELECTION = libc::_SC_CLOCK_SELECTION, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Process CPU-Time Clocks option. + _POSIX_CPUTIME = libc::_SC_CPUTIME, + /// The implementation supports the File Synchronization option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_FSYNC = libc::_SC_FSYNC, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the IPv6 option. + _POSIX_IPV6 = libc::_SC_IPV6, + /// The implementation supports job control. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_JOB_CONTROL = libc::_SC_JOB_CONTROL, + /// The implementation supports memory mapped Files. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_MAPPED_FILES = libc::_SC_MAPPED_FILES, + /// The implementation supports the Process Memory Locking option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_MEMLOCK = libc::_SC_MEMLOCK, + /// The implementation supports the Range Memory Locking option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_MEMLOCK_RANGE = libc::_SC_MEMLOCK_RANGE, + /// The implementation supports memory protection. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_MEMORY_PROTECTION = libc::_SC_MEMORY_PROTECTION, + /// The implementation supports the Message Passing option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_MESSAGE_PASSING = libc::_SC_MESSAGE_PASSING, + /// The implementation supports the Monotonic Clock option. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_MONOTONIC_CLOCK = libc::_SC_MONOTONIC_CLOCK, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "illumos", target_os = "ios", target_os="linux", + target_os = "macos", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Prioritized Input and Output option. + _POSIX_PRIORITIZED_IO = libc::_SC_PRIORITIZED_IO, + /// The implementation supports the Process Scheduling option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_PRIORITY_SCHEDULING = libc::_SC_PRIORITY_SCHEDULING, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Raw Sockets option. + _POSIX_RAW_SOCKETS = libc::_SC_RAW_SOCKETS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports read-write locks. + _POSIX_READER_WRITER_LOCKS = libc::_SC_READER_WRITER_LOCKS, + #[cfg(any(target_os = "android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports realtime signals. + _POSIX_REALTIME_SIGNALS = libc::_SC_REALTIME_SIGNALS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Regular Expression Handling option. + _POSIX_REGEXP = libc::_SC_REGEXP, + /// Each process has a saved set-user-ID and a saved set-group-ID. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_SAVED_IDS = libc::_SC_SAVED_IDS, + /// The implementation supports semaphores. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_SEMAPHORES = libc::_SC_SEMAPHORES, + /// The implementation supports the Shared Memory Objects option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_SHARED_MEMORY_OBJECTS = libc::_SC_SHARED_MEMORY_OBJECTS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the POSIX shell. + _POSIX_SHELL = libc::_SC_SHELL, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Spawn option. + _POSIX_SPAWN = libc::_SC_SPAWN, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports spin locks. + _POSIX_SPIN_LOCKS = libc::_SC_SPIN_LOCKS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Process Sporadic Server option. + _POSIX_SPORADIC_SERVER = libc::_SC_SPORADIC_SERVER, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_SS_REPL_MAX = libc::_SC_SS_REPL_MAX, + /// The implementation supports the Synchronized Input and Output option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_SYNCHRONIZED_IO = libc::_SC_SYNCHRONIZED_IO, + /// The implementation supports the Thread Stack Address Attribute option. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREAD_ATTR_STACKADDR = libc::_SC_THREAD_ATTR_STACKADDR, + /// The implementation supports the Thread Stack Size Attribute option. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREAD_ATTR_STACKSIZE = libc::_SC_THREAD_ATTR_STACKSIZE, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="netbsd", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Thread CPU-Time Clocks option. + _POSIX_THREAD_CPUTIME = libc::_SC_THREAD_CPUTIME, + /// The implementation supports the Non-Robust Mutex Priority Inheritance + /// option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREAD_PRIO_INHERIT = libc::_SC_THREAD_PRIO_INHERIT, + /// The implementation supports the Non-Robust Mutex Priority Protection option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREAD_PRIO_PROTECT = libc::_SC_THREAD_PRIO_PROTECT, + /// The implementation supports the Thread Execution Scheduling option. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREAD_PRIORITY_SCHEDULING = libc::_SC_THREAD_PRIORITY_SCHEDULING, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Thread Process-Shared Synchronization + /// option. + _POSIX_THREAD_PROCESS_SHARED = libc::_SC_THREAD_PROCESS_SHARED, + #[cfg(any(target_os="dragonfly", target_os="linux", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Robust Mutex Priority Inheritance option. + _POSIX_THREAD_ROBUST_PRIO_INHERIT = libc::_SC_THREAD_ROBUST_PRIO_INHERIT, + #[cfg(any(target_os="dragonfly", target_os="linux", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Robust Mutex Priority Protection option. + _POSIX_THREAD_ROBUST_PRIO_PROTECT = libc::_SC_THREAD_ROBUST_PRIO_PROTECT, + /// The implementation supports thread-safe functions. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREAD_SAFE_FUNCTIONS = libc::_SC_THREAD_SAFE_FUNCTIONS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Thread Sporadic Server option. + _POSIX_THREAD_SPORADIC_SERVER = libc::_SC_THREAD_SPORADIC_SERVER, + /// The implementation supports threads. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_THREADS = libc::_SC_THREADS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports timeouts. + _POSIX_TIMEOUTS = libc::_SC_TIMEOUTS, + /// The implementation supports timers. + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_TIMERS = libc::_SC_TIMERS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Trace option. + _POSIX_TRACE = libc::_SC_TRACE, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Trace Event Filter option. + _POSIX_TRACE_EVENT_FILTER = libc::_SC_TRACE_EVENT_FILTER, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_TRACE_EVENT_NAME_MAX = libc::_SC_TRACE_EVENT_NAME_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Trace Inherit option. + _POSIX_TRACE_INHERIT = libc::_SC_TRACE_INHERIT, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Trace Log option. + _POSIX_TRACE_LOG = libc::_SC_TRACE_LOG, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_TRACE_NAME_MAX = libc::_SC_TRACE_NAME_MAX, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_TRACE_SYS_MAX = libc::_SC_TRACE_SYS_MAX, + #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX_TRACE_USER_EVENT_MAX = libc::_SC_TRACE_USER_EVENT_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Typed Memory Objects option. + _POSIX_TYPED_MEMORY_OBJECTS = libc::_SC_TYPED_MEMORY_OBJECTS, + /// Integer value indicating version of this standard (C-language binding) + /// to which the implementation conforms. For implementations conforming to + /// POSIX.1-2008, the value shall be 200809L. + _POSIX_VERSION = libc::_SC_VERSION, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation provides a C-language compilation environment with + /// 32-bit `int`, `long`, `pointer`, and `off_t` types. + _POSIX_V6_ILP32_OFF32 = libc::_SC_V6_ILP32_OFF32, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation provides a C-language compilation environment with + /// 32-bit `int`, `long`, and pointer types and an `off_t` type using at + /// least 64 bits. + _POSIX_V6_ILP32_OFFBIG = libc::_SC_V6_ILP32_OFFBIG, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation provides a C-language compilation environment with + /// 32-bit `int` and 64-bit `long`, `pointer`, and `off_t` types. + _POSIX_V6_LP64_OFF64 = libc::_SC_V6_LP64_OFF64, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation provides a C-language compilation environment with an + /// `int` type using at least 32 bits and `long`, pointer, and `off_t` types + /// using at least 64 bits. + _POSIX_V6_LPBIG_OFFBIG = libc::_SC_V6_LPBIG_OFFBIG, + /// The implementation supports the C-Language Binding option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_C_BIND = libc::_SC_2_C_BIND, + /// The implementation supports the C-Language Development Utilities option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_C_DEV = libc::_SC_2_C_DEV, + /// The implementation supports the Terminal Characteristics option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_CHAR_TERM = libc::_SC_2_CHAR_TERM, + /// The implementation supports the FORTRAN Development Utilities option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_FORT_DEV = libc::_SC_2_FORT_DEV, + /// The implementation supports the FORTRAN Runtime Utilities option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_FORT_RUN = libc::_SC_2_FORT_RUN, + /// The implementation supports the creation of locales by the localedef + /// utility. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_LOCALEDEF = libc::_SC_2_LOCALEDEF, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Batch Environment Services and Utilities + /// option. + _POSIX2_PBS = libc::_SC_2_PBS, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Batch Accounting option. + _POSIX2_PBS_ACCOUNTING = libc::_SC_2_PBS_ACCOUNTING, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Batch Checkpoint/Restart option. + _POSIX2_PBS_CHECKPOINT = libc::_SC_2_PBS_CHECKPOINT, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Locate Batch Job Request option. + _POSIX2_PBS_LOCATE = libc::_SC_2_PBS_LOCATE, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Batch Job Message Request option. + _POSIX2_PBS_MESSAGE = libc::_SC_2_PBS_MESSAGE, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Track Batch Job Request option. + _POSIX2_PBS_TRACK = libc::_SC_2_PBS_TRACK, + /// The implementation supports the Software Development Utilities option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_SW_DEV = libc::_SC_2_SW_DEV, + /// The implementation supports the User Portability Utilities option. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_UPE = libc::_SC_2_UPE, + /// Integer value indicating version of the Shell and Utilities volume of + /// POSIX.1 to which the implementation conforms. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _POSIX2_VERSION = libc::_SC_2_VERSION, + /// The size of a system page in bytes. + /// + /// POSIX also defines an alias named `PAGESIZE`, but Rust does not allow two + /// enum constants to have the same value, so nix omits `PAGESIZE`. + PAGE_SIZE = libc::_SC_PAGE_SIZE, + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTHREAD_DESTRUCTOR_ITERATIONS = libc::_SC_THREAD_DESTRUCTOR_ITERATIONS, + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTHREAD_KEYS_MAX = libc::_SC_THREAD_KEYS_MAX, + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTHREAD_STACK_MIN = libc::_SC_THREAD_STACK_MIN, + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + PTHREAD_THREADS_MAX = libc::_SC_THREAD_THREADS_MAX, + #[cfg(not(target_os = "haiku"))] + RE_DUP_MAX = libc::_SC_RE_DUP_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + RTSIG_MAX = libc::_SC_RTSIG_MAX, + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SEM_NSEMS_MAX = libc::_SC_SEM_NSEMS_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SEM_VALUE_MAX = libc::_SC_SEM_VALUE_MAX, + #[cfg(any(target_os = "android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SIGQUEUE_MAX = libc::_SC_SIGQUEUE_MAX, + STREAM_MAX = libc::_SC_STREAM_MAX, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="netbsd", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + SYMLOOP_MAX = libc::_SC_SYMLOOP_MAX, + #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + TIMER_MAX = libc::_SC_TIMER_MAX, + TTY_NAME_MAX = libc::_SC_TTY_NAME_MAX, + TZNAME_MAX = libc::_SC_TZNAME_MAX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the X/Open Encryption Option Group. + _XOPEN_CRYPT = libc::_SC_XOPEN_CRYPT, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the Issue 4, Version 2 Enhanced + /// Internationalization Option Group. + _XOPEN_ENH_I18N = libc::_SC_XOPEN_ENH_I18N, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _XOPEN_LEGACY = libc::_SC_XOPEN_LEGACY, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the X/Open Realtime Option Group. + _XOPEN_REALTIME = libc::_SC_XOPEN_REALTIME, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the X/Open Realtime Threads Option Group. + _XOPEN_REALTIME_THREADS = libc::_SC_XOPEN_REALTIME_THREADS, + /// The implementation supports the Issue 4, Version 2 Shared Memory Option + /// Group. + #[cfg(not(any(target_os = "redox", target_os = "haiku")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + _XOPEN_SHM = libc::_SC_XOPEN_SHM, + #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", + target_os="linux", target_os = "macos", target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the XSI STREAMS Option Group. + _XOPEN_STREAMS = libc::_SC_XOPEN_STREAMS, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// The implementation supports the XSI option + _XOPEN_UNIX = libc::_SC_XOPEN_UNIX, + #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", + target_os = "ios", target_os="linux", target_os = "macos", + target_os="openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + /// Integer value indicating version of the X/Open Portability Guide to + /// which the implementation conforms. + _XOPEN_VERSION = libc::_SC_XOPEN_VERSION, + /// The number of pages of physical memory. Note that it is possible for + /// the product of this value to overflow. + #[cfg(any(target_os="android", target_os="linux"))] + _PHYS_PAGES = libc::_SC_PHYS_PAGES, + /// The number of currently available pages of physical memory. + #[cfg(any(target_os="android", target_os="linux"))] + _AVPHYS_PAGES = libc::_SC_AVPHYS_PAGES, + /// The number of processors configured. + #[cfg(any(target_os="android", target_os="linux"))] + _NPROCESSORS_CONF = libc::_SC_NPROCESSORS_CONF, + /// The number of processors currently online (available). + #[cfg(any(target_os="android", target_os="linux"))] + _NPROCESSORS_ONLN = libc::_SC_NPROCESSORS_ONLN, +} + +/// Get configurable system variables (see +/// [sysconf(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html)) +/// +/// Returns the value of a configurable system variable. Most supported +/// variables also have associated compile-time constants, but POSIX +/// allows their values to change at runtime. There are generally two types of +/// sysconf variables: options and limits. See sysconf(3) for more details. +/// +/// # Returns +/// +/// - `Ok(Some(x))`: the variable's limit (for limit variables) or its +/// implementation level (for option variables). Implementation levels are +/// usually a decimal-coded date, such as 200112 for POSIX 2001.12 +/// - `Ok(None)`: the variable has no limit (for limit variables) or is +/// unsupported (for option variables) +/// - `Err(x)`: an error occurred +pub fn sysconf(var: SysconfVar) -> Result> { + let raw = unsafe { + Errno::clear(); + libc::sysconf(var as c_int) + }; + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(Errno::last()) + } + } else { + Ok(Some(raw)) + } +} +} + +feature! { +#![feature = "fs"] + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod pivot_root { + use crate::{Result, NixPath}; + use crate::errno::Errno; + + pub fn pivot_root( + new_root: &P1, put_old: &P2) -> Result<()> { + let res = new_root.with_nix_path(|new_root| { + put_old.with_nix_path(|put_old| { + unsafe { + libc::syscall(libc::SYS_pivot_root, new_root.as_ptr(), put_old.as_ptr()) + } + }) + })??; + + Errno::result(res).map(drop) + } +} +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +mod setres { + feature! { + #![feature = "user"] + + use crate::Result; + use crate::errno::Errno; + use super::{Uid, Gid}; + + /// Sets the real, effective, and saved uid. + /// ([see setresuid(2)](https://man7.org/linux/man-pages/man2/setresuid.2.html)) + /// + /// * `ruid`: real user id + /// * `euid`: effective user id + /// * `suid`: saved user id + /// * returns: Ok or libc error code. + /// + /// Err is returned if the user doesn't have permission to set this UID. + #[inline] + pub fn setresuid(ruid: Uid, euid: Uid, suid: Uid) -> Result<()> { + let res = unsafe { libc::setresuid(ruid.into(), euid.into(), suid.into()) }; + + Errno::result(res).map(drop) + } + + /// Sets the real, effective, and saved gid. + /// ([see setresuid(2)](https://man7.org/linux/man-pages/man2/setresuid.2.html)) + /// + /// * `rgid`: real group id + /// * `egid`: effective group id + /// * `sgid`: saved group id + /// * returns: Ok or libc error code. + /// + /// Err is returned if the user doesn't have permission to set this GID. + #[inline] + pub fn setresgid(rgid: Gid, egid: Gid, sgid: Gid) -> Result<()> { + let res = unsafe { libc::setresgid(rgid.into(), egid.into(), sgid.into()) }; + + Errno::result(res).map(drop) + } + } +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +mod getres { + feature! { + #![feature = "user"] + + use crate::Result; + use crate::errno::Errno; + use super::{Uid, Gid}; + + /// Real, effective and saved user IDs. + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub struct ResUid { + pub real: Uid, + pub effective: Uid, + pub saved: Uid + } + + /// Real, effective and saved group IDs. + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub struct ResGid { + pub real: Gid, + pub effective: Gid, + pub saved: Gid + } + + /// Gets the real, effective, and saved user IDs. + /// + /// ([see getresuid(2)](http://man7.org/linux/man-pages/man2/getresuid.2.html)) + /// + /// #Returns + /// + /// - `Ok((Uid, Uid, Uid))`: tuple of real, effective and saved uids on success. + /// - `Err(x)`: libc error code on failure. + /// + #[inline] + pub fn getresuid() -> Result { + let mut ruid = libc::uid_t::max_value(); + let mut euid = libc::uid_t::max_value(); + let mut suid = libc::uid_t::max_value(); + let res = unsafe { libc::getresuid(&mut ruid, &mut euid, &mut suid) }; + + Errno::result(res).map(|_| ResUid{ real: Uid(ruid), effective: Uid(euid), saved: Uid(suid) }) + } + + /// Gets the real, effective, and saved group IDs. + /// + /// ([see getresgid(2)](http://man7.org/linux/man-pages/man2/getresgid.2.html)) + /// + /// #Returns + /// + /// - `Ok((Gid, Gid, Gid))`: tuple of real, effective and saved gids on success. + /// - `Err(x)`: libc error code on failure. + /// + #[inline] + pub fn getresgid() -> Result { + let mut rgid = libc::gid_t::max_value(); + let mut egid = libc::gid_t::max_value(); + let mut sgid = libc::gid_t::max_value(); + let res = unsafe { libc::getresgid(&mut rgid, &mut egid, &mut sgid) }; + + Errno::result(res).map(|_| ResGid { real: Gid(rgid), effective: Gid(egid), saved: Gid(sgid) } ) + } + } +} + +#[cfg(feature = "fs")] +libc_bitflags! { + /// Options for access() + #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] + pub struct AccessFlags : c_int { + /// Test for existence of file. + F_OK; + /// Test for read permission. + R_OK; + /// Test for write permission. + W_OK; + /// Test for execute (search) permission. + X_OK; + } +} + +feature! { +#![feature = "fs"] + +/// Checks the file named by `path` for accessibility according to the flags given by `amode` +/// See [access(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html) +pub fn access(path: &P, amode: AccessFlags) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { + libc::access(cstr.as_ptr(), amode.bits) + } + })?; + Errno::result(res).map(drop) +} + +/// Checks the file named by `path` for accessibility according to the flags given by `mode` +/// +/// If `dirfd` has a value, then `path` is relative to directory associated with the file descriptor. +/// +/// If `dirfd` is `None`, then `path` is relative to the current working directory. +/// +/// # References +/// +/// [faccessat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html) +// redox: does not appear to support the *at family of syscalls. +#[cfg(not(target_os = "redox"))] +pub fn faccessat(dirfd: Option, path: &P, mode: AccessFlags, flags: AtFlags) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { + libc::faccessat(at_rawfd(dirfd), cstr.as_ptr(), mode.bits(), flags.bits()) + } + })?; + Errno::result(res).map(drop) +} + +/// Checks the file named by `path` for accessibility according to the flags given +/// by `mode` using effective UID, effective GID and supplementary group lists. +/// +/// # References +/// +/// * [FreeBSD man page](https://www.freebsd.org/cgi/man.cgi?query=eaccess&sektion=2&n=1) +/// * [Linux man page](https://man7.org/linux/man-pages/man3/euidaccess.3.html) +#[cfg(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "freebsd", + target_os = "dragonfly" +))] +pub fn eaccess(path: &P, mode: AccessFlags) -> Result<()> { + let res = path.with_nix_path(|cstr| { + unsafe { + libc::eaccess(cstr.as_ptr(), mode.bits) + } + })?; + Errno::result(res).map(drop) +} +} + +feature! { +#![feature = "user"] + +/// Representation of a User, based on `libc::passwd` +/// +/// The reason some fields in this struct are `String` and others are `CString` is because some +/// fields are based on the user's locale, which could be non-UTF8, while other fields are +/// guaranteed to conform to [`NAME_REGEX`](https://serverfault.com/a/73101/407341), which only +/// contains ASCII. +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct User { + /// Username + pub name: String, + /// User password (probably hashed) + pub passwd: CString, + /// User ID + pub uid: Uid, + /// Group ID + pub gid: Gid, + /// User information + #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] + pub gecos: CString, + /// Home directory + pub dir: PathBuf, + /// Path to shell + pub shell: PathBuf, + /// Login class + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub class: CString, + /// Last password change + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub change: libc::time_t, + /// Expiration time of account + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub expire: libc::time_t +} + +#[cfg(not(target_os = "redox"))] //RedoxFS does not support passwd +impl From<&libc::passwd> for User { + fn from(pw: &libc::passwd) -> User { + unsafe { + User { + name: if pw.pw_name.is_null() { Default::default() } else { CStr::from_ptr(pw.pw_name).to_string_lossy().into_owned() }, + passwd: if pw.pw_passwd.is_null() { Default::default() } else { CString::new(CStr::from_ptr(pw.pw_passwd).to_bytes()).unwrap() }, + #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] + gecos: if pw.pw_gecos.is_null() { Default::default() } else { CString::new(CStr::from_ptr(pw.pw_gecos).to_bytes()).unwrap() }, + dir: if pw.pw_dir.is_null() { Default::default() } else { PathBuf::from(OsStr::from_bytes(CStr::from_ptr(pw.pw_dir).to_bytes())) }, + shell: if pw.pw_shell.is_null() { Default::default() } else { PathBuf::from(OsStr::from_bytes(CStr::from_ptr(pw.pw_shell).to_bytes())) }, + uid: Uid::from_raw(pw.pw_uid), + gid: Gid::from_raw(pw.pw_gid), + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + class: CString::new(CStr::from_ptr(pw.pw_class).to_bytes()).unwrap(), + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + change: pw.pw_change, + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + expire: pw.pw_expire + } + } + } +} + +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +impl From for libc::passwd { + fn from(u: User) -> Self { + let name = match CString::new(u.name) { + Ok(n) => n.into_raw(), + Err(_) => CString::new("").unwrap().into_raw(), + }; + let dir = match u.dir.into_os_string().into_string() { + Ok(s) => CString::new(s.as_str()).unwrap().into_raw(), + Err(_) => CString::new("").unwrap().into_raw(), + }; + let shell = match u.shell.into_os_string().into_string() { + Ok(s) => CString::new(s.as_str()).unwrap().into_raw(), + Err(_) => CString::new("").unwrap().into_raw(), + }; + Self { + pw_name: name, + pw_passwd: u.passwd.into_raw(), + #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] + pw_gecos: u.gecos.into_raw(), + pw_dir: dir, + pw_shell: shell, + pw_uid: u.uid.0, + pw_gid: u.gid.0, + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + pw_class: u.class.into_raw(), + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + pw_change: u.change, + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + pw_expire: u.expire, + #[cfg(target_os = "illumos")] + pw_age: CString::new("").unwrap().into_raw(), + #[cfg(target_os = "illumos")] + pw_comment: CString::new("").unwrap().into_raw(), + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + pw_fields: 0, + } + } +} + +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +impl User { + fn from_anything(f: F) -> Result> + where + F: Fn(*mut libc::passwd, + *mut c_char, + libc::size_t, + *mut *mut libc::passwd) -> libc::c_int + { + let buflimit = 1048576; + let bufsize = match sysconf(SysconfVar::GETPW_R_SIZE_MAX) { + Ok(Some(n)) => n as usize, + Ok(None) | Err(_) => 16384, + }; + + let mut cbuf = Vec::with_capacity(bufsize); + let mut pwd = mem::MaybeUninit::::uninit(); + let mut res = ptr::null_mut(); + + loop { + let error = f(pwd.as_mut_ptr(), cbuf.as_mut_ptr(), cbuf.capacity(), &mut res); + if error == 0 { + if res.is_null() { + return Ok(None); + } else { + let pwd = unsafe { pwd.assume_init() }; + return Ok(Some(User::from(&pwd))); + } + } else if Errno::last() == Errno::ERANGE { + // Trigger the internal buffer resizing logic. + reserve_double_buffer_size(&mut cbuf, buflimit)?; + } else { + return Err(Errno::last()); + } + } + } + + /// Get a user by UID. + /// + /// Internally, this function calls + /// [getpwuid_r(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html) + /// + /// # Examples + /// + /// ``` + /// use nix::unistd::{Uid, User}; + /// // Returns an Result>, thus the double unwrap. + /// let res = User::from_uid(Uid::from_raw(0)).unwrap().unwrap(); + /// assert_eq!(res.name, "root"); + /// ``` + pub fn from_uid(uid: Uid) -> Result> { + User::from_anything(|pwd, cbuf, cap, res| { + unsafe { libc::getpwuid_r(uid.0, pwd, cbuf, cap, res) } + }) + } + + /// Get a user by name. + /// + /// Internally, this function calls + /// [getpwnam_r(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html) + /// + /// # Examples + /// + /// ``` + /// use nix::unistd::User; + /// // Returns an Result>, thus the double unwrap. + /// let res = User::from_name("root").unwrap().unwrap(); + /// assert_eq!(res.name, "root"); + /// ``` + pub fn from_name(name: &str) -> Result> { + let name = match CString::new(name) { + Ok(c_str) => c_str, + Err(_nul_error) => return Ok(None), + }; + User::from_anything(|pwd, cbuf, cap, res| { + unsafe { libc::getpwnam_r(name.as_ptr(), pwd, cbuf, cap, res) } + }) + } +} + +/// Representation of a Group, based on `libc::group` +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Group { + /// Group name + pub name: String, + /// Group password + pub passwd: CString, + /// Group ID + pub gid: Gid, + /// List of Group members + pub mem: Vec +} + +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +impl From<&libc::group> for Group { + fn from(gr: &libc::group) -> Group { + unsafe { + Group { + name: CStr::from_ptr(gr.gr_name).to_string_lossy().into_owned(), + passwd: CString::new(CStr::from_ptr(gr.gr_passwd).to_bytes()).unwrap(), + gid: Gid::from_raw(gr.gr_gid), + mem: Group::members(gr.gr_mem) + } + } + } +} + +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +impl Group { + unsafe fn members(mem: *mut *mut c_char) -> Vec { + let mut ret = Vec::new(); + + for i in 0.. { + let u = mem.offset(i); + if (*u).is_null() { + break; + } else { + let s = CStr::from_ptr(*u).to_string_lossy().into_owned(); + ret.push(s); + } + } + + ret + } + + fn from_anything(f: F) -> Result> + where + F: Fn(*mut libc::group, + *mut c_char, + libc::size_t, + *mut *mut libc::group) -> libc::c_int + { + let buflimit = 1048576; + let bufsize = match sysconf(SysconfVar::GETGR_R_SIZE_MAX) { + Ok(Some(n)) => n as usize, + Ok(None) | Err(_) => 16384, + }; + + let mut cbuf = Vec::with_capacity(bufsize); + let mut grp = mem::MaybeUninit::::uninit(); + let mut res = ptr::null_mut(); + + loop { + let error = f(grp.as_mut_ptr(), cbuf.as_mut_ptr(), cbuf.capacity(), &mut res); + if error == 0 { + if res.is_null() { + return Ok(None); + } else { + let grp = unsafe { grp.assume_init() }; + return Ok(Some(Group::from(&grp))); + } + } else if Errno::last() == Errno::ERANGE { + // Trigger the internal buffer resizing logic. + reserve_double_buffer_size(&mut cbuf, buflimit)?; + } else { + return Err(Errno::last()); + } + } + } + + /// Get a group by GID. + /// + /// Internally, this function calls + /// [getgrgid_r(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html) + /// + /// # Examples + /// + // Disable this test on all OS except Linux as root group may not exist. + #[cfg_attr(not(target_os = "linux"), doc = " ```no_run")] + #[cfg_attr(target_os = "linux", doc = " ```")] + /// use nix::unistd::{Gid, Group}; + /// // Returns an Result>, thus the double unwrap. + /// let res = Group::from_gid(Gid::from_raw(0)).unwrap().unwrap(); + /// assert!(res.name == "root"); + /// ``` + pub fn from_gid(gid: Gid) -> Result> { + Group::from_anything(|grp, cbuf, cap, res| { + unsafe { libc::getgrgid_r(gid.0, grp, cbuf, cap, res) } + }) + } + + /// Get a group by name. + /// + /// Internally, this function calls + /// [getgrnam_r(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html) + /// + /// # Examples + /// + // Disable this test on all OS except Linux as root group may not exist. + #[cfg_attr(not(target_os = "linux"), doc = " ```no_run")] + #[cfg_attr(target_os = "linux", doc = " ```")] + /// use nix::unistd::Group; + /// // Returns an Result>, thus the double unwrap. + /// let res = Group::from_name("root").unwrap().unwrap(); + /// assert!(res.name == "root"); + /// ``` + pub fn from_name(name: &str) -> Result> { + let name = match CString::new(name) { + Ok(c_str) => c_str, + Err(_nul_error) => return Ok(None), + }; + Group::from_anything(|grp, cbuf, cap, res| { + unsafe { libc::getgrnam_r(name.as_ptr(), grp, cbuf, cap, res) } + }) + } +} +} + +feature! { +#![feature = "term"] + +/// Get the name of the terminal device that is open on file descriptor fd +/// (see [`ttyname(3)`](https://man7.org/linux/man-pages/man3/ttyname.3.html)). +#[cfg(not(target_os = "fuchsia"))] +pub fn ttyname(fd: RawFd) -> Result { + const PATH_MAX: usize = libc::PATH_MAX as usize; + let mut buf = vec![0_u8; PATH_MAX]; + let c_buf = buf.as_mut_ptr() as *mut libc::c_char; + + let ret = unsafe { libc::ttyname_r(fd, c_buf, buf.len()) }; + if ret != 0 { + return Err(Errno::from_i32(ret)); + } + + let nul = buf.iter().position(|c| *c == b'\0').unwrap(); + buf.truncate(nul); + Ok(OsString::from_vec(buf).into()) +} +} + +feature! { +#![all(feature = "socket", feature = "user")] + +/// Get the effective user ID and group ID associated with a Unix domain socket. +/// +/// See also [getpeereid(3)](https://www.freebsd.org/cgi/man.cgi?query=getpeereid) +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", +))] +pub fn getpeereid(fd: RawFd) -> Result<(Uid, Gid)> { + let mut uid = 1; + let mut gid = 1; + + let ret = unsafe { libc::getpeereid(fd, &mut uid, &mut gid) }; + + Errno::result(ret).map(|_| (Uid(uid), Gid(gid))) +} +} + +feature! { +#![all(feature = "fs")] + +/// Set the file flags. +/// +/// See also [chflags(2)](https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2) +#[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "ios" +))] +pub fn chflags(path: &P, flags: FileFlag) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::chflags(cstr.as_ptr(), flags.bits()) + })?; + + Errno::result(res).map(drop) +} +} diff --git a/test/common/mod.rs b/test/common/mod.rs new file mode 100644 index 0000000..bb056aa --- /dev/null +++ b/test/common/mod.rs @@ -0,0 +1,149 @@ +use cfg_if::cfg_if; + +#[macro_export] +macro_rules! skip { + ($($reason: expr),+) => { + use ::std::io::{self, Write}; + + let stderr = io::stderr(); + let mut handle = stderr.lock(); + writeln!(handle, $($reason),+).unwrap(); + return; + } +} + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + #[macro_export] macro_rules! require_capability { + ($name:expr, $capname:ident) => { + use ::caps::{Capability, CapSet, has_cap}; + + if !has_cap(None, CapSet::Effective, Capability::$capname) + .unwrap() + { + skip!("{} requires capability {}. Skipping test.", $name, Capability::$capname); + } + } + } + } else if #[cfg(not(target_os = "redox"))] { + #[macro_export] macro_rules! require_capability { + ($name:expr, $capname:ident) => {} + } + } +} + +/// Skip the test if we don't have the ability to mount file systems. +#[cfg(target_os = "freebsd")] +#[macro_export] +macro_rules! require_mount { + ($name:expr) => { + use ::sysctl::{CtlValue, Sysctl}; + use nix::unistd::Uid; + + let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); + if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() + { + skip!( + "{} requires the ability to mount file systems. Skipping test.", + $name + ); + } + }; +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[macro_export] +macro_rules! skip_if_cirrus { + ($reason:expr) => { + if std::env::var_os("CIRRUS_CI").is_some() { + skip!("{}", $reason); + } + }; +} + +#[cfg(target_os = "freebsd")] +#[macro_export] +macro_rules! skip_if_jailed { + ($name:expr) => { + use ::sysctl::{CtlValue, Sysctl}; + + let ctl = ::sysctl::Ctl::new("security.jail.jailed").unwrap(); + if let CtlValue::Int(1) = ctl.value().unwrap() { + skip!("{} cannot run in a jail. Skipping test.", $name); + } + }; +} + +#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +#[macro_export] +macro_rules! skip_if_not_root { + ($name:expr) => { + use nix::unistd::Uid; + + if !Uid::current().is_root() { + skip!("{} requires root privileges. Skipping test.", $name); + } + }; +} + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + #[macro_export] macro_rules! skip_if_seccomp { + ($name:expr) => { + if let Ok(s) = std::fs::read_to_string("/proc/self/status") { + for l in s.lines() { + let mut fields = l.split_whitespace(); + if fields.next() == Some("Seccomp:") && + fields.next() != Some("0") + { + skip!("{} cannot be run in Seccomp mode. Skipping test.", + stringify!($name)); + } + } + } + } + } + } else if #[cfg(not(target_os = "redox"))] { + #[macro_export] macro_rules! skip_if_seccomp { + ($name:expr) => {} + } + } +} + +cfg_if! { + if #[cfg(target_os = "linux")] { + #[macro_export] macro_rules! require_kernel_version { + ($name:expr, $version_requirement:expr) => { + use semver::{Version, VersionReq}; + + let version_requirement = VersionReq::parse($version_requirement) + .expect("Bad match_version provided"); + + let uname = nix::sys::utsname::uname().unwrap(); + println!("{}", uname.sysname().to_str().unwrap()); + println!("{}", uname.nodename().to_str().unwrap()); + println!("{}", uname.release().to_str().unwrap()); + println!("{}", uname.version().to_str().unwrap()); + println!("{}", uname.machine().to_str().unwrap()); + + // Fix stuff that the semver parser can't handle + let fixed_release = &uname.release().to_str().unwrap().to_string() + // Fedora 33 reports version as 4.18.el8_2.x86_64 or + // 5.18.200-fc33.x86_64. Remove the underscore. + .replace("_", "-") + // Cirrus-CI reports version as 4.19.112+ . Remove the + + .replace("+", ""); + let mut version = Version::parse(fixed_release).unwrap(); + + //Keep only numeric parts + version.pre = semver::Prerelease::EMPTY; + version.build = semver::BuildMetadata::EMPTY; + + if !version_requirement.matches(&version) { + skip!("Skip {} because kernel version `{}` doesn't match the requirement `{}`", + stringify!($name), version, version_requirement); + } + } + } + } +} diff --git a/test/sys/mod.rs b/test/sys/mod.rs new file mode 100644 index 0000000..2031212 --- /dev/null +++ b/test/sys/mod.rs @@ -0,0 +1,60 @@ +mod test_signal; + +// NOTE: DragonFly lacks a kernel-level implementation of Posix AIO as of +// this writing. There is an user-level implementation, but whether aio +// works or not heavily depends on which pthread implementation is chosen +// by the user at link time. For this reason we do not want to run aio test +// cases on DragonFly. +#[cfg(any( + target_os = "freebsd", + target_os = "ios", + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + target_os = "netbsd" +))] +mod test_aio; +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +mod test_ioctl; +#[cfg(not(target_os = "redox"))] +mod test_mman; +#[cfg(not(target_os = "redox"))] +mod test_select; +#[cfg(target_os = "linux")] +mod test_signalfd; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +mod test_socket; +#[cfg(not(any(target_os = "redox")))] +mod test_sockopt; +mod test_stat; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod test_sysinfo; +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +mod test_termios; +mod test_uio; +mod test_wait; + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod test_epoll; +#[cfg(target_os = "linux")] +mod test_inotify; +mod test_pthread; +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +mod test_ptrace; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod test_timerfd; diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs new file mode 100644 index 0000000..84086f8 --- /dev/null +++ b/test/sys/test_aio.rs @@ -0,0 +1,626 @@ +use std::{ + io::{Read, Seek, Write}, + ops::Deref, + os::unix::io::AsRawFd, + pin::Pin, + sync::atomic::{AtomicBool, Ordering}, + thread, time, +}; + +use libc::c_int; +use nix::{ + errno::*, + sys::{ + aio::*, + signal::{ + sigaction, SaFlags, SigAction, SigHandler, SigSet, SigevNotify, + Signal, + }, + time::{TimeSpec, TimeValLike}, + }, +}; +use tempfile::tempfile; + +lazy_static! { + pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); +} + +extern "C" fn sigfunc(_: c_int) { + SIGNALED.store(true, Ordering::Relaxed); +} + +// Helper that polls an AioCb for completion or error +macro_rules! poll_aio { + ($aiocb: expr) => { + loop { + let err = $aiocb.as_mut().error(); + if err != Err(Errno::EINPROGRESS) { + break err; + }; + thread::sleep(time::Duration::from_millis(10)); + } + }; +} + +mod aio_fsync { + use super::*; + + #[test] + fn test_accessors() { + let aiocb = AioFsync::new( + 1001, + AioFsyncMode::O_SYNC, + 42, + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 99, + }, + ); + assert_eq!(1001, aiocb.fd()); + assert_eq!(AioFsyncMode::O_SYNC, aiocb.mode()); + assert_eq!(42, aiocb.priority()); + let sev = aiocb.sigevent().sigevent(); + assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo); + assert_eq!(99, sev.sigev_value.sival_ptr as i64); + } + + /// `AioFsync::submit` should not modify the `AioCb` object if + /// `libc::aio_fsync` returns an error + // Skip on Linux, because Linux's AIO implementation can't detect errors + // synchronously + #[test] + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + fn error() { + use std::mem; + + const INITIAL: &[u8] = b"abcdef123456"; + // Create an invalid AioFsyncMode + let mode = unsafe { mem::transmute(666) }; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let mut aiof = Box::pin(AioFsync::new( + f.as_raw_fd(), + mode, + 0, + SigevNotify::SigevNone, + )); + let err = aiof.as_mut().submit(); + err.expect_err("assertion failed"); + } + + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn ok() { + const INITIAL: &[u8] = b"abcdef123456"; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let fd = f.as_raw_fd(); + let mut aiof = Box::pin(AioFsync::new( + fd, + AioFsyncMode::O_SYNC, + 0, + SigevNotify::SigevNone, + )); + aiof.as_mut().submit().unwrap(); + poll_aio!(&mut aiof).unwrap(); + aiof.as_mut().aio_return().unwrap(); + } +} + +mod aio_read { + use super::*; + + #[test] + fn test_accessors() { + let mut rbuf = vec![0; 4]; + let aiocb = AioRead::new( + 1001, + 2, //offset + &mut rbuf, + 42, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 99, + }, + ); + assert_eq!(1001, aiocb.fd()); + assert_eq!(4, aiocb.nbytes()); + assert_eq!(2, aiocb.offset()); + assert_eq!(42, aiocb.priority()); + let sev = aiocb.sigevent().sigevent(); + assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo); + assert_eq!(99, sev.sigev_value.sival_ptr as i64); + } + + // Tests AioWrite.cancel. We aren't trying to test the OS's implementation, + // only our bindings. So it's sufficient to check that cancel + // returned any AioCancelStat value. + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn cancel() { + const INITIAL: &[u8] = b"abcdef123456"; + let mut rbuf = vec![0; 4]; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let fd = f.as_raw_fd(); + let mut aior = + Box::pin(AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone)); + aior.as_mut().submit().unwrap(); + + aior.as_mut().cancel().unwrap(); + + // Wait for aiow to complete, but don't care whether it succeeded + let _ = poll_aio!(&mut aior); + let _ = aior.as_mut().aio_return(); + } + + /// `AioRead::submit` should not modify the `AioCb` object if + /// `libc::aio_read` returns an error + // Skip on Linux, because Linux's AIO implementation can't detect errors + // synchronously + #[test] + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + fn error() { + const INITIAL: &[u8] = b"abcdef123456"; + let mut rbuf = vec![0; 4]; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let mut aior = Box::pin(AioRead::new( + f.as_raw_fd(), + -1, //an invalid offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + )); + aior.as_mut().submit().expect_err("assertion failed"); + } + + // Test a simple aio operation with no completion notification. We must + // poll for completion + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn ok() { + const INITIAL: &[u8] = b"abcdef123456"; + let mut rbuf = vec![0; 4]; + const EXPECT: &[u8] = b"cdef"; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + { + let fd = f.as_raw_fd(); + let mut aior = Box::pin(AioRead::new( + fd, + 2, + &mut rbuf, + 0, + SigevNotify::SigevNone, + )); + aior.as_mut().submit().unwrap(); + + let err = poll_aio!(&mut aior); + assert_eq!(err, Ok(())); + assert_eq!(aior.as_mut().aio_return().unwrap(), EXPECT.len()); + } + assert_eq!(EXPECT, rbuf.deref().deref()); + } + + // Like ok, but allocates the structure on the stack. + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn on_stack() { + const INITIAL: &[u8] = b"abcdef123456"; + let mut rbuf = vec![0; 4]; + const EXPECT: &[u8] = b"cdef"; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + { + let fd = f.as_raw_fd(); + let mut aior = + AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone); + let mut aior = unsafe { Pin::new_unchecked(&mut aior) }; + aior.as_mut().submit().unwrap(); + + let err = poll_aio!(&mut aior); + assert_eq!(err, Ok(())); + assert_eq!(aior.as_mut().aio_return().unwrap(), EXPECT.len()); + } + assert_eq!(EXPECT, rbuf.deref().deref()); + } +} + +#[cfg(target_os = "freebsd")] +#[cfg(fbsd14)] +mod aio_readv { + use std::io::IoSliceMut; + + use super::*; + + #[test] + fn test_accessors() { + let mut rbuf0 = vec![0; 4]; + let mut rbuf1 = vec![0; 8]; + let mut rbufs = + [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)]; + let aiocb = AioReadv::new( + 1001, + 2, //offset + &mut rbufs, + 42, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 99, + }, + ); + assert_eq!(1001, aiocb.fd()); + assert_eq!(2, aiocb.iovlen()); + assert_eq!(2, aiocb.offset()); + assert_eq!(42, aiocb.priority()); + let sev = aiocb.sigevent().sigevent(); + assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo); + assert_eq!(99, sev.sigev_value.sival_ptr as i64); + } + + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn ok() { + const INITIAL: &[u8] = b"abcdef123456"; + let mut rbuf0 = vec![0; 4]; + let mut rbuf1 = vec![0; 2]; + let mut rbufs = + [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)]; + const EXPECT0: &[u8] = b"cdef"; + const EXPECT1: &[u8] = b"12"; + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + { + let fd = f.as_raw_fd(); + let mut aior = Box::pin(AioReadv::new( + fd, + 2, + &mut rbufs, + 0, + SigevNotify::SigevNone, + )); + aior.as_mut().submit().unwrap(); + + let err = poll_aio!(&mut aior); + assert_eq!(err, Ok(())); + assert_eq!( + aior.as_mut().aio_return().unwrap(), + EXPECT0.len() + EXPECT1.len() + ); + } + assert_eq!(&EXPECT0, &rbuf0); + assert_eq!(&EXPECT1, &rbuf1); + } +} + +mod aio_write { + use super::*; + + #[test] + fn test_accessors() { + let wbuf = vec![0; 4]; + let aiocb = AioWrite::new( + 1001, + 2, //offset + &wbuf, + 42, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 99, + }, + ); + assert_eq!(1001, aiocb.fd()); + assert_eq!(4, aiocb.nbytes()); + assert_eq!(2, aiocb.offset()); + assert_eq!(42, aiocb.priority()); + let sev = aiocb.sigevent().sigevent(); + assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo); + assert_eq!(99, sev.sigev_value.sival_ptr as i64); + } + + // Tests AioWrite.cancel. We aren't trying to test the OS's implementation, + // only our bindings. So it's sufficient to check that cancel + // returned any AioCancelStat value. + #[test] + #[cfg_attr(target_env = "musl", ignore)] + fn cancel() { + let wbuf: &[u8] = b"CDEF"; + + let f = tempfile().unwrap(); + let mut aiow = Box::pin(AioWrite::new( + f.as_raw_fd(), + 0, + wbuf, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); + let err = aiow.as_mut().error(); + assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); + + aiow.as_mut().cancel().unwrap(); + + // Wait for aiow to complete, but don't care whether it succeeded + let _ = poll_aio!(&mut aiow); + let _ = aiow.as_mut().aio_return(); + } + + // Test a simple aio operation with no completion notification. We must + // poll for completion. + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn ok() { + const INITIAL: &[u8] = b"abcdef123456"; + let wbuf = "CDEF".to_string().into_bytes(); + let mut rbuf = Vec::new(); + const EXPECT: &[u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let mut aiow = Box::pin(AioWrite::new( + f.as_raw_fd(), + 2, + &wbuf, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); + + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + + f.rewind().unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert_eq!(len, EXPECT.len()); + assert_eq!(rbuf, EXPECT); + } + + // Like ok, but allocates the structure on the stack. + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn on_stack() { + const INITIAL: &[u8] = b"abcdef123456"; + let wbuf = "CDEF".to_string().into_bytes(); + let mut rbuf = Vec::new(); + const EXPECT: &[u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let mut aiow = AioWrite::new( + f.as_raw_fd(), + 2, //offset + &wbuf, + 0, //priority + SigevNotify::SigevNone, + ); + let mut aiow = unsafe { Pin::new_unchecked(&mut aiow) }; + aiow.as_mut().submit().unwrap(); + + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + + f.rewind().unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert_eq!(len, EXPECT.len()); + assert_eq!(rbuf, EXPECT); + } + + /// `AioWrite::write` should not modify the `AioCb` object if + /// `libc::aio_write` returns an error. + // Skip on Linux, because Linux's AIO implementation can't detect errors + // synchronously + #[test] + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + fn error() { + let wbuf = "CDEF".to_string().into_bytes(); + let mut aiow = Box::pin(AioWrite::new( + 666, // An invalid file descriptor + 0, //offset + &wbuf, + 0, //priority + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().expect_err("assertion failed"); + // Dropping the AioWrite at this point should not panic + } +} + +#[cfg(target_os = "freebsd")] +#[cfg(fbsd14)] +mod aio_writev { + use std::io::IoSlice; + + use super::*; + + #[test] + fn test_accessors() { + let wbuf0 = vec![0; 4]; + let wbuf1 = vec![0; 8]; + let wbufs = [IoSlice::new(&wbuf0), IoSlice::new(&wbuf1)]; + let aiocb = AioWritev::new( + 1001, + 2, //offset + &wbufs, + 42, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 99, + }, + ); + assert_eq!(1001, aiocb.fd()); + assert_eq!(2, aiocb.iovlen()); + assert_eq!(2, aiocb.offset()); + assert_eq!(42, aiocb.priority()); + let sev = aiocb.sigevent().sigevent(); + assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo); + assert_eq!(99, sev.sigev_value.sival_ptr as i64); + } + + // Test a simple aio operation with no completion notification. We must + // poll for completion. + #[test] + #[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] + fn ok() { + const INITIAL: &[u8] = b"abcdef123456"; + let wbuf0 = b"BC"; + let wbuf1 = b"DEF"; + let wbufs = [IoSlice::new(wbuf0), IoSlice::new(wbuf1)]; + let wlen = wbuf0.len() + wbuf1.len(); + let mut rbuf = Vec::new(); + const EXPECT: &[u8] = b"aBCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let mut aiow = Box::pin(AioWritev::new( + f.as_raw_fd(), + 1, + &wbufs, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); + + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen); + + f.rewind().unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert_eq!(len, EXPECT.len()); + assert_eq!(rbuf, EXPECT); + } +} + +// Test an aio operation with completion delivered by a signal +#[test] +#[cfg_attr( + any( + all(target_env = "musl", target_arch = "x86_64"), + target_arch = "mips", + target_arch = "mips64" + ), + ignore +)] +fn sigev_signal() { + let _m = crate::SIGNAL_MTX.lock(); + let sa = SigAction::new( + SigHandler::Handler(sigfunc), + SaFlags::SA_RESETHAND, + SigSet::empty(), + ); + SIGNALED.store(false, Ordering::Relaxed); + unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); + + const INITIAL: &[u8] = b"abcdef123456"; + const WBUF: &[u8] = b"CDEF"; + let mut rbuf = Vec::new(); + const EXPECT: &[u8] = b"abCDEF123456"; + + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + let mut aiow = Box::pin(AioWrite::new( + f.as_raw_fd(), + 2, //offset + WBUF, + 0, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 0, //TODO: validate in sigfunc + }, + )); + aiow.as_mut().submit().unwrap(); + while !SIGNALED.load(Ordering::Relaxed) { + thread::sleep(time::Duration::from_millis(10)); + } + + assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); + f.rewind().unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert_eq!(len, EXPECT.len()); + assert_eq!(rbuf, EXPECT); +} + +// Tests using aio_cancel_all for all outstanding IOs. +#[test] +#[cfg_attr(target_env = "musl", ignore)] +fn test_aio_cancel_all() { + let wbuf: &[u8] = b"CDEF"; + + let f = tempfile().unwrap(); + let mut aiocb = Box::pin(AioWrite::new( + f.as_raw_fd(), + 0, //offset + wbuf, + 0, //priority + SigevNotify::SigevNone, + )); + aiocb.as_mut().submit().unwrap(); + let err = aiocb.as_mut().error(); + assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); + + aio_cancel_all(f.as_raw_fd()).unwrap(); + + // Wait for aiocb to complete, but don't care whether it succeeded + let _ = poll_aio!(&mut aiocb); + let _ = aiocb.as_mut().aio_return(); +} + +#[test] +// On Cirrus on Linux, this test fails due to a glibc bug. +// https://github.com/nix-rust/nix/issues/1099 +#[cfg_attr(target_os = "linux", ignore)] +// On Cirrus, aio_suspend is failing with EINVAL +// https://github.com/nix-rust/nix/issues/1361 +#[cfg_attr(target_os = "macos", ignore)] +fn test_aio_suspend() { + const INITIAL: &[u8] = b"abcdef123456"; + const WBUF: &[u8] = b"CDEFG"; + let timeout = TimeSpec::seconds(10); + let mut rbuf = vec![0; 4]; + let rlen = rbuf.len(); + let mut f = tempfile().unwrap(); + f.write_all(INITIAL).unwrap(); + + let mut wcb = Box::pin(AioWrite::new( + f.as_raw_fd(), + 2, //offset + WBUF, + 0, //priority + SigevNotify::SigevNone, + )); + + let mut rcb = Box::pin(AioRead::new( + f.as_raw_fd(), + 8, //offset + &mut rbuf, + 0, //priority + SigevNotify::SigevNone, + )); + wcb.as_mut().submit().unwrap(); + rcb.as_mut().submit().unwrap(); + loop { + { + let cbbuf = [ + &*wcb as &dyn AsRef, + &*rcb as &dyn AsRef, + ]; + let r = aio_suspend(&cbbuf[..], Some(timeout)); + match r { + Err(Errno::EINTR) => continue, + Err(e) => panic!("aio_suspend returned {:?}", e), + Ok(_) => (), + }; + } + if rcb.as_mut().error() != Err(Errno::EINPROGRESS) + && wcb.as_mut().error() != Err(Errno::EINPROGRESS) + { + break; + } + } + + assert_eq!(wcb.as_mut().aio_return().unwrap(), WBUF.len()); + assert_eq!(rcb.as_mut().aio_return().unwrap(), rlen); +} diff --git a/test/sys/test_aio_drop.rs b/test/sys/test_aio_drop.rs new file mode 100644 index 0000000..bbe6623 --- /dev/null +++ b/test/sys/test_aio_drop.rs @@ -0,0 +1,35 @@ +// Test dropping an AioCb that hasn't yet finished. +// This must happen in its own process, because on OSX this test seems to hose +// the AIO subsystem and causes subsequent tests to fail +#[test] +#[should_panic(expected = "Dropped an in-progress AioCb")] +#[cfg(all( + not(target_env = "musl"), + not(target_env = "uclibc"), + any( + target_os = "linux", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" + ) +))] +fn test_drop() { + use nix::sys::aio::*; + use nix::sys::signal::*; + use std::os::unix::io::AsRawFd; + use tempfile::tempfile; + + const WBUF: &[u8] = b"CDEF"; + + let f = tempfile().unwrap(); + f.set_len(6).unwrap(); + let mut aiocb = Box::pin(AioWrite::new( + f.as_raw_fd(), + 2, //offset + WBUF, + 0, //priority + SigevNotify::SigevNone, + )); + aiocb.as_mut().submit().unwrap(); +} diff --git a/test/sys/test_epoll.rs b/test/sys/test_epoll.rs new file mode 100644 index 0000000..9156915 --- /dev/null +++ b/test/sys/test_epoll.rs @@ -0,0 +1,24 @@ +use nix::errno::Errno; +use nix::sys::epoll::{epoll_create1, epoll_ctl}; +use nix::sys::epoll::{EpollCreateFlags, EpollEvent, EpollFlags, EpollOp}; + +#[test] +pub fn test_epoll_errno() { + let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); + let result = epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None); + result.expect_err("assertion failed"); + assert_eq!(result.unwrap_err(), Errno::ENOENT); + + let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None); + result.expect_err("assertion failed"); + assert_eq!(result.unwrap_err(), Errno::EINVAL); +} + +#[test] +pub fn test_epoll_ctl() { + let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); + let mut event = + EpollEvent::new(EpollFlags::EPOLLIN | EpollFlags::EPOLLERR, 1); + epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, &mut event).unwrap(); + epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None).unwrap(); +} diff --git a/test/sys/test_inotify.rs b/test/sys/test_inotify.rs new file mode 100644 index 0000000..bb5851a --- /dev/null +++ b/test/sys/test_inotify.rs @@ -0,0 +1,65 @@ +use nix::errno::Errno; +use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify}; +use std::ffi::OsString; +use std::fs::{rename, File}; + +#[test] +pub fn test_inotify() { + let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + + instance + .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS) + .unwrap(); + + let events = instance.read_events(); + assert_eq!(events.unwrap_err(), Errno::EAGAIN); + + File::create(tempdir.path().join("test")).unwrap(); + + let events = instance.read_events().unwrap(); + assert_eq!(events[0].name, Some(OsString::from("test"))); +} + +#[test] +pub fn test_inotify_multi_events() { + let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + + instance + .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS) + .unwrap(); + + let events = instance.read_events(); + assert_eq!(events.unwrap_err(), Errno::EAGAIN); + + File::create(tempdir.path().join("test")).unwrap(); + rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap(); + + // Now there should be 5 events in queue: + // - IN_CREATE on test + // - IN_OPEN on test + // - IN_CLOSE_WRITE on test + // - IN_MOVED_FROM on test with a cookie + // - IN_MOVED_TO on test2 with the same cookie + + let events = instance.read_events().unwrap(); + assert_eq!(events.len(), 5); + + assert_eq!(events[0].mask, AddWatchFlags::IN_CREATE); + assert_eq!(events[0].name, Some(OsString::from("test"))); + + assert_eq!(events[1].mask, AddWatchFlags::IN_OPEN); + assert_eq!(events[1].name, Some(OsString::from("test"))); + + assert_eq!(events[2].mask, AddWatchFlags::IN_CLOSE_WRITE); + assert_eq!(events[2].name, Some(OsString::from("test"))); + + assert_eq!(events[3].mask, AddWatchFlags::IN_MOVED_FROM); + assert_eq!(events[3].name, Some(OsString::from("test"))); + + assert_eq!(events[4].mask, AddWatchFlags::IN_MOVED_TO); + assert_eq!(events[4].name, Some(OsString::from("test2"))); + + assert_eq!(events[3].cookie, events[4].cookie); +} diff --git a/test/sys/test_ioctl.rs b/test/sys/test_ioctl.rs new file mode 100644 index 0000000..40f60cf --- /dev/null +++ b/test/sys/test_ioctl.rs @@ -0,0 +1,376 @@ +#![allow(dead_code)] + +// Simple tests to ensure macro generated fns compile +ioctl_none_bad!(do_bad, 0x1234); +ioctl_read_bad!(do_bad_read, 0x1234, u16); +ioctl_write_int_bad!(do_bad_write_int, 0x1234); +ioctl_write_ptr_bad!(do_bad_write_ptr, 0x1234, u8); +ioctl_readwrite_bad!(do_bad_readwrite, 0x1234, u32); +ioctl_none!(do_none, 0, 0); +ioctl_read!(read_test, 0, 0, u32); +ioctl_write_int!(write_ptr_int, 0, 0); +ioctl_write_ptr!(write_ptr_u8, 0, 0, u8); +ioctl_write_ptr!(write_ptr_u32, 0, 0, u32); +ioctl_write_ptr!(write_ptr_u64, 0, 0, u64); +ioctl_readwrite!(readwrite_test, 0, 0, u64); +ioctl_read_buf!(readbuf_test, 0, 0, u32); +const SPI_IOC_MAGIC: u8 = b'k'; +const SPI_IOC_MESSAGE: u8 = 0; +ioctl_write_buf!(writebuf_test_consts, SPI_IOC_MAGIC, SPI_IOC_MESSAGE, u8); +ioctl_write_buf!(writebuf_test_u8, 0, 0, u8); +ioctl_write_buf!(writebuf_test_u32, 0, 0, u32); +ioctl_write_buf!(writebuf_test_u64, 0, 0, u64); +ioctl_readwrite_buf!(readwritebuf_test, 0, 0, u32); + +// See C code for source of values for op calculations (does NOT work for mips/powerpc): +// https://gist.github.com/posborne/83ea6880770a1aef332e +// +// TODO: Need a way to compute these constants at test time. Using precomputed +// values is fragile and needs to be maintained. + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod linux { + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + #[test] + fn test_op_none() { + if cfg!(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc", + target_arch = "powerpc64" + )) { + assert_eq!(request_code_none!(b'q', 10) as u32, 0x2000_710A); + assert_eq!(request_code_none!(b'a', 255) as u32, 0x2000_61FF); + } else { + assert_eq!(request_code_none!(b'q', 10) as u32, 0x0000_710A); + assert_eq!(request_code_none!(b'a', 255) as u32, 0x0000_61FF); + } + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + #[test] + fn test_op_write() { + if cfg!(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc", + target_arch = "powerpc64" + )) { + assert_eq!(request_code_write!(b'z', 10, 1) as u32, 0x8001_7A0A); + assert_eq!(request_code_write!(b'z', 10, 512) as u32, 0x8200_7A0A); + } else { + assert_eq!(request_code_write!(b'z', 10, 1) as u32, 0x4001_7A0A); + assert_eq!(request_code_write!(b'z', 10, 512) as u32, 0x4200_7A0A); + } + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_write_64() { + if cfg!(any(target_arch = "mips64", target_arch = "powerpc64")) { + assert_eq!( + request_code_write!(b'z', 10, 1u64 << 32) as u32, + 0x8000_7A0A + ); + } else { + assert_eq!( + request_code_write!(b'z', 10, 1u64 << 32) as u32, + 0x4000_7A0A + ); + } + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + #[test] + fn test_op_read() { + if cfg!(any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc", + target_arch = "powerpc64" + )) { + assert_eq!(request_code_read!(b'z', 10, 1) as u32, 0x4001_7A0A); + assert_eq!(request_code_read!(b'z', 10, 512) as u32, 0x4200_7A0A); + } else { + assert_eq!(request_code_read!(b'z', 10, 1) as u32, 0x8001_7A0A); + assert_eq!(request_code_read!(b'z', 10, 512) as u32, 0x8200_7A0A); + } + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_64() { + if cfg!(any(target_arch = "mips64", target_arch = "powerpc64")) { + assert_eq!( + request_code_read!(b'z', 10, 1u64 << 32) as u32, + 0x4000_7A0A + ); + } else { + assert_eq!( + request_code_read!(b'z', 10, 1u64 << 32) as u32, + 0x8000_7A0A + ); + } + } + + // The cast is not unnecessary on all platforms. + #[allow(clippy::unnecessary_cast)] + #[test] + fn test_op_read_write() { + assert_eq!(request_code_readwrite!(b'z', 10, 1) as u32, 0xC001_7A0A); + assert_eq!(request_code_readwrite!(b'z', 10, 512) as u32, 0xC200_7A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_write_64() { + assert_eq!( + request_code_readwrite!(b'z', 10, 1u64 << 32) as u32, + 0xC000_7A0A + ); + } +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +mod bsd { + #[test] + fn test_op_none() { + assert_eq!(request_code_none!(b'q', 10), 0x2000_710A); + assert_eq!(request_code_none!(b'a', 255), 0x2000_61FF); + } + + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[test] + fn test_op_write_int() { + assert_eq!(request_code_write_int!(b'v', 4), 0x2004_7604); + assert_eq!(request_code_write_int!(b'p', 2), 0x2004_7002); + } + + #[test] + fn test_op_write() { + assert_eq!(request_code_write!(b'z', 10, 1), 0x8001_7A0A); + assert_eq!(request_code_write!(b'z', 10, 512), 0x8200_7A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_write_64() { + assert_eq!(request_code_write!(b'z', 10, 1u64 << 32), 0x8000_7A0A); + } + + #[test] + fn test_op_read() { + assert_eq!(request_code_read!(b'z', 10, 1), 0x4001_7A0A); + assert_eq!(request_code_read!(b'z', 10, 512), 0x4200_7A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_64() { + assert_eq!(request_code_read!(b'z', 10, 1u64 << 32), 0x4000_7A0A); + } + + #[test] + fn test_op_read_write() { + assert_eq!(request_code_readwrite!(b'z', 10, 1), 0xC001_7A0A); + assert_eq!(request_code_readwrite!(b'z', 10, 512), 0xC200_7A0A); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn test_op_read_write_64() { + assert_eq!(request_code_readwrite!(b'z', 10, 1u64 << 32), 0xC000_7A0A); + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux_ioctls { + use std::mem; + use std::os::unix::io::AsRawFd; + + use libc::{termios, TCGETS, TCSBRK, TCSETS, TIOCNXCL}; + use tempfile::tempfile; + + use nix::errno::Errno; + + ioctl_none_bad!(tiocnxcl, TIOCNXCL); + #[test] + fn test_ioctl_none_bad() { + let file = tempfile().unwrap(); + let res = unsafe { tiocnxcl(file.as_raw_fd()) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } + + ioctl_read_bad!(tcgets, TCGETS, termios); + #[test] + fn test_ioctl_read_bad() { + let file = tempfile().unwrap(); + let mut termios = unsafe { mem::zeroed() }; + let res = unsafe { tcgets(file.as_raw_fd(), &mut termios) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } + + ioctl_write_int_bad!(tcsbrk, TCSBRK); + #[test] + fn test_ioctl_write_int_bad() { + let file = tempfile().unwrap(); + let res = unsafe { tcsbrk(file.as_raw_fd(), 0) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } + + ioctl_write_ptr_bad!(tcsets, TCSETS, termios); + #[test] + fn test_ioctl_write_ptr_bad() { + let file = tempfile().unwrap(); + let termios: termios = unsafe { mem::zeroed() }; + let res = unsafe { tcsets(file.as_raw_fd(), &termios) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } + + // FIXME: Find a suitable example for `ioctl_readwrite_bad` + + // From linux/videodev2.h + ioctl_none!(log_status, b'V', 70); + #[test] + fn test_ioctl_none() { + let file = tempfile().unwrap(); + let res = unsafe { log_status(file.as_raw_fd()) }; + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); + } + + #[repr(C)] + pub struct v4l2_audio { + index: u32, + name: [u8; 32], + capability: u32, + mode: u32, + reserved: [u32; 2], + } + + // From linux/videodev2.h + ioctl_write_ptr!(s_audio, b'V', 34, v4l2_audio); + #[test] + fn test_ioctl_write_ptr() { + let file = tempfile().unwrap(); + let data: v4l2_audio = unsafe { mem::zeroed() }; + let res = unsafe { s_audio(file.as_raw_fd(), &data) }; + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); + } + + // From linux/net/bluetooth/hci_sock.h + const HCI_IOC_MAGIC: u8 = b'H'; + const HCI_IOC_HCIDEVUP: u8 = 201; + ioctl_write_int!(hcidevup, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP); + #[test] + fn test_ioctl_write_int() { + let file = tempfile().unwrap(); + let res = unsafe { hcidevup(file.as_raw_fd(), 0) }; + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); + } + + // From linux/videodev2.h + ioctl_read!(g_audio, b'V', 33, v4l2_audio); + #[test] + fn test_ioctl_read() { + let file = tempfile().unwrap(); + let mut data: v4l2_audio = unsafe { mem::zeroed() }; + let res = unsafe { g_audio(file.as_raw_fd(), &mut data) }; + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); + } + + // From linux/videodev2.h + ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio); + #[test] + fn test_ioctl_readwrite() { + let file = tempfile().unwrap(); + let mut data: v4l2_audio = unsafe { mem::zeroed() }; + let res = unsafe { enum_audio(file.as_raw_fd(), &mut data) }; + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); + } + + // FIXME: Find a suitable example for `ioctl_read_buf`. + + #[repr(C)] + pub struct spi_ioc_transfer { + tx_buf: u64, + rx_buf: u64, + len: u32, + speed_hz: u32, + delay_usecs: u16, + bits_per_word: u8, + cs_change: u8, + tx_nbits: u8, + rx_nbits: u8, + pad: u16, + } + + // From linux/spi/spidev.h + ioctl_write_buf!( + spi_ioc_message, + super::SPI_IOC_MAGIC, + super::SPI_IOC_MESSAGE, + spi_ioc_transfer + ); + #[test] + fn test_ioctl_write_buf() { + let file = tempfile().unwrap(); + let data: [spi_ioc_transfer; 4] = unsafe { mem::zeroed() }; + let res = unsafe { spi_ioc_message(file.as_raw_fd(), &data[..]) }; + assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS)); + } + + // FIXME: Find a suitable example for `ioctl_readwrite_buf`. +} + +#[cfg(target_os = "freebsd")] +mod freebsd_ioctls { + use std::mem; + use std::os::unix::io::AsRawFd; + + use libc::termios; + use tempfile::tempfile; + + use nix::errno::Errno; + + // From sys/sys/ttycom.h + const TTY_IOC_MAGIC: u8 = b't'; + const TTY_IOC_TYPE_NXCL: u8 = 14; + const TTY_IOC_TYPE_GETA: u8 = 19; + const TTY_IOC_TYPE_SETA: u8 = 20; + + ioctl_none!(tiocnxcl, TTY_IOC_MAGIC, TTY_IOC_TYPE_NXCL); + #[test] + fn test_ioctl_none() { + let file = tempfile().unwrap(); + let res = unsafe { tiocnxcl(file.as_raw_fd()) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } + + ioctl_read!(tiocgeta, TTY_IOC_MAGIC, TTY_IOC_TYPE_GETA, termios); + #[test] + fn test_ioctl_read() { + let file = tempfile().unwrap(); + let mut termios = unsafe { mem::zeroed() }; + let res = unsafe { tiocgeta(file.as_raw_fd(), &mut termios) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } + + ioctl_write_ptr!(tiocseta, TTY_IOC_MAGIC, TTY_IOC_TYPE_SETA, termios); + #[test] + fn test_ioctl_write_ptr() { + let file = tempfile().unwrap(); + let termios: termios = unsafe { mem::zeroed() }; + let res = unsafe { tiocseta(file.as_raw_fd(), &termios) }; + assert_eq!(res, Err(Errno::ENOTTY)); + } +} diff --git a/test/sys/test_mman.rs b/test/sys/test_mman.rs new file mode 100644 index 0000000..e748427 --- /dev/null +++ b/test/sys/test_mman.rs @@ -0,0 +1,122 @@ +use nix::sys::mman::{mmap, MapFlags, ProtFlags}; +use std::num::NonZeroUsize; + +#[test] +fn test_mmap_anonymous() { + unsafe { + let ptr = mmap( + None, + NonZeroUsize::new(1).unwrap(), + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, + -1, + 0, + ) + .unwrap() as *mut u8; + assert_eq!(*ptr, 0x00u8); + *ptr = 0xffu8; + assert_eq!(*ptr, 0xffu8); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "netbsd"))] +fn test_mremap_grow() { + use nix::libc::{c_void, size_t}; + use nix::sys::mman::{mremap, MRemapFlags}; + + const ONE_K: size_t = 1024; + let one_k_non_zero = NonZeroUsize::new(ONE_K).unwrap(); + + let slice: &mut [u8] = unsafe { + let mem = mmap( + None, + one_k_non_zero, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + -1, + 0, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) + }; + assert_eq!(slice[ONE_K - 1], 0x00); + slice[ONE_K - 1] = 0xFF; + assert_eq!(slice[ONE_K - 1], 0xFF); + + let slice: &mut [u8] = unsafe { + #[cfg(target_os = "linux")] + let mem = mremap( + slice.as_mut_ptr() as *mut c_void, + ONE_K, + 10 * ONE_K, + MRemapFlags::MREMAP_MAYMOVE, + None, + ) + .unwrap(); + #[cfg(target_os = "netbsd")] + let mem = mremap( + slice.as_mut_ptr() as *mut c_void, + ONE_K, + 10 * ONE_K, + MRemapFlags::MAP_REMAPDUP, + None, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem as *mut u8, 10 * ONE_K) + }; + + // The first KB should still have the old data in it. + assert_eq!(slice[ONE_K - 1], 0xFF); + + // The additional range should be zero-init'd and accessible. + assert_eq!(slice[10 * ONE_K - 1], 0x00); + slice[10 * ONE_K - 1] = 0xFF; + assert_eq!(slice[10 * ONE_K - 1], 0xFF); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "netbsd"))] +// Segfaults for unknown reasons under QEMU for 32-bit targets +#[cfg_attr(all(target_pointer_width = "32", qemu), ignore)] +fn test_mremap_shrink() { + use nix::libc::{c_void, size_t}; + use nix::sys::mman::{mremap, MRemapFlags}; + use std::num::NonZeroUsize; + + const ONE_K: size_t = 1024; + let ten_one_k = NonZeroUsize::new(10 * ONE_K).unwrap(); + let slice: &mut [u8] = unsafe { + let mem = mmap( + None, + ten_one_k, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + -1, + 0, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) + }; + assert_eq!(slice[ONE_K - 1], 0x00); + slice[ONE_K - 1] = 0xFF; + assert_eq!(slice[ONE_K - 1], 0xFF); + + let slice: &mut [u8] = unsafe { + let mem = mremap( + slice.as_mut_ptr() as *mut c_void, + ten_one_k.into(), + ONE_K, + MRemapFlags::empty(), + None, + ) + .unwrap(); + // Since we didn't supply MREMAP_MAYMOVE, the address should be the + // same. + assert_eq!(mem, slice.as_mut_ptr() as *mut c_void); + std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) + }; + + // The first KB should still be accessible and have the old data in it. + assert_eq!(slice[ONE_K - 1], 0xFF); +} diff --git a/test/sys/test_pthread.rs b/test/sys/test_pthread.rs new file mode 100644 index 0000000..ce048ba --- /dev/null +++ b/test/sys/test_pthread.rs @@ -0,0 +1,22 @@ +use nix::sys::pthread::*; + +#[cfg(any(target_env = "musl", target_os = "redox"))] +#[test] +fn test_pthread_self() { + let tid = pthread_self(); + assert!(!tid.is_null()); +} + +#[cfg(not(any(target_env = "musl", target_os = "redox")))] +#[test] +fn test_pthread_self() { + let tid = pthread_self(); + assert_ne!(tid, 0); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_pthread_kill_none() { + pthread_kill(pthread_self(), None) + .expect("Should be able to send signal to my thread."); +} diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs new file mode 100644 index 0000000..530560f --- /dev/null +++ b/test/sys/test_ptrace.rs @@ -0,0 +1,275 @@ +#[cfg(all( + target_os = "linux", + any(target_arch = "x86_64", target_arch = "x86"), + target_env = "gnu" +))] +use memoffset::offset_of; +use nix::errno::Errno; +use nix::sys::ptrace; +#[cfg(any(target_os = "android", target_os = "linux"))] +use nix::sys::ptrace::Options; +use nix::unistd::getpid; + +#[cfg(any(target_os = "android", target_os = "linux"))] +use std::mem; + +use crate::*; + +#[test] +fn test_ptrace() { + // Just make sure ptrace can be called at all, for now. + // FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS + require_capability!("test_ptrace", CAP_SYS_PTRACE); + let err = ptrace::attach(getpid()).unwrap_err(); + assert!( + err == Errno::EPERM || err == Errno::EINVAL || err == Errno::ENOSYS + ); +} + +// Just make sure ptrace_setoptions can be called at all, for now. +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptrace_setoptions() { + require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE); + let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD) + .unwrap_err(); + assert_ne!(err, Errno::EOPNOTSUPP); +} + +// Just make sure ptrace_getevent can be called at all, for now. +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptrace_getevent() { + require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE); + let err = ptrace::getevent(getpid()).unwrap_err(); + assert_ne!(err, Errno::EOPNOTSUPP); +} + +// Just make sure ptrace_getsiginfo can be called at all, for now. +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptrace_getsiginfo() { + require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE); + if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) { + panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!"); + } +} + +// Just make sure ptrace_setsiginfo can be called at all, for now. +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptrace_setsiginfo() { + require_capability!("test_ptrace_setsiginfo", CAP_SYS_PTRACE); + let siginfo = unsafe { mem::zeroed() }; + if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) { + panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!"); + } +} + +#[test] +fn test_ptrace_cont() { + use nix::sys::ptrace; + use nix::sys::signal::{raise, Signal}; + use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + + require_capability!("test_ptrace_cont", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + // FIXME: qemu-user doesn't implement ptrace on all architectures + // and returns ENOSYS in this case. + // We (ab)use this behavior to detect the affected platforms + // and skip the test then. + // On valid platforms the ptrace call should return Errno::EPERM, this + // is already tested by `test_ptrace`. + let err = ptrace::attach(getpid()).unwrap_err(); + if err == Errno::ENOSYS { + return; + } + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + // As recommended by ptrace(2), raise SIGTRAP to pause the child + // until the parent is ready to continue + loop { + raise(Signal::SIGTRAP).unwrap(); + } + } + Parent { child } => { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)) + ); + ptrace::cont(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)) + ); + ptrace::cont(child, Some(Signal::SIGKILL)).unwrap(); + match waitpid(child, None) { + Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) + if pid == child => + { + // FIXME It's been observed on some systems (apple) the + // tracee may not be killed but remain as a zombie process + // affecting other wait based tests. Add an extra kill just + // to make sure there are no zombies. + let _ = waitpid(child, Some(WaitPidFlag::WNOHANG)); + while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() { + let _ = waitpid(child, Some(WaitPidFlag::WNOHANG)); + } + } + _ => panic!("The process should have been killed"), + } + } + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_ptrace_interrupt() { + use nix::sys::ptrace; + use nix::sys::signal::Signal; + use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + use std::thread::sleep; + use std::time::Duration; + + require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => loop { + sleep(Duration::from_millis(1000)); + }, + Parent { child } => { + ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD) + .unwrap(); + ptrace::interrupt(child).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128)) + ); + ptrace::syscall(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceSyscall(child)) + ); + ptrace::detach(child, Some(Signal::SIGKILL)).unwrap(); + match waitpid(child, None) { + Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) + if pid == child => + { + let _ = waitpid(child, Some(WaitPidFlag::WNOHANG)); + while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() { + let _ = waitpid(child, Some(WaitPidFlag::WNOHANG)); + } + } + _ => panic!("The process should have been killed"), + } + } + } +} + +// ptrace::{setoptions, getregs} are only available in these platforms +#[cfg(all( + target_os = "linux", + any(target_arch = "x86_64", target_arch = "x86"), + target_env = "gnu" +))] +#[test] +fn test_ptrace_syscall() { + use nix::sys::ptrace; + use nix::sys::signal::kill; + use nix::sys::signal::Signal; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::getpid; + use nix::unistd::ForkResult::*; + + require_capability!("test_ptrace_syscall", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + // first sigstop until parent is ready to continue + let pid = getpid(); + kill(pid, Signal::SIGSTOP).unwrap(); + kill(pid, Signal::SIGTERM).unwrap(); + unsafe { + ::libc::_exit(0); + } + } + + Parent { child } => { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)) + ); + + // set this option to recognize syscall-stops + ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD) + .unwrap(); + + #[cfg(target_arch = "x86_64")] + let get_syscall_id = + || ptrace::getregs(child).unwrap().orig_rax as libc::c_long; + + #[cfg(target_arch = "x86")] + let get_syscall_id = + || ptrace::getregs(child).unwrap().orig_eax as libc::c_long; + + // this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`. + #[cfg(target_arch = "x86_64")] + let rax_offset = offset_of!(libc::user_regs_struct, orig_rax); + #[cfg(target_arch = "x86")] + let rax_offset = offset_of!(libc::user_regs_struct, orig_eax); + + let get_syscall_from_user_area = || { + // Find the offset of `user.regs.rax` (or `user.regs.eax` for x86) + let rax_offset = offset_of!(libc::user, regs) + rax_offset; + ptrace::read_user(child, rax_offset as _).unwrap() + as libc::c_long + }; + + // kill entry + ptrace::syscall(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceSyscall(child)) + ); + assert_eq!(get_syscall_id(), ::libc::SYS_kill); + assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill); + + // kill exit + ptrace::syscall(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceSyscall(child)) + ); + assert_eq!(get_syscall_id(), ::libc::SYS_kill); + assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill); + + // receive signal + ptrace::syscall(child, None).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGTERM)) + ); + + // inject signal + ptrace::syscall(child, Signal::SIGTERM).unwrap(); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)) + ); + } + } +} diff --git a/test/sys/test_select.rs b/test/sys/test_select.rs new file mode 100644 index 0000000..40bda4d --- /dev/null +++ b/test/sys/test_select.rs @@ -0,0 +1,81 @@ +use nix::sys::select::*; +use nix::sys::signal::SigSet; +use nix::sys::time::{TimeSpec, TimeValLike}; +use nix::unistd::{pipe, write}; + +#[test] +pub fn test_pselect() { + let _mtx = crate::SIGNAL_MTX.lock(); + + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let timeout = TimeSpec::seconds(10); + let sigmask = SigSet::empty(); + assert_eq!( + 1, + pselect(None, &mut fd_set, None, None, &timeout, &sigmask).unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); +} + +#[test] +pub fn test_pselect_nfds2() { + let (r1, w1) = pipe().unwrap(); + write(w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + + let mut fd_set = FdSet::new(); + fd_set.insert(r1); + fd_set.insert(r2); + + let timeout = TimeSpec::seconds(10); + assert_eq!( + 1, + pselect( + ::std::cmp::max(r1, r2) + 1, + &mut fd_set, + None, + None, + &timeout, + None + ) + .unwrap() + ); + assert!(fd_set.contains(r1)); + assert!(!fd_set.contains(r2)); +} + +macro_rules! generate_fdset_bad_fd_tests { + ($fd:expr, $($method:ident),* $(,)?) => { + $( + #[test] + #[should_panic] + fn $method() { + FdSet::new().$method($fd); + } + )* + } +} + +mod test_fdset_negative_fd { + use super::*; + generate_fdset_bad_fd_tests!(-1, insert, remove, contains); +} + +mod test_fdset_too_large_fd { + use super::*; + use std::convert::TryInto; + generate_fdset_bad_fd_tests!( + FD_SETSIZE.try_into().unwrap(), + insert, + remove, + contains, + ); +} diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs new file mode 100644 index 0000000..3ad14f4 --- /dev/null +++ b/test/sys/test_signal.rs @@ -0,0 +1,147 @@ +#[cfg(not(target_os = "redox"))] +use nix::errno::Errno; +use nix::sys::signal::*; +use nix::unistd::*; +use std::convert::TryFrom; +use std::sync::atomic::{AtomicBool, Ordering}; + +#[test] +fn test_kill_none() { + kill(getpid(), None).expect("Should be able to send signal to myself."); +} + +#[test] +#[cfg(not(target_os = "fuchsia"))] +fn test_killpg_none() { + killpg(getpgrp(), None) + .expect("Should be able to send signal to my process group."); +} + +#[test] +fn test_old_sigaction_flags() { + let _m = crate::SIGNAL_MTX.lock(); + + extern "C" fn handler(_: ::libc::c_int) {} + let act = SigAction::new( + SigHandler::Handler(handler), + SaFlags::empty(), + SigSet::empty(), + ); + let oact = unsafe { sigaction(SIGINT, &act) }.unwrap(); + let _flags = oact.flags(); + let oact = unsafe { sigaction(SIGINT, &act) }.unwrap(); + let _flags = oact.flags(); +} + +#[test] +fn test_sigprocmask_noop() { + sigprocmask(SigmaskHow::SIG_BLOCK, None, None) + .expect("this should be an effective noop"); +} + +#[test] +fn test_sigprocmask() { + let _m = crate::SIGNAL_MTX.lock(); + + // This needs to be a signal that rust doesn't use in the test harness. + const SIGNAL: Signal = Signal::SIGCHLD; + + let mut old_signal_set = SigSet::empty(); + sigprocmask(SigmaskHow::SIG_BLOCK, None, Some(&mut old_signal_set)) + .expect("expect to be able to retrieve old signals"); + + // Make sure the old set doesn't contain the signal, otherwise the following + // test don't make sense. + assert!( + !old_signal_set.contains(SIGNAL), + "the {:?} signal is already blocked, please change to a \ + different one", + SIGNAL + ); + + // Now block the signal. + let mut signal_set = SigSet::empty(); + signal_set.add(SIGNAL); + sigprocmask(SigmaskHow::SIG_BLOCK, Some(&signal_set), None) + .expect("expect to be able to block signals"); + + // And test it again, to make sure the change was effective. + old_signal_set.clear(); + sigprocmask(SigmaskHow::SIG_BLOCK, None, Some(&mut old_signal_set)) + .expect("expect to be able to retrieve old signals"); + assert!( + old_signal_set.contains(SIGNAL), + "expected the {:?} to be blocked", + SIGNAL + ); + + // Reset the signal. + sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&signal_set), None) + .expect("expect to be able to block signals"); +} + +lazy_static! { + static ref SIGNALED: AtomicBool = AtomicBool::new(false); +} + +extern "C" fn test_sigaction_handler(signal: libc::c_int) { + let signal = Signal::try_from(signal).unwrap(); + SIGNALED.store(signal == Signal::SIGINT, Ordering::Relaxed); +} + +#[cfg(not(target_os = "redox"))] +extern "C" fn test_sigaction_action( + _: libc::c_int, + _: *mut libc::siginfo_t, + _: *mut libc::c_void, +) { +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_signal_sigaction() { + let _m = crate::SIGNAL_MTX.lock(); + + let action_handler = SigHandler::SigAction(test_sigaction_action); + assert_eq!( + unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(), + Errno::ENOTSUP + ); +} + +#[test] +fn test_signal() { + let _m = crate::SIGNAL_MTX.lock(); + + unsafe { signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap(); + raise(Signal::SIGINT).unwrap(); + assert_eq!( + unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), + SigHandler::SigIgn + ); + + let handler = SigHandler::Handler(test_sigaction_handler); + assert_eq!( + unsafe { signal(Signal::SIGINT, handler) }.unwrap(), + SigHandler::SigDfl + ); + raise(Signal::SIGINT).unwrap(); + assert!(SIGNALED.load(Ordering::Relaxed)); + + #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] + assert_eq!( + unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), + handler + ); + + // System V based OSes (e.g. illumos and Solaris) always resets the + // disposition to SIG_DFL prior to calling the signal handler + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + assert_eq!( + unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), + SigHandler::SigDfl + ); + + // Restore default signal handler + unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(); +} diff --git a/test/sys/test_signalfd.rs b/test/sys/test_signalfd.rs new file mode 100644 index 0000000..87153c9 --- /dev/null +++ b/test/sys/test_signalfd.rs @@ -0,0 +1,27 @@ +use std::convert::TryFrom; + +#[test] +fn test_signalfd() { + use nix::sys::signal::{self, raise, SigSet, Signal}; + use nix::sys::signalfd::SignalFd; + + // Grab the mutex for altering signals so we don't interfere with other tests. + let _m = crate::SIGNAL_MTX.lock(); + + // Block the SIGUSR1 signal from automatic processing for this thread + let mut mask = SigSet::empty(); + mask.add(signal::SIGUSR1); + mask.thread_block().unwrap(); + + let mut fd = SignalFd::new(&mask).unwrap(); + + // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill` + // because `kill` with `getpid` isn't correct during multi-threaded execution like during a + // cargo test session. Instead use `raise` which does the correct thing by default. + raise(signal::SIGUSR1).expect("Error: raise(SIGUSR1) failed"); + + // And now catch that same signal. + let res = fd.read_signal().unwrap().unwrap(); + let signo = Signal::try_from(res.ssi_signo as i32).unwrap(); + assert_eq!(signo, signal::SIGUSR1); +} diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs new file mode 100644 index 0000000..5adc77e --- /dev/null +++ b/test/sys/test_socket.rs @@ -0,0 +1,2628 @@ +#[cfg(any(target_os = "linux", target_os = "android"))] +use crate::*; +use libc::{c_char, sockaddr_storage}; +#[allow(deprecated)] +use nix::sys::socket::InetAddr; +use nix::sys::socket::{ + getsockname, sockaddr, sockaddr_in6, AddressFamily, UnixAddr, +}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::mem::{self, MaybeUninit}; +use std::net::{self, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::os::unix::io::RawFd; +use std::path::Path; +use std::slice; +use std::str::FromStr; + +#[allow(deprecated)] +#[test] +pub fn test_inetv4_addr_to_sock_addr() { + let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap(); + let addr = InetAddr::from_std(&actual); + + match addr { + InetAddr::V4(addr) => { + let ip: u32 = 0x7f00_0001; + let port: u16 = 3000; + let saddr = addr.sin_addr.s_addr; + + assert_eq!(saddr, ip.to_be()); + assert_eq!(addr.sin_port, port.to_be()); + } + _ => panic!("nope"), + } + + assert_eq!(addr.to_string(), "127.0.0.1:3000"); + + let inet = addr.to_std(); + assert_eq!(actual, inet); +} + +#[allow(deprecated)] +#[test] +pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() { + use nix::sys::socket::{sockaddr_storage_to_addr, SockAddr}; + + let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap(); + let addr = InetAddr::from_std(&actual); + let sockaddr = SockAddr::new_inet(addr); + + let (storage, ffi_size) = { + let mut storage = MaybeUninit::::zeroed(); + let storage_ptr = storage.as_mut_ptr().cast::(); + let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); + assert_eq!(mem::size_of::(), ffi_size as usize); + unsafe { + storage_ptr.copy_from_nonoverlapping(ffi_ptr as *const sockaddr, 1); + (storage.assume_init(), ffi_size) + } + }; + + let from_storage = + sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); + assert_eq!(from_storage, sockaddr); + let from_storage = + sockaddr_storage_to_addr(&storage, mem::size_of::()) + .unwrap(); + assert_eq!(from_storage, sockaddr); +} + +#[cfg(any(target_os = "linux"))] +#[cfg_attr(qemu, ignore)] +#[test] +pub fn test_timestamping() { + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socket, sockopt::Timestamping, + ControlMessageOwned, MsgFlags, SockFlag, SockType, SockaddrIn, + TimestampingFlag, + }; + use std::io::{IoSlice, IoSliceMut}; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + nix::sys::socket::bind(rsock, &sock_addr).unwrap(); + + setsockopt(rsock, Timestamping, &TimestampingFlag::all()).unwrap(); + + let sbuf = [0u8; 2048]; + let mut rbuf = [0u8; 2048]; + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + let mut iov2 = [IoSliceMut::new(&mut rbuf)]; + + let mut cmsg = cmsg_space!(nix::sys::socket::Timestamps); + sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap(); + let recv = recvmsg::<()>(rsock, &mut iov2, Some(&mut cmsg), flags).unwrap(); + + let mut ts = None; + for c in recv.cmsgs() { + if let ControlMessageOwned::ScmTimestampsns(timestamps) = c { + ts = Some(timestamps.system); + } + } + let ts = ts.expect("ScmTimestampns is present"); + let sys_time = + ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_REALTIME) + .unwrap(); + let diff = if ts > sys_time { + ts - sys_time + } else { + sys_time - ts + }; + assert!(std::time::Duration::from(diff).as_secs() < 60); +} + +#[allow(deprecated)] +#[test] +pub fn test_inetv6_addr_roundtrip_sockaddr_storage_to_addr() { + use nix::sys::socket::{sockaddr_storage_to_addr, SockAddr}; + + let port: u16 = 3000; + let flowinfo: u32 = 1; + let scope_id: u32 = 2; + let ip: Ipv6Addr = "fe80::1".parse().unwrap(); + + let actual = + SocketAddr::V6(SocketAddrV6::new(ip, port, flowinfo, scope_id)); + let addr = InetAddr::from_std(&actual); + let sockaddr = SockAddr::new_inet(addr); + + let (storage, ffi_size) = { + let mut storage = MaybeUninit::::zeroed(); + let storage_ptr = storage.as_mut_ptr().cast::(); + let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); + assert_eq!(mem::size_of::(), ffi_size as usize); + unsafe { + storage_ptr.copy_from_nonoverlapping( + (ffi_ptr as *const sockaddr).cast::(), + 1, + ); + (storage.assume_init(), ffi_size) + } + }; + + let from_storage = + sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); + assert_eq!(from_storage, sockaddr); + let from_storage = + sockaddr_storage_to_addr(&storage, mem::size_of::()) + .unwrap(); + assert_eq!(from_storage, sockaddr); +} + +#[test] +pub fn test_path_to_sock_addr() { + let path = "/foo/bar"; + let actual = Path::new(path); + let addr = UnixAddr::new(actual).unwrap(); + + let expect: &[c_char] = unsafe { + slice::from_raw_parts(path.as_ptr() as *const c_char, path.len()) + }; + assert_eq!(unsafe { &(*addr.as_ptr()).sun_path[..8] }, expect); + + assert_eq!(addr.path(), Some(actual)); +} + +fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() +} + +#[test] +pub fn test_addr_equality_path() { + let path = "/foo/bar"; + let actual = Path::new(path); + let addr1 = UnixAddr::new(actual).unwrap(); + let mut addr2 = addr1; + + unsafe { (*addr2.as_mut_ptr()).sun_path[10] = 127 }; + + assert_eq!(addr1, addr2); + assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +pub fn test_abstract_sun_path_too_long() { + let name = String::from("nix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0testttttnix\0abstract\0test\0make\0sure\0this\0is\0long\0enough"); + let addr = UnixAddr::new_abstract(name.as_bytes()); + addr.expect_err("assertion failed"); +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +pub fn test_addr_equality_abstract() { + let name = String::from("nix\0abstract\0test"); + let addr1 = UnixAddr::new_abstract(name.as_bytes()).unwrap(); + let mut addr2 = addr1; + + assert_eq!(addr1, addr2); + assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); + + unsafe { (*addr2.as_mut_ptr()).sun_path[17] = 127 }; + assert_ne!(addr1, addr2); + assert_ne!(calculate_hash(&addr1), calculate_hash(&addr2)); +} + +// Test getting/setting abstract addresses (without unix socket creation) +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +pub fn test_abstract_uds_addr() { + let empty = String::new(); + let addr = UnixAddr::new_abstract(empty.as_bytes()).unwrap(); + let sun_path: [u8; 0] = []; + assert_eq!(addr.as_abstract(), Some(&sun_path[..])); + + let name = String::from("nix\0abstract\0test"); + let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap(); + let sun_path = [ + 110u8, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101, + 115, 116, + ]; + assert_eq!(addr.as_abstract(), Some(&sun_path[..])); + assert_eq!(addr.path(), None); + + // Internally, name is null-prefixed (abstract namespace) + assert_eq!(unsafe { (*addr.as_ptr()).sun_path[0] }, 0); +} + +// Test getting an unnamed address (without unix socket creation) +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +pub fn test_unnamed_uds_addr() { + use crate::nix::sys::socket::SockaddrLike; + + let addr = UnixAddr::new_unnamed(); + + assert!(addr.is_unnamed()); + assert_eq!(addr.len(), 2); + assert!(addr.path().is_none()); + assert_eq!(addr.path_len(), 0); + + assert!(addr.as_abstract().is_none()); +} + +#[test] +pub fn test_getsockname() { + use nix::sys::socket::bind; + use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; + + let tempdir = tempfile::tempdir().unwrap(); + let sockname = tempdir.path().join("sock"); + let sock = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + let sockaddr = UnixAddr::new(&sockname).unwrap(); + bind(sock, &sockaddr).expect("bind failed"); + assert_eq!(sockaddr, getsockname(sock).expect("getsockname failed")); +} + +#[test] +pub fn test_socketpair() { + use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType}; + use nix::unistd::{read, write}; + + let (fd1, fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + write(fd1, b"hello").unwrap(); + let mut buf = [0; 5]; + read(fd2, &mut buf).unwrap(); + + assert_eq!(&buf[..], b"hello"); +} + +#[test] +pub fn test_std_conversions() { + use nix::sys::socket::*; + + let std_sa = SocketAddrV4::from_str("127.0.0.1:6789").unwrap(); + let sock_addr = SockaddrIn::from(std_sa); + assert_eq!(std_sa, sock_addr.into()); + + let std_sa = SocketAddrV6::from_str("[::1]:6000").unwrap(); + let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa); + assert_eq!(std_sa, sock_addr.into()); +} + +mod recvfrom { + use super::*; + use nix::sys::socket::*; + use nix::{errno::Errno, Result}; + use std::thread; + + const MSG: &[u8] = b"Hello, World!"; + + fn sendrecv( + rsock: RawFd, + ssock: RawFd, + f_send: Fs, + mut f_recv: Fr, + ) -> Option + where + Fs: Fn(RawFd, &[u8], MsgFlags) -> Result + Send + 'static, + Fr: FnMut(usize, Option), + { + let mut buf: [u8; 13] = [0u8; 13]; + let mut l = 0; + let mut from = None; + + let send_thread = thread::spawn(move || { + let mut l = 0; + while l < std::mem::size_of_val(MSG) { + l += f_send(ssock, &MSG[l..], MsgFlags::empty()).unwrap(); + } + }); + + while l < std::mem::size_of_val(MSG) { + let (len, from_) = recvfrom(rsock, &mut buf[l..]).unwrap(); + f_recv(len, from_); + from = from_; + l += len; + } + assert_eq!(&buf, MSG); + send_thread.join().unwrap(); + from + } + + #[test] + pub fn stream() { + let (fd2, fd1) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + // Ignore from for stream sockets + let _ = sendrecv(fd1, fd2, send, |_, _| {}); + } + + #[test] + pub fn udp() { + let std_sa = SocketAddrV4::from_str("127.0.0.1:6789").unwrap(); + let sock_addr = SockaddrIn::from(std_sa); + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + bind(rsock, &sock_addr).unwrap(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + let from = sendrecv( + rsock, + ssock, + move |s, m, flags| sendto(s, m, &sock_addr, flags), + |_, _| {}, + ); + // UDP sockets should set the from address + assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); + } + + #[cfg(target_os = "linux")] + mod udp_offload { + use super::*; + use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment}; + use std::io::IoSlice; + + #[test] + // Disable the test under emulation because it fails in Cirrus-CI. Lack + // of QEMU support is suspected. + #[cfg_attr(qemu, ignore)] + pub fn gso() { + require_kernel_version!(udp_offload::gso, ">= 4.18"); + + // In this test, we send the data and provide a GSO segment size. + // Since we are sending the buffer of size 13, six UDP packets + // with size 2 and two UDP packet with size 1 will be sent. + let segment_size: u16 = 2; + + let sock_addr = SockaddrIn::new(127, 0, 0, 1, 6791); + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + setsockopt(rsock, UdpGsoSegment, &(segment_size as _)) + .expect("setsockopt UDP_SEGMENT failed"); + + bind(rsock, &sock_addr).unwrap(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let mut num_packets_received: i32 = 0; + + sendrecv( + rsock, + ssock, + move |s, m, flags| { + let iov = [IoSlice::new(m)]; + let cmsg = ControlMessage::UdpGsoSegments(&segment_size); + sendmsg(s, &iov, &[cmsg], flags, Some(&sock_addr)) + }, + { + let num_packets_received_ref = &mut num_packets_received; + + move |len, _| { + // check that we receive UDP packets with payload size + // less or equal to segment size + assert!(len <= segment_size as usize); + *num_packets_received_ref += 1; + } + }, + ); + + // Buffer size is 13, we will receive six packets of size 2, + // and one packet of size 1. + assert_eq!(7, num_packets_received); + } + + #[test] + // Disable the test on emulated platforms because it fails in Cirrus-CI. + // Lack of QEMU support is suspected. + #[cfg_attr(qemu, ignore)] + pub fn gro() { + require_kernel_version!(udp_offload::gro, ">= 5.3"); + + // It's hard to guarantee receiving GRO packets. Just checking + // that `setsockopt` doesn't fail with error + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + setsockopt(rsock, UdpGroSegment, &true) + .expect("setsockopt UDP_GRO failed"); + } + } + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + ))] + #[test] + pub fn udp_sendmmsg() { + use std::io::IoSlice; + + let std_sa = SocketAddrV4::from_str("127.0.0.1:6793").unwrap(); + let std_sa2 = SocketAddrV4::from_str("127.0.0.1:6794").unwrap(); + let sock_addr = SockaddrIn::from(std_sa); + let sock_addr2 = SockaddrIn::from(std_sa2); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + bind(rsock, &sock_addr).unwrap(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let from = sendrecv( + rsock, + ssock, + move |s, m, flags| { + let batch_size = 15; + let mut iovs = Vec::with_capacity(1 + batch_size); + let mut addrs = Vec::with_capacity(1 + batch_size); + let mut data = MultiHeaders::preallocate(1 + batch_size, None); + let iov = IoSlice::new(m); + // first chunk: + iovs.push([iov]); + addrs.push(Some(sock_addr)); + + for _ in 0..batch_size { + iovs.push([iov]); + addrs.push(Some(sock_addr2)); + } + + let res = sendmmsg(s, &mut data, &iovs, addrs, [], flags)?; + let mut sent_messages = 0; + let mut sent_bytes = 0; + for item in res { + sent_messages += 1; + sent_bytes += item.bytes; + } + // + assert_eq!(sent_messages, iovs.len()); + assert_eq!(sent_bytes, sent_messages * m.len()); + Ok(sent_messages) + }, + |_, _| {}, + ); + // UDP sockets should set the from address + assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); + } + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + ))] + #[test] + pub fn udp_recvmmsg() { + use nix::sys::socket::{recvmmsg, MsgFlags}; + use std::io::IoSliceMut; + + const NUM_MESSAGES_SENT: usize = 2; + const DATA: [u8; 2] = [1, 2]; + + let inet_addr = SocketAddrV4::from_str("127.0.0.1:6798").unwrap(); + let sock_addr = SockaddrIn::from(inet_addr); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + bind(rsock, &sock_addr).unwrap(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let send_thread = thread::spawn(move || { + for _ in 0..NUM_MESSAGES_SENT { + sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty()) + .unwrap(); + } + }); + + let mut msgs = std::collections::LinkedList::new(); + + // Buffers to receive exactly `NUM_MESSAGES_SENT` messages + let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT]; + msgs.extend( + receive_buffers + .iter_mut() + .map(|buf| [IoSliceMut::new(&mut buf[..])]), + ); + + let mut data = + MultiHeaders::::preallocate(msgs.len(), None); + + let res: Vec> = + recvmmsg(rsock, &mut data, msgs.iter(), MsgFlags::empty(), None) + .expect("recvmmsg") + .collect(); + assert_eq!(res.len(), DATA.len()); + + for RecvMsg { address, bytes, .. } in res.into_iter() { + assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap()); + assert_eq!(DATA.len(), bytes); + } + + for buf in &receive_buffers { + assert_eq!(&buf[..DATA.len()], DATA); + } + + send_thread.join().unwrap(); + } + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + ))] + #[test] + pub fn udp_recvmmsg_dontwait_short_read() { + use nix::sys::socket::{recvmmsg, MsgFlags}; + use std::io::IoSliceMut; + + const NUM_MESSAGES_SENT: usize = 2; + const DATA: [u8; 4] = [1, 2, 3, 4]; + + let inet_addr = SocketAddrV4::from_str("127.0.0.1:6799").unwrap(); + let sock_addr = SockaddrIn::from(inet_addr); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + bind(rsock, &sock_addr).unwrap(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let send_thread = thread::spawn(move || { + for _ in 0..NUM_MESSAGES_SENT { + sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty()) + .unwrap(); + } + }); + // Ensure we've sent all the messages before continuing so `recvmmsg` + // will return right away + send_thread.join().unwrap(); + + let mut msgs = std::collections::LinkedList::new(); + + // Buffers to receive >`NUM_MESSAGES_SENT` messages to ensure `recvmmsg` + // will return when there are fewer than requested messages in the + // kernel buffers when using `MSG_DONTWAIT`. + let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT + 2]; + msgs.extend( + receive_buffers + .iter_mut() + .map(|buf| [IoSliceMut::new(&mut buf[..])]), + ); + + let mut data = MultiHeaders::::preallocate( + NUM_MESSAGES_SENT + 2, + None, + ); + + let res: Vec> = recvmmsg( + rsock, + &mut data, + msgs.iter(), + MsgFlags::MSG_DONTWAIT, + None, + ) + .expect("recvmmsg") + .collect(); + assert_eq!(res.len(), NUM_MESSAGES_SENT); + + for RecvMsg { address, bytes, .. } in res.into_iter() { + assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap()); + assert_eq!(DATA.len(), bytes); + } + + for buf in &receive_buffers[..NUM_MESSAGES_SENT] { + assert_eq!(&buf[..DATA.len()], DATA); + } + } + + #[test] + pub fn udp_inet6() { + let addr = std::net::Ipv6Addr::from_str("::1").unwrap(); + let rport = 6789; + let rstd_sa = SocketAddrV6::new(addr, rport, 0, 0); + let raddr = SockaddrIn6::from(rstd_sa); + let sport = 6790; + let sstd_sa = SocketAddrV6::new(addr, sport, 0, 0); + let saddr = SockaddrIn6::from(sstd_sa); + let rsock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + match bind(rsock, &raddr) { + Err(Errno::EADDRNOTAVAIL) => { + println!("IPv6 not available, skipping test."); + return; + } + Err(e) => panic!("bind: {}", e), + Ok(()) => (), + } + let ssock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + bind(ssock, &saddr).unwrap(); + let from = sendrecv( + rsock, + ssock, + move |s, m, flags| sendto(s, m, &raddr, flags), + |_, _| {}, + ); + assert_eq!(AddressFamily::Inet6, from.unwrap().family().unwrap()); + let osent_addr = from.unwrap(); + let sent_addr = osent_addr.as_sockaddr_in6().unwrap(); + assert_eq!(sent_addr.ip(), addr); + assert_eq!(sent_addr.port(), sport); + } +} + +// Test error handling of our recvmsg wrapper +#[test] +pub fn test_recvmsg_ebadf() { + use nix::errno::Errno; + use nix::sys::socket::{recvmsg, MsgFlags}; + use std::io::IoSliceMut; + + let mut buf = [0u8; 5]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + + let fd = -1; // Bad file descriptor + let r = recvmsg::<()>(fd, &mut iov, None, MsgFlags::empty()); + + assert_eq!(r.err().unwrap(), Errno::EBADF); +} + +// Disable the test on emulated platforms due to a bug in QEMU versions < +// 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 +#[cfg_attr(qemu, ignore)] +#[test] +pub fn test_scm_rights() { + use nix::sys::socket::{ + recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage, + ControlMessageOwned, MsgFlags, SockFlag, SockType, + }; + use nix::unistd::{close, pipe, read, write}; + use std::io::{IoSlice, IoSliceMut}; + + let (fd1, fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let (r, w) = pipe().unwrap(); + let mut received_r: Option = None; + + { + let iov = [IoSlice::new(b"hello")]; + let fds = [r]; + let cmsg = ControlMessage::ScmRights(&fds); + assert_eq!( + sendmsg::<()>(fd1, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(), + 5 + ); + close(r).unwrap(); + close(fd1).unwrap(); + } + + { + let mut buf = [0u8; 5]; + + let mut iov = [IoSliceMut::new(&mut buf[..])]; + let mut cmsgspace = cmsg_space!([RawFd; 1]); + let msg = recvmsg::<()>( + fd2, + &mut iov, + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); + + for cmsg in msg.cmsgs() { + if let ControlMessageOwned::ScmRights(fd) = cmsg { + assert_eq!(received_r, None); + assert_eq!(fd.len(), 1); + received_r = Some(fd[0]); + } else { + panic!("unexpected cmsg"); + } + } + assert_eq!(msg.bytes, 5); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + close(fd2).unwrap(); + } + + let received_r = received_r.expect("Did not receive passed fd"); + // Ensure that the received file descriptor works + write(w, b"world").unwrap(); + let mut buf = [0u8; 5]; + read(received_r, &mut buf).unwrap(); + assert_eq!(&buf[..], b"world"); + close(received_r).unwrap(); + close(w).unwrap(); +} + +// Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(qemu, ignore)] +#[test] +pub fn test_af_alg_cipher() { + use nix::sys::socket::sockopt::AlgSetKey; + use nix::sys::socket::{ + accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr, + ControlMessage, MsgFlags, SockFlag, SockType, + }; + use nix::unistd::read; + use std::io::IoSlice; + + skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); + // Travis's seccomp profile blocks AF_ALG + // https://docs.docker.com/engine/security/seccomp/ + skip_if_seccomp!(test_af_alg_cipher); + + let alg_type = "skcipher"; + let alg_name = "ctr-aes-aesni"; + // 256-bits secret key + let key = vec![0u8; 32]; + // 16-bytes IV + let iv_len = 16; + let iv = vec![1u8; iv_len]; + // 256-bytes plain payload + let payload_len = 256; + let payload = vec![2u8; payload_len]; + + let sock = socket( + AddressFamily::Alg, + SockType::SeqPacket, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + let sockaddr = AlgAddr::new(alg_type, alg_name); + bind(sock, &sockaddr).expect("bind failed"); + + assert_eq!(sockaddr.alg_name().to_string_lossy(), alg_name); + assert_eq!(sockaddr.alg_type().to_string_lossy(), alg_type); + + setsockopt(sock, AlgSetKey::default(), &key).expect("setsockopt"); + let session_socket = accept(sock).expect("accept failed"); + + let msgs = [ + ControlMessage::AlgSetOp(&libc::ALG_OP_ENCRYPT), + ControlMessage::AlgSetIv(iv.as_slice()), + ]; + let iov = IoSlice::new(&payload); + sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None) + .expect("sendmsg encrypt"); + + // allocate buffer for encrypted data + let mut encrypted = vec![0u8; payload_len]; + let num_bytes = read(session_socket, &mut encrypted).expect("read encrypt"); + assert_eq!(num_bytes, payload_len); + + let iov = IoSlice::new(&encrypted); + + let iv = vec![1u8; iv_len]; + + let msgs = [ + ControlMessage::AlgSetOp(&libc::ALG_OP_DECRYPT), + ControlMessage::AlgSetIv(iv.as_slice()), + ]; + sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None) + .expect("sendmsg decrypt"); + + // allocate buffer for decrypted data + let mut decrypted = vec![0u8; payload_len]; + let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt"); + + assert_eq!(num_bytes, payload_len); + assert_eq!(decrypted, payload); +} + +// Disable the test on emulated platforms due to not enabled support of AF_ALG +// in QEMU from rust cross +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(qemu, ignore)] +#[test] +pub fn test_af_alg_aead() { + use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT}; + use nix::fcntl::{fcntl, FcntlArg, OFlag}; + use nix::sys::socket::sockopt::{AlgSetAeadAuthSize, AlgSetKey}; + use nix::sys::socket::{ + accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr, + ControlMessage, MsgFlags, SockFlag, SockType, + }; + use nix::unistd::{close, read}; + use std::io::IoSlice; + + skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); + // Travis's seccomp profile blocks AF_ALG + // https://docs.docker.com/engine/security/seccomp/ + skip_if_seccomp!(test_af_alg_aead); + + let auth_size = 4usize; + let assoc_size = 16u32; + + let alg_type = "aead"; + let alg_name = "gcm(aes)"; + // 256-bits secret key + let key = vec![0u8; 32]; + // 12-bytes IV + let iv_len = 12; + let iv = vec![1u8; iv_len]; + // 256-bytes plain payload + let payload_len = 256; + let mut payload = + vec![2u8; payload_len + (assoc_size as usize) + auth_size]; + + for i in 0..assoc_size { + payload[i as usize] = 10; + } + + let len = payload.len(); + + for i in 0..auth_size { + payload[len - 1 - i] = 0; + } + + let sock = socket( + AddressFamily::Alg, + SockType::SeqPacket, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + let sockaddr = AlgAddr::new(alg_type, alg_name); + bind(sock, &sockaddr).expect("bind failed"); + + setsockopt(sock, AlgSetAeadAuthSize, &auth_size) + .expect("setsockopt AlgSetAeadAuthSize"); + setsockopt(sock, AlgSetKey::default(), &key).expect("setsockopt AlgSetKey"); + let session_socket = accept(sock).expect("accept failed"); + + let msgs = [ + ControlMessage::AlgSetOp(&ALG_OP_ENCRYPT), + ControlMessage::AlgSetIv(iv.as_slice()), + ControlMessage::AlgSetAeadAssoclen(&assoc_size), + ]; + + let iov = IoSlice::new(&payload); + sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None) + .expect("sendmsg encrypt"); + + // allocate buffer for encrypted data + let mut encrypted = + vec![0u8; (assoc_size as usize) + payload_len + auth_size]; + let num_bytes = read(session_socket, &mut encrypted).expect("read encrypt"); + assert_eq!(num_bytes, payload_len + auth_size + (assoc_size as usize)); + close(session_socket).expect("close"); + + for i in 0..assoc_size { + encrypted[i as usize] = 10; + } + + let iov = IoSlice::new(&encrypted); + + let iv = vec![1u8; iv_len]; + + let session_socket = accept(sock).expect("accept failed"); + + let msgs = [ + ControlMessage::AlgSetOp(&ALG_OP_DECRYPT), + ControlMessage::AlgSetIv(iv.as_slice()), + ControlMessage::AlgSetAeadAssoclen(&assoc_size), + ]; + sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None) + .expect("sendmsg decrypt"); + + // allocate buffer for decrypted data + let mut decrypted = + vec![0u8; payload_len + (assoc_size as usize) + auth_size]; + // Starting with kernel 4.9, the interface changed slightly such that the + // authentication tag memory is only needed in the output buffer for encryption + // and in the input buffer for decryption. + // Do not block on read, as we may have fewer bytes than buffer size + fcntl(session_socket, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) + .expect("fcntl non_blocking"); + let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt"); + + assert!(num_bytes >= payload_len + (assoc_size as usize)); + assert_eq!( + decrypted[(assoc_size as usize)..(payload_len + (assoc_size as usize))], + payload[(assoc_size as usize)..payload_len + (assoc_size as usize)] + ); +} + +// Verify `ControlMessage::Ipv4PacketInfo` for `sendmsg`. +// This creates a (udp) socket bound to localhost, then sends a message to +// itself but uses Ipv4PacketInfo to force the source address to be localhost. +// +// This would be a more interesting test if we could assume that the test host +// has more than one IP address (since we could select a different address to +// test from). +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "netbsd"))] +#[test] +pub fn test_sendmsg_ipv4packetinfo() { + use cfg_if::cfg_if; + use nix::sys::socket::{ + bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags, + SockFlag, SockType, SockaddrIn, + }; + use std::io::IoSlice; + + let sock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + let sock_addr = SockaddrIn::new(127, 0, 0, 1, 4000); + + bind(sock, &sock_addr).expect("bind failed"); + + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + cfg_if! { + if #[cfg(target_os = "netbsd")] { + let pi = libc::in_pktinfo { + ipi_ifindex: 0, /* Unspecified interface */ + ipi_addr: libc::in_addr { s_addr: 0 }, + }; + } else { + let pi = libc::in_pktinfo { + ipi_ifindex: 0, /* Unspecified interface */ + ipi_addr: libc::in_addr { s_addr: 0 }, + ipi_spec_dst: sock_addr.as_ref().sin_addr, + }; + } + } + + let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)]; + + sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr)) + .expect("sendmsg"); +} + +// Verify `ControlMessage::Ipv6PacketInfo` for `sendmsg`. +// This creates a (udp) socket bound to ip6-localhost, then sends a message to +// itself but uses Ipv6PacketInfo to force the source address to be +// ip6-localhost. +// +// This would be a more interesting test if we could assume that the test host +// has more than one IP address (since we could select a different address to +// test from). +#[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "freebsd" +))] +#[test] +pub fn test_sendmsg_ipv6packetinfo() { + use nix::errno::Errno; + use nix::sys::socket::{ + bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags, + SockFlag, SockType, SockaddrIn6, + }; + use std::io::IoSlice; + + let sock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + let std_sa = SocketAddrV6::from_str("[::1]:6000").unwrap(); + let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa); + + if let Err(Errno::EADDRNOTAVAIL) = bind(sock, &sock_addr) { + println!("IPv6 not available, skipping test."); + return; + } + + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let pi = libc::in6_pktinfo { + ipi6_ifindex: 0, /* Unspecified interface */ + ipi6_addr: sock_addr.as_ref().sin6_addr, + }; + + let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)]; + + sendmsg::( + sock, + &iov, + &cmsg, + MsgFlags::empty(), + Some(&sock_addr), + ) + .expect("sendmsg"); +} + +// Verify that ControlMessage::Ipv4SendSrcAddr works for sendmsg. This +// creates a UDP socket bound to all local interfaces (0.0.0.0). It then +// sends message to itself at 127.0.0.1 while explicitly specifying +// 127.0.0.1 as the source address through an Ipv4SendSrcAddr +// (IP_SENDSRCADDR) control message. +// +// Note that binding to 0.0.0.0 is *required* on FreeBSD; sendmsg +// returns EINVAL otherwise. (See FreeBSD's ip(4) man page.) +#[cfg(any( + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "dragonfly", +))] +#[test] +pub fn test_sendmsg_ipv4sendsrcaddr() { + use nix::sys::socket::{ + bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags, + SockFlag, SockType, SockaddrIn, + }; + use std::io::IoSlice; + + let sock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + let unspec_sock_addr = SockaddrIn::new(0, 0, 0, 0, 0); + bind(sock, &unspec_sock_addr).expect("bind failed"); + let bound_sock_addr: SockaddrIn = getsockname(sock).unwrap(); + let localhost_sock_addr: SockaddrIn = + SockaddrIn::new(127, 0, 0, 1, bound_sock_addr.port()); + + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + let cmsg = [ControlMessage::Ipv4SendSrcAddr( + &localhost_sock_addr.as_ref().sin_addr, + )]; + + sendmsg( + sock, + &iov, + &cmsg, + MsgFlags::empty(), + Some(&localhost_sock_addr), + ) + .expect("sendmsg"); +} + +/// Tests that passing multiple fds using a single `ControlMessage` works. +// Disable the test on emulated platforms due to a bug in QEMU versions < +// 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 +#[cfg_attr(qemu, ignore)] +#[test] +fn test_scm_rights_single_cmsg_multiple_fds() { + use nix::sys::socket::{ + recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, + }; + use std::io::{IoSlice, IoSliceMut}; + use std::os::unix::io::{AsRawFd, RawFd}; + use std::os::unix::net::UnixDatagram; + use std::thread; + + let (send, receive) = UnixDatagram::pair().unwrap(); + let thread = thread::spawn(move || { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + + let mut space = cmsg_space!([RawFd; 2]); + let msg = recvmsg::<()>( + receive.as_raw_fd(), + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .unwrap(); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + + let mut cmsgs = msg.cmsgs(); + match cmsgs.next() { + Some(ControlMessageOwned::ScmRights(fds)) => { + assert_eq!( + fds.len(), + 2, + "unexpected fd count (expected 2 fds, got {})", + fds.len() + ); + } + _ => panic!(), + } + assert!(cmsgs.next().is_none(), "unexpected control msg"); + + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + }); + + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + let fds = [libc::STDIN_FILENO, libc::STDOUT_FILENO]; // pass stdin and stdout + let cmsg = [ControlMessage::ScmRights(&fds)]; + sendmsg::<()>(send.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), None) + .unwrap(); + thread.join().unwrap(); +} + +// Verify `sendmsg` builds a valid `msghdr` when passing an empty +// `cmsgs` argument. This should result in a msghdr with a nullptr +// msg_control field and a msg_controllen of 0 when calling into the +// raw `sendmsg`. +#[test] +pub fn test_sendmsg_empty_cmsgs() { + use nix::sys::socket::{ + recvmsg, sendmsg, socketpair, AddressFamily, MsgFlags, SockFlag, + SockType, + }; + use nix::unistd::close; + use std::io::{IoSlice, IoSliceMut}; + + let (fd1, fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + + { + let iov = [IoSlice::new(b"hello")]; + assert_eq!( + sendmsg::<()>(fd1, &iov, &[], MsgFlags::empty(), None).unwrap(), + 5 + ); + close(fd1).unwrap(); + } + + { + let mut buf = [0u8; 5]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + + let mut cmsgspace = cmsg_space!([RawFd; 1]); + let msg = recvmsg::<()>( + fd2, + &mut iov, + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); + + for _ in msg.cmsgs() { + panic!("unexpected cmsg"); + } + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + assert_eq!(msg.bytes, 5); + close(fd2).unwrap(); + } +} + +#[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", +))] +#[test] +fn test_scm_credentials() { + use nix::sys::socket::{ + recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage, + ControlMessageOwned, MsgFlags, SockFlag, SockType, UnixCredentials, + }; + #[cfg(any(target_os = "android", target_os = "linux"))] + use nix::sys::socket::{setsockopt, sockopt::PassCred}; + use nix::unistd::{close, getgid, getpid, getuid}; + use std::io::{IoSlice, IoSliceMut}; + + let (send, recv) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + #[cfg(any(target_os = "android", target_os = "linux"))] + setsockopt(recv, PassCred, &true).unwrap(); + + { + let iov = [IoSlice::new(b"hello")]; + #[cfg(any(target_os = "android", target_os = "linux"))] + let cred = UnixCredentials::new(); + #[cfg(any(target_os = "android", target_os = "linux"))] + let cmsg = ControlMessage::ScmCredentials(&cred); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + let cmsg = ControlMessage::ScmCreds; + assert_eq!( + sendmsg::<()>(send, &iov, &[cmsg], MsgFlags::empty(), None) + .unwrap(), + 5 + ); + close(send).unwrap(); + } + + { + let mut buf = [0u8; 5]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + + let mut cmsgspace = cmsg_space!(UnixCredentials); + let msg = recvmsg::<()>( + recv, + &mut iov, + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); + let mut received_cred = None; + + for cmsg in msg.cmsgs() { + let cred = match cmsg { + #[cfg(any(target_os = "android", target_os = "linux"))] + ControlMessageOwned::ScmCredentials(cred) => cred, + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + ControlMessageOwned::ScmCreds(cred) => cred, + other => panic!("unexpected cmsg {:?}", other), + }; + assert!(received_cred.is_none()); + assert_eq!(cred.pid(), getpid().as_raw()); + assert_eq!(cred.uid(), getuid().as_raw()); + assert_eq!(cred.gid(), getgid().as_raw()); + received_cred = Some(cred); + } + received_cred.expect("no creds received"); + assert_eq!(msg.bytes, 5); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + close(recv).unwrap(); + } +} + +/// Ensure that we can send `SCM_CREDENTIALS` and `SCM_RIGHTS` with a single +/// `sendmsg` call. +#[cfg(any(target_os = "android", target_os = "linux"))] +// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation +// see https://bugs.launchpad.net/qemu/+bug/1781280 +#[cfg_attr(qemu, ignore)] +#[test] +fn test_scm_credentials_and_rights() { + let space = cmsg_space!(libc::ucred, RawFd); + test_impl_scm_credentials_and_rights(space); +} + +/// Ensure that passing a an oversized control message buffer to recvmsg +/// still works. +#[cfg(any(target_os = "android", target_os = "linux"))] +// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation +// see https://bugs.launchpad.net/qemu/+bug/1781280 +#[cfg_attr(qemu, ignore)] +#[test] +fn test_too_large_cmsgspace() { + let space = vec![0u8; 1024]; + test_impl_scm_credentials_and_rights(space); +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_impl_scm_credentials_and_rights(mut space: Vec) { + use libc::ucred; + use nix::sys::socket::sockopt::PassCred; + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socketpair, ControlMessage, + ControlMessageOwned, MsgFlags, SockFlag, SockType, + }; + use nix::unistd::{close, getgid, getpid, getuid, pipe, write}; + use std::io::{IoSlice, IoSliceMut}; + + let (send, recv) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + setsockopt(recv, PassCred, &true).unwrap(); + + let (r, w) = pipe().unwrap(); + let mut received_r: Option = None; + + { + let iov = [IoSlice::new(b"hello")]; + let cred = ucred { + pid: getpid().as_raw(), + uid: getuid().as_raw(), + gid: getgid().as_raw(), + } + .into(); + let fds = [r]; + let cmsgs = [ + ControlMessage::ScmCredentials(&cred), + ControlMessage::ScmRights(&fds), + ]; + assert_eq!( + sendmsg::<()>(send, &iov, &cmsgs, MsgFlags::empty(), None).unwrap(), + 5 + ); + close(r).unwrap(); + close(send).unwrap(); + } + + { + let mut buf = [0u8; 5]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + let msg = + recvmsg::<()>(recv, &mut iov, Some(&mut space), MsgFlags::empty()) + .unwrap(); + let mut received_cred = None; + + assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs"); + + for cmsg in msg.cmsgs() { + match cmsg { + ControlMessageOwned::ScmRights(fds) => { + assert_eq!(received_r, None, "already received fd"); + assert_eq!(fds.len(), 1); + received_r = Some(fds[0]); + } + ControlMessageOwned::ScmCredentials(cred) => { + assert!(received_cred.is_none()); + assert_eq!(cred.pid(), getpid().as_raw()); + assert_eq!(cred.uid(), getuid().as_raw()); + assert_eq!(cred.gid(), getgid().as_raw()); + received_cred = Some(cred); + } + _ => panic!("unexpected cmsg"), + } + } + received_cred.expect("no creds received"); + assert_eq!(msg.bytes, 5); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + close(recv).unwrap(); + } + + let received_r = received_r.expect("Did not receive passed fd"); + // Ensure that the received file descriptor works + write(w, b"world").unwrap(); + let mut buf = [0u8; 5]; + read(received_r, &mut buf).unwrap(); + assert_eq!(&buf[..], b"world"); + close(received_r).unwrap(); + close(w).unwrap(); +} + +// Test creating and using named unix domain sockets +#[test] +pub fn test_named_unixdomain() { + use nix::sys::socket::{accept, bind, connect, listen, socket, UnixAddr}; + use nix::sys::socket::{SockFlag, SockType}; + use nix::unistd::{close, read, write}; + use std::thread; + + let tempdir = tempfile::tempdir().unwrap(); + let sockname = tempdir.path().join("sock"); + let s1 = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + let sockaddr = UnixAddr::new(&sockname).unwrap(); + bind(s1, &sockaddr).expect("bind failed"); + listen(s1, 10).expect("listen failed"); + + let thr = thread::spawn(move || { + let s2 = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + connect(s2, &sockaddr).expect("connect failed"); + write(s2, b"hello").expect("write failed"); + close(s2).unwrap(); + }); + + let s3 = accept(s1).expect("accept failed"); + + let mut buf = [0; 5]; + read(s3, &mut buf).unwrap(); + close(s3).unwrap(); + close(s1).unwrap(); + thr.join().unwrap(); + + assert_eq!(&buf[..], b"hello"); +} + +// Test using unnamed unix domain addresses +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +pub fn test_unnamed_unixdomain() { + use nix::sys::socket::{getsockname, socketpair}; + use nix::sys::socket::{SockFlag, SockType}; + use nix::unistd::close; + + let (fd_1, fd_2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .expect("socketpair failed"); + + let addr_1: UnixAddr = getsockname(fd_1).expect("getsockname failed"); + assert!(addr_1.is_unnamed()); + + close(fd_1).unwrap(); + close(fd_2).unwrap(); +} + +// Test creating and using unnamed unix domain addresses for autobinding sockets +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +pub fn test_unnamed_unixdomain_autobind() { + use nix::sys::socket::{bind, getsockname, socket}; + use nix::sys::socket::{SockFlag, SockType}; + use nix::unistd::close; + + let fd = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + // unix(7): "If a bind(2) call specifies addrlen as `sizeof(sa_family_t)`, or [...], then the + // socket is autobound to an abstract address" + bind(fd, &UnixAddr::new_unnamed()).expect("bind failed"); + + let addr: UnixAddr = getsockname(fd).expect("getsockname failed"); + let addr = addr.as_abstract().unwrap(); + + // changed from 8 to 5 bytes in Linux 2.3.15, and rust's minimum supported Linux version is 3.2 + // (as of 2022-11) + assert_eq!(addr.len(), 5); + + close(fd).unwrap(); +} + +// Test creating and using named system control sockets +#[cfg(any(target_os = "macos", target_os = "ios"))] +#[test] +pub fn test_syscontrol() { + use nix::errno::Errno; + use nix::sys::socket::{ + socket, SockFlag, SockProtocol, SockType, SysControlAddr, + }; + + let fd = socket( + AddressFamily::System, + SockType::Datagram, + SockFlag::empty(), + SockProtocol::KextControl, + ) + .expect("socket failed"); + SysControlAddr::from_name(fd, "com.apple.net.utun_control", 0) + .expect("resolving sys_control name failed"); + assert_eq!( + SysControlAddr::from_name(fd, "foo.bar.lol", 0).err(), + Some(Errno::ENOENT) + ); + + // requires root privileges + // connect(fd, &sockaddr).expect("connect failed"); +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +fn loopback_address( + family: AddressFamily, +) -> Option { + use nix::ifaddrs::getifaddrs; + use nix::net::if_::*; + use nix::sys::socket::SockaddrLike; + use std::io; + use std::io::Write; + + let mut addrs = match getifaddrs() { + Ok(iter) => iter, + Err(e) => { + let stdioerr = io::stderr(); + let mut handle = stdioerr.lock(); + writeln!(handle, "getifaddrs: {:?}", e).unwrap(); + return None; + } + }; + // return first address matching family + addrs.find(|ifaddr| { + ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK) + && ifaddr.address.as_ref().and_then(SockaddrLike::family) + == Some(family) + }) +} + +#[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", +))] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) + ), + ignore +)] +#[test] +pub fn test_recv_ipv4pktinfo() { + use nix::net::if_::*; + use nix::sys::socket::sockopt::Ipv4PacketInfo; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet); + let (lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv4 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv4PacketInfo, &true).expect("setsockopt failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + + let mut space = cmsg_space!(libc::in_pktinfo); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + + let mut cmsgs = msg.cmsgs(); + if let Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) = cmsgs.next() + { + let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); + assert_eq!( + pktinfo.ipi_ifindex as libc::c_uint, i, + "unexpected ifindex (expected {}, got {})", + i, pktinfo.ipi_ifindex + ); + } + assert!(cmsgs.next().is_none(), "unexpected additional control msg"); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[cfg(any( + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) + ), + ignore +)] +#[test] +pub fn test_recvif() { + use nix::net::if_::*; + use nix::sys::socket::sockopt::{Ipv4RecvDstAddr, Ipv4RecvIf}; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet); + let (lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv4 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv4RecvIf, &true) + .expect("setsockopt IP_RECVIF failed"); + setsockopt(receive, Ipv4RecvDstAddr, &true) + .expect("setsockopt IP_RECVDSTADDR failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::sockaddr_dl, libc::in_addr); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs"); + + let mut rx_recvif = false; + let mut rx_recvdstaddr = false; + for cmsg in msg.cmsgs() { + match cmsg { + ControlMessageOwned::Ipv4RecvIf(dl) => { + rx_recvif = true; + let i = if_nametoindex(lo_name.as_bytes()) + .expect("if_nametoindex"); + assert_eq!( + dl.sdl_index as libc::c_uint, i, + "unexpected ifindex (expected {}, got {})", + i, dl.sdl_index + ); + } + ControlMessageOwned::Ipv4RecvDstAddr(addr) => { + rx_recvdstaddr = true; + if let Some(sin) = lo.as_sockaddr_in() { + assert_eq!(sin.as_ref().sin_addr.s_addr, + addr.s_addr, + "unexpected destination address (expected {}, got {})", + sin.as_ref().sin_addr.s_addr, + addr.s_addr); + } else { + panic!("unexpected Sockaddr"); + } + } + _ => panic!("unexpected additional control msg"), + } + } + assert!(rx_recvif); + assert!(rx_recvdstaddr); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg_attr(qemu, ignore)] +#[test] +pub fn test_recvif_ipv4() { + use nix::sys::socket::sockopt::Ipv4OrigDstAddr; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet); + let (_lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv4 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv4OrigDstAddr, &true) + .expect("setsockopt IP_ORIGDSTADDR failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::sockaddr_in); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + + let mut rx_recvorigdstaddr = false; + for cmsg in msg.cmsgs() { + match cmsg { + ControlMessageOwned::Ipv4OrigDstAddr(addr) => { + rx_recvorigdstaddr = true; + if let Some(sin) = lo.as_sockaddr_in() { + assert_eq!(sin.as_ref().sin_addr.s_addr, + addr.sin_addr.s_addr, + "unexpected destination address (expected {}, got {})", + sin.as_ref().sin_addr.s_addr, + addr.sin_addr.s_addr); + } else { + panic!("unexpected Sockaddr"); + } + } + _ => panic!("unexpected additional control msg"), + } + } + assert!(rx_recvorigdstaddr); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg_attr(qemu, ignore)] +#[test] +pub fn test_recvif_ipv6() { + use nix::sys::socket::sockopt::Ipv6OrigDstAddr; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet6); + let (_lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv6 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv6OrigDstAddr, &true) + .expect("setsockopt IP_ORIGDSTADDR failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::sockaddr_in6); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + + let mut rx_recvorigdstaddr = false; + for cmsg in msg.cmsgs() { + match cmsg { + ControlMessageOwned::Ipv6OrigDstAddr(addr) => { + rx_recvorigdstaddr = true; + if let Some(sin) = lo.as_sockaddr_in6() { + assert_eq!(sin.as_ref().sin6_addr.s6_addr, + addr.sin6_addr.s6_addr, + "unexpected destination address (expected {:?}, got {:?})", + sin.as_ref().sin6_addr.s6_addr, + addr.sin6_addr.s6_addr); + } else { + panic!("unexpected Sockaddr"); + } + } + _ => panic!("unexpected additional control msg"), + } + } + assert!(rx_recvorigdstaddr); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) + ), + ignore +)] +#[test] +pub fn test_recv_ipv6pktinfo() { + use nix::net::if_::*; + use nix::sys::socket::sockopt::Ipv6RecvPacketInfo; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet6); + let (lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv6 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv6RecvPacketInfo, &true).expect("setsockopt failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + + let mut space = cmsg_space!(libc::in6_pktinfo); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + + let mut cmsgs = msg.cmsgs(); + if let Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) = cmsgs.next() + { + let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); + assert_eq!( + pktinfo.ipi6_ifindex as libc::c_uint, i, + "unexpected ifindex (expected {}, got {})", + i, pktinfo.ipi6_ifindex + ); + } + assert!(cmsgs.next().is_none(), "unexpected additional control msg"); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(graviton, ignore = "Not supported by the CI environment")] +#[test] +pub fn test_vsock() { + use nix::errno::Errno; + use nix::sys::socket::{ + bind, connect, listen, socket, AddressFamily, SockFlag, SockType, + VsockAddr, + }; + use nix::unistd::close; + use std::thread; + + let port: u32 = 3000; + + let s1 = socket( + AddressFamily::Vsock, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + // VMADDR_CID_HYPERVISOR is reserved, so we expect an EADDRNOTAVAIL error. + let sockaddr_hv = VsockAddr::new(libc::VMADDR_CID_HYPERVISOR, port); + assert_eq!(bind(s1, &sockaddr_hv).err(), Some(Errno::EADDRNOTAVAIL)); + + let sockaddr_any = VsockAddr::new(libc::VMADDR_CID_ANY, port); + assert_eq!(bind(s1, &sockaddr_any), Ok(())); + listen(s1, 10).expect("listen failed"); + + let thr = thread::spawn(move || { + let cid: u32 = libc::VMADDR_CID_HOST; + + let s2 = socket( + AddressFamily::Vsock, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + + let sockaddr_host = VsockAddr::new(cid, port); + + // The current implementation does not support loopback devices, so, + // for now, we expect a failure on the connect. + assert_ne!(connect(s2, &sockaddr_host), Ok(())); + + close(s2).unwrap(); + }); + + close(s1).unwrap(); + thr.join().unwrap(); +} + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(all(target_os = "linux"))] +#[test] +fn test_recvmsg_timestampns() { + use nix::sys::socket::*; + use nix::sys::time::*; + use std::io::{IoSlice, IoSliceMut}; + use std::time::*; + + // Set up + let message = "Ohayō!".as_bytes(); + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + let localhost = SockaddrIn::new(127, 0, 0, 1, 0); + bind(in_socket, &localhost).unwrap(); + let address: SockaddrIn = getsockname(in_socket).unwrap(); + // Get initial time + let time0 = SystemTime::now(); + // Send the message + let iov = [IoSlice::new(message)]; + let flags = MsgFlags::empty(); + let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + // Receive the message + let mut buffer = vec![0u8; message.len()]; + let mut cmsgspace = nix::cmsg_space!(TimeSpec); + + let mut iov = [IoSliceMut::new(&mut buffer)]; + let r = recvmsg::<()>(in_socket, &mut iov, Some(&mut cmsgspace), flags) + .unwrap(); + let rtime = match r.cmsgs().next() { + Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, + Some(_) => panic!("Unexpected control message"), + None => panic!("No control message"), + }; + // Check the final time + let time1 = SystemTime::now(); + // the packet's received timestamp should lie in-between the two system + // times, unless the system clock was adjusted in the meantime. + let rduration = + Duration::new(rtime.tv_sec() as u64, rtime.tv_nsec() as u32); + assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); + assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); + // Close socket + nix::unistd::close(in_socket).unwrap(); +} + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(all(target_os = "linux"))] +#[test] +fn test_recvmmsg_timestampns() { + use nix::sys::socket::*; + use nix::sys::time::*; + use std::io::{IoSlice, IoSliceMut}; + use std::time::*; + + // Set up + let message = "Ohayō!".as_bytes(); + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + bind(in_socket, &localhost).unwrap(); + let address: SockaddrIn = getsockname(in_socket).unwrap(); + // Get initial time + let time0 = SystemTime::now(); + // Send the message + let iov = [IoSlice::new(message)]; + let flags = MsgFlags::empty(); + let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + // Receive the message + let mut buffer = vec![0u8; message.len()]; + let cmsgspace = nix::cmsg_space!(TimeSpec); + let iov = vec![[IoSliceMut::new(&mut buffer)]]; + let mut data = MultiHeaders::preallocate(1, Some(cmsgspace)); + let r: Vec> = + recvmmsg(in_socket, &mut data, iov.iter(), flags, None) + .unwrap() + .collect(); + let rtime = match r[0].cmsgs().next() { + Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, + Some(_) => panic!("Unexpected control message"), + None => panic!("No control message"), + }; + // Check the final time + let time1 = SystemTime::now(); + // the packet's received timestamp should lie in-between the two system + // times, unless the system clock was adjusted in the meantime. + let rduration = + Duration::new(rtime.tv_sec() as u64, rtime.tv_nsec() as u32); + assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); + assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); + // Close socket + nix::unistd::close(in_socket).unwrap(); +} + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[test] +fn test_recvmsg_rxq_ovfl() { + use nix::sys::socket::sockopt::{RcvBuf, RxqOvfl}; + use nix::sys::socket::*; + use nix::Error; + use std::io::{IoSlice, IoSliceMut}; + + let message = [0u8; 2048]; + let bufsize = message.len() * 2; + + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + let out_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + bind(in_socket, &localhost).unwrap(); + + let address: SockaddrIn = getsockname(in_socket).unwrap(); + connect(out_socket, &address).unwrap(); + + // Set SO_RXQ_OVFL flag. + setsockopt(in_socket, RxqOvfl, &1).unwrap(); + + // Set the receiver buffer size to hold only 2 messages. + setsockopt(in_socket, RcvBuf, &bufsize).unwrap(); + + let mut drop_counter = 0; + + for _ in 0..2 { + let iov = [IoSlice::new(&message)]; + let flags = MsgFlags::empty(); + + // Send the 3 messages (the receiver buffer can only hold 2 messages) + // to create an overflow. + for _ in 0..3 { + let l = + sendmsg(out_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + } + + // Receive the message and check the drop counter if any. + loop { + let mut buffer = vec![0u8; message.len()]; + let mut cmsgspace = nix::cmsg_space!(u32); + + let mut iov = [IoSliceMut::new(&mut buffer)]; + + match recvmsg::<()>( + in_socket, + &mut iov, + Some(&mut cmsgspace), + MsgFlags::MSG_DONTWAIT, + ) { + Ok(r) => { + drop_counter = match r.cmsgs().next() { + Some(ControlMessageOwned::RxqOvfl(drop_counter)) => { + drop_counter + } + Some(_) => panic!("Unexpected control message"), + None => 0, + }; + } + Err(Error::EAGAIN) => { + break; + } + _ => { + panic!("unknown recvmsg() error"); + } + } + } + } + + // One packet lost. + assert_eq!(drop_counter, 1); + + // Close sockets + nix::unistd::close(in_socket).unwrap(); + nix::unistd::close(out_socket).unwrap(); +} + +#[cfg(any(target_os = "linux", target_os = "android",))] +mod linux_errqueue { + use super::FromStr; + use nix::sys::socket::*; + + // Send a UDP datagram to a bogus destination address and observe an ICMP error (v4). + // + // Disable the test on QEMU because QEMU emulation of IP_RECVERR is broken (as documented on PR + // #1514). + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recverr_v4() { + #[repr(u8)] + enum IcmpTypes { + DestUnreach = 3, // ICMP_DEST_UNREACH + } + #[repr(u8)] + enum IcmpUnreachCodes { + PortUnreach = 3, // ICMP_PORT_UNREACH + } + + test_recverr_impl::( + "127.0.0.1:6800", + AddressFamily::Inet, + sockopt::Ipv4RecvErr, + libc::SO_EE_ORIGIN_ICMP, + IcmpTypes::DestUnreach as u8, + IcmpUnreachCodes::PortUnreach as u8, + // Closure handles protocol-specific testing and returns generic sock_extended_err for + // protocol-independent test impl. + |cmsg| { + if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) = + cmsg + { + if let Some(origin) = err_addr { + // Validate that our network error originated from 127.0.0.1:0. + assert_eq!(origin.sin_family, AddressFamily::Inet as _); + assert_eq!( + origin.sin_addr.s_addr, + u32::from_be(0x7f000001) + ); + assert_eq!(origin.sin_port, 0); + } else { + panic!("Expected some error origin"); + } + *ext_err + } else { + panic!("Unexpected control message {:?}", cmsg); + } + }, + ) + } + + // Essentially the same test as v4. + // + // Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on + // PR #1514). + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recverr_v6() { + #[repr(u8)] + enum IcmpV6Types { + DestUnreach = 1, // ICMPV6_DEST_UNREACH + } + #[repr(u8)] + enum IcmpV6UnreachCodes { + PortUnreach = 4, // ICMPV6_PORT_UNREACH + } + + test_recverr_impl::( + "[::1]:6801", + AddressFamily::Inet6, + sockopt::Ipv6RecvErr, + libc::SO_EE_ORIGIN_ICMP6, + IcmpV6Types::DestUnreach as u8, + IcmpV6UnreachCodes::PortUnreach as u8, + // Closure handles protocol-specific testing and returns generic sock_extended_err for + // protocol-independent test impl. + |cmsg| { + if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) = + cmsg + { + if let Some(origin) = err_addr { + // Validate that our network error originated from localhost:0. + assert_eq!( + origin.sin6_family, + AddressFamily::Inet6 as _ + ); + assert_eq!( + origin.sin6_addr.s6_addr, + std::net::Ipv6Addr::LOCALHOST.octets() + ); + assert_eq!(origin.sin6_port, 0); + } else { + panic!("Expected some error origin"); + } + *ext_err + } else { + panic!("Unexpected control message {:?}", cmsg); + } + }, + ) + } + + fn test_recverr_impl( + sa: &str, + af: AddressFamily, + opt: OPT, + ee_origin: u8, + ee_type: u8, + ee_code: u8, + testf: TESTF, + ) where + OPT: SetSockOpt, + TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err, + { + use nix::errno::Errno; + use std::io::IoSliceMut; + + const MESSAGE_CONTENTS: &str = "ABCDEF"; + let std_sa = std::net::SocketAddr::from_str(sa).unwrap(); + let sock_addr = SockaddrStorage::from(std_sa); + let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None) + .unwrap(); + setsockopt(sock, opt, &true).unwrap(); + if let Err(e) = sendto( + sock, + MESSAGE_CONTENTS.as_bytes(), + &sock_addr, + MsgFlags::empty(), + ) { + assert_eq!(e, Errno::EADDRNOTAVAIL); + println!("{:?} not available, skipping test.", af); + return; + } + + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut cspace = cmsg_space!(libc::sock_extended_err, SA); + + let msg = recvmsg( + sock, + &mut iovec, + Some(&mut cspace), + MsgFlags::MSG_ERRQUEUE, + ) + .unwrap(); + // The sent message / destination associated with the error is returned: + assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len()); + // recvmsg(2): "The original destination address of the datagram that caused the error is + // supplied via msg_name;" however, this is not literally true. E.g., an earlier version + // of this test used 0.0.0.0 (::0) as the destination address, which was mutated into + // 127.0.0.1 (::1). + assert_eq!(msg.address, Some(sock_addr)); + + // Check for expected control message. + let ext_err = match msg.cmsgs().next() { + Some(cmsg) => testf(&cmsg), + None => panic!("No control message"), + }; + + assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32); + assert_eq!(ext_err.ee_origin, ee_origin); + // ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6) + // header. + assert_eq!(ext_err.ee_type, ee_type); + assert_eq!(ext_err.ee_code, ee_code); + // ip(7): ee_info contains the discovered MTU for EMSGSIZE errors. + assert_eq!(ext_err.ee_info, 0); + + let bytes = msg.bytes; + assert_eq!(&buf[..bytes], MESSAGE_CONTENTS.as_bytes()); + } +} + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(target_os = "linux")] +#[test] +pub fn test_txtime() { + use nix::sys::socket::{ + bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage, + MsgFlags, SockFlag, SockType, SockaddrIn, + }; + use nix::sys::time::TimeValLike; + use nix::time::{clock_gettime, ClockId}; + + require_kernel_version!(test_txtime, ">= 5.8"); + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6802").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let txtime_cfg = libc::sock_txtime { + clockid: libc::CLOCK_MONOTONIC, + flags: 0, + }; + setsockopt(ssock, sockopt::TxTime, &txtime_cfg).unwrap(); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + bind(rsock, &sock_addr).unwrap(); + + let sbuf = [0u8; 2048]; + let iov1 = [std::io::IoSlice::new(&sbuf)]; + + let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap(); + let delay = std::time::Duration::from_secs(1).into(); + let txtime = (now + delay).num_nanoseconds() as u64; + + let cmsg = ControlMessage::TxTime(&txtime); + sendmsg(ssock, &iov1, &[cmsg], MsgFlags::empty(), Some(&sock_addr)) + .unwrap(); + + let mut rbuf = [0u8; 2048]; + let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; + recvmsg::<()>(rsock, &mut iov2, None, MsgFlags::empty()).unwrap(); +} diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs new file mode 100644 index 0000000..34bef94 --- /dev/null +++ b/test/sys/test_sockopt.rs @@ -0,0 +1,431 @@ +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::*; +use nix::sys::socket::{ + getsockopt, setsockopt, socket, sockopt, AddressFamily, SockFlag, + SockProtocol, SockType, +}; +use rand::{thread_rng, Rng}; + +// NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not. +#[cfg(any(target_os = "dragonfly", target_os = "freebsd",))] +#[test] +pub fn test_local_peercred_seqpacket() { + use nix::{ + sys::socket::socketpair, + unistd::{Gid, Uid}, + }; + + let (fd1, _fd2) = socketpair( + AddressFamily::Unix, + SockType::SeqPacket, + None, + SockFlag::empty(), + ) + .unwrap(); + let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap(); + assert_eq!(xucred.version(), 0); + assert_eq!(Uid::from_raw(xucred.uid()), Uid::current()); + assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current()); +} + +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" +))] +#[test] +pub fn test_local_peercred_stream() { + use nix::{ + sys::socket::socketpair, + unistd::{Gid, Uid}, + }; + + let (fd1, _fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap(); + assert_eq!(xucred.version(), 0); + assert_eq!(Uid::from_raw(xucred.uid()), Uid::current()); + assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current()); +} + +#[cfg(target_os = "linux")] +#[test] +fn is_so_mark_functional() { + use nix::sys::socket::sockopt; + + require_capability!("is_so_mark_functional", CAP_NET_ADMIN); + + let s = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(s, sockopt::Mark, &1337).unwrap(); + let mark = getsockopt(s, sockopt::Mark).unwrap(); + assert_eq!(mark, 1337); +} + +#[test] +fn test_so_buf() { + let fd = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + SockProtocol::Udp, + ) + .unwrap(); + let bufsize: usize = thread_rng().gen_range(4096..131_072); + setsockopt(fd, sockopt::SndBuf, &bufsize).unwrap(); + let actual = getsockopt(fd, sockopt::SndBuf).unwrap(); + assert!(actual >= bufsize); + setsockopt(fd, sockopt::RcvBuf, &bufsize).unwrap(); + let actual = getsockopt(fd, sockopt::RcvBuf).unwrap(); + assert!(actual >= bufsize); +} + +#[test] +fn test_so_tcp_maxseg() { + use nix::sys::socket::{accept, bind, connect, listen, SockaddrIn}; + use nix::unistd::{close, write}; + use std::net::SocketAddrV4; + use std::str::FromStr; + + let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap(); + let sock_addr = SockaddrIn::from(std_sa); + + let rsock = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + bind(rsock, &sock_addr).unwrap(); + listen(rsock, 10).unwrap(); + let initial = getsockopt(rsock, sockopt::TcpMaxSeg).unwrap(); + // Initial MSS is expected to be 536 (https://tools.ietf.org/html/rfc879#section-1) but some + // platforms keep it even lower. This might fail if you've tuned your initial MSS to be larger + // than 700 + cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + let segsize: u32 = 873; + assert!(initial < segsize); + setsockopt(rsock, sockopt::TcpMaxSeg, &segsize).unwrap(); + } else { + assert!(initial < 700); + } + } + + // Connect and check the MSS that was advertised + let ssock = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + connect(ssock, &sock_addr).unwrap(); + let rsess = accept(rsock).unwrap(); + write(rsess, b"hello").unwrap(); + let actual = getsockopt(ssock, sockopt::TcpMaxSeg).unwrap(); + // Actual max segment size takes header lengths into account, max IPv4 options (60 bytes) + max + // TCP options (40 bytes) are subtracted from the requested maximum as a lower boundary. + cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + assert!((segsize - 100) <= actual); + assert!(actual <= segsize); + } else { + assert!(initial < actual); + assert!(536 < actual); + } + } + close(rsock).unwrap(); + close(ssock).unwrap(); +} + +#[test] +fn test_so_type() { + let sockfd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + assert_eq!(Ok(SockType::Stream), getsockopt(sockfd, sockopt::SockType)); +} + +/// getsockopt(_, sockopt::SockType) should gracefully handle unknown socket +/// types. Regression test for https://github.com/nix-rust/nix/issues/1819 +#[cfg(any(target_os = "android", target_os = "linux",))] +#[test] +fn test_so_type_unknown() { + use nix::errno::Errno; + + require_capability!("test_so_type", CAP_NET_RAW); + let sockfd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_PACKET, 0) }; + assert!(sockfd >= 0, "Error opening socket: {}", nix::Error::last()); + + assert_eq!(Err(Errno::EINVAL), getsockopt(sockfd, sockopt::SockType)); +} + +// The CI doesn't supported getsockopt and setsockopt on emulated processors. +// It's believed that a QEMU issue, the tests run ok on a fully emulated system. +// Current CI just run the binary with QEMU but the Kernel remains the same as the host. +// So the syscall doesn't work properly unless the kernel is also emulated. +#[test] +#[cfg(all( + any(target_arch = "x86", target_arch = "x86_64"), + any(target_os = "freebsd", target_os = "linux") +))] +fn test_tcp_congestion() { + use std::ffi::OsString; + + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let val = getsockopt(fd, sockopt::TcpCongestion).unwrap(); + setsockopt(fd, sockopt::TcpCongestion, &val).unwrap(); + + setsockopt( + fd, + sockopt::TcpCongestion, + &OsString::from("tcp_congestion_does_not_exist"), + ) + .unwrap_err(); + + assert_eq!(getsockopt(fd, sockopt::TcpCongestion).unwrap(), val); +} + +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_bindtodevice() { + skip_if_not_root!("test_bindtodevice"); + + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let val = getsockopt(fd, sockopt::BindToDevice).unwrap(); + setsockopt(fd, sockopt::BindToDevice, &val).unwrap(); + + assert_eq!(getsockopt(fd, sockopt::BindToDevice).unwrap(), val); +} + +#[test] +fn test_so_tcp_keepalive() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(fd, sockopt::KeepAlive, &true).unwrap(); + assert!(getsockopt(fd, sockopt::KeepAlive).unwrap()); + + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" + ))] + { + let x = getsockopt(fd, sockopt::TcpKeepIdle).unwrap(); + setsockopt(fd, sockopt::TcpKeepIdle, &(x + 1)).unwrap(); + assert_eq!(getsockopt(fd, sockopt::TcpKeepIdle).unwrap(), x + 1); + + let x = getsockopt(fd, sockopt::TcpKeepCount).unwrap(); + setsockopt(fd, sockopt::TcpKeepCount, &(x + 1)).unwrap(); + assert_eq!(getsockopt(fd, sockopt::TcpKeepCount).unwrap(), x + 1); + + let x = getsockopt(fd, sockopt::TcpKeepInterval).unwrap(); + setsockopt(fd, sockopt::TcpKeepInterval, &(x + 1)).unwrap(); + assert_eq!(getsockopt(fd, sockopt::TcpKeepInterval).unwrap(), x + 1); + } +} + +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(qemu, ignore)] +fn test_get_mtu() { + use nix::sys::socket::{bind, connect, SockaddrIn}; + use std::net::SocketAddrV4; + use std::str::FromStr; + + let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap(); + let std_sb = SocketAddrV4::from_str("127.0.0.1:4002").unwrap(); + + let usock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + SockProtocol::Udp, + ) + .unwrap(); + + // Bind and initiate connection + bind(usock, &SockaddrIn::from(std_sa)).unwrap(); + connect(usock, &SockaddrIn::from(std_sb)).unwrap(); + + // Loopback connections have 2^16 - the maximum - MTU + assert_eq!(getsockopt(usock, sockopt::IpMtu), Ok(u16::MAX as i32)) +} + +#[test] +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +fn test_ttl_opts() { + let fd4 = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(fd4, sockopt::Ipv4Ttl, &1) + .expect("setting ipv4ttl on an inet socket should succeed"); + let fd6 = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(fd6, sockopt::Ipv6Ttl, &1) + .expect("setting ipv6ttl on an inet6 socket should succeed"); +} + +#[test] +#[cfg(any(target_os = "ios", target_os = "macos"))] +fn test_dontfrag_opts() { + let fd4 = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(fd4, sockopt::IpDontFrag, &true) + .expect("setting IP_DONTFRAG on an inet stream socket should succeed"); + setsockopt(fd4, sockopt::IpDontFrag, &false).expect( + "unsetting IP_DONTFRAG on an inet stream socket should succeed", + ); + let fd4d = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(fd4d, sockopt::IpDontFrag, &true).expect( + "setting IP_DONTFRAG on an inet datagram socket should succeed", + ); + setsockopt(fd4d, sockopt::IpDontFrag, &false).expect( + "unsetting IP_DONTFRAG on an inet datagram socket should succeed", + ); +} + +#[test] +#[cfg(any( + target_os = "android", + target_os = "ios", + target_os = "linux", + target_os = "macos", +))] +// Disable the test under emulation because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +fn test_v6dontfrag_opts() { + let fd6 = socket( + AddressFamily::Inet6, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(fd6, sockopt::Ipv6DontFrag, &true).expect( + "setting IPV6_DONTFRAG on an inet6 stream socket should succeed", + ); + setsockopt(fd6, sockopt::Ipv6DontFrag, &false).expect( + "unsetting IPV6_DONTFRAG on an inet6 stream socket should succeed", + ); + let fd6d = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(fd6d, sockopt::Ipv6DontFrag, &true).expect( + "setting IPV6_DONTFRAG on an inet6 datagram socket should succeed", + ); + setsockopt(fd6d, sockopt::Ipv6DontFrag, &false).expect( + "unsetting IPV6_DONTFRAG on an inet6 datagram socket should succeed", + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_so_priority() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + let priority = 3; + setsockopt(fd, sockopt::Priority, &priority).unwrap(); + assert_eq!(getsockopt(fd, sockopt::Priority).unwrap(), priority); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_ip_tos() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + let tos = 0x80; // CS4 + setsockopt(fd, sockopt::IpTos, &tos).unwrap(); + assert_eq!(getsockopt(fd, sockopt::IpTos).unwrap(), tos); +} + +#[test] +#[cfg(target_os = "linux")] +// Disable the test under emulation because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +fn test_ipv6_tclass() { + let fd = socket( + AddressFamily::Inet6, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + let class = 0x80; // CS4 + setsockopt(fd, sockopt::Ipv6TClass, &class).unwrap(); + assert_eq!(getsockopt(fd, sockopt::Ipv6TClass).unwrap(), class); +} diff --git a/test/sys/test_stat.rs b/test/sys/test_stat.rs new file mode 100644 index 0000000..426b4b6 --- /dev/null +++ b/test/sys/test_stat.rs @@ -0,0 +1,29 @@ +// The conversion is not useless on all platforms. +#[allow(clippy::useless_conversion)] +#[cfg(target_os = "freebsd")] +#[test] +fn test_chflags() { + use nix::{ + sys::stat::{fstat, FileFlag}, + unistd::chflags, + }; + use std::os::unix::io::AsRawFd; + use tempfile::NamedTempFile; + + let f = NamedTempFile::new().unwrap(); + + let initial = FileFlag::from_bits_truncate( + fstat(f.as_raw_fd()).unwrap().st_flags.into(), + ); + // UF_OFFLINE is preserved by all FreeBSD file systems, but not interpreted + // in any way, so it's handy for testing. + let commanded = initial ^ FileFlag::UF_OFFLINE; + + chflags(f.path(), commanded).unwrap(); + + let changed = FileFlag::from_bits_truncate( + fstat(f.as_raw_fd()).unwrap().st_flags.into(), + ); + + assert_eq!(commanded, changed); +} diff --git a/test/sys/test_sysinfo.rs b/test/sys/test_sysinfo.rs new file mode 100644 index 0000000..2897366 --- /dev/null +++ b/test/sys/test_sysinfo.rs @@ -0,0 +1,20 @@ +use nix::sys::sysinfo::*; + +#[test] +fn sysinfo_works() { + let info = sysinfo().unwrap(); + + let (l1, l5, l15) = info.load_average(); + assert!(l1 >= 0.0); + assert!(l5 >= 0.0); + assert!(l15 >= 0.0); + + info.uptime(); // just test Duration construction + + assert!( + info.swap_free() <= info.swap_total(), + "more swap available than installed (free: {}, total: {})", + info.swap_free(), + info.swap_total() + ); +} diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs new file mode 100644 index 0000000..aaf0008 --- /dev/null +++ b/test/sys/test_termios.rs @@ -0,0 +1,136 @@ +use std::os::unix::prelude::*; +use tempfile::tempfile; + +use nix::errno::Errno; +use nix::fcntl; +use nix::pty::openpty; +use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags}; +use nix::unistd::{close, read, write}; + +/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s +fn write_all(f: RawFd, buf: &[u8]) { + let mut len = 0; + while len < buf.len() { + len += write(f, &buf[len..]).unwrap(); + } +} + +// Test tcgetattr on a terminal +#[test] +fn test_tcgetattr_pty() { + // openpty uses ptname(3) internally + let _m = crate::PTSNAME_MTX.lock(); + + let pty = openpty(None, None).expect("openpty failed"); + termios::tcgetattr(pty.slave).unwrap(); + close(pty.master).expect("closing the master failed"); + close(pty.slave).expect("closing the slave failed"); +} + +// Test tcgetattr on something that isn't a terminal +#[test] +fn test_tcgetattr_enotty() { + let file = tempfile().unwrap(); + assert_eq!( + termios::tcgetattr(file.as_raw_fd()).err(), + Some(Errno::ENOTTY) + ); +} + +// Test tcgetattr on an invalid file descriptor +#[test] +fn test_tcgetattr_ebadf() { + assert_eq!(termios::tcgetattr(-1).err(), Some(Errno::EBADF)); +} + +// Test modifying output flags +#[test] +fn test_output_flags() { + // openpty uses ptname(3) internally + let _m = crate::PTSNAME_MTX.lock(); + + // Open one pty to get attributes for the second one + let mut termios = { + let pty = openpty(None, None).expect("openpty failed"); + assert!(pty.master > 0); + assert!(pty.slave > 0); + let termios = tcgetattr(pty.slave).expect("tcgetattr failed"); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + termios + }; + + // Make sure postprocessing '\r' isn't specified by default or this test is useless. + assert!(!termios + .output_flags + .contains(OutputFlags::OPOST | OutputFlags::OCRNL)); + + // Specify that '\r' characters should be transformed to '\n' + // OPOST is specified to enable post-processing + termios + .output_flags + .insert(OutputFlags::OPOST | OutputFlags::OCRNL); + + // Open a pty + let pty = openpty(None, &termios).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + + // Write into the master + let string = "foofoofoo\r"; + write_all(pty.master, string.as_bytes()); + + // Read from the slave verifying that the output has been properly transformed + let mut buf = [0u8; 10]; + crate::read_exact(pty.slave, &mut buf); + let transformed_string = "foofoofoo\n"; + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + assert_eq!(&buf, transformed_string.as_bytes()); +} + +// Test modifying local flags +#[test] +fn test_local_flags() { + // openpty uses ptname(3) internally + let _m = crate::PTSNAME_MTX.lock(); + + // Open one pty to get attributes for the second one + let mut termios = { + let pty = openpty(None, None).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + let termios = tcgetattr(pty.slave).unwrap(); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + termios + }; + + // Make sure echo is specified by default or this test is useless. + assert!(termios.local_flags.contains(LocalFlags::ECHO)); + + // Disable local echo + termios.local_flags.remove(LocalFlags::ECHO); + + // Open a new pty with our modified termios settings + let pty = openpty(None, &termios).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + + // Set the master is in nonblocking mode or reading will never return. + let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap(); + let new_flags = + fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK; + fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap(); + + // Write into the master + let string = "foofoofoo\r"; + write_all(pty.master, string.as_bytes()); + + // Try to read from the master, which should not have anything as echoing was disabled. + let mut buf = [0u8; 10]; + let read = read(pty.master, &mut buf).unwrap_err(); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + assert_eq!(read, Errno::EAGAIN); +} diff --git a/test/sys/test_timerfd.rs b/test/sys/test_timerfd.rs new file mode 100644 index 0000000..08e2921 --- /dev/null +++ b/test/sys/test_timerfd.rs @@ -0,0 +1,69 @@ +use nix::sys::time::{TimeSpec, TimeValLike}; +use nix::sys::timerfd::{ + ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags, +}; +use std::time::Instant; + +#[test] +pub fn test_timerfd_oneshot() { + let timer = + TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); + + let before = Instant::now(); + + timer + .set( + Expiration::OneShot(TimeSpec::seconds(1)), + TimerSetTimeFlags::empty(), + ) + .unwrap(); + + timer.wait().unwrap(); + + let millis = before.elapsed().as_millis(); + assert!(millis > 900); +} + +#[test] +pub fn test_timerfd_interval() { + let timer = + TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); + + let before = Instant::now(); + timer + .set( + Expiration::IntervalDelayed( + TimeSpec::seconds(1), + TimeSpec::seconds(2), + ), + TimerSetTimeFlags::empty(), + ) + .unwrap(); + + timer.wait().unwrap(); + + let start_delay = before.elapsed().as_millis(); + assert!(start_delay > 900); + + timer.wait().unwrap(); + + let interval_delay = before.elapsed().as_millis(); + assert!(interval_delay > 2900); +} + +#[test] +pub fn test_timerfd_unset() { + let timer = + TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); + + timer + .set( + Expiration::OneShot(TimeSpec::seconds(1)), + TimerSetTimeFlags::empty(), + ) + .unwrap(); + + timer.unset().unwrap(); + + assert!(timer.get().unwrap().is_none()); +} diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs new file mode 100644 index 0000000..0f4b8a6 --- /dev/null +++ b/test/sys/test_uio.rs @@ -0,0 +1,270 @@ +use nix::sys::uio::*; +use nix::unistd::*; +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; +use std::fs::OpenOptions; +use std::io::IoSlice; +use std::os::unix::io::AsRawFd; +use std::{cmp, iter}; + +#[cfg(not(target_os = "redox"))] +use std::io::IoSliceMut; + +use tempfile::tempdir; +#[cfg(not(target_os = "redox"))] +use tempfile::tempfile; + +#[test] +fn test_writev() { + let mut to_write = Vec::with_capacity(16 * 128); + for _ in 0..16 { + let s: String = thread_rng() + .sample_iter(&Alphanumeric) + .map(char::from) + .take(128) + .collect(); + let b = s.as_bytes(); + to_write.extend(b.iter().cloned()); + } + // Allocate and fill iovecs + let mut iovecs = Vec::new(); + let mut consumed = 0; + while consumed < to_write.len() { + let left = to_write.len() - consumed; + let slice_len = if left <= 64 { + left + } else { + thread_rng().gen_range(64..cmp::min(256, left)) + }; + let b = &to_write[consumed..consumed + slice_len]; + iovecs.push(IoSlice::new(b)); + consumed += slice_len; + } + let pipe_res = pipe(); + let (reader, writer) = pipe_res.expect("Couldn't create pipe"); + // FileDesc will close its filedesc (reader). + let mut read_buf: Vec = iter::repeat(0u8).take(128 * 16).collect(); + // Blocking io, should write all data. + let write_res = writev(writer, &iovecs); + let written = write_res.expect("couldn't write"); + // Check whether we written all data + assert_eq!(to_write.len(), written); + let read_res = read(reader, &mut read_buf[..]); + let read = read_res.expect("couldn't read"); + // Check we have read as much as we written + assert_eq!(read, written); + // Check equality of written and read data + assert_eq!(&to_write, &read_buf); + close(writer).expect("closed writer"); + close(reader).expect("closed reader"); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_readv() { + let s: String = thread_rng() + .sample_iter(&Alphanumeric) + .map(char::from) + .take(128) + .collect(); + let to_write = s.as_bytes().to_vec(); + let mut storage = Vec::new(); + let mut allocated = 0; + while allocated < to_write.len() { + let left = to_write.len() - allocated; + let vec_len = if left <= 64 { + left + } else { + thread_rng().gen_range(64..cmp::min(256, left)) + }; + let v: Vec = iter::repeat(0u8).take(vec_len).collect(); + storage.push(v); + allocated += vec_len; + } + let mut iovecs = Vec::with_capacity(storage.len()); + for v in &mut storage { + iovecs.push(IoSliceMut::new(&mut v[..])); + } + let (reader, writer) = pipe().expect("couldn't create pipe"); + // Blocking io, should write all data. + write(writer, &to_write).expect("write failed"); + let read = readv(reader, &mut iovecs[..]).expect("read failed"); + // Check whether we've read all data + assert_eq!(to_write.len(), read); + // Cccumulate data from iovecs + let mut read_buf = Vec::with_capacity(to_write.len()); + for iovec in &iovecs { + read_buf.extend(iovec.iter().cloned()); + } + // Check whether iovecs contain all written data + assert_eq!(read_buf.len(), to_write.len()); + // Check equality of written and read data + assert_eq!(&read_buf, &to_write); + close(reader).expect("couldn't close reader"); + close(writer).expect("couldn't close writer"); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_pwrite() { + use std::io::Read; + + let mut file = tempfile().unwrap(); + let buf = [1u8; 8]; + assert_eq!(Ok(8), pwrite(file.as_raw_fd(), &buf, 8)); + let mut file_content = Vec::new(); + file.read_to_end(&mut file_content).unwrap(); + let mut expected = vec![0u8; 8]; + expected.extend(vec![1; 8]); + assert_eq!(file_content, expected); +} + +#[test] +fn test_pread() { + use std::io::Write; + + let tempdir = tempdir().unwrap(); + + let path = tempdir.path().join("pread_test_file"); + let mut file = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + let file_content: Vec = (0..64).collect(); + file.write_all(&file_content).unwrap(); + + let mut buf = [0u8; 16]; + assert_eq!(Ok(16), pread(file.as_raw_fd(), &mut buf, 16)); + let expected: Vec<_> = (16..32).collect(); + assert_eq!(&buf[..], &expected[..]); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_pwritev() { + use std::io::Read; + + let to_write: Vec = (0..128).collect(); + let expected: Vec = [vec![0; 100], to_write.clone()].concat(); + + let iovecs = [ + IoSlice::new(&to_write[0..17]), + IoSlice::new(&to_write[17..64]), + IoSlice::new(&to_write[64..128]), + ]; + + let tempdir = tempdir().unwrap(); + + // pwritev them into a temporary file + let path = tempdir.path().join("pwritev_test_file"); + let mut file = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + + let written = pwritev(file.as_raw_fd(), &iovecs, 100).ok().unwrap(); + assert_eq!(written, to_write.len()); + + // Read the data back and make sure it matches + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(contents, expected); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_preadv() { + use std::io::Write; + + let to_write: Vec = (0..200).collect(); + let expected: Vec = (100..200).collect(); + + let tempdir = tempdir().unwrap(); + + let path = tempdir.path().join("preadv_test_file"); + + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + file.write_all(&to_write).unwrap(); + + let mut buffers: Vec> = vec![vec![0; 24], vec![0; 1], vec![0; 75]]; + + { + // Borrow the buffers into IoVecs and preadv into them + let mut iovecs: Vec<_> = buffers + .iter_mut() + .map(|buf| IoSliceMut::new(&mut buf[..])) + .collect(); + assert_eq!(Ok(100), preadv(file.as_raw_fd(), &mut iovecs, 100)); + } + + let all = buffers.concat(); + assert_eq!(all, expected); +} + +#[test] +#[cfg(all(target_os = "linux", not(target_env = "uclibc")))] +// uclibc doesn't implement process_vm_readv +// qemu-user doesn't implement process_vm_readv/writev on most arches +#[cfg_attr(qemu, ignore)] +fn test_process_vm_readv() { + use crate::*; + use nix::sys::signal::*; + use nix::sys::wait::*; + use nix::unistd::ForkResult::*; + + require_capability!("test_process_vm_readv", CAP_SYS_PTRACE); + let _m = crate::FORK_MTX.lock(); + + // Pre-allocate memory in the child, since allocation isn't safe + // post-fork (~= async-signal-safe) + let mut vector = vec![1u8, 2, 3, 4, 5]; + + let (r, w) = pipe().unwrap(); + match unsafe { fork() }.expect("Error: Fork Failed") { + Parent { child } => { + close(w).unwrap(); + // wait for child + read(r, &mut [0u8]).unwrap(); + close(r).unwrap(); + + let ptr = vector.as_ptr() as usize; + let remote_iov = RemoteIoVec { base: ptr, len: 5 }; + let mut buf = vec![0u8; 5]; + + let ret = process_vm_readv( + child, + &mut [IoSliceMut::new(&mut buf)], + &[remote_iov], + ); + + kill(child, SIGTERM).unwrap(); + waitpid(child, None).unwrap(); + + assert_eq!(Ok(5), ret); + assert_eq!(20u8, buf.iter().sum()); + } + Child => { + let _ = close(r); + for i in &mut vector { + *i += 1; + } + let _ = write(w, b"\0"); + let _ = close(w); + loop { + pause(); + } + } + } +} diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs new file mode 100644 index 0000000..d472f1e --- /dev/null +++ b/test/sys/test_wait.rs @@ -0,0 +1,257 @@ +use libc::_exit; +use nix::errno::Errno; +use nix::sys::signal::*; +use nix::sys::wait::*; +use nix::unistd::ForkResult::*; +use nix::unistd::*; + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_wait_signal() { + let _m = crate::FORK_MTX.lock(); + + // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe. + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + pause(); + unsafe { _exit(123) } + } + Parent { child } => { + kill(child, Some(SIGKILL)).expect("Error: Kill Failed"); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Signaled(child, SIGKILL, false)) + ); + } + } +} + +#[test] +#[cfg(any( + target_os = "android", + target_os = "freebsd", + //target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] +fn test_waitid_signal() { + let _m = crate::FORK_MTX.lock(); + + // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe. + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + pause(); + unsafe { _exit(123) } + } + Parent { child } => { + kill(child, Some(SIGKILL)).expect("Error: Kill Failed"); + assert_eq!( + waitid(Id::Pid(child), WaitPidFlag::WEXITED), + Ok(WaitStatus::Signaled(child, SIGKILL, false)), + ); + } + } +} + +#[test] +fn test_wait_exit() { + let _m = crate::FORK_MTX.lock(); + + // Safe: Child only calls `_exit`, which is async-signal-safe. + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => unsafe { + _exit(12); + }, + Parent { child } => { + assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 12))); + } + } +} + +#[cfg(not(target_os = "haiku"))] +#[test] +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] +fn test_waitid_exit() { + let _m = crate::FORK_MTX.lock(); + + // Safe: Child only calls `_exit`, which is async-signal-safe. + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => unsafe { + _exit(12); + }, + Parent { child } => { + assert_eq!( + waitid(Id::Pid(child), WaitPidFlag::WEXITED), + Ok(WaitStatus::Exited(child, 12)), + ); + } + } +} + +#[test] +fn test_waitstatus_from_raw() { + let pid = Pid::from_raw(1); + assert_eq!( + WaitStatus::from_raw(pid, 0x0002), + Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)) + ); + assert_eq!( + WaitStatus::from_raw(pid, 0x0200), + Ok(WaitStatus::Exited(pid, 2)) + ); + assert_eq!(WaitStatus::from_raw(pid, 0x7f7f), Err(Errno::EINVAL)); +} + +#[test] +fn test_waitstatus_pid() { + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.unwrap() { + Child => unsafe { _exit(0) }, + Parent { child } => { + let status = waitpid(child, None).unwrap(); + assert_eq!(status.pid(), Some(child)); + } + } +} + +#[test] +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "haiku", + all(target_os = "linux", not(target_env = "uclibc")), +))] +fn test_waitid_pid() { + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.unwrap() { + Child => unsafe { _exit(0) }, + Parent { child } => { + let status = waitid(Id::Pid(child), WaitPidFlag::WEXITED).unwrap(); + assert_eq!(status.pid(), Some(child)); + } + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +// FIXME: qemu-user doesn't implement ptrace on most arches +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod ptrace { + use crate::*; + use libc::_exit; + use nix::sys::ptrace::{self, Event, Options}; + use nix::sys::signal::*; + use nix::sys::wait::*; + use nix::unistd::ForkResult::*; + use nix::unistd::*; + + fn ptrace_child() -> ! { + ptrace::traceme().unwrap(); + // As recommended by ptrace(2), raise SIGTRAP to pause the child + // until the parent is ready to continue + raise(SIGTRAP).unwrap(); + unsafe { _exit(0) } + } + + fn ptrace_wait_parent(child: Pid) { + // Wait for the raised SIGTRAP + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, SIGTRAP)) + ); + // We want to test a syscall stop and a PTRACE_EVENT stop + ptrace::setoptions( + child, + Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT, + ) + .expect("setoptions failed"); + + // First, stop on the next system call, which will be exit() + ptrace::syscall(child, None).expect("syscall failed"); + assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child))); + // Then get the ptrace event for the process exiting + ptrace::cont(child, None).expect("cont failed"); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::PtraceEvent( + child, + SIGTRAP, + Event::PTRACE_EVENT_EXIT as i32 + )) + ); + // Finally get the normal wait() result, now that the process has exited + ptrace::cont(child, None).expect("cont failed"); + assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0))); + } + + #[cfg(not(target_env = "uclibc"))] + fn ptrace_waitid_parent(child: Pid) { + // Wait for the raised SIGTRAP + // + // Unlike waitpid(), waitid() can distinguish trap events from regular + // stop events, so unlike ptrace_wait_parent(), we get a PtraceEvent here + assert_eq!( + waitid(Id::Pid(child), WaitPidFlag::WEXITED), + Ok(WaitStatus::PtraceEvent(child, SIGTRAP, 0)), + ); + // We want to test a syscall stop and a PTRACE_EVENT stop + ptrace::setoptions( + child, + Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT, + ) + .expect("setopts failed"); + + // First, stop on the next system call, which will be exit() + ptrace::syscall(child, None).expect("syscall failed"); + assert_eq!( + waitid(Id::Pid(child), WaitPidFlag::WEXITED), + Ok(WaitStatus::PtraceSyscall(child)), + ); + // Then get the ptrace event for the process exiting + ptrace::cont(child, None).expect("cont failed"); + assert_eq!( + waitid(Id::Pid(child), WaitPidFlag::WEXITED), + Ok(WaitStatus::PtraceEvent( + child, + SIGTRAP, + Event::PTRACE_EVENT_EXIT as i32 + )), + ); + // Finally get the normal wait() result, now that the process has exited + ptrace::cont(child, None).expect("cont failed"); + assert_eq!( + waitid(Id::Pid(child), WaitPidFlag::WEXITED), + Ok(WaitStatus::Exited(child, 0)), + ); + } + + #[test] + fn test_wait_ptrace() { + require_capability!("test_wait_ptrace", CAP_SYS_PTRACE); + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => ptrace_child(), + Parent { child } => ptrace_wait_parent(child), + } + } + + #[test] + #[cfg(not(target_env = "uclibc"))] + fn test_waitid_ptrace() { + require_capability!("test_waitid_ptrace", CAP_SYS_PTRACE); + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => ptrace_child(), + Parent { child } => ptrace_waitid_parent(child), + } + } +} diff --git a/test/test.rs b/test/test.rs new file mode 100644 index 0000000..6b42aad --- /dev/null +++ b/test/test.rs @@ -0,0 +1,124 @@ +#[macro_use] +extern crate cfg_if; +#[cfg_attr(not(any(target_os = "redox", target_os = "haiku")), macro_use)] +extern crate nix; +#[macro_use] +extern crate lazy_static; + +mod common; +mod sys; +#[cfg(not(target_os = "redox"))] +mod test_dir; +mod test_fcntl; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod test_kmod; +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fushsia", + target_os = "linux", + target_os = "netbsd" +))] +mod test_mq; +#[cfg(not(target_os = "redox"))] +mod test_net; +mod test_nix_path; +#[cfg(target_os = "freebsd")] +mod test_nmount; +mod test_poll; +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +mod test_pty; +mod test_resource; +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + all(target_os = "freebsd", fbsd14), + target_os = "linux" +))] +mod test_sched; +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos" +))] +mod test_sendfile; +mod test_stat; +mod test_time; +#[cfg(all( + any( + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" +))] +mod test_timer; +mod test_unistd; + +use nix::unistd::{chdir, getcwd, read}; +use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; +use std::os::unix::io::RawFd; +use std::path::PathBuf; + +/// Helper function analogous to `std::io::Read::read_exact`, but for `RawFD`s +fn read_exact(f: RawFd, buf: &mut [u8]) { + let mut len = 0; + while len < buf.len() { + // get_mut would be better than split_at_mut, but it requires nightly + let (_, remaining) = buf.split_at_mut(len); + len += read(f, remaining).unwrap(); + } +} + +lazy_static! { + /// Any test that changes the process's current working directory must grab + /// the RwLock exclusively. Any process that cares about the current + /// working directory must grab it shared. + pub static ref CWD_LOCK: RwLock<()> = RwLock::new(()); + /// Any test that creates child processes must grab this mutex, regardless + /// of what it does with those children. + pub static ref FORK_MTX: Mutex<()> = Mutex::new(()); + /// Any test that changes the process's supplementary groups must grab this + /// mutex + pub static ref GROUPS_MTX: Mutex<()> = Mutex::new(()); + /// Any tests that loads or unloads kernel modules must grab this mutex + pub static ref KMOD_MTX: Mutex<()> = Mutex::new(()); + /// Any test that calls ptsname(3) must grab this mutex. + pub static ref PTSNAME_MTX: Mutex<()> = Mutex::new(()); + /// Any test that alters signal handling must grab this mutex. + pub static ref SIGNAL_MTX: Mutex<()> = Mutex::new(()); +} + +/// RAII object that restores a test's original directory on drop +struct DirRestore<'a> { + d: PathBuf, + _g: RwLockWriteGuard<'a, ()>, +} + +impl<'a> DirRestore<'a> { + fn new() -> Self { + let guard = crate::CWD_LOCK.write(); + DirRestore { + _g: guard, + d: getcwd().unwrap(), + } + } +} + +impl<'a> Drop for DirRestore<'a> { + fn drop(&mut self) { + let r = chdir(&self.d); + if std::thread::panicking() { + r.unwrap(); + } + } +} diff --git a/test/test_clearenv.rs b/test/test_clearenv.rs new file mode 100644 index 0000000..28a7768 --- /dev/null +++ b/test/test_clearenv.rs @@ -0,0 +1,9 @@ +use std::env; + +#[test] +fn clearenv() { + env::set_var("FOO", "BAR"); + unsafe { nix::env::clearenv() }.unwrap(); + assert_eq!(env::var("FOO").unwrap_err(), env::VarError::NotPresent); + assert_eq!(env::vars().count(), 0); +} diff --git a/test/test_dir.rs b/test/test_dir.rs new file mode 100644 index 0000000..2af4aa5 --- /dev/null +++ b/test/test_dir.rs @@ -0,0 +1,65 @@ +use nix::dir::{Dir, Type}; +use nix::fcntl::OFlag; +use nix::sys::stat::Mode; +use std::fs::File; +use tempfile::tempdir; + +#[cfg(test)] +fn flags() -> OFlag { + #[cfg(target_os = "illumos")] + let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC; + + #[cfg(not(target_os = "illumos"))] + let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_DIRECTORY; + + f +} + +#[test] +#[allow(clippy::unnecessary_sort_by)] // False positive +fn read() { + let tmp = tempdir().unwrap(); + File::create(tmp.path().join("foo")).unwrap(); + std::os::unix::fs::symlink("foo", tmp.path().join("bar")).unwrap(); + let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap(); + let mut entries: Vec<_> = dir.iter().map(|e| e.unwrap()).collect(); + entries.sort_by(|a, b| a.file_name().cmp(b.file_name())); + let entry_names: Vec<_> = entries + .iter() + .map(|e| e.file_name().to_str().unwrap().to_owned()) + .collect(); + assert_eq!(&entry_names[..], &[".", "..", "bar", "foo"]); + + // Check file types. The system is allowed to return DT_UNKNOWN (aka None here) but if it does + // return a type, ensure it's correct. + assert!(&[Some(Type::Directory), None].contains(&entries[0].file_type())); // .: dir + assert!(&[Some(Type::Directory), None].contains(&entries[1].file_type())); // ..: dir + assert!(&[Some(Type::Symlink), None].contains(&entries[2].file_type())); // bar: symlink + assert!(&[Some(Type::File), None].contains(&entries[3].file_type())); // foo: regular file +} + +#[test] +fn rewind() { + let tmp = tempdir().unwrap(); + let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap(); + let entries1: Vec<_> = dir + .iter() + .map(|e| e.unwrap().file_name().to_owned()) + .collect(); + let entries2: Vec<_> = dir + .iter() + .map(|e| e.unwrap().file_name().to_owned()) + .collect(); + let entries3: Vec<_> = dir + .into_iter() + .map(|e| e.unwrap().file_name().to_owned()) + .collect(); + assert_eq!(entries1, entries2); + assert_eq!(entries2, entries3); +} + +#[cfg(not(target_os = "haiku"))] +#[test] +fn ebadf() { + assert_eq!(Dir::from_fd(-1).unwrap_err(), nix::Error::EBADF); +} diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs new file mode 100644 index 0000000..e51044a --- /dev/null +++ b/test/test_fcntl.rs @@ -0,0 +1,565 @@ +#[cfg(not(target_os = "redox"))] +use nix::errno::*; +#[cfg(not(target_os = "redox"))] +use nix::fcntl::{open, readlink, OFlag}; +#[cfg(not(target_os = "redox"))] +use nix::fcntl::{openat, readlinkat, renameat}; +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +use nix::fcntl::{renameat2, RenameFlags}; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::Mode; +#[cfg(not(target_os = "redox"))] +use nix::unistd::{close, read}; +#[cfg(not(target_os = "redox"))] +use std::fs::File; +#[cfg(not(target_os = "redox"))] +use std::io::prelude::*; +#[cfg(not(target_os = "redox"))] +use std::os::unix::fs; +#[cfg(not(target_os = "redox"))] +use tempfile::{self, NamedTempFile}; + +#[test] +#[cfg(not(target_os = "redox"))] +// QEMU does not handle openat well enough to satisfy this test +// https://gitlab.com/qemu-project/qemu/-/issues/829 +#[cfg_attr(qemu, ignore)] +fn test_openat() { + const CONTENTS: &[u8] = b"abcd"; + let mut tmp = NamedTempFile::new().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + + let dirfd = + open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty()) + .unwrap(); + let fd = openat( + dirfd, + tmp.path().file_name().unwrap(), + OFlag::O_RDONLY, + Mode::empty(), + ) + .unwrap(); + + let mut buf = [0u8; 1024]; + assert_eq!(4, read(fd, &mut buf).unwrap()); + assert_eq!(CONTENTS, &buf[0..4]); + + close(fd).unwrap(); + close(dirfd).unwrap(); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_renameat() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = + open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + File::create(old_path).unwrap(); + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = + open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap(); + assert_eq!( + renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(), + Errno::ENOENT + ); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); + assert!(new_dir.path().join("new").exists()); +} + +#[test] +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +fn test_renameat2_behaves_like_renameat_with_no_flags() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = + open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + File::create(old_path).unwrap(); + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = + open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::empty(), + ) + .unwrap(); + assert_eq!( + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::empty() + ) + .unwrap_err(), + Errno::ENOENT + ); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); + assert!(new_dir.path().join("new").exists()); +} + +#[test] +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +fn test_renameat2_exchange() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = + open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + { + let mut old_f = File::create(&old_path).unwrap(); + old_f.write_all(b"old").unwrap(); + } + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = + open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let new_path = new_dir.path().join("new"); + { + let mut new_f = File::create(&new_path).unwrap(); + new_f.write_all(b"new").unwrap(); + } + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::RENAME_EXCHANGE, + ) + .unwrap(); + let mut buf = String::new(); + let mut new_f = File::open(&new_path).unwrap(); + new_f.read_to_string(&mut buf).unwrap(); + assert_eq!(buf, "old"); + buf = "".to_string(); + let mut old_f = File::open(&old_path).unwrap(); + old_f.read_to_string(&mut buf).unwrap(); + assert_eq!(buf, "new"); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); +} + +#[test] +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x32", + target_arch = "powerpc", + target_arch = "s390x" + ) +))] +fn test_renameat2_noreplace() { + let old_dir = tempfile::tempdir().unwrap(); + let old_dirfd = + open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let old_path = old_dir.path().join("old"); + File::create(old_path).unwrap(); + let new_dir = tempfile::tempdir().unwrap(); + let new_dirfd = + open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let new_path = new_dir.path().join("new"); + File::create(new_path).unwrap(); + assert_eq!( + renameat2( + Some(old_dirfd), + "old", + Some(new_dirfd), + "new", + RenameFlags::RENAME_NOREPLACE + ) + .unwrap_err(), + Errno::EEXIST + ); + close(old_dirfd).unwrap(); + close(new_dirfd).unwrap(); + assert!(new_dir.path().join("new").exists()); + assert!(old_dir.path().join("old").exists()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_readlink() { + let tempdir = tempfile::tempdir().unwrap(); + let src = tempdir.path().join("a"); + let dst = tempdir.path().join("b"); + println!("a: {:?}, b: {:?}", &src, &dst); + fs::symlink(src.as_path(), dst.as_path()).unwrap(); + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let expected_dir = src.to_str().unwrap(); + + assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir); + assert_eq!( + readlinkat(dirfd, "b").unwrap().to_str().unwrap(), + expected_dir + ); +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod linux_android { + use libc::loff_t; + use std::io::prelude::*; + use std::io::IoSlice; + use std::os::unix::prelude::*; + + use nix::fcntl::*; + use nix::unistd::{close, pipe, read, write}; + + use tempfile::tempfile; + #[cfg(any(target_os = "linux"))] + use tempfile::NamedTempFile; + + use crate::*; + + /// This test creates a temporary file containing the contents + /// 'foobarbaz' and uses the `copy_file_range` call to transfer + /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The + /// resulting file is read and should contain the contents `bar`. + /// The from_offset should be updated by the call to reflect + /// the 3 bytes read (6). + #[test] + // QEMU does not support copy_file_range. Skip under qemu + #[cfg_attr(qemu, ignore)] + fn test_copy_file_range() { + const CONTENTS: &[u8] = b"foobarbaz"; + + let mut tmp1 = tempfile().unwrap(); + let mut tmp2 = tempfile().unwrap(); + + tmp1.write_all(CONTENTS).unwrap(); + tmp1.flush().unwrap(); + + let mut from_offset: i64 = 3; + copy_file_range( + tmp1.as_raw_fd(), + Some(&mut from_offset), + tmp2.as_raw_fd(), + None, + 3, + ) + .unwrap(); + + let mut res: String = String::new(); + tmp2.rewind().unwrap(); + tmp2.read_to_string(&mut res).unwrap(); + + assert_eq!(res, String::from("bar")); + assert_eq!(from_offset, 6); + } + + #[test] + fn test_splice() { + const CONTENTS: &[u8] = b"abcdef123456"; + let mut tmp = tempfile().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + + let (rd, wr) = pipe().unwrap(); + let mut offset: loff_t = 5; + let res = splice( + tmp.as_raw_fd(), + Some(&mut offset), + wr, + None, + 2, + SpliceFFlags::empty(), + ) + .unwrap(); + + assert_eq!(2, res); + + let mut buf = [0u8; 1024]; + assert_eq!(2, read(rd, &mut buf).unwrap()); + assert_eq!(b"f1", &buf[0..2]); + assert_eq!(7, offset); + + close(rd).unwrap(); + close(wr).unwrap(); + } + + #[test] + fn test_tee() { + let (rd1, wr1) = pipe().unwrap(); + let (rd2, wr2) = pipe().unwrap(); + + write(wr1, b"abc").unwrap(); + let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap(); + + assert_eq!(2, res); + + let mut buf = [0u8; 1024]; + + // Check the tee'd bytes are at rd2. + assert_eq!(2, read(rd2, &mut buf).unwrap()); + assert_eq!(b"ab", &buf[0..2]); + + // Check all the bytes are still at rd1. + assert_eq!(3, read(rd1, &mut buf).unwrap()); + assert_eq!(b"abc", &buf[0..3]); + + close(rd1).unwrap(); + close(wr1).unwrap(); + close(rd2).unwrap(); + close(wr2).unwrap(); + } + + #[test] + fn test_vmsplice() { + let (rd, wr) = pipe().unwrap(); + + let buf1 = b"abcdef"; + let buf2 = b"defghi"; + let iovecs = vec![IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])]; + + let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap(); + + assert_eq!(6, res); + + // Check the bytes can be read at rd. + let mut buf = [0u8; 32]; + assert_eq!(6, read(rd, &mut buf).unwrap()); + assert_eq!(b"abcdef", &buf[0..6]); + + close(rd).unwrap(); + close(wr).unwrap(); + } + + #[cfg(any(target_os = "linux"))] + #[test] + fn test_fallocate() { + let tmp = NamedTempFile::new().unwrap(); + + let fd = tmp.as_raw_fd(); + fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap(); + + // Check if we read exactly 100 bytes + let mut buf = [0u8; 200]; + assert_eq!(100, read(fd, &mut buf).unwrap()); + } + + // The tests below are disabled for the listed targets + // due to OFD locks not being available in the kernel/libc + // versions used in the CI environment, probably because + // they run under QEMU. + + #[test] + #[cfg(all(target_os = "linux", not(target_env = "musl")))] + #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile + fn test_ofd_write_lock() { + use nix::sys::stat::fstat; + use std::mem; + + let tmp = NamedTempFile::new().unwrap(); + + let fd = tmp.as_raw_fd(); + let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); + if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { + // OverlayFS is a union file system. It returns one inode value in + // stat(2), but a different one shows up in /proc/locks. So we must + // skip the test. + skip!("/proc/locks does not work on overlayfs"); + } + let inode = fstat(fd).expect("fstat failed").st_ino as usize; + + let mut flock: libc::flock = unsafe { + mem::zeroed() // required for Linux/mips + }; + flock.l_type = libc::F_WRLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + flock.l_start = 0; + flock.l_len = 0; + flock.l_pid = 0; + fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed"); + assert_eq!( + Some(("OFDLCK".to_string(), "WRITE".to_string())), + lock_info(inode) + ); + + flock.l_type = libc::F_UNLCK as libc::c_short; + fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed"); + assert_eq!(None, lock_info(inode)); + } + + #[test] + #[cfg(all(target_os = "linux", not(target_env = "musl")))] + #[cfg_attr(target_env = "uclibc", ignore)] // uclibc doesn't support OFD locks, but the test should still compile + fn test_ofd_read_lock() { + use nix::sys::stat::fstat; + use std::mem; + + let tmp = NamedTempFile::new().unwrap(); + + let fd = tmp.as_raw_fd(); + let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); + if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC { + // OverlayFS is a union file system. It returns one inode value in + // stat(2), but a different one shows up in /proc/locks. So we must + // skip the test. + skip!("/proc/locks does not work on overlayfs"); + } + let inode = fstat(fd).expect("fstat failed").st_ino as usize; + + let mut flock: libc::flock = unsafe { + mem::zeroed() // required for Linux/mips + }; + flock.l_type = libc::F_RDLCK as libc::c_short; + flock.l_whence = libc::SEEK_SET as libc::c_short; + flock.l_start = 0; + flock.l_len = 0; + flock.l_pid = 0; + fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed"); + assert_eq!( + Some(("OFDLCK".to_string(), "READ".to_string())), + lock_info(inode) + ); + + flock.l_type = libc::F_UNLCK as libc::c_short; + fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed"); + assert_eq!(None, lock_info(inode)); + } + + #[cfg(all(target_os = "linux", not(target_env = "musl")))] + fn lock_info(inode: usize) -> Option<(String, String)> { + use std::{fs::File, io::BufReader}; + + let file = File::open("/proc/locks").expect("open /proc/locks failed"); + let buf = BufReader::new(file); + + for line in buf.lines() { + let line = line.unwrap(); + let parts: Vec<_> = line.split_whitespace().collect(); + let lock_type = parts[1]; + let lock_access = parts[3]; + let ino_parts: Vec<_> = parts[5].split(':').collect(); + let ino: usize = ino_parts[2].parse().unwrap(); + if ino == inode { + return Some((lock_type.to_string(), lock_access.to_string())); + } + } + None + } +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "wasi", + target_env = "uclibc", + target_os = "freebsd" +))] +mod test_posix_fadvise { + + use nix::errno::Errno; + use nix::fcntl::*; + use nix::unistd::pipe; + use std::os::unix::io::{AsRawFd, RawFd}; + use tempfile::NamedTempFile; + + #[test] + fn test_success() { + let tmp = NamedTempFile::new().unwrap(); + let fd = tmp.as_raw_fd(); + posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED) + .expect("posix_fadvise failed"); + } + + #[test] + fn test_errno() { + let (rd, _wr) = pipe().unwrap(); + let res = posix_fadvise( + rd as RawFd, + 0, + 100, + PosixFadviseAdvice::POSIX_FADV_WILLNEED, + ); + assert_eq!(res, Err(Errno::ESPIPE)); + } +} + +#[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "wasi", + target_os = "freebsd" +))] +mod test_posix_fallocate { + + use nix::errno::Errno; + use nix::fcntl::*; + use nix::unistd::pipe; + use std::{ + io::Read, + os::unix::io::{AsRawFd, RawFd}, + }; + use tempfile::NamedTempFile; + + #[test] + fn success() { + const LEN: usize = 100; + let mut tmp = NamedTempFile::new().unwrap(); + let fd = tmp.as_raw_fd(); + let res = posix_fallocate(fd, 0, LEN as libc::off_t); + match res { + Ok(_) => { + let mut data = [1u8; LEN]; + assert_eq!(tmp.read(&mut data).expect("read failure"), LEN); + assert_eq!(&data[..], &[0u8; LEN][..]); + } + Err(Errno::EINVAL) => { + // POSIX requires posix_fallocate to return EINVAL both for + // invalid arguments (i.e. len < 0) and if the operation is not + // supported by the file system. + // There's no way to tell for sure whether the file system + // supports posix_fallocate, so we must pass the test if it + // returns EINVAL. + } + _ => res.unwrap(), + } + } + + #[test] + fn errno() { + let (rd, _wr) = pipe().unwrap(); + let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err(); + match err { + Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (), + errno => panic!("unexpected errno {}", errno,), + } + } +} diff --git a/test/test_kmod/hello_mod/Makefile b/test/test_kmod/hello_mod/Makefile new file mode 100644 index 0000000..74c99b7 --- /dev/null +++ b/test/test_kmod/hello_mod/Makefile @@ -0,0 +1,7 @@ +obj-m += hello.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean diff --git a/test/test_kmod/hello_mod/hello.c b/test/test_kmod/hello_mod/hello.c new file mode 100644 index 0000000..1c34987 --- /dev/null +++ b/test/test_kmod/hello_mod/hello.c @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: GPL-2.0+ or MIT + */ +#include +#include + +static int number= 1; +static char *who = "World"; + +module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(myint, "Just some number"); +module_param(who, charp, 0000); +MODULE_PARM_DESC(who, "Whot to greet"); + +int init_module(void) +{ + printk(KERN_INFO "Hello %s (%d)!\n", who, number); + return 0; +} + +void cleanup_module(void) +{ + printk(KERN_INFO "Goodbye %s (%d)!\n", who, number); +} + +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/test/test_kmod/mod.rs b/test/test_kmod/mod.rs new file mode 100644 index 0000000..6f9aaa8 --- /dev/null +++ b/test/test_kmod/mod.rs @@ -0,0 +1,188 @@ +use crate::*; +use std::fs::copy; +use std::path::PathBuf; +use std::process::Command; +use tempfile::{tempdir, TempDir}; + +fn compile_kernel_module() -> (PathBuf, String, TempDir) { + let _m = crate::FORK_MTX.lock(); + + let tmp_dir = + tempdir().expect("unable to create temporary build directory"); + + copy( + "test/test_kmod/hello_mod/hello.c", + tmp_dir.path().join("hello.c"), + ) + .expect("unable to copy hello.c to temporary build directory"); + copy( + "test/test_kmod/hello_mod/Makefile", + tmp_dir.path().join("Makefile"), + ) + .expect("unable to copy Makefile to temporary build directory"); + + let status = Command::new("make") + .current_dir(tmp_dir.path()) + .status() + .expect("failed to run make"); + + assert!(status.success()); + + // Return the relative path of the build kernel module + (tmp_dir.path().join("hello.ko"), "hello".to_owned(), tmp_dir) +} + +use nix::errno::Errno; +use nix::kmod::{delete_module, DeleteModuleFlags}; +use nix::kmod::{finit_module, init_module, ModuleInitFlags}; +use std::ffi::CString; +use std::fs::File; +use std::io::Read; + +#[test] +fn test_finit_and_delete_module() { + require_capability!("test_finit_and_delete_module", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let f = File::open(kmod_path).expect("unable to open kernel module"); + finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()) + .expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ) + .expect("unable to unload kernel module"); +} + +#[test] +fn test_finit_and_delete_module_with_params() { + require_capability!( + "test_finit_and_delete_module_with_params", + CAP_SYS_MODULE + ); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let f = File::open(kmod_path).expect("unable to open kernel module"); + finit_module( + &f, + &CString::new("who=Rust number=2018").unwrap(), + ModuleInitFlags::empty(), + ) + .expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ) + .expect("unable to unload kernel module"); +} + +#[test] +fn test_init_and_delete_module() { + require_capability!("test_init_and_delete_module", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let mut f = File::open(kmod_path).expect("unable to open kernel module"); + let mut contents: Vec = Vec::new(); + f.read_to_end(&mut contents) + .expect("unable to read kernel module content to buffer"); + init_module(&contents, &CString::new("").unwrap()) + .expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ) + .expect("unable to unload kernel module"); +} + +#[test] +fn test_init_and_delete_module_with_params() { + require_capability!( + "test_init_and_delete_module_with_params", + CAP_SYS_MODULE + ); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let mut f = File::open(kmod_path).expect("unable to open kernel module"); + let mut contents: Vec = Vec::new(); + f.read_to_end(&mut contents) + .expect("unable to read kernel module content to buffer"); + init_module(&contents, &CString::new("who=Nix number=2015").unwrap()) + .expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ) + .expect("unable to unload kernel module"); +} + +#[test] +fn test_finit_module_invalid() { + require_capability!("test_finit_module_invalid", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let kmod_path = "/dev/zero"; + + let f = File::open(kmod_path).expect("unable to open kernel module"); + let result = + finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); + + assert_eq!(result.unwrap_err(), Errno::EINVAL); +} + +#[test] +fn test_finit_module_twice_and_delete_module() { + require_capability!( + "test_finit_module_twice_and_delete_module", + CAP_SYS_MODULE + ); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let f = File::open(kmod_path).expect("unable to open kernel module"); + finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()) + .expect("unable to load kernel module"); + + let result = + finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); + + assert_eq!(result.unwrap_err(), Errno::EEXIST); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ) + .expect("unable to unload kernel module"); +} + +#[test] +fn test_delete_module_not_loaded() { + require_capability!("test_delete_module_not_loaded", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); + + let result = delete_module( + &CString::new("hello").unwrap(), + DeleteModuleFlags::empty(), + ); + + assert_eq!(result.unwrap_err(), Errno::ENOENT); +} diff --git a/test/test_mount.rs b/test/test_mount.rs new file mode 100644 index 0000000..2fd612e --- /dev/null +++ b/test/test_mount.rs @@ -0,0 +1,271 @@ +mod common; + +// Implementation note: to allow unprivileged users to run it, this test makes +// use of user and mount namespaces. On systems that allow unprivileged user +// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run +// without root. + +#[cfg(target_os = "linux")] +mod test_mount { + use std::fs::{self, File}; + use std::io::{self, Read, Write}; + use std::os::unix::fs::OpenOptionsExt; + use std::os::unix::fs::PermissionsExt; + use std::process::{self, Command}; + + use libc::{EACCES, EROFS}; + + use nix::errno::Errno; + use nix::mount::{mount, umount, MsFlags}; + use nix::sched::{unshare, CloneFlags}; + use nix::sys::stat::{self, Mode}; + use nix::unistd::getuid; + + static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh +exit 23"; + + const EXPECTED_STATUS: i32 = 23; + + const NONE: Option<&'static [u8]> = None; + #[allow(clippy::bind_instead_of_map)] // False positive + pub fn test_mount_tmpfs_without_flags_allows_rwx() { + let tempdir = tempfile::tempdir().unwrap(); + + mount( + NONE, + tempdir.path(), + Some(b"tmpfs".as_ref()), + MsFlags::empty(), + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {}", e)); + + let test_path = tempdir.path().join("test"); + + // Verify write. + fs::OpenOptions::new() + .create(true) + .write(true) + .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) + .open(&test_path) + .or_else(|e| { + if Errno::from_i32(e.raw_os_error().unwrap()) + == Errno::EOVERFLOW + { + // Skip tests on certain Linux kernels which have a bug + // regarding tmpfs in namespaces. + // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is + // not. There is no legitimate reason for open(2) to return + // EOVERFLOW here. + // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087 + let stderr = io::stderr(); + let mut handle = stderr.lock(); + writeln!( + handle, + "Buggy Linux kernel detected. Skipping test." + ) + .unwrap(); + process::exit(0); + } else { + panic!("open failed: {}", e); + } + }) + .and_then(|mut f| f.write(SCRIPT_CONTENTS)) + .unwrap_or_else(|e| panic!("write failed: {}", e)); + + // Verify read. + let mut buf = Vec::new(); + File::open(&test_path) + .and_then(|mut f| f.read_to_end(&mut buf)) + .unwrap_or_else(|e| panic!("read failed: {}", e)); + assert_eq!(buf, SCRIPT_CONTENTS); + + // Verify execute. + assert_eq!( + EXPECTED_STATUS, + Command::new(&test_path) + .status() + .unwrap_or_else(|e| panic!("exec failed: {}", e)) + .code() + .unwrap_or_else(|| panic!("child killed by signal")) + ); + + umount(tempdir.path()) + .unwrap_or_else(|e| panic!("umount failed: {}", e)); + } + + pub fn test_mount_rdonly_disallows_write() { + let tempdir = tempfile::tempdir().unwrap(); + + mount( + NONE, + tempdir.path(), + Some(b"tmpfs".as_ref()), + MsFlags::MS_RDONLY, + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {}", e)); + + // EROFS: Read-only file system + assert_eq!( + EROFS, + File::create(tempdir.path().join("test")) + .unwrap_err() + .raw_os_error() + .unwrap() + ); + + umount(tempdir.path()) + .unwrap_or_else(|e| panic!("umount failed: {}", e)); + } + + pub fn test_mount_noexec_disallows_exec() { + let tempdir = tempfile::tempdir().unwrap(); + + mount( + NONE, + tempdir.path(), + Some(b"tmpfs".as_ref()), + MsFlags::MS_NOEXEC, + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {}", e)); + + let test_path = tempdir.path().join("test"); + + fs::OpenOptions::new() + .create(true) + .write(true) + .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) + .open(&test_path) + .and_then(|mut f| f.write(SCRIPT_CONTENTS)) + .unwrap_or_else(|e| panic!("write failed: {}", e)); + + // Verify that we cannot execute despite a+x permissions being set. + let mode = stat::Mode::from_bits_truncate( + fs::metadata(&test_path) + .map(|md| md.permissions().mode()) + .unwrap_or_else(|e| panic!("metadata failed: {}", e)), + ); + + assert!( + mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH), + "{:?} did not have execute permissions", + &test_path + ); + + // EACCES: Permission denied + assert_eq!( + EACCES, + Command::new(&test_path) + .status() + .unwrap_err() + .raw_os_error() + .unwrap() + ); + + umount(tempdir.path()) + .unwrap_or_else(|e| panic!("umount failed: {}", e)); + } + + pub fn test_mount_bind() { + let tempdir = tempfile::tempdir().unwrap(); + let file_name = "test"; + + { + let mount_point = tempfile::tempdir().unwrap(); + + mount( + Some(tempdir.path()), + mount_point.path(), + NONE, + MsFlags::MS_BIND, + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {}", e)); + + fs::OpenOptions::new() + .create(true) + .write(true) + .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) + .open(mount_point.path().join(file_name)) + .and_then(|mut f| f.write(SCRIPT_CONTENTS)) + .unwrap_or_else(|e| panic!("write failed: {}", e)); + + umount(mount_point.path()) + .unwrap_or_else(|e| panic!("umount failed: {}", e)); + } + + // Verify the file written in the mount shows up in source directory, even + // after unmounting. + + let mut buf = Vec::new(); + File::open(tempdir.path().join(file_name)) + .and_then(|mut f| f.read_to_end(&mut buf)) + .unwrap_or_else(|e| panic!("read failed: {}", e)); + assert_eq!(buf, SCRIPT_CONTENTS); + } + + pub fn setup_namespaces() { + // Hold on to the uid in the parent namespace. + let uid = getuid(); + + unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| { + let stderr = io::stderr(); + let mut handle = stderr.lock(); + writeln!(handle, + "unshare failed: {}. Are unprivileged user namespaces available?", + e).unwrap(); + writeln!(handle, "mount is not being tested").unwrap(); + // Exit with success because not all systems support unprivileged user namespaces, and + // that's not what we're testing for. + process::exit(0); + }); + + // Map user as uid 1000. + fs::OpenOptions::new() + .write(true) + .open("/proc/self/uid_map") + .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes())) + .unwrap_or_else(|e| panic!("could not write uid map: {}", e)); + } +} + +// Test runner + +/// Mimic normal test output (hackishly). +#[cfg(target_os = "linux")] +macro_rules! run_tests { + ( $($test_fn:ident),* ) => {{ + println!(); + + $( + print!("test test_mount::{} ... ", stringify!($test_fn)); + $test_fn(); + println!("ok"); + )* + + println!(); + }} +} + +#[cfg(target_os = "linux")] +fn main() { + use test_mount::{ + setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec, + test_mount_rdonly_disallows_write, + test_mount_tmpfs_without_flags_allows_rwx, + }; + skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351"); + setup_namespaces(); + + run_tests!( + test_mount_tmpfs_without_flags_allows_rwx, + test_mount_rdonly_disallows_write, + test_mount_noexec_disallows_exec, + test_mount_bind + ); +} + +#[cfg(not(target_os = "linux"))] +fn main() {} diff --git a/test/test_mq.rs b/test/test_mq.rs new file mode 100644 index 0000000..7b48e7a --- /dev/null +++ b/test/test_mq.rs @@ -0,0 +1,190 @@ +use cfg_if::cfg_if; +use std::ffi::CString; +use std::str; + +use nix::errno::Errno; +use nix::mqueue::{mq_attr_member_t, mq_close, mq_open, mq_receive, mq_send}; +use nix::mqueue::{MQ_OFlag, MqAttr}; +use nix::sys::stat::Mode; + +// Defined as a macro such that the error source is reported as the caller's location. +macro_rules! assert_attr_eq { + ($read_attr:ident, $initial_attr:ident) => { + cfg_if! { + if #[cfg(any(target_os = "dragonfly", target_os = "netbsd"))] { + // NetBSD (and others which inherit its implementation) include other flags + // in read_attr, such as those specified by oflag. Just make sure at least + // the correct bits are set. + assert_eq!($read_attr.flags() & $initial_attr.flags(), $initial_attr.flags()); + assert_eq!($read_attr.maxmsg(), $initial_attr.maxmsg()); + assert_eq!($read_attr.msgsize(), $initial_attr.msgsize()); + assert_eq!($read_attr.curmsgs(), $initial_attr.curmsgs()); + } else { + assert_eq!($read_attr, $initial_attr); + } + } + } +} + +#[test] +fn test_mq_send_and_receive() { + const MSG_SIZE: mq_attr_member_t = 32; + let attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = &CString::new(b"/a_nix_test_queue".as_ref()).unwrap(); + + let oflag0 = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; + let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; + let r0 = mq_open(mq_name, oflag0, mode, Some(&attr)); + if let Err(Errno::ENOSYS) = r0 { + println!("message queues not supported or module not loaded?"); + return; + }; + let mqd0 = r0.unwrap(); + let msg_to_send = "msg_1"; + mq_send(&mqd0, msg_to_send.as_bytes(), 1).unwrap(); + + let oflag1 = MQ_OFlag::O_CREAT | MQ_OFlag::O_RDONLY; + let mqd1 = mq_open(mq_name, oflag1, mode, Some(&attr)).unwrap(); + let mut buf = [0u8; 32]; + let mut prio = 0u32; + let len = mq_receive(&mqd1, &mut buf, &mut prio).unwrap(); + assert_eq!(prio, 1); + + mq_close(mqd1).unwrap(); + mq_close(mqd0).unwrap(); + assert_eq!(msg_to_send, str::from_utf8(&buf[0..len]).unwrap()); +} + +#[test] +fn test_mq_getattr() { + use nix::mqueue::mq_getattr; + const MSG_SIZE: mq_attr_member_t = 32; + let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap(); + let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; + let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; + let r = mq_open(mq_name, oflag, mode, Some(&initial_attr)); + if let Err(Errno::ENOSYS) = r { + println!("message queues not supported or module not loaded?"); + return; + }; + let mqd = r.unwrap(); + + let read_attr = mq_getattr(&mqd).unwrap(); + assert_attr_eq!(read_attr, initial_attr); + mq_close(mqd).unwrap(); +} + +// FIXME: Fix failures for mips in QEMU +#[test] +#[cfg_attr( + all(qemu, any(target_arch = "mips", target_arch = "mips64")), + ignore +)] +fn test_mq_setattr() { + use nix::mqueue::{mq_getattr, mq_setattr}; + const MSG_SIZE: mq_attr_member_t = 32; + let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap(); + let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; + let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; + let r = mq_open(mq_name, oflag, mode, Some(&initial_attr)); + if let Err(Errno::ENOSYS) = r { + println!("message queues not supported or module not loaded?"); + return; + }; + let mqd = r.unwrap(); + + let new_attr = MqAttr::new(0, 20, MSG_SIZE * 2, 100); + let old_attr = mq_setattr(&mqd, &new_attr).unwrap(); + assert_attr_eq!(old_attr, initial_attr); + + // No changes here because according to the Linux man page only + // O_NONBLOCK can be set (see tests below) + #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] + { + let new_attr_get = mq_getattr(&mqd).unwrap(); + assert_ne!(new_attr_get, new_attr); + } + + let new_attr_non_blocking = MqAttr::new( + MQ_OFlag::O_NONBLOCK.bits() as mq_attr_member_t, + 10, + MSG_SIZE, + 0, + ); + mq_setattr(&mqd, &new_attr_non_blocking).unwrap(); + let new_attr_get = mq_getattr(&mqd).unwrap(); + + // now the O_NONBLOCK flag has been set + #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] + { + assert_ne!(new_attr_get, initial_attr); + } + assert_attr_eq!(new_attr_get, new_attr_non_blocking); + mq_close(mqd).unwrap(); +} + +// FIXME: Fix failures for mips in QEMU +#[test] +#[cfg_attr( + all(qemu, any(target_arch = "mips", target_arch = "mips64")), + ignore +)] +fn test_mq_set_nonblocking() { + use nix::mqueue::{mq_getattr, mq_remove_nonblock, mq_set_nonblock}; + const MSG_SIZE: mq_attr_member_t = 32; + let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap(); + let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; + let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; + let r = mq_open(mq_name, oflag, mode, Some(&initial_attr)); + if let Err(Errno::ENOSYS) = r { + println!("message queues not supported or module not loaded?"); + return; + }; + let mqd = r.unwrap(); + mq_set_nonblock(&mqd).unwrap(); + let new_attr = mq_getattr(&mqd); + let o_nonblock_bits = MQ_OFlag::O_NONBLOCK.bits() as mq_attr_member_t; + assert_eq!(new_attr.unwrap().flags() & o_nonblock_bits, o_nonblock_bits); + mq_remove_nonblock(&mqd).unwrap(); + let new_attr = mq_getattr(&mqd); + assert_eq!(new_attr.unwrap().flags() & o_nonblock_bits, 0); + mq_close(mqd).unwrap(); +} + +#[test] +fn test_mq_unlink() { + use nix::mqueue::mq_unlink; + const MSG_SIZE: mq_attr_member_t = 32; + let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name_opened = &CString::new(b"/mq_unlink_test".as_ref()).unwrap(); + #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] + let mq_name_not_opened = + &CString::new(b"/mq_unlink_test".as_ref()).unwrap(); + let oflag = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; + let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; + let r = mq_open(mq_name_opened, oflag, mode, Some(&initial_attr)); + if let Err(Errno::ENOSYS) = r { + println!("message queues not supported or module not loaded?"); + return; + }; + let mqd = r.unwrap(); + + let res_unlink = mq_unlink(mq_name_opened); + assert_eq!(res_unlink, Ok(())); + + // NetBSD (and others which inherit its implementation) defer removing the message + // queue name until all references are closed, whereas Linux and others remove the + // message queue name immediately. + #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] + { + let res_unlink_not_opened = mq_unlink(mq_name_not_opened); + assert_eq!(res_unlink_not_opened, Err(Errno::ENOENT)); + } + + mq_close(mqd).unwrap(); + let res_unlink_after_close = mq_unlink(mq_name_opened); + assert_eq!(res_unlink_after_close, Err(Errno::ENOENT)); +} diff --git a/test/test_net.rs b/test/test_net.rs new file mode 100644 index 0000000..c44655a --- /dev/null +++ b/test/test_net.rs @@ -0,0 +1,19 @@ +use nix::net::if_::*; + +#[cfg(any(target_os = "android", target_os = "linux"))] +const LOOPBACK: &[u8] = b"lo"; + +#[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "haiku" +)))] +const LOOPBACK: &[u8] = b"lo0"; + +#[cfg(target_os = "haiku")] +const LOOPBACK: &[u8] = b"loop"; + +#[test] +fn test_if_nametoindex() { + if_nametoindex(LOOPBACK).expect("assertion failed"); +} diff --git a/test/test_nix_path.rs b/test/test_nix_path.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/test_nix_path.rs @@ -0,0 +1 @@ + diff --git a/test/test_nmount.rs b/test/test_nmount.rs new file mode 100644 index 0000000..dec806a --- /dev/null +++ b/test/test_nmount.rs @@ -0,0 +1,49 @@ +use crate::*; +use nix::{ + errno::Errno, + mount::{unmount, MntFlags, Nmount}, +}; +use std::{ffi::CString, fs::File, path::Path}; +use tempfile::tempdir; + +#[test] +fn ok() { + require_mount!("nullfs"); + + let mountpoint = tempdir().unwrap(); + let target = tempdir().unwrap(); + let _sentry = File::create(target.path().join("sentry")).unwrap(); + + let fstype = CString::new("fstype").unwrap(); + let nullfs = CString::new("nullfs").unwrap(); + Nmount::new() + .str_opt(&fstype, &nullfs) + .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) + .str_opt_owned("target", target.path().to_str().unwrap()) + .nmount(MntFlags::empty()) + .unwrap(); + + // Now check that the sentry is visible through the mountpoint + let exists = Path::exists(&mountpoint.path().join("sentry")); + + // Cleanup the mountpoint before asserting + unmount(mountpoint.path(), MntFlags::empty()).unwrap(); + + assert!(exists); +} + +#[test] +fn bad_fstype() { + let mountpoint = tempdir().unwrap(); + let target = tempdir().unwrap(); + let _sentry = File::create(target.path().join("sentry")).unwrap(); + + let e = Nmount::new() + .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) + .str_opt_owned("target", target.path().to_str().unwrap()) + .nmount(MntFlags::empty()) + .unwrap_err(); + + assert_eq!(e.error(), Errno::EINVAL); + assert_eq!(e.errmsg(), Some("Invalid fstype")); +} diff --git a/test/test_poll.rs b/test/test_poll.rs new file mode 100644 index 0000000..53964e2 --- /dev/null +++ b/test/test_poll.rs @@ -0,0 +1,84 @@ +use nix::{ + errno::Errno, + poll::{poll, PollFd, PollFlags}, + unistd::{pipe, write}, +}; + +macro_rules! loop_while_eintr { + ($poll_expr: expr) => { + loop { + match $poll_expr { + Ok(nfds) => break nfds, + Err(Errno::EINTR) => (), + Err(e) => panic!("{}", e), + } + } + }; +} + +#[test] +fn test_poll() { + let (r, w) = pipe().unwrap(); + let mut fds = [PollFd::new(r, PollFlags::POLLIN)]; + + // Poll an idle pipe. Should timeout + let nfds = loop_while_eintr!(poll(&mut fds, 100)); + assert_eq!(nfds, 0); + assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN)); + + write(w, b".").unwrap(); + + // Poll a readable pipe. Should return an event. + let nfds = poll(&mut fds, 100).unwrap(); + assert_eq!(nfds, 1); + assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); +} + +// ppoll(2) is the same as poll except for how it handles timeouts and signals. +// Repeating the test for poll(2) should be sufficient to check that our +// bindings are correct. +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" +))] +#[test] +fn test_ppoll() { + use nix::poll::ppoll; + use nix::sys::signal::SigSet; + use nix::sys::time::{TimeSpec, TimeValLike}; + + let timeout = TimeSpec::milliseconds(1); + let (r, w) = pipe().unwrap(); + let mut fds = [PollFd::new(r, PollFlags::POLLIN)]; + + // Poll an idle pipe. Should timeout + let sigset = SigSet::empty(); + let nfds = loop_while_eintr!(ppoll(&mut fds, Some(timeout), Some(sigset))); + assert_eq!(nfds, 0); + assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN)); + + write(w, b".").unwrap(); + + // Poll a readable pipe. Should return an event. + let nfds = ppoll(&mut fds, Some(timeout), None).unwrap(); + assert_eq!(nfds, 1); + assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); +} + +#[test] +fn test_pollfd_fd() { + use std::os::unix::io::AsRawFd; + + let pfd = PollFd::new(0x1234, PollFlags::empty()); + assert_eq!(pfd.as_raw_fd(), 0x1234); +} + +#[test] +fn test_pollfd_events() { + let mut pfd = PollFd::new(-1, PollFlags::POLLIN); + assert_eq!(pfd.events(), PollFlags::POLLIN); + pfd.set_events(PollFlags::POLLOUT); + assert_eq!(pfd.events(), PollFlags::POLLOUT); +} diff --git a/test/test_pty.rs b/test/test_pty.rs new file mode 100644 index 0000000..5c27e2d --- /dev/null +++ b/test/test_pty.rs @@ -0,0 +1,313 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::os::unix::prelude::*; +use std::path::Path; +use tempfile::tempfile; + +use libc::{_exit, STDOUT_FILENO}; +use nix::fcntl::{open, OFlag}; +use nix::pty::*; +use nix::sys::stat; +use nix::sys::termios::*; +use nix::unistd::{close, pause, write}; + +/// Regression test for Issue #659 +/// This is the correct way to explicitly close a `PtyMaster` +#[test] +fn test_explicit_close() { + let mut f = { + let m = posix_openpt(OFlag::O_RDWR).unwrap(); + close(m.into_raw_fd()).unwrap(); + tempfile().unwrap() + }; + // This should work. But if there's been a double close, then it will + // return EBADF + f.write_all(b"whatever").unwrap(); +} + +/// Test equivalence of `ptsname` and `ptsname_r` +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptsname_equivalence() { + let _m = crate::PTSNAME_MTX.lock(); + + // Open a new PTTY master + let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); + assert!(master_fd.as_raw_fd() > 0); + + // Get the name of the slave + let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); + let slave_name_r = ptsname_r(&master_fd).unwrap(); + assert_eq!(slave_name, slave_name_r); +} + +/// Test data copying of `ptsname` +// TODO need to run in a subprocess, since ptsname is non-reentrant +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptsname_copy() { + let _m = crate::PTSNAME_MTX.lock(); + + // Open a new PTTY master + let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); + assert!(master_fd.as_raw_fd() > 0); + + // Get the name of the slave + let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap(); + let slave_name2 = unsafe { ptsname(&master_fd) }.unwrap(); + assert_eq!(slave_name1, slave_name2); + // Also make sure that the string was actually copied and they point to different parts of + // memory. + assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); +} + +/// Test data copying of `ptsname_r` +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptsname_r_copy() { + // Open a new PTTY master + let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); + assert!(master_fd.as_raw_fd() > 0); + + // Get the name of the slave + let slave_name1 = ptsname_r(&master_fd).unwrap(); + let slave_name2 = ptsname_r(&master_fd).unwrap(); + assert_eq!(slave_name1, slave_name2); + assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); +} + +/// Test that `ptsname` returns different names for different devices +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptsname_unique() { + let _m = crate::PTSNAME_MTX.lock(); + + // Open a new PTTY master + let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap(); + assert!(master1_fd.as_raw_fd() > 0); + + // Open a second PTTY master + let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap(); + assert!(master2_fd.as_raw_fd() > 0); + + // Get the name of the slave + let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap(); + let slave_name2 = unsafe { ptsname(&master2_fd) }.unwrap(); + assert_ne!(slave_name1, slave_name2); +} + +/// Common setup for testing PTTY pairs +fn open_ptty_pair() -> (PtyMaster, File) { + let _m = crate::PTSNAME_MTX.lock(); + + // Open a new PTTY master + let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed"); + + // Allow a slave to be generated for it + grantpt(&master).expect("grantpt failed"); + unlockpt(&master).expect("unlockpt failed"); + + // Get the name of the slave + let slave_name = unsafe { ptsname(&master) }.expect("ptsname failed"); + + // Open the slave device + let slave_fd = + open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()) + .unwrap(); + + #[cfg(target_os = "illumos")] + // TODO: rewrite using ioctl! + #[allow(clippy::comparison_chain)] + { + use libc::{ioctl, I_FIND, I_PUSH}; + + // On illumos systems, as per pts(7D), one must push STREAMS modules + // after opening a device path returned from ptsname(). + let ptem = b"ptem\0"; + let ldterm = b"ldterm\0"; + let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) }; + if r < 0 { + panic!("I_FIND failure"); + } else if r == 0 { + if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 { + panic!("I_PUSH ptem failure"); + } + if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 { + panic!("I_PUSH ldterm failure"); + } + } + } + + let slave = unsafe { File::from_raw_fd(slave_fd) }; + + (master, slave) +} + +/// Test opening a master/slave PTTY pair +/// +/// This uses a common `open_ptty_pair` because much of these functions aren't useful by +/// themselves. So for this test we perform the basic act of getting a file handle for a +/// master/slave PTTY pair, then just sanity-check the raw values. +#[test] +fn test_open_ptty_pair() { + let (master, slave) = open_ptty_pair(); + assert!(master.as_raw_fd() > 0); + assert!(slave.as_raw_fd() > 0); +} + +/// Put the terminal in raw mode. +fn make_raw(fd: RawFd) { + let mut termios = tcgetattr(fd).unwrap(); + cfmakeraw(&mut termios); + tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap(); +} + +/// Test `io::Read` on the PTTY master +#[test] +fn test_read_ptty_pair() { + let (mut master, mut slave) = open_ptty_pair(); + make_raw(slave.as_raw_fd()); + + let mut buf = [0u8; 5]; + slave.write_all(b"hello").unwrap(); + master.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"hello"); + + let mut master = &master; + slave.write_all(b"hello").unwrap(); + master.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"hello"); +} + +/// Test `io::Write` on the PTTY master +#[test] +fn test_write_ptty_pair() { + let (mut master, mut slave) = open_ptty_pair(); + make_raw(slave.as_raw_fd()); + + let mut buf = [0u8; 5]; + master.write_all(b"adios").unwrap(); + slave.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"adios"); + + let mut master = &master; + master.write_all(b"adios").unwrap(); + slave.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"adios"); +} + +#[test] +fn test_openpty() { + // openpty uses ptname(3) internally + let _m = crate::PTSNAME_MTX.lock(); + + let pty = openpty(None, None).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + + // Writing to one should be readable on the other one + let string = "foofoofoo\n"; + let mut buf = [0u8; 10]; + write(pty.master, string.as_bytes()).unwrap(); + crate::read_exact(pty.slave, &mut buf); + + assert_eq!(&buf, string.as_bytes()); + + // Read the echo as well + let echoed_string = "foofoofoo\r\n"; + let mut buf = [0u8; 11]; + crate::read_exact(pty.master, &mut buf); + assert_eq!(&buf, echoed_string.as_bytes()); + + let string2 = "barbarbarbar\n"; + let echoed_string2 = "barbarbarbar\r\n"; + let mut buf = [0u8; 14]; + write(pty.slave, string2.as_bytes()).unwrap(); + crate::read_exact(pty.master, &mut buf); + + assert_eq!(&buf, echoed_string2.as_bytes()); + + close(pty.master).unwrap(); + close(pty.slave).unwrap(); +} + +#[test] +fn test_openpty_with_termios() { + // openpty uses ptname(3) internally + let _m = crate::PTSNAME_MTX.lock(); + + // Open one pty to get attributes for the second one + let mut termios = { + let pty = openpty(None, None).unwrap(); + assert!(pty.master > 0); + assert!(pty.slave > 0); + let termios = tcgetattr(pty.slave).unwrap(); + close(pty.master).unwrap(); + close(pty.slave).unwrap(); + termios + }; + // Make sure newlines are not transformed so the data is preserved when sent. + termios.output_flags.remove(OutputFlags::ONLCR); + + let pty = openpty(None, &termios).unwrap(); + // Must be valid file descriptors + assert!(pty.master > 0); + assert!(pty.slave > 0); + + // Writing to one should be readable on the other one + let string = "foofoofoo\n"; + let mut buf = [0u8; 10]; + write(pty.master, string.as_bytes()).unwrap(); + crate::read_exact(pty.slave, &mut buf); + + assert_eq!(&buf, string.as_bytes()); + + // read the echo as well + let echoed_string = "foofoofoo\n"; + crate::read_exact(pty.master, &mut buf); + assert_eq!(&buf, echoed_string.as_bytes()); + + let string2 = "barbarbarbar\n"; + let echoed_string2 = "barbarbarbar\n"; + let mut buf = [0u8; 13]; + write(pty.slave, string2.as_bytes()).unwrap(); + crate::read_exact(pty.master, &mut buf); + + assert_eq!(&buf, echoed_string2.as_bytes()); + + close(pty.master).unwrap(); + close(pty.slave).unwrap(); +} + +#[test] +fn test_forkpty() { + use nix::sys::signal::*; + use nix::sys::wait::wait; + use nix::unistd::ForkResult::*; + // forkpty calls openpty which uses ptname(3) internally. + let _m0 = crate::PTSNAME_MTX.lock(); + // forkpty spawns a child process + let _m1 = crate::FORK_MTX.lock(); + + let string = "naninani\n"; + let echoed_string = "naninani\r\n"; + let pty = unsafe { forkpty(None, None).unwrap() }; + match pty.fork_result { + Child => { + write(STDOUT_FILENO, string.as_bytes()).unwrap(); + pause(); // we need the child to stay alive until the parent calls read + unsafe { + _exit(0); + } + } + Parent { child } => { + let mut buf = [0u8; 10]; + assert!(child.as_raw() > 0); + crate::read_exact(pty.master, &mut buf); + kill(child, SIGTERM).unwrap(); + wait().unwrap(); // keep other tests using generic wait from getting our child + assert_eq!(&buf, echoed_string.as_bytes()); + close(pty.master).unwrap(); + } + } +} diff --git a/test/test_ptymaster_drop.rs b/test/test_ptymaster_drop.rs new file mode 100644 index 0000000..ffbaa56 --- /dev/null +++ b/test/test_ptymaster_drop.rs @@ -0,0 +1,20 @@ +#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +mod t { + use nix::fcntl::OFlag; + use nix::pty::*; + use nix::unistd::close; + use std::os::unix::io::AsRawFd; + + /// Regression test for Issue #659 + /// + /// `PtyMaster` should panic rather than double close the file descriptor + /// This must run in its own test process because it deliberately creates a + /// race condition. + #[test] + #[should_panic(expected = "Closing an invalid file descriptor!")] + fn test_double_close() { + let m = posix_openpt(OFlag::O_RDWR).unwrap(); + close(m.as_raw_fd()).unwrap(); + drop(m); // should panic here + } +} diff --git a/test/test_resource.rs b/test/test_resource.rs new file mode 100644 index 0000000..2ab581b --- /dev/null +++ b/test/test_resource.rs @@ -0,0 +1,34 @@ +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "illumos", + target_os = "haiku" +)))] +use nix::sys::resource::{getrlimit, setrlimit, Resource}; + +/// Tests the RLIMIT_NOFILE functionality of getrlimit(), where the resource RLIMIT_NOFILE refers +/// to the maximum file descriptor number that can be opened by the process (aka the maximum number +/// of file descriptors that the process can open, since Linux 4.5). +/// +/// We first fetch the existing file descriptor maximum values using getrlimit(), then edit the +/// soft limit to make sure it has a new and distinct value to the hard limit. We then setrlimit() +/// to put the new soft limit in effect, and then getrlimit() once more to ensure the limits have +/// been updated. +#[test] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "illumos", + target_os = "haiku" +)))] +pub fn test_resource_limits_nofile() { + let (mut soft_limit, hard_limit) = + getrlimit(Resource::RLIMIT_NOFILE).unwrap(); + + soft_limit -= 1; + assert_ne!(soft_limit, hard_limit); + setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); + + let (new_soft_limit, _) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); + assert_eq!(new_soft_limit, soft_limit); +} diff --git a/test/test_sched.rs b/test/test_sched.rs new file mode 100644 index 0000000..c52616b --- /dev/null +++ b/test/test_sched.rs @@ -0,0 +1,39 @@ +use nix::sched::{sched_getaffinity, sched_getcpu, sched_setaffinity, CpuSet}; +use nix::unistd::Pid; + +#[test] +fn test_sched_affinity() { + // If pid is zero, then the mask of the calling process is returned. + let initial_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap(); + let mut at_least_one_cpu = false; + let mut last_valid_cpu = 0; + for field in 0..CpuSet::count() { + if initial_affinity.is_set(field).unwrap() { + at_least_one_cpu = true; + last_valid_cpu = field; + } + } + assert!(at_least_one_cpu); + + // Now restrict the running CPU + let mut new_affinity = CpuSet::new(); + new_affinity.set(last_valid_cpu).unwrap(); + sched_setaffinity(Pid::from_raw(0), &new_affinity).unwrap(); + + // And now re-check the affinity which should be only the one we set. + let updated_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap(); + for field in 0..CpuSet::count() { + // Should be set only for the CPU we set previously + assert_eq!( + updated_affinity.is_set(field).unwrap(), + field == last_valid_cpu + ) + } + + // Now check that we're also currently running on the CPU in question. + let cur_cpu = sched_getcpu().unwrap(); + assert_eq!(cur_cpu, last_valid_cpu); + + // Finally, reset the initial CPU set + sched_setaffinity(Pid::from_raw(0), &initial_affinity).unwrap(); +} diff --git a/test/test_sendfile.rs b/test/test_sendfile.rs new file mode 100644 index 0000000..f73a3b5 --- /dev/null +++ b/test/test_sendfile.rs @@ -0,0 +1,208 @@ +use std::io::prelude::*; +use std::os::unix::prelude::*; + +use libc::off_t; +use nix::sys::sendfile::*; +use tempfile::tempfile; + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + use nix::unistd::{close, pipe, read}; + } else if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] { + use std::net::Shutdown; + use std::os::unix::net::UnixStream; + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[test] +fn test_sendfile_linux() { + const CONTENTS: &[u8] = b"abcdef123456"; + let mut tmp = tempfile().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + + let (rd, wr) = pipe().unwrap(); + let mut offset: off_t = 5; + let res = sendfile(wr, tmp.as_raw_fd(), Some(&mut offset), 2).unwrap(); + + assert_eq!(2, res); + + let mut buf = [0u8; 1024]; + assert_eq!(2, read(rd, &mut buf).unwrap()); + assert_eq!(b"f1", &buf[0..2]); + assert_eq!(7, offset); + + close(rd).unwrap(); + close(wr).unwrap(); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_sendfile64_linux() { + const CONTENTS: &[u8] = b"abcdef123456"; + let mut tmp = tempfile().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + + let (rd, wr) = pipe().unwrap(); + let mut offset: libc::off64_t = 5; + let res = sendfile64(wr, tmp.as_raw_fd(), Some(&mut offset), 2).unwrap(); + + assert_eq!(2, res); + + let mut buf = [0u8; 1024]; + assert_eq!(2, read(rd, &mut buf).unwrap()); + assert_eq!(b"f1", &buf[0..2]); + assert_eq!(7, offset); + + close(rd).unwrap(); + close(wr).unwrap(); +} + +#[cfg(target_os = "freebsd")] +#[test] +fn test_sendfile_freebsd() { + // Declare the content + let header_strings = + vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"]; + let body = "Xabcdef123456"; + let body_offset = 1; + let trailer_strings = vec!["\n", "Served by Make Believe\n"]; + + // Write the body to a file + let mut tmp = tempfile().unwrap(); + tmp.write_all(body.as_bytes()).unwrap(); + + // Prepare headers and trailers for sendfile + let headers: Vec<&[u8]> = + header_strings.iter().map(|s| s.as_bytes()).collect(); + let trailers: Vec<&[u8]> = + trailer_strings.iter().map(|s| s.as_bytes()).collect(); + + // Prepare socket pair + let (mut rd, wr) = UnixStream::pair().unwrap(); + + // Call the test method + let (res, bytes_written) = sendfile( + tmp.as_raw_fd(), + wr.as_raw_fd(), + body_offset as off_t, + None, + Some(headers.as_slice()), + Some(trailers.as_slice()), + SfFlags::empty(), + 0, + ); + assert!(res.is_ok()); + wr.shutdown(Shutdown::Both).unwrap(); + + // Prepare the expected result + let expected_string = header_strings.concat() + + &body[body_offset..] + + &trailer_strings.concat(); + + // Verify the message that was sent + assert_eq!(bytes_written as usize, expected_string.as_bytes().len()); + + let mut read_string = String::new(); + let bytes_read = rd.read_to_string(&mut read_string).unwrap(); + assert_eq!(bytes_written as usize, bytes_read); + assert_eq!(expected_string, read_string); +} + +#[cfg(target_os = "dragonfly")] +#[test] +fn test_sendfile_dragonfly() { + // Declare the content + let header_strings = + vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"]; + let body = "Xabcdef123456"; + let body_offset = 1; + let trailer_strings = vec!["\n", "Served by Make Believe\n"]; + + // Write the body to a file + let mut tmp = tempfile().unwrap(); + tmp.write_all(body.as_bytes()).unwrap(); + + // Prepare headers and trailers for sendfile + let headers: Vec<&[u8]> = + header_strings.iter().map(|s| s.as_bytes()).collect(); + let trailers: Vec<&[u8]> = + trailer_strings.iter().map(|s| s.as_bytes()).collect(); + + // Prepare socket pair + let (mut rd, wr) = UnixStream::pair().unwrap(); + + // Call the test method + let (res, bytes_written) = sendfile( + tmp.as_raw_fd(), + wr.as_raw_fd(), + body_offset as off_t, + None, + Some(headers.as_slice()), + Some(trailers.as_slice()), + ); + assert!(res.is_ok()); + wr.shutdown(Shutdown::Both).unwrap(); + + // Prepare the expected result + let expected_string = header_strings.concat() + + &body[body_offset..] + + &trailer_strings.concat(); + + // Verify the message that was sent + assert_eq!(bytes_written as usize, expected_string.as_bytes().len()); + + let mut read_string = String::new(); + let bytes_read = rd.read_to_string(&mut read_string).unwrap(); + assert_eq!(bytes_written as usize, bytes_read); + assert_eq!(expected_string, read_string); +} + +#[cfg(any(target_os = "ios", target_os = "macos"))] +#[test] +fn test_sendfile_darwin() { + // Declare the content + let header_strings = + vec!["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"]; + let body = "Xabcdef123456"; + let body_offset = 1; + let trailer_strings = vec!["\n", "Served by Make Believe\n"]; + + // Write the body to a file + let mut tmp = tempfile().unwrap(); + tmp.write_all(body.as_bytes()).unwrap(); + + // Prepare headers and trailers for sendfile + let headers: Vec<&[u8]> = + header_strings.iter().map(|s| s.as_bytes()).collect(); + let trailers: Vec<&[u8]> = + trailer_strings.iter().map(|s| s.as_bytes()).collect(); + + // Prepare socket pair + let (mut rd, wr) = UnixStream::pair().unwrap(); + + // Call the test method + let (res, bytes_written) = sendfile( + tmp.as_raw_fd(), + wr.as_raw_fd(), + body_offset as off_t, + None, + Some(headers.as_slice()), + Some(trailers.as_slice()), + ); + assert!(res.is_ok()); + wr.shutdown(Shutdown::Both).unwrap(); + + // Prepare the expected result + let expected_string = header_strings.concat() + + &body[body_offset..] + + &trailer_strings.concat(); + + // Verify the message that was sent + assert_eq!(bytes_written as usize, expected_string.as_bytes().len()); + + let mut read_string = String::new(); + let bytes_read = rd.read_to_string(&mut read_string).unwrap(); + assert_eq!(bytes_written as usize, bytes_read); + assert_eq!(expected_string, read_string); +} diff --git a/test/test_stat.rs b/test/test_stat.rs new file mode 100644 index 0000000..55f15c0 --- /dev/null +++ b/test/test_stat.rs @@ -0,0 +1,421 @@ +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::fs; +use std::fs::File; +#[cfg(not(target_os = "redox"))] +use std::os::unix::fs::symlink; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::os::unix::fs::PermissionsExt; +use std::os::unix::prelude::AsRawFd; +#[cfg(not(target_os = "redox"))] +use std::path::Path; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::time::{Duration, UNIX_EPOCH}; + +use libc::mode_t; +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +use libc::{S_IFLNK, S_IFMT}; + +#[cfg(not(target_os = "redox"))] +use nix::errno::Errno; +#[cfg(not(target_os = "redox"))] +use nix::fcntl; +#[cfg(any( + target_os = "linux", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" +))] +use nix::sys::stat::lutimes; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::stat::utimensat; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::FchmodatFlags; +use nix::sys::stat::Mode; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::stat::UtimensatFlags; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::{self}; +use nix::sys::stat::{fchmod, stat}; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::{fchmodat, mkdirat}; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::stat::{futimens, utimes}; + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +use nix::sys::stat::FileStat; + +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; +#[cfg(not(target_os = "redox"))] +use nix::unistd::chdir; + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +use nix::Result; + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn assert_stat_results(stat_result: Result) { + let stats = stat_result.expect("stat call failed"); + assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent + assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent + assert!(stats.st_mode > 0); // must be positive integer + assert_eq!(stats.st_nlink, 1); // there links created, must be 1 + assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file + assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent + assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file +} + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +// (Android's st_blocks is ulonglong which is always non-negative.) +#[cfg_attr(target_os = "android", allow(unused_comparisons))] +#[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes +fn assert_lstat_results(stat_result: Result) { + let stats = stat_result.expect("stat call failed"); + assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent + assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent + assert!(stats.st_mode > 0); // must be positive integer + + // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t + // (u16 on Android), and that will be a compile error. + // On other platforms they are the same (either both are u16 or u32). + assert_eq!( + (stats.st_mode as usize) & (S_IFMT as usize), + S_IFLNK as usize + ); // should be a link + assert_eq!(stats.st_nlink, 1); // there links created, must be 1 + assert!(stats.st_size > 0); // size is > 0 because it points to another file + assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent + + // st_blocks depends on whether the machine's file system uses fast + // or slow symlinks, so just make sure it's not negative + assert!(stats.st_blocks >= 0); +} + +#[test] +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn test_stat_and_fstat() { + use nix::sys::stat::fstat; + + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("foo.txt"); + let file = File::create(&filename).unwrap(); + + let stat_result = stat(&filename); + assert_stat_results(stat_result); + + let fstat_result = fstat(file.as_raw_fd()); + assert_stat_results(fstat_result); +} + +#[test] +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn test_fstatat() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("foo.txt"); + File::create(&filename).unwrap(); + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()); + + let result = + stat::fstatat(dirfd.unwrap(), &filename, fcntl::AtFlags::empty()); + assert_stat_results(result); +} + +#[test] +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn test_stat_fstat_lstat() { + use nix::sys::stat::{fstat, lstat}; + + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("bar.txt"); + let linkname = tempdir.path().join("barlink"); + + File::create(&filename).unwrap(); + symlink("bar.txt", &linkname).unwrap(); + let link = File::open(&linkname).unwrap(); + + // should be the same result as calling stat, + // since it's a regular file + let stat_result = stat(&filename); + assert_stat_results(stat_result); + + let lstat_result = lstat(&linkname); + assert_lstat_results(lstat_result); + + let fstat_result = fstat(link.as_raw_fd()); + assert_stat_results(fstat_result); +} + +#[test] +fn test_fchmod() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("foo.txt"); + let file = File::create(&filename).unwrap(); + + let mut mode1 = Mode::empty(); + mode1.insert(Mode::S_IRUSR); + mode1.insert(Mode::S_IWUSR); + fchmod(file.as_raw_fd(), mode1).unwrap(); + + let file_stat1 = stat(&filename).unwrap(); + assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); + + let mut mode2 = Mode::empty(); + mode2.insert(Mode::S_IROTH); + fchmod(file.as_raw_fd(), mode2).unwrap(); + + let file_stat2 = stat(&filename).unwrap(); + assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_fchmodat() { + let _dr = crate::DirRestore::new(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let fullpath = tempdir.path().join(filename); + File::create(&fullpath).unwrap(); + + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + let mut mode1 = Mode::empty(); + mode1.insert(Mode::S_IRUSR); + mode1.insert(Mode::S_IWUSR); + fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink) + .unwrap(); + + let file_stat1 = stat(&fullpath).unwrap(); + assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); + + chdir(tempdir.path()).unwrap(); + + let mut mode2 = Mode::empty(); + mode2.insert(Mode::S_IROTH); + fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap(); + + let file_stat2 = stat(&fullpath).unwrap(); + assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); +} + +/// Asserts that the atime and mtime in a file's metadata match expected values. +/// +/// The atime and mtime are expressed with a resolution of seconds because some file systems +/// (like macOS's HFS+) do not have higher granularity. +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn assert_times_eq( + exp_atime_sec: u64, + exp_mtime_sec: u64, + attr: &fs::Metadata, +) { + assert_eq!( + Duration::new(exp_atime_sec, 0), + attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap() + ); + assert_eq!( + Duration::new(exp_mtime_sec, 0), + attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap() + ); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_utimes() { + let tempdir = tempfile::tempdir().unwrap(); + let fullpath = tempdir.path().join("file"); + drop(File::create(&fullpath).unwrap()); + + utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550)) + .unwrap(); + assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +#[cfg(any( + target_os = "linux", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd" +))] +fn test_lutimes() { + let tempdir = tempfile::tempdir().unwrap(); + let target = tempdir.path().join("target"); + let fullpath = tempdir.path().join("symlink"); + drop(File::create(&target).unwrap()); + symlink(&target, &fullpath).unwrap(); + + let exp_target_metadata = fs::symlink_metadata(&target).unwrap(); + lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)) + .unwrap(); + assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap()); + + let target_metadata = fs::symlink_metadata(&target).unwrap(); + assert_eq!( + exp_target_metadata.accessed().unwrap(), + target_metadata.accessed().unwrap(), + "atime of symlink target was unexpectedly modified" + ); + assert_eq!( + exp_target_metadata.modified().unwrap(), + target_metadata.modified().unwrap(), + "mtime of symlink target was unexpectedly modified" + ); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_futimens() { + let tempdir = tempfile::tempdir().unwrap(); + let fullpath = tempdir.path().join("file"); + drop(File::create(&fullpath).unwrap()); + + let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap(); + assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_utimensat() { + let _dr = crate::DirRestore::new(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let fullpath = tempdir.path().join(filename); + drop(File::create(&fullpath).unwrap()); + + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + utimensat( + Some(dirfd), + filename, + &TimeSpec::seconds(12345), + &TimeSpec::seconds(678), + UtimensatFlags::FollowSymlink, + ) + .unwrap(); + assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap()); + + chdir(tempdir.path()).unwrap(); + + utimensat( + None, + filename, + &TimeSpec::seconds(500), + &TimeSpec::seconds(800), + UtimensatFlags::FollowSymlink, + ) + .unwrap(); + assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_mkdirat_success_path() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = "example_subdir"; + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); + assert!(Path::exists(&tempdir.path().join(filename))); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_mkdirat_success_mode() { + let expected_bits = + stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "example_subdir"; + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); + let permissions = fs::metadata(tempdir.path().join(filename)) + .unwrap() + .permissions(); + let mode = permissions.mode(); + assert_eq!(mode as mode_t, expected_bits) +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_mkdirat_fail() { + let tempdir = tempfile::tempdir().unwrap(); + let not_dir_filename = "example_not_dir"; + let filename = "example_subdir_dir"; + let dirfd = fcntl::open( + &tempdir.path().join(not_dir_filename), + fcntl::OFlag::O_CREAT, + stat::Mode::empty(), + ) + .unwrap(); + let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err(); + assert_eq!(result, Errno::ENOTDIR); +} + +#[test] +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "haiku", + target_os = "redox" +)))] +fn test_mknod() { + use stat::{lstat, mknod, SFlag}; + + let file_name = "test_file"; + let tempdir = tempfile::tempdir().unwrap(); + let target = tempdir.path().join(file_name); + mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap(); + let mode = lstat(&target).unwrap().st_mode as mode_t; + assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); + assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); +} + +#[test] +#[cfg(not(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "haiku", + target_os = "redox" +)))] +fn test_mknodat() { + use fcntl::{AtFlags, OFlag}; + use nix::dir::Dir; + use stat::{fstatat, mknodat, SFlag}; + + let file_name = "test_file"; + let tempdir = tempfile::tempdir().unwrap(); + let target_dir = + Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); + mknodat( + target_dir.as_raw_fd(), + file_name, + SFlag::S_IFREG, + Mode::S_IRWXU, + 0, + ) + .unwrap(); + let mode = fstatat( + target_dir.as_raw_fd(), + file_name, + AtFlags::AT_SYMLINK_NOFOLLOW, + ) + .unwrap() + .st_mode as mode_t; + assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); + assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); +} diff --git a/test/test_time.rs b/test/test_time.rs new file mode 100644 index 0000000..5f76e61 --- /dev/null +++ b/test/test_time.rs @@ -0,0 +1,59 @@ +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +use nix::time::clock_getcpuclockid; +use nix::time::{clock_gettime, ClockId}; + +#[cfg(not(target_os = "redox"))] +#[test] +pub fn test_clock_getres() { + nix::time::clock_getres(ClockId::CLOCK_REALTIME).expect("assertion failed"); +} + +#[test] +pub fn test_clock_gettime() { + clock_gettime(ClockId::CLOCK_REALTIME).expect("assertion failed"); +} + +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +#[test] +pub fn test_clock_getcpuclockid() { + let clock_id = clock_getcpuclockid(nix::unistd::Pid::this()).unwrap(); + clock_gettime(clock_id).unwrap(); +} + +#[cfg(not(target_os = "redox"))] +#[test] +pub fn test_clock_id_res() { + ClockId::CLOCK_REALTIME.res().unwrap(); +} + +#[test] +pub fn test_clock_id_now() { + ClockId::CLOCK_REALTIME.now().unwrap(); +} + +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +#[test] +pub fn test_clock_id_pid_cpu_clock_id() { + ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) + .map(ClockId::now) + .unwrap() + .unwrap(); +} diff --git a/test/test_timer.rs b/test/test_timer.rs new file mode 100644 index 0000000..ffd1468 --- /dev/null +++ b/test/test_timer.rs @@ -0,0 +1,102 @@ +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify, + Signal, +}; +use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; +use nix::time::ClockId; +use std::convert::TryFrom; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::{Duration, Instant}; + +const SIG: Signal = Signal::SIGALRM; +static ALARM_CALLED: AtomicBool = AtomicBool::new(false); + +pub extern "C" fn handle_sigalarm(raw_signal: libc::c_int) { + let signal = Signal::try_from(raw_signal).unwrap(); + if signal == SIG { + ALARM_CALLED.store(true, Ordering::Release); + } +} + +#[test] +fn alarm_fires() { + // Avoid interfering with other signal using tests by taking a mutex shared + // among other tests in this crate. + let _m = crate::SIGNAL_MTX.lock(); + const TIMER_PERIOD: Duration = Duration::from_millis(100); + + // + // Setup + // + + // Create a handler for the test signal, `SIG`. The handler is responsible + // for flipping `ALARM_CALLED`. + let handler = SigHandler::Handler(handle_sigalarm); + let signal_action = + SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty()); + let old_handler = unsafe { + sigaction(SIG, &signal_action) + .expect("unable to set signal handler for alarm") + }; + + // Create the timer. We use the monotonic clock here, though any would do + // really. The timer is set to fire every 250 milliseconds with no delay for + // the initial firing. + let clockid = ClockId::CLOCK_MONOTONIC; + let sigevent = SigEvent::new(SigevNotify::SigevSignal { + signal: SIG, + si_value: 0, + }); + let mut timer = + Timer::new(clockid, sigevent).expect("failed to create timer"); + let expiration = Expiration::Interval(TIMER_PERIOD.into()); + let flags = TimerSetTimeFlags::empty(); + timer.set(expiration, flags).expect("could not set timer"); + + // + // Test + // + + // Determine that there's still an expiration tracked by the + // timer. Depending on when this runs either an `Expiration::Interval` or + // `Expiration::IntervalDelayed` will be present. That is, if the timer has + // not fired yet we'll get our original `expiration`, else the one that + // represents a delay to the next expiration. We're only interested in the + // timer still being extant. + match timer.get() { + Ok(Some(exp)) => assert!(matches!( + exp, + Expiration::Interval(..) | Expiration::IntervalDelayed(..) + )), + _ => panic!("timer lost its expiration"), + } + + // Wait for 2 firings of the alarm before checking that it has fired and + // been handled at least the once. If we wait for 3 seconds and the handler + // is never called something has gone sideways and the test fails. + let starttime = Instant::now(); + loop { + thread::sleep(2 * TIMER_PERIOD); + if ALARM_CALLED.load(Ordering::Acquire) { + break; + } + if starttime.elapsed() > Duration::from_secs(3) { + panic!("Timeout waiting for SIGALRM"); + } + } + + // Cleanup: + // 1) deregister the OS's timer. + // 2) Wait for a full timer period, since POSIX does not require that + // disabling the timer will clear pending signals, and on NetBSD at least + // it does not. + // 2) Replace the old signal handler now that we've completed the test. If + // the test fails this process panics, so the fact we might not get here + // is okay. + drop(timer); + thread::sleep(TIMER_PERIOD); + unsafe { + sigaction(SIG, &old_handler).expect("unable to reset signal handler"); + } +} diff --git a/test/test_unistd.rs b/test/test_unistd.rs new file mode 100644 index 0000000..9e20f97 --- /dev/null +++ b/test/test_unistd.rs @@ -0,0 +1,1407 @@ +use libc::{_exit, mode_t, off_t}; +use nix::errno::Errno; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::fcntl::readlink; +use nix::fcntl::OFlag; +#[cfg(not(target_os = "redox"))] +use nix::fcntl::{self, open}; +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt}; +#[cfg(not(target_os = "redox"))] +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, +}; +use nix::sys::stat::{self, Mode, SFlag}; +use nix::sys::wait::*; +use nix::unistd::ForkResult::*; +use nix::unistd::*; +use std::env; +#[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] +use std::ffi::CString; +#[cfg(not(target_os = "redox"))] +use std::fs::DirBuilder; +use std::fs::{self, File}; +use std::io::Write; +use std::os::unix::prelude::*; +#[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "haiku" +)))] +use std::path::Path; +use tempfile::{tempdir, tempfile}; + +use crate::*; + +#[test] +#[cfg(not(any(target_os = "netbsd")))] +fn test_fork_and_waitpid() { + let _m = crate::FORK_MTX.lock(); + + // Safe: Child only calls `_exit`, which is signal-safe + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => unsafe { _exit(0) }, + Parent { child } => { + // assert that child was created and pid > 0 + let child_raw: ::libc::pid_t = child.into(); + assert!(child_raw > 0); + let wait_status = waitpid(child, None); + match wait_status { + // assert that waitpid returned correct status and the pid is the one of the child + Ok(WaitStatus::Exited(pid_t, _)) => assert_eq!(pid_t, child), + + // panic, must never happen + s @ Ok(_) => { + panic!("Child exited {:?}, should never happen", s) + } + + // panic, waitpid should never fail + Err(s) => panic!("Error: waitpid returned Err({:?}", s), + } + } + } +} + +#[test] +fn test_wait() { + // Grab FORK_MTX so wait doesn't reap a different test's child process + let _m = crate::FORK_MTX.lock(); + + // Safe: Child only calls `_exit`, which is signal-safe + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => unsafe { _exit(0) }, + Parent { child } => { + let wait_status = wait(); + + // just assert that (any) one child returns with WaitStatus::Exited + assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0))); + } + } +} + +#[test] +fn test_mkstemp() { + let mut path = env::temp_dir(); + path.push("nix_tempfile.XXXXXX"); + + let result = mkstemp(&path); + match result { + Ok((fd, path)) => { + close(fd).unwrap(); + unlink(path.as_path()).unwrap(); + } + Err(e) => panic!("mkstemp failed: {}", e), + } +} + +#[test] +fn test_mkstemp_directory() { + // mkstemp should fail if a directory is given + mkstemp(&env::temp_dir()).expect_err("assertion failed"); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_mkfifo() { + let tempdir = tempdir().unwrap(); + let mkfifo_fifo = tempdir.path().join("mkfifo_fifo"); + + mkfifo(&mkfifo_fifo, Mode::S_IRUSR).unwrap(); + + let stats = stat::stat(&mkfifo_fifo).unwrap(); + let typ = stat::SFlag::from_bits_truncate(stats.st_mode as mode_t); + assert_eq!(typ, SFlag::S_IFIFO); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_mkfifo_directory() { + // mkfifo should fail if a directory is given + mkfifo(&env::temp_dir(), Mode::S_IRUSR).expect_err("assertion failed"); +} + +#[test] +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "redox", + target_os = "haiku" +)))] +fn test_mkfifoat_none() { + let _m = crate::CWD_LOCK.read(); + + let tempdir = tempdir().unwrap(); + let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo"); + + mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap(); + + let stats = stat::stat(&mkfifoat_fifo).unwrap(); + let typ = stat::SFlag::from_bits_truncate(stats.st_mode); + assert_eq!(typ, SFlag::S_IFIFO); +} + +#[test] +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "redox", + target_os = "haiku" +)))] +fn test_mkfifoat() { + use nix::fcntl; + + let tempdir = tempdir().unwrap(); + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let mkfifoat_name = "mkfifoat_name"; + + mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap(); + + let stats = + stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).unwrap(); + let typ = stat::SFlag::from_bits_truncate(stats.st_mode); + assert_eq!(typ, SFlag::S_IFIFO); +} + +#[test] +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "redox", + target_os = "haiku" +)))] +fn test_mkfifoat_directory_none() { + let _m = crate::CWD_LOCK.read(); + + // mkfifoat should fail if a directory is given + mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR) + .expect_err("assertion failed"); +} + +#[test] +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "redox", + target_os = "haiku" +)))] +fn test_mkfifoat_directory() { + // mkfifoat should fail if a directory is given + let tempdir = tempdir().unwrap(); + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let mkfifoat_dir = "mkfifoat_dir"; + stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap(); + + mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR) + .expect_err("assertion failed"); +} + +#[test] +fn test_getpid() { + let pid: ::libc::pid_t = getpid().into(); + let ppid: ::libc::pid_t = getppid().into(); + assert!(pid > 0); + assert!(ppid > 0); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_getsid() { + let none_sid: ::libc::pid_t = getsid(None).unwrap().into(); + let pid_sid: ::libc::pid_t = getsid(Some(getpid())).unwrap().into(); + assert!(none_sid > 0); + assert_eq!(none_sid, pid_sid); +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod linux_android { + use nix::unistd::gettid; + + #[test] + fn test_gettid() { + let tid: ::libc::pid_t = gettid().into(); + assert!(tid > 0); + } +} + +#[test] +// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms +#[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +fn test_setgroups() { + // Skip this test when not run as root as `setgroups()` requires root. + skip_if_not_root!("test_setgroups"); + + let _m = crate::GROUPS_MTX.lock(); + + // Save the existing groups + let old_groups = getgroups().unwrap(); + + // Set some new made up groups + let groups = [Gid::from_raw(123), Gid::from_raw(456)]; + setgroups(&groups).unwrap(); + + let new_groups = getgroups().unwrap(); + assert_eq!(new_groups, groups); + + // Revert back to the old groups + setgroups(&old_groups).unwrap(); +} + +#[test] +// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms +#[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos" +)))] +fn test_initgroups() { + // Skip this test when not run as root as `initgroups()` and `setgroups()` + // require root. + skip_if_not_root!("test_initgroups"); + + let _m = crate::GROUPS_MTX.lock(); + + // Save the existing groups + let old_groups = getgroups().unwrap(); + + // It doesn't matter if the root user is not called "root" or if a user + // called "root" doesn't exist. We are just checking that the extra, + // made-up group, `123`, is set. + // FIXME: Test the other half of initgroups' functionality: whether the + // groups that the user belongs to are also set. + let user = CString::new("root").unwrap(); + let group = Gid::from_raw(123); + let group_list = getgrouplist(&user, group).unwrap(); + assert!(group_list.contains(&group)); + + initgroups(&user, group).unwrap(); + + let new_groups = getgroups().unwrap(); + assert_eq!(new_groups, group_list); + + // Revert back to the old groups + setgroups(&old_groups).unwrap(); +} + +#[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] +macro_rules! execve_test_factory ( + ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => ( + + #[cfg(test)] + mod $test_name { + use std::ffi::CStr; + use super::*; + + const EMPTY: &'static [u8] = b"\0"; + const DASH_C: &'static [u8] = b"-c\0"; + const BIGARG: &'static [u8] = b"echo nix!!! && echo foo=$foo && echo baz=$baz\0"; + const FOO: &'static [u8] = b"foo=bar\0"; + const BAZ: &'static [u8] = b"baz=quux\0"; + + fn syscall_cstr_ref() -> Result { + $syscall( + $exe, + $(CString::new($pathname).unwrap().as_c_str(), )* + &[CStr::from_bytes_with_nul(EMPTY).unwrap(), + CStr::from_bytes_with_nul(DASH_C).unwrap(), + CStr::from_bytes_with_nul(BIGARG).unwrap()], + &[CStr::from_bytes_with_nul(FOO).unwrap(), + CStr::from_bytes_with_nul(BAZ).unwrap()] + $(, $flags)*) + } + + fn syscall_cstring() -> Result { + $syscall( + $exe, + $(CString::new($pathname).unwrap().as_c_str(), )* + &[CString::from(CStr::from_bytes_with_nul(EMPTY).unwrap()), + CString::from(CStr::from_bytes_with_nul(DASH_C).unwrap()), + CString::from(CStr::from_bytes_with_nul(BIGARG).unwrap())], + &[CString::from(CStr::from_bytes_with_nul(FOO).unwrap()), + CString::from(CStr::from_bytes_with_nul(BAZ).unwrap())] + $(, $flags)*) + } + + fn common_test(syscall: fn() -> Result) { + if "execveat" == stringify!($syscall) { + // Though undocumented, Docker's default seccomp profile seems to + // block this syscall. https://github.com/nix-rust/nix/issues/1122 + skip_if_seccomp!($test_name); + } + + let m = crate::FORK_MTX.lock(); + // The `exec`d process will write to `writer`, and we'll read that + // data from `reader`. + let (reader, writer) = pipe().unwrap(); + + // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function. + // NOTE: Technically, this makes the macro unsafe to use because you could pass anything. + // The tests make sure not to do that, though. + match unsafe{fork()}.unwrap() { + Child => { + // Make `writer` be the stdout of the new process. + dup2(writer, 1).unwrap(); + let r = syscall(); + let _ = std::io::stderr() + .write_all(format!("{:?}", r).as_bytes()); + // Should only get here in event of error + unsafe{ _exit(1) }; + }, + Parent { child } => { + // Wait for the child to exit. + let ws = waitpid(child, None); + drop(m); + assert_eq!(ws, Ok(WaitStatus::Exited(child, 0))); + // Read 1024 bytes. + let mut buf = [0u8; 1024]; + read(reader, &mut buf).unwrap(); + // It should contain the things we printed using `/bin/sh`. + let string = String::from_utf8_lossy(&buf); + assert!(string.contains("nix!!!")); + assert!(string.contains("foo=bar")); + assert!(string.contains("baz=quux")); + } + } + } + + // These tests frequently fail on musl, probably due to + // https://github.com/nix-rust/nix/issues/555 + #[cfg_attr(target_env = "musl", ignore)] + #[test] + fn test_cstr_ref() { + common_test(syscall_cstr_ref); + } + + // These tests frequently fail on musl, probably due to + // https://github.com/nix-rust/nix/issues/555 + #[cfg_attr(target_env = "musl", ignore)] + #[test] + fn test_cstring() { + common_test(syscall_cstring); + } + } + + ) +); + +cfg_if! { + if #[cfg(target_os = "android")] { + execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh").unwrap().as_c_str()); + execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd()); + } else if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux"))] { + // These tests frequently fail on musl, probably due to + // https://github.com/nix-rust/nix/issues/555 + execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str()); + execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd()); + } else if #[cfg(any(target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] { + execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str()); + // No fexecve() on ios, macos, NetBSD, OpenBSD. + } +} + +#[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))] +execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap()); + +cfg_if! { + if #[cfg(target_os = "android")] { + use nix::fcntl::AtFlags; + execve_test_factory!(test_execveat_empty, execveat, + File::open("/system/bin/sh").unwrap().into_raw_fd(), + "", AtFlags::AT_EMPTY_PATH); + execve_test_factory!(test_execveat_relative, execveat, + File::open("/system/bin/").unwrap().into_raw_fd(), + "./sh", AtFlags::empty()); + execve_test_factory!(test_execveat_absolute, execveat, + File::open("/").unwrap().into_raw_fd(), + "/system/bin/sh", AtFlags::empty()); + } else if #[cfg(all(target_os = "linux", any(target_arch ="x86_64", target_arch ="x86")))] { + use nix::fcntl::AtFlags; + execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(), + "", AtFlags::AT_EMPTY_PATH); + execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(), + "./sh", AtFlags::empty()); + execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(), + "/bin/sh", AtFlags::empty()); + } +} + +#[test] +#[cfg(not(target_os = "fuchsia"))] +fn test_fchdir() { + // fchdir changes the process's cwd + let _dr = crate::DirRestore::new(); + + let tmpdir = tempdir().unwrap(); + let tmpdir_path = tmpdir.path().canonicalize().unwrap(); + let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd(); + + fchdir(tmpdir_fd).expect("assertion failed"); + assert_eq!(getcwd().unwrap(), tmpdir_path); + + close(tmpdir_fd).expect("assertion failed"); +} + +#[test] +fn test_getcwd() { + // chdir changes the process's cwd + let _dr = crate::DirRestore::new(); + + let tmpdir = tempdir().unwrap(); + let tmpdir_path = tmpdir.path().canonicalize().unwrap(); + chdir(&tmpdir_path).expect("assertion failed"); + assert_eq!(getcwd().unwrap(), tmpdir_path); + + // make path 500 chars longer so that buffer doubling in getcwd + // kicks in. Note: One path cannot be longer than 255 bytes + // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually + // 4096 on linux, 1024 on macos) + let mut inner_tmp_dir = tmpdir_path; + for _ in 0..5 { + let newdir = "a".repeat(100); + inner_tmp_dir.push(newdir); + mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU) + .expect("assertion failed"); + } + chdir(inner_tmp_dir.as_path()).expect("assertion failed"); + assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path()); +} + +#[test] +fn test_chown() { + // Testing for anything other than our own UID/GID is hard. + let uid = Some(getuid()); + let gid = Some(getgid()); + + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("file"); + { + File::create(&path).unwrap(); + } + + chown(&path, uid, gid).unwrap(); + chown(&path, uid, None).unwrap(); + chown(&path, None, gid).unwrap(); + + fs::remove_file(&path).unwrap(); + chown(&path, uid, gid).unwrap_err(); +} + +#[test] +fn test_fchown() { + // Testing for anything other than our own UID/GID is hard. + let uid = Some(getuid()); + let gid = Some(getgid()); + + let path = tempfile().unwrap(); + let fd = path.as_raw_fd(); + + fchown(fd, uid, gid).unwrap(); + fchown(fd, uid, None).unwrap(); + fchown(fd, None, gid).unwrap(); + fchown(999999999, uid, gid).unwrap_err(); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_fchownat() { + let _dr = crate::DirRestore::new(); + // Testing for anything other than our own UID/GID is hard. + let uid = Some(getuid()); + let gid = Some(getgid()); + + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("file"); + { + File::create(&path).unwrap(); + } + + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + + fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink) + .unwrap(); + + chdir(tempdir.path()).unwrap(); + fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap(); + + fs::remove_file(&path).unwrap(); + fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err(); +} + +#[test] +fn test_lseek() { + const CONTENTS: &[u8] = b"abcdef123456"; + let mut tmp = tempfile().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + let tmpfd = tmp.into_raw_fd(); + + let offset: off_t = 5; + lseek(tmpfd, offset, Whence::SeekSet).unwrap(); + + let mut buf = [0u8; 7]; + crate::read_exact(tmpfd, &mut buf); + assert_eq!(b"f123456", &buf); + + close(tmpfd).unwrap(); +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_lseek64() { + const CONTENTS: &[u8] = b"abcdef123456"; + let mut tmp = tempfile().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + let tmpfd = tmp.into_raw_fd(); + + lseek64(tmpfd, 5, Whence::SeekSet).unwrap(); + + let mut buf = [0u8; 7]; + crate::read_exact(tmpfd, &mut buf); + assert_eq!(b"f123456", &buf); + + close(tmpfd).unwrap(); +} + +cfg_if! { + if #[cfg(any(target_os = "android", target_os = "linux"))] { + macro_rules! require_acct{ + () => { + require_capability!("test_acct", CAP_SYS_PACCT); + } + } + } else if #[cfg(target_os = "freebsd")] { + macro_rules! require_acct{ + () => { + skip_if_not_root!("test_acct"); + skip_if_jailed!("test_acct"); + } + } + } else if #[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "haiku")))] { + macro_rules! require_acct{ + () => { + skip_if_not_root!("test_acct"); + } + } + } +} + +#[test] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +fn test_acct() { + use std::process::Command; + use std::{thread, time}; + use tempfile::NamedTempFile; + + let _m = crate::FORK_MTX.lock(); + require_acct!(); + + let file = NamedTempFile::new().unwrap(); + let path = file.path().to_str().unwrap(); + + acct::enable(path).unwrap(); + + loop { + Command::new("echo").arg("Hello world").output().unwrap(); + let len = fs::metadata(path).unwrap().len(); + if len > 0 { + break; + } + thread::sleep(time::Duration::from_millis(10)); + } + acct::disable().unwrap(); +} + +#[test] +fn test_fpathconf_limited() { + let f = tempfile().unwrap(); + // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test + let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX); + assert!( + path_max + .expect("fpathconf failed") + .expect("PATH_MAX is unlimited") + > 0 + ); +} + +#[test] +fn test_pathconf_limited() { + // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test + let path_max = pathconf("/", PathconfVar::PATH_MAX); + assert!( + path_max + .expect("pathconf failed") + .expect("PATH_MAX is unlimited") + > 0 + ); +} + +#[test] +fn test_sysconf_limited() { + // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test + let open_max = sysconf(SysconfVar::OPEN_MAX); + assert!( + open_max + .expect("sysconf failed") + .expect("OPEN_MAX is unlimited") + > 0 + ); +} + +#[cfg(target_os = "freebsd")] +#[test] +fn test_sysconf_unsupported() { + // I know of no sysconf variables that are unsupported everywhere, but + // _XOPEN_CRYPT is unsupported on FreeBSD 11.0, which is one of the platforms + // we test. + let open_max = sysconf(SysconfVar::_XOPEN_CRYPT); + assert!(open_max.expect("sysconf failed").is_none()) +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +#[test] +fn test_getresuid() { + let resuids = getresuid().unwrap(); + assert_ne!(resuids.real.as_raw(), libc::uid_t::MAX); + assert_ne!(resuids.effective.as_raw(), libc::uid_t::MAX); + assert_ne!(resuids.saved.as_raw(), libc::uid_t::MAX); +} + +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] +#[test] +fn test_getresgid() { + let resgids = getresgid().unwrap(); + assert_ne!(resgids.real.as_raw(), libc::gid_t::MAX); + assert_ne!(resgids.effective.as_raw(), libc::gid_t::MAX); + assert_ne!(resgids.saved.as_raw(), libc::gid_t::MAX); +} + +// Test that we can create a pair of pipes. No need to verify that they pass +// data; that's the domain of the OS, not nix. +#[test] +fn test_pipe() { + let (fd0, fd1) = pipe().unwrap(); + let m0 = stat::SFlag::from_bits_truncate( + stat::fstat(fd0).unwrap().st_mode as mode_t, + ); + // S_IFIFO means it's a pipe + assert_eq!(m0, SFlag::S_IFIFO); + let m1 = stat::SFlag::from_bits_truncate( + stat::fstat(fd1).unwrap().st_mode as mode_t, + ); + assert_eq!(m1, SFlag::S_IFIFO); +} + +// pipe2(2) is the same as pipe(2), except it allows setting some flags. Check +// that we can set a flag. +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris" +))] +#[test] +fn test_pipe2() { + use nix::fcntl::{fcntl, FcntlArg, FdFlag}; + + let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap(); + let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap()); + assert!(f0.contains(FdFlag::FD_CLOEXEC)); + let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap()); + assert!(f1.contains(FdFlag::FD_CLOEXEC)); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +fn test_truncate() { + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("file"); + + { + let mut tmp = File::create(&path).unwrap(); + const CONTENTS: &[u8] = b"12345678"; + tmp.write_all(CONTENTS).unwrap(); + } + + truncate(&path, 4).unwrap(); + + let metadata = fs::metadata(&path).unwrap(); + assert_eq!(4, metadata.len()); +} + +#[test] +fn test_ftruncate() { + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("file"); + + let tmpfd = { + let mut tmp = File::create(&path).unwrap(); + const CONTENTS: &[u8] = b"12345678"; + tmp.write_all(CONTENTS).unwrap(); + tmp.into_raw_fd() + }; + + ftruncate(tmpfd, 2).unwrap(); + close(tmpfd).unwrap(); + + let metadata = fs::metadata(&path).unwrap(); + assert_eq!(2, metadata.len()); +} + +// Used in `test_alarm`. +#[cfg(not(target_os = "redox"))] +static mut ALARM_CALLED: bool = false; + +// Used in `test_alarm`. +#[cfg(not(target_os = "redox"))] +pub extern "C" fn alarm_signal_handler(raw_signal: libc::c_int) { + assert_eq!( + raw_signal, + libc::SIGALRM, + "unexpected signal: {}", + raw_signal + ); + unsafe { ALARM_CALLED = true }; +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_alarm() { + use std::{ + thread, + time::{Duration, Instant}, + }; + + // Maybe other tests that fork interfere with this one? + let _m = crate::SIGNAL_MTX.lock(); + + let handler = SigHandler::Handler(alarm_signal_handler); + let signal_action = + SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty()); + let old_handler = unsafe { + sigaction(Signal::SIGALRM, &signal_action) + .expect("unable to set signal handler for alarm") + }; + + // Set an alarm. + assert_eq!(alarm::set(60), None); + + // Overwriting an alarm should return the old alarm. + assert_eq!(alarm::set(1), Some(60)); + + // We should be woken up after 1 second by the alarm, so we'll sleep for 3 + // seconds to be sure. + let starttime = Instant::now(); + loop { + thread::sleep(Duration::from_millis(100)); + if unsafe { ALARM_CALLED } { + break; + } + if starttime.elapsed() > Duration::from_secs(3) { + panic!("Timeout waiting for SIGALRM"); + } + } + + // Reset the signal. + unsafe { + sigaction(Signal::SIGALRM, &old_handler) + .expect("unable to set signal handler for alarm"); + } +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_canceling_alarm() { + let _m = crate::SIGNAL_MTX.lock(); + + assert_eq!(alarm::cancel(), None); + + assert_eq!(alarm::set(60), None); + assert_eq!(alarm::cancel(), Some(60)); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_symlinkat() { + let _m = crate::CWD_LOCK.read(); + + let tempdir = tempdir().unwrap(); + + let target = tempdir.path().join("a"); + let linkpath = tempdir.path().join("b"); + symlinkat(&target, None, &linkpath).unwrap(); + assert_eq!( + readlink(&linkpath).unwrap().to_str().unwrap(), + target.to_str().unwrap() + ); + + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let target = "c"; + let linkpath = "d"; + symlinkat(target, Some(dirfd), linkpath).unwrap(); + assert_eq!( + readlink(&tempdir.path().join(linkpath)) + .unwrap() + .to_str() + .unwrap(), + target + ); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_linkat_file() { + let tempdir = tempdir().unwrap(); + let oldfilename = "foo.txt"; + let oldfilepath = tempdir.path().join(oldfilename); + + let newfilename = "bar.txt"; + let newfilepath = tempdir.path().join(newfilename); + + // Create file + File::create(oldfilepath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt hard link file at relative path + linkat( + Some(dirfd), + oldfilename, + Some(dirfd), + newfilename, + LinkatFlags::SymlinkFollow, + ) + .unwrap(); + assert!(newfilepath.exists()); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_linkat_olddirfd_none() { + let _dr = crate::DirRestore::new(); + + let tempdir_oldfile = tempdir().unwrap(); + let oldfilename = "foo.txt"; + let oldfilepath = tempdir_oldfile.path().join(oldfilename); + + let tempdir_newfile = tempdir().unwrap(); + let newfilename = "bar.txt"; + let newfilepath = tempdir_newfile.path().join(newfilename); + + // Create file + File::create(oldfilepath).unwrap(); + + // Get file descriptor for base directory of new file + let dirfd = fcntl::open( + tempdir_newfile.path(), + fcntl::OFlag::empty(), + stat::Mode::empty(), + ) + .unwrap(); + + // Attempt hard link file using curent working directory as relative path for old file path + chdir(tempdir_oldfile.path()).unwrap(); + linkat( + None, + oldfilename, + Some(dirfd), + newfilename, + LinkatFlags::SymlinkFollow, + ) + .unwrap(); + assert!(newfilepath.exists()); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_linkat_newdirfd_none() { + let _dr = crate::DirRestore::new(); + + let tempdir_oldfile = tempdir().unwrap(); + let oldfilename = "foo.txt"; + let oldfilepath = tempdir_oldfile.path().join(oldfilename); + + let tempdir_newfile = tempdir().unwrap(); + let newfilename = "bar.txt"; + let newfilepath = tempdir_newfile.path().join(newfilename); + + // Create file + File::create(oldfilepath).unwrap(); + + // Get file descriptor for base directory of old file + let dirfd = fcntl::open( + tempdir_oldfile.path(), + fcntl::OFlag::empty(), + stat::Mode::empty(), + ) + .unwrap(); + + // Attempt hard link file using current working directory as relative path for new file path + chdir(tempdir_newfile.path()).unwrap(); + linkat( + Some(dirfd), + oldfilename, + None, + newfilename, + LinkatFlags::SymlinkFollow, + ) + .unwrap(); + assert!(newfilepath.exists()); +} + +#[test] +#[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "redox", + target_os = "haiku" +)))] +fn test_linkat_no_follow_symlink() { + let _m = crate::CWD_LOCK.read(); + + let tempdir = tempdir().unwrap(); + let oldfilename = "foo.txt"; + let oldfilepath = tempdir.path().join(oldfilename); + + let symoldfilename = "symfoo.txt"; + let symoldfilepath = tempdir.path().join(symoldfilename); + + let newfilename = "nofollowsymbar.txt"; + let newfilepath = tempdir.path().join(newfilename); + + // Create file + File::create(&oldfilepath).unwrap(); + + // Create symlink to file + symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt link symlink of file at relative path + linkat( + Some(dirfd), + symoldfilename, + Some(dirfd), + newfilename, + LinkatFlags::NoSymlinkFollow, + ) + .unwrap(); + + // Assert newfile is actually a symlink to oldfile. + assert_eq!( + readlink(&newfilepath).unwrap().to_str().unwrap(), + oldfilepath.to_str().unwrap() + ); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_linkat_follow_symlink() { + let _m = crate::CWD_LOCK.read(); + + let tempdir = tempdir().unwrap(); + let oldfilename = "foo.txt"; + let oldfilepath = tempdir.path().join(oldfilename); + + let symoldfilename = "symfoo.txt"; + let symoldfilepath = tempdir.path().join(symoldfilename); + + let newfilename = "nofollowsymbar.txt"; + let newfilepath = tempdir.path().join(newfilename); + + // Create file + File::create(&oldfilepath).unwrap(); + + // Create symlink to file + symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt link target of symlink of file at relative path + linkat( + Some(dirfd), + symoldfilename, + Some(dirfd), + newfilename, + LinkatFlags::SymlinkFollow, + ) + .unwrap(); + + let newfilestat = stat::stat(&newfilepath).unwrap(); + + // Check the file type of the new link + assert_eq!( + (stat::SFlag::from_bits_truncate(newfilestat.st_mode as mode_t) + & SFlag::S_IFMT), + SFlag::S_IFREG + ); + + // Check the number of hard links to the original file + assert_eq!(newfilestat.st_nlink, 2); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_unlinkat_dir_noremovedir() { + let tempdir = tempdir().unwrap(); + let dirname = "foo_dir"; + let dirpath = tempdir.path().join(dirname); + + // Create dir + DirBuilder::new().recursive(true).create(dirpath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt unlink dir at relative path without proper flag + let err_result = + unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err(); + assert!(err_result == Errno::EISDIR || err_result == Errno::EPERM); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_unlinkat_dir_removedir() { + let tempdir = tempdir().unwrap(); + let dirname = "foo_dir"; + let dirpath = tempdir.path().join(dirname); + + // Create dir + DirBuilder::new().recursive(true).create(&dirpath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt unlink dir at relative path with proper flag + unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap(); + assert!(!dirpath.exists()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_unlinkat_file() { + let tempdir = tempdir().unwrap(); + let filename = "foo.txt"; + let filepath = tempdir.path().join(filename); + + // Create file + File::create(&filepath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt unlink file at relative path + unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap(); + assert!(!filepath.exists()); +} + +#[test] +fn test_access_not_existing() { + let tempdir = tempdir().unwrap(); + let dir = tempdir.path().join("does_not_exist.txt"); + assert_eq!( + access(&dir, AccessFlags::F_OK).err().unwrap(), + Errno::ENOENT + ); +} + +#[test] +fn test_access_file_exists() { + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("does_exist.txt"); + let _file = File::create(path.clone()).unwrap(); + access(&path, AccessFlags::R_OK | AccessFlags::W_OK) + .expect("assertion failed"); +} + +//Clippy false positive https://github.com/rust-lang/rust-clippy/issues/9111 +#[allow(clippy::needless_borrow)] +#[cfg(not(target_os = "redox"))] +#[test] +fn test_user_into_passwd() { + // get the UID of the "nobody" user + #[cfg(not(target_os = "haiku"))] + let test_username = "nobody"; + // "nobody" unavailable on haiku + #[cfg(target_os = "haiku")] + let test_username = "user"; + + let nobody = User::from_name(test_username).unwrap().unwrap(); + let pwd: libc::passwd = nobody.into(); + let _: User = (&pwd).into(); +} + +/// Tests setting the filesystem UID with `setfsuid`. +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_setfsuid() { + use std::os::unix::fs::PermissionsExt; + use std::{fs, io, thread}; + require_capability!("test_setfsuid", CAP_SETUID); + + // get the UID of the "nobody" user + let nobody = User::from_name("nobody").unwrap().unwrap(); + + // create a temporary file with permissions '-rw-r-----' + let file = tempfile::NamedTempFile::new_in("/var/tmp").unwrap(); + let temp_path = file.into_temp_path(); + let temp_path_2 = temp_path.to_path_buf(); + let mut permissions = fs::metadata(&temp_path).unwrap().permissions(); + permissions.set_mode(0o640); + + // spawn a new thread where to test setfsuid + thread::spawn(move || { + // set filesystem UID + let fuid = setfsuid(nobody.uid); + // trying to open the temporary file should fail with EACCES + let res = fs::File::open(&temp_path); + let err = res.expect_err("assertion failed"); + assert_eq!(err.kind(), io::ErrorKind::PermissionDenied); + + // assert fuid actually changes + let prev_fuid = setfsuid(Uid::from_raw(-1i32 as u32)); + assert_ne!(prev_fuid, fuid); + }) + .join() + .unwrap(); + + // open the temporary file with the current thread filesystem UID + fs::File::open(temp_path_2).unwrap(); +} + +#[test] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +fn test_ttyname() { + let fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed"); + assert!(fd.as_raw_fd() > 0); + + // on linux, we can just call ttyname on the pty master directly, but + // apparently osx requires that ttyname is called on a slave pty (can't + // find this documented anywhere, but it seems to empirically be the case) + grantpt(&fd).expect("grantpt failed"); + unlockpt(&fd).expect("unlockpt failed"); + let sname = unsafe { ptsname(&fd) }.expect("ptsname failed"); + let fds = open(Path::new(&sname), OFlag::O_RDWR, stat::Mode::empty()) + .expect("open failed"); + assert!(fds > 0); + + let name = ttyname(fds).expect("ttyname failed"); + assert!(name.starts_with("/dev")); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +fn test_ttyname_not_pty() { + let fd = File::open("/dev/zero").unwrap(); + assert!(fd.as_raw_fd() > 0); + assert_eq!(ttyname(fd.as_raw_fd()), Err(Errno::ENOTTY)); +} + +#[test] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] +fn test_ttyname_invalid_fd() { + assert_eq!(ttyname(-1), Err(Errno::EBADF)); +} + +#[test] +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", +))] +fn test_getpeereid() { + use std::os::unix::net::UnixStream; + let (sock_a, sock_b) = UnixStream::pair().unwrap(); + + let (uid_a, gid_a) = getpeereid(sock_a.as_raw_fd()).unwrap(); + let (uid_b, gid_b) = getpeereid(sock_b.as_raw_fd()).unwrap(); + + let uid = geteuid(); + let gid = getegid(); + + assert_eq!(uid, uid_a); + assert_eq!(gid, gid_a); + assert_eq!(uid_a, uid_b); + assert_eq!(gid_a, gid_b); +} + +#[test] +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", +))] +fn test_getpeereid_invalid_fd() { + // getpeereid is not POSIX, so error codes are inconsistent between different Unices. + getpeereid(-1).expect_err("assertion failed"); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_faccessat_none_not_existing() { + use nix::fcntl::AtFlags; + let tempdir = tempfile::tempdir().unwrap(); + let dir = tempdir.path().join("does_not_exist.txt"); + assert_eq!( + faccessat(None, &dir, AccessFlags::F_OK, AtFlags::empty()) + .err() + .unwrap(), + Errno::ENOENT + ); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_faccessat_not_existing() { + use nix::fcntl::AtFlags; + let tempdir = tempfile::tempdir().unwrap(); + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let not_exist_file = "does_not_exist.txt"; + assert_eq!( + faccessat( + Some(dirfd), + not_exist_file, + AccessFlags::F_OK, + AtFlags::empty(), + ) + .err() + .unwrap(), + Errno::ENOENT + ); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_faccessat_none_file_exists() { + use nix::fcntl::AtFlags; + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().join("does_exist.txt"); + let _file = File::create(path.clone()).unwrap(); + assert!(faccessat( + None, + &path, + AccessFlags::R_OK | AccessFlags::W_OK, + AtFlags::empty(), + ) + .is_ok()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_faccessat_file_exists() { + use nix::fcntl::AtFlags; + let tempdir = tempfile::tempdir().unwrap(); + let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); + let exist_file = "does_exist.txt"; + let path = tempdir.path().join(exist_file); + let _file = File::create(path.clone()).unwrap(); + assert!(faccessat( + Some(dirfd), + &path, + AccessFlags::R_OK | AccessFlags::W_OK, + AtFlags::empty(), + ) + .is_ok()); +} + +#[test] +#[cfg(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "freebsd", + target_os = "dragonfly" +))] +fn test_eaccess_not_existing() { + let tempdir = tempdir().unwrap(); + let dir = tempdir.path().join("does_not_exist.txt"); + assert_eq!( + eaccess(&dir, AccessFlags::F_OK).err().unwrap(), + Errno::ENOENT + ); +} + +#[test] +#[cfg(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "freebsd", + target_os = "dragonfly" +))] +fn test_eaccess_file_exists() { + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("does_exist.txt"); + let _file = File::create(path.clone()).unwrap(); + eaccess(&path, AccessFlags::R_OK | AccessFlags::W_OK) + .expect("assertion failed"); +} -- 2.7.4