diff --git a/.cirrus.yml b/.cirrus.yml index 8d6e121020..522bdafcdb 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -5,75 +5,96 @@ cargo_cache: env: # Build by default; don't just check BUILD: build + CLIPPYFLAGS: -D warnings -A unknown-lints RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings TOOL: cargo - # The MSRV - TOOLCHAIN: 1.46.0 + MSRV: 1.65.0 ZFLAGS: # Tests that don't require executing the build binaries build: &BUILD build_script: - . $HOME/.cargo/env || true - - $TOOL +$TOOLCHAIN $BUILD $ZFLAGS --target $TARGET --all-targets - - $TOOL +$TOOLCHAIN doc $ZFLAGS --no-deps --target $TARGET - - $TOOL +$TOOLCHAIN clippy $ZFLAGS --target $TARGET -- -D warnings + - $TOOL -Vv + - rustc -Vv + - $TOOL $BUILD $ZFLAGS --target $TARGET --all-targets --all-features + - $TOOL doc $ZFLAGS --no-deps --target $TARGET --all-features + - $TOOL clippy $ZFLAGS --target $TARGET --all-targets --all-features -- $CLIPPYFLAGS + - if [ -z "$NOHACK" ]; then mkdir -p $HOME/.cargo/bin; export PATH=$HOME/.cargo/bin:$PATH; fi + - if [ -z "$NOHACK" ]; then curl -LsSf https://github.com/taiki-e/cargo-hack/releases/latest/download/cargo-hack-${HOST:-$TARGET}.tar.gz | tar xzf - -C ~/.cargo/bin; fi + - if [ -z "$NOHACK" ]; then $TOOL hack $ZFLAGS check --target $TARGET --each-feature; fi # Tests that do require executing the binaries test: &TEST << : *BUILD test_script: - . $HOME/.cargo/env || true - - $TOOL +$TOOLCHAIN test --target $TARGET + - $TOOL test --target $TARGET --all-features # Test FreeBSD in a full VM. Test the i686 target too, in the # same VM. The binary will be built in 32-bit mode, but will execute on a # 64-bit kernel and in a 64-bit environment. Our tests don't execute any of # the system's binaries, so the environment shouldn't matter. task: - name: FreeBSD amd64 & i686 env: TARGET: x86_64-unknown-freebsd - freebsd_instance: - image: freebsd-12-2-release-amd64 + matrix: + - name: FreeBSD 14 amd64 & i686 + freebsd_instance: + image_family: freebsd-14-0-snap + cpu: 1 + # Enable tests that would fail on FreeBSD 12 + RUSTFLAGS: --cfg fbsd14 -D warnings + RUSTDOCFLAGS: --cfg fbsd14 setup_script: + - kldload mqueuefs - fetch https://sh.rustup.rs -o rustup.sh - - sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN + - sh rustup.sh -y --profile=minimal --default-toolchain $MSRV - . $HOME/.cargo/env - rustup target add i686-unknown-freebsd - - rustup component add --toolchain $TOOLCHAIN clippy - - cp Cargo.lock.msrv Cargo.lock + - rustup component add clippy << : *TEST i386_test_script: - . $HOME/.cargo/env - - cargo build --target i686-unknown-freebsd - - cargo doc --no-deps --target i686-unknown-freebsd - - cargo test --target i686-unknown-freebsd + - cargo build --target i686-unknown-freebsd --all-features + - cargo doc --no-deps --target i686-unknown-freebsd --all-features + - cargo test --target i686-unknown-freebsd --all-features + i386_feature_script: + - . $HOME/.cargo/env + - if [ -z "$NOHACK" ]; then cargo hack check --each-feature --target i686-unknown-freebsd; fi before_cache_script: rm -rf $CARGO_HOME/registry/index -# Test OSX in a full VM +# Test macOS aarch64 in a full VM task: - name: OSX x86_64 + name: macOS aarch64 env: - TARGET: x86_64-apple-darwin - osx_instance: - image: big-sur-xcode + TARGET: aarch64-apple-darwin + macos_instance: + image: ghcr.io/cirruslabs/macos-ventura-base:latest setup_script: - curl --proto '=https' --tlsv1.2 -sSf -o rustup.sh https://sh.rustup.rs - - sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN + - sh rustup.sh -y --profile=minimal --default-toolchain $MSRV - . $HOME/.cargo/env - - rustup component add --toolchain $TOOLCHAIN clippy - - cp Cargo.lock.msrv Cargo.lock + - rustup component add clippy << : *TEST before_cache_script: rm -rf $CARGO_HOME/registry/index # Use cross for QEMU-based testing # cross needs to execute Docker, so we must use Cirrus's Docker Builder task. task: + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable env: RUST_TEST_THREADS: 1 # QEMU works best with 1 thread HOME: /tmp/home + HOST: x86_64-unknown-linux-gnu PATH: $HOME/.cargo/bin:$PATH RUSTFLAGS: --cfg qemu -D warnings TOOL: cross @@ -114,10 +135,9 @@ task: setup_script: - mkdir /tmp/home - curl --proto '=https' --tlsv1.2 -sSf -o rustup.sh https://sh.rustup.rs - - sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN + - sh rustup.sh -y --profile=minimal --default-toolchain $MSRV - . $HOME/.cargo/env - - cargo install cross - - cp Cargo.lock.msrv Cargo.lock + - cargo install cross --version 0.2.5 << : *TEST before_cache_script: rm -rf $CARGO_HOME/registry/index @@ -126,24 +146,41 @@ task: matrix: - name: Linux aarch64 arm_container: - image: rust:1.46 + image: rust:1.65.0 + cpu: 1 + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable env: - RUSTFLAGS: --cfg graviton -D warnings TARGET: aarch64-unknown-linux-gnu - name: Linux x86_64 container: - image: rust:1.46 + image: rust:1.65.0 + cpu: 1 env: TARGET: x86_64-unknown-linux-gnu - name: Linux x86_64 musl container: - image: rust:1.46 + image: rust:1.65.0 + cpu: 1 + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable env: TARGET: x86_64-unknown-linux-musl setup_script: - rustup target add $TARGET - rustup component add clippy - - cp Cargo.lock.msrv Cargo.lock << : *TEST before_cache_script: rm -rf $CARGO_HOME/registry/index @@ -151,11 +188,10 @@ task: name: Rust Stable container: image: rust:latest + cpu: 1 env: TARGET: x86_64-unknown-linux-gnu - TOOLCHAIN: setup_script: - - rustup target add $TARGET - rustup component add clippy << : *TEST before_cache_script: rm -rf $CARGO_HOME/registry/index @@ -163,9 +199,19 @@ task: # Tasks for cross-compiling, but no testing task: container: - image: rust:1.46 + image: rust:1.65.0 + cpu: 1 + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable env: BUILD: check + HOST: x86_64-unknown-linux-gnu matrix: # Cross claims to support Android, but when it tries to run Nix's tests it # reports undefined symbol references. @@ -193,28 +239,13 @@ task: - name: Illumos env: TARGET: x86_64-unknown-illumos - # illumos toolchain isn't available via rustup until 1.50 - TOOLCHAIN: 1.50.0 - container: - image: rust:1.50 # Cross claims to support running tests on iOS, but it actually doesn't. # https://github.com/rust-embedded/cross/issues/535 - name: iOS aarch64 env: + # cargo hack tries to invoke the iphonesimulator SDK for iOS + NOHACK: 1 TARGET: aarch64-apple-ios - # Rustup only supports cross-building from arbitrary hosts for iOS at - # 1.49.0 and above. Below that it's possible to cross-build from an OSX - # host, but OSX VMs are more expensive than Linux VMs. - TOOLCHAIN: 1.49.0 - - name: iOS x86_64 - env: - TARGET: x86_64-apple-ios - TOOLCHAIN: 1.49.0 - # Cross testing on powerpc fails with "undefined reference to renameat2". - # Perhaps cross is using too-old a version? - - name: Linux powerpc - env: - TARGET: powerpc-unknown-linux-gnu # Cross claims to support Linux powerpc64, but it really doesn't. # https://github.com/rust-embedded/cross/issues/441 - name: Linux powerpc64 @@ -231,69 +262,83 @@ task: TARGET: x86_64-unknown-netbsd setup_script: - rustup target add $TARGET - - rustup toolchain install $TOOLCHAIN --profile minimal --target $TARGET - - rustup component add --toolchain $TOOLCHAIN clippy - - cp Cargo.lock.msrv Cargo.lock + - rustup component add clippy << : *BUILD before_cache_script: rm -rf $CARGO_HOME/registry/index task: container: - image: rust:1.46 + # Redox's MSRV policy is unclear. Until they define it, use nightly. + image: rustlang/rust:nightly + cpu: 1 + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable env: BUILD: check name: Redox x86_64 env: - TARGET: x86_64-unknown-redox - # Redox requires a nightly compiler. - # If stuff breaks, change nightly to the date at - # https://gitlab.redox-os.org/redox-os/redox/-/blob/master/rust-toolchain - TOOLCHAIN: nightly-2021-06-15 + HOST: x86_64-unknown-linux-gnu + TARGET: x86_64-unknown-redox + CLIPPYFLAGS: -D warnings setup_script: - rustup target add $TARGET - - rustup toolchain install $TOOLCHAIN --profile minimal --target $TARGET - - rustup component add --toolchain $TOOLCHAIN clippy - << : *BUILD - before_cache_script: rm -rf $CARGO_HOME/registry/index - -# DragonflyBSD temporarily needs a pinned nightly toolchain -# rustc is broken on DragonflyBSD as of Dec-5, probably by -# https://github.com/rust-lang/rust/commit/e68887e67cc6b7bb4ea5113a40eaa4c0831bda13 -task: - container: - image: rust:1.46 - name: DragonFly BSD x86_64 - env: - BUILD: check - ZFLAGS: -Zbuild-std - TARGET: x86_64-unknown-dragonfly - # Redox requires a nightly compiler. - # If stuff breaks, change nightly to the date at - # https://gitlab.redox-os.org/redox-os/redox/-/blob/master/rust-toolchain - TOOLCHAIN: nightly-2021-12-04 - # Temporarily allow deprecation on DragonflyBSD until an alternative is - # available. - #https://github.com/rust-lang/libc/pull/2522 - RUSTFLAGS: -D warnings -A deprecated - setup_script: - - rustup toolchain add $TOOLCHAIN --profile minimal - - rustup component add rust-src --toolchain $TOOLCHAIN - - rustup component add clippy --toolchain $TOOLCHAIN + - rustup component add clippy << : *BUILD before_cache_script: rm -rf $CARGO_HOME/registry/index -# Rust Tier 3 targets can't use Rustup +## Rust Tier 3 targets can't use Rustup task: container: image: rustlang/rust:nightly + cpu: 1 env: BUILD: check - TOOLCHAIN: nightly + HOST: x86_64-unknown-linux-gnu ZFLAGS: -Zbuild-std + CLIPPYFLAGS: -D warnings matrix: + - name: DragonFly BSD x86_64 + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable + env: + TARGET: x86_64-unknown-dragonfly - name: OpenBSD x86_64 env: TARGET: x86_64-unknown-openbsd + - name: Linux armv7 uclibceabihf + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable + env: + TARGET: armv7-unknown-linux-uclibceabihf + - name: Haiku x86_64 + depends_on: + - FreeBSD 14 amd64 & i686 + - Linux x86_64 + - macOS aarch64 + - Rust Formatter + - OpenBSD x86_64 + - Minver + - Rust Stable + env: + TARGET: x86_64-unknown-haiku setup_script: - rustup component add rust-src << : *BUILD @@ -305,11 +350,21 @@ task: task: name: Minver env: - TOOLCHAIN: nightly + HOST: x86_64-unknown-linux-gnu container: image: rustlang/rust:nightly + cpu: 1 setup_script: - cargo update -Zminimal-versions - script: + check_script: - cargo check before_cache_script: rm -rf $CARGO_HOME/registry/index + +# Tasks that checks if the code is formatted right using `cargo fmt` tool +task: + name: Rust Formatter + container: + image: rust:latest + cpu: 1 + setup_script: rustup component add rustfmt + test_script: cargo fmt --all -- --check **/*.rs diff --git a/.gitignore b/.gitignore index 87f1a14769..0b0404b4a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ syntax: glob Cargo.lock target +.idea *.diff *.rej *.orig diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d5b2a326..3a171afd68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,387 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## [0.23.1] - 2021-12-16 +## [0.27.1] - 2023-08-28 + +### Fixed +- Fixed generating the documentation on docs.rs. + ([#2111](https://github.com/nix-rust/nix/pull/2111)) + +## [0.27.0] - 2023-08-28 ### Added +- Added `AT_EACCESS` to `AtFlags` on all platforms but android + ([#1995](https://github.com/nix-rust/nix/pull/1995)) +- Add `PF_ROUTE` to `SockType` on macOS, iOS, all of the BSDs, Fuchsia, Haiku, Illumos. + ([#1867](https://github.com/nix-rust/nix/pull/1867)) +- Added `nix::ucontext` module on `aarch64-unknown-linux-gnu`. + (#[1662](https://github.com/nix-rust/nix/pull/1662)) +- Added `CanRaw` to `SockProtocol` and `CanBcm` as a separate `SocProtocol` constant. + ([#1912](https://github.com/nix-rust/nix/pull/1912)) +- Added `Generic` and `NFLOG` to `SockProtocol`. + ([#2092](https://github.com/nix-rust/nix/pull/2092)) +- Added `mq_timedreceive` to `::nix::mqueue`. + ([#1966])(https://github.com/nix-rust/nix/pull/1966) +- Added `LocalPeerPid` to `nix::sys::socket::sockopt` for macOS. ([#1967](https://github.com/nix-rust/nix/pull/1967)) +- Added `TFD_TIMER_CANCEL_ON_SET` to `::nix::sys::time::TimerSetTimeFlags` on Linux and Android. + ([#2040](https://github.com/nix-rust/nix/pull/2040)) +- Added `SOF_TIMESTAMPING_OPT_ID` and `SOF_TIMESTAMPING_OPT_TSONLY` to `nix::sys::socket::TimestampingFlag`. + ([#2048](https://github.com/nix-rust/nix/pull/2048)) +- Enabled socket timestamping options on Android. ([#2077](https://github.com/nix-rust/nix/pull/2077)) +- Added vsock support for macOS ([#2056](https://github.com/nix-rust/nix/pull/2056)) +- Added `SO_SETFIB` and `SO_USER_COOKIE` to `nix::sys::socket::sockopt` for FreeBSD. + ([#2085](https://github.com/nix-rust/nix/pull/2085)) +- Added `SO_RTABLE` for OpenBSD and `SO_ACCEPTFILTER` for FreeBSD/NetBSD to `nix::sys::socket::sockopt`. + ([#2085](https://github.com/nix-rust/nix/pull/2085)) +- Added `MSG_WAITFORONE` to `MsgFlags` on Android, Fuchsia, Linux, NetBSD, + FreeBSD, OpenBSD, and Solaris. + ([#2014](https://github.com/nix-rust/nix/pull/2014)) +- Added `SO_TS_CLOCK` for FreeBSD to `nix::sys::socket::sockopt`. + ([#2093](https://github.com/nix-rust/nix/pull/2093)) +- Added support for prctl in Linux. + (#[1550](https://github.com/nix-rust/nix/pull/1550)) +- `nix::socket` and `nix::select` are now available on Redox. + ([#2012](https://github.com/nix-rust/nix/pull/2012)) +- Implemented AsFd, AsRawFd, FromRawFd, and IntoRawFd for `mqueue::MqdT`. + ([#2097](https://github.com/nix-rust/nix/pull/2097)) +- Add the ability to set `kevent_flags` on `SigEvent`. + ([#1731](https://github.com/nix-rust/nix/pull/1731)) + +### Changed + +- All Cargo features have been removed from the default set. Users will need to + specify which features they depend on in their Cargo.toml. + ([#2091](https://github.com/nix-rust/nix/pull/2091)) +- Implemented I/O safety for many, but not all, of Nix's APIs. Many public + functions argument and return types have changed: + | Original Type | New Type | + | ------------- | --------------------- | + | AsRawFd | AsFd | + | RawFd | BorrowedFd or OwnedFd | + + (#[1906](https://github.com/nix-rust/nix/pull/1906)) +- Use I/O safety with `copy_file_range`, and expose it on FreeBSD. + (#[1906](https://github.com/nix-rust/nix/pull/1906)) +- The MSRV is now 1.65 + ([#1862](https://github.com/nix-rust/nix/pull/1862)) + ([#2104](https://github.com/nix-rust/nix/pull/2104)) +- The epoll interface now uses a type. + ([#1882](https://github.com/nix-rust/nix/pull/1882)) +- With I/O-safe type applied in `pty::OpenptyResult` and `pty::ForkptyResult`, + users no longer need to manually close the file descriptors in these types. + ([#1921](https://github.com/nix-rust/nix/pull/1921)) +- Refactored `name` parameter of `mq_open` and `mq_unlink` to be generic over + `NixPath`. + ([#2102](https://github.com/nix-rust/nix/pull/2102)). +- Made `clone` unsafe, like `fork`. + ([#1993](https://github.com/nix-rust/nix/pull/1993)) + +### Removed + +- `sys::event::{kevent, kevent_ts}` are deprecated in favor of + `sys::kevent::Kqueue::kevent`, and `sys::event::kqueue` is deprecated in + favor of `sys::kevent::Kqueue::new`. + ([#1943](https://github.com/nix-rust/nix/pull/1943)) +- Removed deprecated IoVec API. + ([#1855](https://github.com/nix-rust/nix/pull/1855)) +- Removed deprecated net APIs. + ([#1861](https://github.com/nix-rust/nix/pull/1861)) +- `nix::sys::signalfd::signalfd` is deprecated. Use + `nix::sys::signalfd::SignalFd` instead. + ([#1938](https://github.com/nix-rust/nix/pull/1938)) +- Removed `SigEvent` support on Fuchsia, where it was unsound. + ([#2079](https://github.com/nix-rust/nix/pull/2079)) +- Removed `flock` from `::nix::fcntl` on Solaris. + ([#2082](https://github.com/nix-rust/nix/pull/2082)) + +## [0.26.3] - 2023-08-27 + +### Fixed +- Fix: send `ETH_P_ALL` in htons format + ([#1925](https://github.com/nix-rust/nix/pull/1925)) +- Fix: `recvmsg` now sets the length of the received `sockaddr_un` field + correctly on Linux platforms. ([#2041](https://github.com/nix-rust/nix/pull/2041)) +- Fix potentially invalid conversions in + `SockaddrIn::from`, + `SockaddrIn6::from`, `IpMembershipRequest::new`, and + `Ipv6MembershipRequest::new` with future Rust versions. + ([#2061](https://github.com/nix-rust/nix/pull/2061)) +- Fixed an incorrect lifetime returned from `recvmsg`. + ([#2095](https://github.com/nix-rust/nix/pull/2095)) + +## [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::ucontext` 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 @@ -20,8 +398,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). `0..FD_SETSIZE`. (#[1575](https://github.com/nix-rust/nix/pull/1575)) -### Removed - ## [0.23.0] - 2021-09-28 ### Added @@ -130,6 +506,30 @@ This project adheres to [Semantic Versioning](https://semver.org/). - 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)) @@ -155,8 +555,19 @@ This project adheres to [Semantic Versioning](https://semver.org/). `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 @@ -212,6 +623,20 @@ This project adheres to [Semantic Versioning](https://semver.org/). - 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 @@ -275,8 +700,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[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)) -### Fixed -### Removed ## [0.18.0] - 26 July 2020 ### Added @@ -389,22 +812,16 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added - Add `CLK_TCK` to `SysconfVar` (#[1177](https://github.com/nix-rust/nix/pull/1177)) -### Changed -### Fixed ### Removed - Removed deprecated Error::description from error types (#[1175](https://github.com/nix-rust/nix/pull/1175)) ## [0.16.1] - 23 December 2019 -### Added -### Changed ### Fixed - Fixed the build for OpenBSD (#[1168](https://github.com/nix-rust/nix/pull/1168)) -### Removed - ## [0.16.0] - 1 December 2019 ### Added - Added `ptrace::seize()`: similar to `attach()` on Linux @@ -532,8 +949,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Enabled `sched_yield` for all nix hosts. ([#1090](https://github.com/nix-rust/nix/pull/1090)) -### Removed - ## [0.14.1] - 2019-06-06 ### Added - Macros exported by `nix` may now be imported via `use` on the Rust 2018 @@ -558,8 +973,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Fix the build on Android and Linux/mips with recent versions of libc. ([#1072](https://github.com/nix-rust/nix/pull/1072/commits)) -### Removed - ## [0.14.0] - 2019-05-21 ### Added - Add IP_RECVIF & IP_RECVDSTADDR. Enable IP_PKTINFO and IP6_PKTINFO on netbsd/openbsd. @@ -628,6 +1041,23 @@ This project adheres to [Semantic Versioning](https://semver.org/). 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. @@ -645,14 +1075,30 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Added an `mprotect` wrapper. ([#991](https://github.com/nix-rust/nix/pull/991)) -### Changed ### 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 @@ -704,7 +1150,24 @@ This project adheres to [Semantic Versioning](https://semver.org/). - 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00221157b7..8898a7c1fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,32 +79,22 @@ environment. We also have [continuous integration set up on Cirrus-CI][cirrus-ci], which might find some issues on other platforms. The CI will run once you open a pull request. -There is also infrastructure for running tests for other targets -locally. More information is available in the [CI Readme][ci-readme]. - [cirrus-ci]: https://cirrus-ci.com/github/nix-rust/nix -[ci-readme]: ci/README.md ### Disabling a test in the CI environment -Sometimes there are features that cannot be tested in the CI environment. -To stop a test from running under CI, add `skip_if_cirrus!()` to it. Please +Sometimes there are features that cannot be tested in the CI environment. To +stop a test from running under CI, add `skip_if_cirrus!()` to it. Please describe the reason it shouldn't run under CI, and a link to an issue if -possible! - -## bors, the bot who merges all the PRs - -All pull requests are merged via [bors], an integration bot. After the -pull request has been reviewed, the reviewer will leave a comment like - -> bors r+ - -to let bors know that it was approved. Then bors will check that it passes -tests when merged with the latest changes in the `master` branch, and -merge if the tests succeed. +possible! Other tests cannot be run under QEMU, which is used for some +architectures. To skip them, add a `#[cfg_attr(qemu, ignore)]` attribute to +the test. -[bors]: https://bors-ng.github.io/ +## GitHub Merge Queues +We use GitHub merge queues to ensure that subtle merge conflicts won't result +in failing code. If you add or remove a CI job, remember to adjust the +required status checks in the repository's branch protection rules! ## API conventions diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv deleted file mode 100644 index 955bccf4b5..0000000000 --- a/Cargo.lock.msrv +++ /dev/null @@ -1,373 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "assert-impl" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e79d69c321c1d98eb55e4890ed07e885c071b9575960a9e742d2931581f496" - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "bitflags" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" - -[[package]] -name = "byteorder" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" - -[[package]] -name = "caps" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d092fbb6657fb1f98a7da70c14335ac97e5a9477e1a8156d4bbf19a3a7aece51" -dependencies = [ - "errno", - "libc", - "thiserror", -] - -[[package]] -name = "cc" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1" - -[[package]] -name = "cfg-if" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "errno" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c858c42ac0b88532f48fca88b0ed947cad4f1f64d904bcd6c9f138f7b95d70" -dependencies = [ - "kernel32-sys", - "libc", - "winapi 0.2.4", -] - -[[package]] -name = "getrandom" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" -dependencies = [ - "cfg-if 0.1.2", - "libc", - "wasi", -] - -[[package]] -name = "instant" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1ca084b49bfd975182288e1a5f1d27ea34ff2d6ae084ae5e66e1652427eada" -dependencies = [ - "winapi 0.2.4", - "winapi-build", -] - -[[package]] -name = "lazy_static" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" - -[[package]] -name = "libc" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" - -[[package]] -name = "lock_api" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "memoffset" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "nix" -version = "0.23.0" -dependencies = [ - "assert-impl", - "bitflags", - "caps", - "cc", - "cfg-if 1.0.0", - "lazy_static", - "libc", - "memoffset", - "parking_lot", - "rand", - "semver", - "sysctl", - "tempfile", -] - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi 0.3.9", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" - -[[package]] -name = "proc-macro2" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f287c234c9b2d0308d692dee5c449c1a171167a6f8150f7cf2a49d8fd96967" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab938ebe6f1c82426b5fb82eaf10c3e3028c53deaa3fbe38f5904b37cf4d767" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core", -] - -[[package]] -name = "redox_syscall" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" -dependencies = [ - "bitflags", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "semver" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b5842e81eb9bbea19276a9dbbda22ac042532f390a67ab08b895617978abf3" - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - -[[package]] -name = "syn" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8c8eab7d9f493cd89d4068085651d81ac7d39c56eb64f7158ea514b156e280" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "sysctl" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7da87d95ab91294ebf2132bf36e1c4f6ac1df11db4c63105858cb175e2141c" -dependencies = [ - "byteorder", - "errno", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.9", -] - -[[package]] -name = "thiserror" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d032db01164196ffdea5d016aa5cacd9d163a4fb00b85e9fc3ad18c5b1a3951d" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d53f5a0d2bd66d1d841e69a4beb74a226216b3f158ff0c534578f76e7beac9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "winapi" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5350e40d908c7e8b9e5c9edb541ca47cc617c6229d3575a46da6f550f36c96fd" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index d2ca8ee804..5a7806075a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "nix" description = "Rust friendly bindings to *nix APIs" -edition = "2018" -version = "0.23.1" -rust-version = "1.46" +edition = "2021" +version = "0.27.1" +rust-version = "1.65" authors = ["The nix-rust Project Developers"] repository = "https://github.com/nix-rust/nix" license = "MIT" @@ -11,6 +11,8 @@ categories = ["os::unix-apis"] include = ["src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] [package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] targets = [ "x86_64-unknown-linux-gnu", "aarch64-linux-android", @@ -26,29 +28,60 @@ targets = [ ] [dependencies] -libc = { version = "0.2.102", features = [ "extra_traits" ] } -bitflags = "1.1" +libc = { version = "0.2.147", features = ["extra_traits"] } +bitflags = "2.3.1" cfg-if = "1.0" +pin-utils = { version = "0.1.0", optional = true } +memoffset = { version = "0.9", optional = true } -[target.'cfg(not(target_os = "redox"))'.dependencies] -memoffset = "0.6.3" +[features] +default = [] -[target.'cfg(target_os = "dragonfly")'.build-dependencies] -cc = "1" +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.2" -parking_lot = "0.11.2" +parking_lot = "0.12" rand = "0.8" -tempfile = "3.2.0" -semver = "1.0.0" +tempfile = "3.7.1" +semver = "1.0.7" [target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies] -caps = "0.5.1" +caps = "0.5.3" [target.'cfg(target_os = "freebsd")'.dev-dependencies] -sysctl = "0.1" +sysctl = "0.4" [[test]] name = "test" @@ -62,15 +95,11 @@ path = "test/sys/test_aio_drop.rs" name = "test-clearenv" path = "test/test_clearenv.rs" -[[test]] -name = "test-lio-listio-resubmit" -path = "test/sys/test_lio_listio_resubmit.rs" - [[test]] name = "test-mount" path = "test/test_mount.rs" harness = false [[test]] -name = "test-ptymaster-drop" -path = "test/test_ptymaster_drop.rs" +name = "test-prctl" +path = "test/sys/test_prctl.rs" diff --git a/README.md b/README.md index a8759f1ce8..e172de2750 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,13 @@ call: // 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<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr>; +// 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 +nix target support consists of three 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: @@ -41,52 +41,70 @@ limitations. Support for platforms is split into three tiers: 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. + *do not* necessarily block the inclusion of new code. That is, at + our discretion a Tier 3 target may be dropped at any time, if it + would otherwise block development. + +Platforms not listed are supported on a best-effort basis, relying on our users +to report any problems. The following targets are supported by `nix`: -Tier 1: - * 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-apple-darwin - * 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-unknown-illumos - * x86_64-unknown-netbsd - -Tier 3: - * x86_64-fuchsia - * x86_64-unknown-dragonfly - * x86_64-unknown-linux-gnux32 - * x86_64-unknown-openbsd - * x86_64-unknown-redox + + + + + + + + + + + +
Tier 1Tier 2Tier 3
+
    +
  • 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
  • +
+
+
    +
  • aarch64-apple-ios
  • +
  • aarch64-linux-android
  • +
  • arm-linux-androideabi
  • +
  • arm-unknown-linux-musleabi
  • +
  • armv7-linux-androideabi
  • +
  • i686-linux-android
  • +
  • s390x-unknown-linux-gnu
  • +
  • x86_64-linux-android
  • +
  • x86_64-unknown-illumos
  • +
  • x86_64-unknown-netbsd
  • +
+
  • armv7-unknown-linux-uclibceabihf
  • +
  • powerpc64-unknown-linux-gnu
  • +
  • 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.46.0 and higher. It's MSRV will not be +nix is supported on Rust 1.65 and higher. Its MSRV will not be changed in the future without bumping the major or minor version. ## Contributing diff --git a/RELEASE_PROCEDURE.md b/RELEASE_PROCEDURE.md index fab41dd339..9c68f78b18 100644 --- a/RELEASE_PROCEDURE.md +++ b/RELEASE_PROCEDURE.md @@ -16,3 +16,4 @@ The release is prepared as follows: - Confirm that everything's ready for a release by running `cargo release ` - Create the release with `cargo release -x ` +- Push the created tag to GitHub. diff --git a/bors.toml b/bors.toml deleted file mode 100644 index 03810b7e8e..0000000000 --- a/bors.toml +++ /dev/null @@ -1,49 +0,0 @@ -status = [ - "Android aarch64", - "Android arm", - "Android armv7", - "Android i686", - "Android x86_64", - "DragonFly BSD x86_64", - "FreeBSD amd64 & i686", - "Fuchsia x86_64", - "Linux MIPS", - "Linux MIPS64 el", - "Linux MIPS64", - "Linux aarch64", - "Linux arm gnueabi", - "Linux arm-musleabi", - "Linux armv7 gnueabihf", - "Linux i686 musl", - "Linux i686", - "Linux mipsel", - "Linux powerpc", - "Linux powerpc64", - "Linux powerpc64le", - "Linux s390x", - "Linux x32", - "Linux x86_64 musl", - "Linux x86_64", - "Minver", - "NetBSD x86_64", - "OpenBSD x86_64", - "OSX x86_64", - "Redox x86_64", - "Rust Stable", - "iOS aarch64", - "iOS x86_64", - "Illumos", -] - -# Set bors's timeout to 1 hour -# -# bors's timeout should always be at least twice as long as the test suite -# takes. This is to allow the CI provider to fast-fail a test; if one of the -# builders immediately reports a failure, then bors will move on to the next -# batch, leaving the slower builders to work through the already-doomed run and -# the next one. -# -# At the time this was written, nix's test suite took about twenty minutes to -# run. The timeout was raised to one hour to give nix room to grow and time -# for delays on Cirrus's end. -timeout_sec = 3600 diff --git a/release.toml b/release.toml index df2c9da45d..23488fbfa5 100644 --- a/release.toml +++ b/release.toml @@ -1,4 +1,3 @@ -no-dev-version = true pre-release-replacements = [ { file="CHANGELOG.md", search="Unreleased", replace="{{version}}" }, { file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}" } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000..5c8d9318b3 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 \ No newline at end of file diff --git a/src/dir.rs b/src/dir.rs index ed70a458ac..96a5843bc6 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,10 +1,13 @@ -use crate::{Error, NixPath, Result}; +//! 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; -use std::ffi; -use crate::sys; #[cfg(target_os = "linux")] use libc::{dirent64 as dirent, readdir64_r as readdir_r}; @@ -26,21 +29,26 @@ use libc::{dirent, readdir_r}; /// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc /// does). #[derive(Debug, Eq, Hash, PartialEq)] -pub struct Dir( - ptr::NonNull -); +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 { + 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 { + 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) } @@ -52,12 +60,15 @@ impl Dir { } /// 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 - })?; + let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else( + || { + let e = Error::last(); + unsafe { libc::close(fd) }; + e + }, + )?; Ok(Dir(d)) } @@ -90,6 +101,9 @@ impl Drop for Dir { } } +// The pass by mut is technically needless only because the inner NonNull is +// Copy. But philosophically we're mutating the Dir, so we pass by mut. +#[allow(clippy::needless_pass_by_ref_mut)] fn next(dir: &mut Dir) -> Option> { unsafe { // Note: POSIX specifies that portable applications should dynamically allocate a @@ -99,9 +113,11 @@ fn next(dir: &mut Dir) -> Option> { // 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)) - { + 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() { @@ -112,6 +128,7 @@ fn next(dir: &mut Dir) -> Option> { } } +/// Return type of [`Dir::iter`]. #[derive(Debug, Eq, Hash, PartialEq)] pub struct Iter<'d>(&'d mut Dir); @@ -141,6 +158,14 @@ impl Iterator for OwningIter { } } +/// 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; @@ -173,52 +198,53 @@ impl IntoIterator for Dir { #[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`. - #[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"))] - pub fn ino(&self) -> u64 { - self.0.d_ino as u64 - } - - /// Returns the inode number (`d_fileno`) of the underlying `dirent`. - #[cfg(not(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")))] - #[allow(clippy::useless_conversion)] // Not useless on all OSes + #[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 { - u64::from(self.0.d_fileno) + cfg_if! { + if #[cfg(any(target_os = "aix", + 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()) } + unsafe { ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } } /// Returns the type of this directory entry, if known. @@ -227,7 +253,12 @@ impl Entry { /// 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")))] + #[cfg(not(any( + target_os = "aix", + 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), @@ -239,8 +270,13 @@ impl Entry { /* libc::DT_UNKNOWN | */ _ => None, } - // illumos and Solaris systems do not have the d_type member at all: - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + // illumos, Solaris, and Haiku systems do not have the d_type member at all: + #[cfg(any( + target_os = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "haiku" + ))] None } } diff --git a/src/env.rs b/src/env.rs index bcae28713e..95177a1d2a 100644 --- a/src/env.rs +++ b/src/env.rs @@ -42,7 +42,6 @@ pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> { cfg_if! { if #[cfg(any(target_os = "fuchsia", target_os = "wasi", - target_env = "wasi", target_env = "uclibc", target_os = "linux", target_os = "android", diff --git a/src/errno.rs b/src/errno.rs index 3da246e823..50b35248f8 100644 --- a/src/errno.rs +++ b/src/errno.rs @@ -1,8 +1,8 @@ +use crate::Result; use cfg_if::cfg_if; use libc::{c_int, c_void}; use std::convert::TryFrom; -use std::{fmt, io, error}; -use crate::{Error, Result}; +use std::{error, fmt, io}; pub use self::consts::*; @@ -30,6 +30,14 @@ cfg_if! { 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() + } + } else if #[cfg(any(target_os = "aix"))] { + unsafe fn errno_location() -> *mut c_int { + libc::_Errno() + } } } @@ -43,49 +51,10 @@ fn clear() { /// Returns the platform-specific value of errno pub fn errno() -> i32 { - unsafe { - (*errno_location()) as i32 - } + unsafe { *errno_location() } } impl Errno { - /// Convert this `Error` to an [`Errno`](enum.Errno.html). - /// - /// # Example - /// - /// ``` - /// # use nix::Error; - /// # use nix::errno::Errno; - /// let e = Error::from(Errno::EPERM); - /// assert_eq!(Some(Errno::EPERM), e.as_errno()); - /// ``` - #[deprecated( - since = "0.22.0", - note = "It's a no-op now; just delete it." - )] - pub const fn as_errno(self) -> Option { - Some(self) - } - - /// Create a nix Error from a given errno - #[deprecated( - since = "0.22.0", - note = "It's a no-op now; just delete it." - )] - #[allow(clippy::wrong_self_convention)] // False positive - pub fn from_errno(errno: Errno) -> Error { - errno - } - - /// Create a new invalid argument error (`EINVAL`) - #[deprecated( - since = "0.22.0", - note = "Use Errno::EINVAL instead" - )] - pub const fn invalid_argument() -> Error { - Errno::EINVAL - } - pub fn last() -> Self { last() } @@ -112,21 +81,6 @@ impl Errno { Ok(value) } } - - /// Backwards compatibility hack for Nix <= 0.21.0 users - /// - /// In older versions of Nix, `Error::Sys` was an enum variant. Now it's a - /// function, which is compatible with most of the former use cases of the - /// enum variant. But you should use `Error(Errno::...)` instead. - #[deprecated( - since = "0.22.0", - note = "Use Errno::... instead" - )] - #[allow(non_snake_case)] - #[inline] - pub const fn Sys(errno: Errno) -> Error { - errno - } } /// The sentinel value indicates that a function failed and more detailed @@ -136,23 +90,33 @@ pub trait ErrnoSentinel: Sized { } impl ErrnoSentinel for isize { - fn sentinel() -> Self { -1 } + fn sentinel() -> Self { + -1 + } } impl ErrnoSentinel for i32 { - fn sentinel() -> Self { -1 } + fn sentinel() -> Self { + -1 + } } impl ErrnoSentinel for i64 { - fn sentinel() -> Self { -1 } + fn sentinel() -> Self { + -1 + } } impl ErrnoSentinel for *mut c_void { - fn sentinel() -> Self { -1isize as *mut c_void } + fn sentinel() -> Self { + -1isize as *mut c_void + } } impl ErrnoSentinel for libc::sighandler_t { - fn sentinel() -> Self { libc::SIG_ERR } + fn sentinel() -> Self { + libc::SIG_ERR + } } impl error::Error for Errno {} @@ -173,9 +137,7 @@ 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) + ioerror.raw_os_error().map(Errno::from_i32).ok_or(ioerror) } } @@ -186,748 +148,1152 @@ fn last() -> 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", - 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", + 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", - EPFNOSUPPORT => "Protocol family not supported", - 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", - 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(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 = "aix", + 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 = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL2NSYNC => "Level 2 not synchronized", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL3HLT => "Level 3 halted", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EL3RST => "Level 3 reset", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + 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 = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EUNATCH => "Protocol driver not attached", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + ENOCSI => "No CSI structure available", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + 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 = "aix", + 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 = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "fuchsia" + ))] + EPROTO => "Protocol error", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + 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 = "aix", + 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"))] - 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"))] - 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"))] + EBADMSG => "Trying to read unreadable message", + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "aix", + 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 = "aix", + 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 = "aix", + 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 = "aix", + 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 = "aix", + 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" + ))] + 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( + 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", + 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", + 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"))] - EILSEQ => "Illegal byte sequence", - - #[cfg(any(target_os = "macos", target_os = "freebsd", - target_os = "dragonfly", target_os = "ios", - target_os = "openbsd", target_os = "netbsd"))] - 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"))] - 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"))] - EPROTO => "Protocol error", - - #[cfg(any(target_os = "macos", target_os = "freebsd", - target_os = "ios", target_os = "openbsd"))] + 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 = "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"))] - 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"))] - 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"))] - 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 = "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 = "aix", + 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 = "aix", + 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 = "aix", + 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 = "aix", + 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 = "aix", + 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 = "aix", + 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 = "aix", + 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", + EPWROFF => "Device power is off", #[cfg(any(target_os = "macos", target_os = "ios"))] - EDEVERR => "Device error, e.g. paper out", + EDEVERR => "Device error, e.g. paper out", #[cfg(any(target_os = "macos", target_os = "ios"))] - EBADEXEC => "Bad executable", + EBADEXEC => "Bad executable", #[cfg(any(target_os = "macos", target_os = "ios"))] - EBADARCH => "Bad CPU type in executable", + EBADARCH => "Bad CPU type in executable", #[cfg(any(target_os = "macos", target_os = "ios"))] - ESHLIBVERS => "Shared library version mismatch", + 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"))] - 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"))] - 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", + 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 = "aix", + 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 = "aix", + target_os = "netbsd", + target_os = "redox" + ))] + ENOSR => "No STREAM resources", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "aix", + target_os = "netbsd", + target_os = "redox" + ))] + ENOSTR => "Not a STREAM", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "aix", + target_os = "netbsd", + target_os = "redox" + ))] + ETIME => "STREAM ioctl timeout", + + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "aix", + 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", + ENOPOLICY => "No such policy registered", #[cfg(any(target_os = "macos", target_os = "ios"))] - EQFULL => "Interface output queue is full", + EQFULL => "Interface output queue is full", #[cfg(target_os = "openbsd")] - EOPNOTSUPP => "Operation not supported", + EOPNOTSUPP => "Operation not supported", #[cfg(target_os = "openbsd")] - EIPSEC => "IPsec processing failure", + EIPSEC => "IPsec processing failure", #[cfg(target_os = "dragonfly")] - EASYNC => "Async", + EASYNC => "Async", #[cfg(any(target_os = "illumos", target_os = "solaris"))] - EDEADLOCK => "Resource deadlock would occur", + EDEADLOCK => "Resource deadlock would occur", #[cfg(any(target_os = "illumos", target_os = "solaris"))] - ELOCKUNMAPPED => "Locked lock was unmapped", + ELOCKUNMAPPED => "Locked lock was unmapped", #[cfg(any(target_os = "illumos", target_os = "solaris"))] - ENOTACTIVE => "Facility is not active", + ENOTACTIVE => "Facility is not active", } } -#[cfg(any(target_os = "linux", target_os = "android", - target_os = "fuchsia"))] +#[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, + 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, + 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, + #[cfg(not(any(target_os = "android", target_arch = "mips")))] + ERFKILL = libc::ERFKILL, + #[cfg(not(any(target_os = "android", target_arch = "mips")))] + EHWPOISON = libc::EHWPOISON, } - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EDEADLOCK instead" - )] - pub const EDEADLOCK: Errno = Errno::EDEADLK; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ENOTSUP instead" - )] - pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; - impl Errno { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - pub const EDEADLOCK: Errno = Errno::EDEADLK; - pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; } pub const fn from_i32(e: i32) -> Errno { @@ -1063,11 +1429,11 @@ mod consts { libc::EKEYREJECTED => EKEYREJECTED, libc::EOWNERDEAD => EOWNERDEAD, libc::ENOTRECOVERABLE => ENOTRECOVERABLE, - #[cfg(not(any(target_os = "android", target_arch="mips")))] + #[cfg(not(any(target_os = "android", target_arch = "mips")))] libc::ERFKILL => ERFKILL, - #[cfg(not(any(target_os = "android", target_arch="mips")))] + #[cfg(not(any(target_os = "android", target_arch = "mips")))] libc::EHWPOISON => EHWPOISON, - _ => UnknownErrno, + _ => UnknownErrno, } } } @@ -1078,135 +1444,119 @@ mod consts { #[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, + 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, + 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, + EOWNERDEAD = libc::EOWNERDEAD, + EQFULL = libc::EQFULL, } - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ELAST instead" - )] - pub const ELAST: Errno = Errno::EQFULL; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EDEADLOCK instead" - )] - pub const EDEADLOCK: Errno = Errno::EDEADLK; - impl Errno { - pub const ELAST: Errno = Errno::EQFULL; + pub const ELAST: Errno = Errno::EQFULL; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EDEADLOCK: Errno = Errno::EDEADLK; } pub const fn from_i32(e: i32) -> Errno { @@ -1319,7 +1669,7 @@ mod consts { libc::ENOTRECOVERABLE => ENOTRECOVERABLE, libc::EOWNERDEAD => EOWNERDEAD, libc::EQFULL => EQFULL, - _ => UnknownErrno, + _ => UnknownErrno, } } } @@ -1330,143 +1680,122 @@ mod consts { #[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, - } - - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ELAST instead" - )] - pub const ELAST: Errno = Errno::EOWNERDEAD; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EDEADLOCK instead" - )] - pub const EDEADLOCK: Errno = Errno::EDEADLK; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EOPNOTSUPP instead" - )] - pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; - - 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, + 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, @@ -1557,141 +1886,121 @@ mod consts { libc::ECAPMODE => ECAPMODE, libc::ENOTRECOVERABLE => ENOTRECOVERABLE, libc::EOWNERDEAD => EOWNERDEAD, - _ => UnknownErrno, + _ => 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, + 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, - EASYNC = libc::EASYNC, + 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, } - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ELAST instead" - )] - pub const ELAST: Errno = Errno::EASYNC; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EDEADLOCK instead" - )] - pub const EDEADLOCK: Errno = Errno::EDEADLK; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EOPNOTSUPP instead" - )] - pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; - impl Errno { - pub const ELAST: Errno = Errno::EASYNC; + 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 EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; } pub const fn from_i32(e: i32) -> Errno { @@ -1718,7 +2027,7 @@ mod consts { libc::EXDEV => EXDEV, libc::ENODEV => ENODEV, libc::ENOTDIR => ENOTDIR, - libc::EISDIR=> EISDIR, + libc::EISDIR => EISDIR, libc::EINVAL => EINVAL, libc::ENFILE => ENFILE, libc::EMFILE => EMFILE, @@ -1792,149 +2101,137 @@ mod consts { libc::EPROTO => EPROTO, libc::ENOMEDIUM => ENOMEDIUM, libc::EASYNC => EASYNC, - _ => UnknownErrno, + _ => 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, + 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, - } - - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ELAST instead" - )] - pub const ELAST: Errno = Errno::ENOTSUP; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - - 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, + 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, @@ -2017,7 +2314,7 @@ mod consts { libc::ENOTRECOVERABLE => ENOTRECOVERABLE, libc::EOWNERDEAD => EOWNERDEAD, libc::EPROTO => EPROTO, - _ => UnknownErrno, + _ => UnknownErrno, } } } @@ -2028,118 +2325,107 @@ mod consts { #[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, + 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, + 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, } - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ELAST instead" - )] - pub const ELAST: Errno = Errno::ENOTSUP; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - impl Errno { - pub const ELAST: Errno = Errno::ENOTSUP; + pub const ELAST: Errno = Errno::ENOTSUP; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } @@ -2208,47 +2494,504 @@ mod consts { libc::ETOOMANYREFS => ETOOMANYREFS, libc::ETIMEDOUT => ETIMEDOUT, libc::ECONNREFUSED => ECONNREFUSED, - libc::ELOOP => ELOOP, - libc::ENAMETOOLONG => ENAMETOOLONG, + 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::ENOTEMPTY => ENOTEMPTY, - libc::EPROCLIM => EPROCLIM, - libc::EUSERS => EUSERS, - libc::EDQUOT => EDQUOT, + libc::EALREADY => EALREADY, + libc::EINPROGRESS => EINPROGRESS, 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, + _ => UnknownErrno, } } } -#[cfg(target_os = "redox")] +#[cfg(target_os = "haiku")] mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] @@ -2269,7 +3012,6 @@ mod consts { ENOMEM = libc::ENOMEM, EACCES = libc::EACCES, EFAULT = libc::EFAULT, - ENOTBLK = libc::ENOTBLK, EBUSY = libc::EBUSY, EEXIST = libc::EEXIST, EXDEV = libc::EXDEV, @@ -2298,10 +3040,7 @@ mod consts { EPROTOTYPE = libc::EPROTOTYPE, ENOPROTOOPT = libc::ENOPROTOOPT, EPROTONOSUPPORT = libc::EPROTONOSUPPORT, - ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, - EOPNOTSUPP = libc::EOPNOTSUPP, - EPFNOSUPPORT = libc::EPFNOSUPPORT, - EAFNOSUPPORT = libc::EAFNOSUPPORT, + ENOTSUP = libc::ENOTSUP, EADDRINUSE = libc::EADDRINUSE, EADDRNOTAVAIL = libc::EADDRNOTAVAIL, ENETDOWN = libc::ENETDOWN, @@ -2313,7 +3052,6 @@ mod consts { EISCONN = libc::EISCONN, ENOTCONN = libc::ENOTCONN, ESHUTDOWN = libc::ESHUTDOWN, - ETOOMANYREFS = libc::ETOOMANYREFS, ETIMEDOUT = libc::ETIMEDOUT, ECONNREFUSED = libc::ECONNREFUSED, ELOOP = libc::ELOOP, @@ -2321,35 +3059,26 @@ mod consts { 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, + EILSEQ = libc::EILSEQ, + ENOATTR = libc::ENOATTR, EBADMSG = libc::EBADMSG, - ENODATA = libc::ENODATA, - ENOSR = libc::ENOSR, - ENOSTR = libc::ENOSTR, - ETIME = libc::ETIME, EMULTIHOP = libc::EMULTIHOP, ENOLINK = libc::ENOLINK, EPROTO = libc::EPROTO, } - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - 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 { @@ -2370,7 +3099,6 @@ mod consts { libc::ENOMEM => ENOMEM, libc::EACCES => EACCES, libc::EFAULT => EFAULT, - libc::ENOTBLK => ENOTBLK, libc::EBUSY => EBUSY, libc::EEXIST => EEXIST, libc::EXDEV => EXDEV, @@ -2399,10 +3127,7 @@ mod consts { libc::EPROTOTYPE => EPROTOTYPE, libc::ENOPROTOOPT => ENOPROTOOPT, libc::EPROTONOSUPPORT => EPROTONOSUPPORT, - libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, - libc::EOPNOTSUPP => EOPNOTSUPP, - libc::EPFNOSUPPORT => EPFNOSUPPORT, - libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::ENOTSUP => ENOTSUP, libc::EADDRINUSE => EADDRINUSE, libc::EADDRNOTAVAIL => EADDRNOTAVAIL, libc::ENETDOWN => ENETDOWN, @@ -2414,7 +3139,6 @@ mod consts { libc::EISCONN => EISCONN, libc::ENOTCONN => ENOTCONN, libc::ESHUTDOWN => ESHUTDOWN, - libc::ETOOMANYREFS => ETOOMANYREFS, libc::ETIMEDOUT => ETIMEDOUT, libc::ECONNREFUSED => ECONNREFUSED, libc::ELOOP => ELOOP, @@ -2422,22 +3146,17 @@ mod consts { 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::EILSEQ => EILSEQ, + libc::ENOATTR => ENOATTR, libc::EBADMSG => EBADMSG, - libc::ENODATA => ENODATA, - libc::ENOSR => ENOSR, - libc::ENOSTR => ENOSTR, - libc::ETIME => ETIME, libc::EMULTIHOP => EMULTIHOP, libc::ENOLINK => ENOLINK, libc::EPROTO => EPROTO, @@ -2446,7 +3165,7 @@ mod consts { } } -#[cfg(any(target_os = "illumos", target_os = "solaris"))] +#[cfg(target_os = "aix")] mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] @@ -2487,64 +3206,16 @@ mod consts { 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, + ENOLCK = libc::ENOLCK, ENOSYS = libc::ENOSYS, - ELOOP = libc::ELOOP, - ERESTART = libc::ERESTART, - ESTRPIPE = libc::ESTRPIPE, ENOTEMPTY = libc::ENOTEMPTY, - EUSERS = libc::EUSERS, + ELOOP = libc::ELOOP, + ENOMSG = libc::ENOMSG, + EIDRM = libc::EIDRM, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, ENOTSOCK = libc::ENOTSOCK, EDESTADDRREQ = libc::EDESTADDRREQ, EMSGSIZE = libc::EMSGSIZE, @@ -2552,7 +3223,6 @@ mod consts { ENOPROTOOPT = libc::ENOPROTOOPT, EPROTONOSUPPORT = libc::EPROTONOSUPPORT, ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, - EOPNOTSUPP = libc::EOPNOTSUPP, EPFNOSUPPORT = libc::EPFNOSUPPORT, EAFNOSUPPORT = libc::EAFNOSUPPORT, EADDRINUSE = libc::EADDRINUSE, @@ -2571,25 +3241,35 @@ mod consts { ECONNREFUSED = libc::ECONNREFUSED, EHOSTDOWN = libc::EHOSTDOWN, EHOSTUNREACH = libc::EHOSTUNREACH, - EALREADY = libc::EALREADY, - EINPROGRESS = libc::EINPROGRESS, + ECHRNG = libc::ECHRNG, + EL2NSYNC = libc::EL2NSYNC, + EL3HLT = libc::EL3HLT, + EL3RST = libc::EL3RST, + ELNRNG = libc::ELNRNG, + EUNATCH = libc::EUNATCH, + ENOCSI = libc::ENOCSI, + EL2HLT = libc::EL2HLT, + ENOLINK = libc::ENOLINK, + EPROTO = libc::EPROTO, + EMULTIHOP = libc::EMULTIHOP, + EBADMSG = libc::EBADMSG, + EOVERFLOW = libc::EOVERFLOW, + EILSEQ = libc::EILSEQ, + ERESTART = libc::ERESTART, + EOWNERDEAD = libc::EOWNERDEAD, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + ENOTSUP = libc::ENOTSUP, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, ESTALE = libc::ESTALE, - } - - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::ELAST instead" - )] - pub const ELAST: Errno = Errno::ELAST; - #[deprecated( - since = "0.22.1", - note = "use nix::errno::Errno::EWOULDBLOCK instead" - )] - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; - - impl Errno { - pub const ELAST: Errno = Errno::ESTALE; - pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + EREMOTE = libc::EREMOTE, + ECANCELED = libc::ECANCELED, + ENODATA = libc::ENODATA, + ENOSR = libc::ENOSR, + ENOSTR = libc::ENOSTR, + ETIME = libc::ETIME, + EOPNOTSUPP = libc::EOPNOTSUPP, } pub const fn from_i32(e: i32) -> Errno { @@ -2630,64 +3310,16 @@ mod consts { 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::ENOLCK => ENOLCK, libc::ENOSYS => ENOSYS, - libc::ELOOP => ELOOP, - libc::ERESTART => ERESTART, - libc::ESTRPIPE => ESTRPIPE, libc::ENOTEMPTY => ENOTEMPTY, - libc::EUSERS => EUSERS, + libc::ELOOP => ELOOP, + libc::ENOMSG => ENOMSG, + libc::EIDRM => EIDRM, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, libc::ENOTSOCK => ENOTSOCK, libc::EDESTADDRREQ => EDESTADDRREQ, libc::EMSGSIZE => EMSGSIZE, @@ -2695,7 +3327,6 @@ mod consts { libc::ENOPROTOOPT => ENOPROTOOPT, libc::EPROTONOSUPPORT => EPROTONOSUPPORT, libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, - libc::EOPNOTSUPP => EOPNOTSUPP, libc::EPFNOSUPPORT => EPFNOSUPPORT, libc::EAFNOSUPPORT => EAFNOSUPPORT, libc::EADDRINUSE => EADDRINUSE, @@ -2714,9 +3345,35 @@ mod consts { libc::ECONNREFUSED => ECONNREFUSED, libc::EHOSTDOWN => EHOSTDOWN, libc::EHOSTUNREACH => EHOSTUNREACH, - libc::EALREADY => EALREADY, - libc::EINPROGRESS => EINPROGRESS, + 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::ENOLINK => ENOLINK, + libc::EPROTO => EPROTO, + libc::EMULTIHOP => EMULTIHOP, + libc::EBADMSG => EBADMSG, + libc::EOVERFLOW => EOVERFLOW, + libc::EILSEQ => EILSEQ, + libc::ERESTART => ERESTART, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + libc::EOWNERDEAD => EOWNERDEAD, + libc::ENOTSUP => ENOTSUP, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::ECANCELED => ECANCELED, + libc::ENODATA => ENODATA, + libc::ENOSR => ENOSR, + libc::ENOSTR => ENOSTR, + libc::ETIME => ETIME, + libc::EOPNOTSUPP => EOPNOTSUPP, _ => UnknownErrno, } } diff --git a/src/fcntl.rs b/src/fcntl.rs index dd8e59a6ec..9bfecda5ac 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -5,27 +5,36 @@ use std::ffi::OsString; use std::os::raw; use std::os::unix::ffi::OsStringExt; use std::os::unix::io::RawFd; -use crate::sys::stat::Mode; -use crate::{NixPath, Result}; +// For splice and copy_file_range +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux" +))] +use std::{ + os::unix::io::{AsFd, AsRawFd}, + ptr, +}; -#[cfg(any(target_os = "android", target_os = "linux"))] -use std::ptr; // For splice and copy_file_range -#[cfg(any(target_os = "android", target_os = "linux"))] -use crate::sys::uio::IoVec; // For vmsplice +#[cfg(feature = "fs")] +use crate::{sys::stat::Mode, NixPath, Result}; #[cfg(any( target_os = "linux", target_os = "android", target_os = "emscripten", target_os = "fuchsia", - any(target_os = "wasi", target_env = "wasi"), + target_os = "wasi", target_env = "uclibc", target_os = "freebsd" ))] -pub use self::posix_fadvise::*; +#[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; @@ -34,23 +43,30 @@ libc_bitflags! { AT_NO_AUTOMOUNT; #[cfg(any(target_os = "android", target_os = "linux"))] AT_EMPTY_PATH; - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(not(target_os = "android"))] 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")))] + #[cfg(not(any(target_os = "aix", + 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. /// @@ -64,9 +80,11 @@ libc_bitflags!( 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", @@ -75,11 +93,13 @@ libc_bitflags!( 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", @@ -89,6 +109,7 @@ libc_bitflags!( 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", @@ -99,18 +120,23 @@ libc_bitflags!( 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(target_os = "redox"))] + #[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; @@ -118,11 +144,13 @@ libc_bitflags!( 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. /// @@ -134,9 +162,11 @@ libc_bitflags!( 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", @@ -146,17 +176,21 @@ libc_bitflags!( 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. /// @@ -165,11 +199,18 @@ libc_bitflags!( } ); +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) } +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) @@ -184,8 +225,8 @@ pub fn openat( 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) } + let fd = path.with_nix_path(|cstr| unsafe { + libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) })?; Errno::result(fd) } @@ -209,12 +250,12 @@ pub fn renameat( })??; Errno::result(res).map(drop) } +} -#[cfg(all( - target_os = "linux", - target_env = "gnu", -))] +#[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; @@ -222,10 +263,9 @@ libc_bitflags! { } } -#[cfg(all( - target_os = "linux", - target_env = "gnu", -))] +feature! { +#![feature = "fs"] +#[cfg(all(target_os = "linux", target_env = "gnu"))] pub fn renameat2( old_dirfd: Option, old_path: &P1, @@ -278,56 +318,85 @@ fn readlink_maybe_at( }) } -fn inner_readlink(dirfd: Option, path: &P) -> Result { +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); + + { + // 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) - } + let mut try_size = { + 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 + + 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), + { + 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() { + return wrap_readlink_result(v, res); } } + + // 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), + } } } @@ -336,7 +405,10 @@ pub fn readlink(path: &P) -> Result { } #[cfg(not(target_os = "redox"))] -pub fn readlinkat(dirfd: RawFd, path: &P) -> Result { +pub fn readlinkat( + dirfd: RawFd, + path: &P, +) -> Result { inner_readlink(Some(dirfd), path) } @@ -348,10 +420,13 @@ pub(crate) fn at_rawfd(fd: Option) -> raw::c_int { Some(fd) => fd, } } +} -#[cfg(any(target_os = "android", target_os = "linux"))] +#[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; @@ -364,14 +439,19 @@ libc_bitflags!( } ); +#[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] @@ -391,9 +471,17 @@ pub enum FcntlArg<'a> { 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"))] + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "freebsd" + ))] F_ADD_SEALS(SealFlag), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "freebsd" + ))] F_GET_SEALS, #[cfg(any(target_os = "macos", target_os = "ios"))] F_FULLFSYNC, @@ -422,7 +510,9 @@ 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_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), @@ -439,9 +529,19 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result { 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"))] - F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[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), @@ -455,6 +555,7 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result { Errno::result(res) } +// TODO: convert to libc_enum #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum FlockArg { @@ -466,7 +567,7 @@ pub enum FlockArg { UnlockNonblock, } -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> { use self::FlockArg::*; @@ -475,18 +576,25 @@ pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> { 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), + 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. /// @@ -505,48 +613,72 @@ libc_bitflags! { } } +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. +/// transferring data from the kernel to user space and back again. There may be +/// additional optimizations for specific file systems. 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 +/// copying. If they are not used, the relevant file descriptors 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>, +// Note: FreeBSD defines the offset argument as "off_t". Linux and Android +// define it as "loff_t". But on both OSes, on all supported platforms, those +// are 64 bits. So Nix uses i64 to make the docs simple and consistent. +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +pub fn copy_file_range( + fd_in: Fd1, + off_in: Option<&mut i64>, + fd_out: Fd2, + off_out: Option<&mut i64>, len: usize, ) -> Result { let off_in = off_in - .map(|offset| offset as *mut libc::loff_t) + .map(|offset| offset as *mut i64) .unwrap_or(ptr::null_mut()); let off_out = off_out - .map(|offset| offset as *mut libc::loff_t) + .map(|offset| offset as *mut i64) .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, - ) - }; + cfg_if::cfg_if! { + if #[cfg(target_os = "freebsd")] { + let ret = unsafe { + libc::copy_file_range( + fd_in.as_fd().as_raw_fd(), + off_in, + fd_out.as_fd().as_raw_fd(), + off_out, + len, + 0, + ) + }; + } else { + // May Linux distros still don't include copy_file_range in their + // libc implementations, so we need to make a direct syscall. + let ret = unsafe { + libc::syscall( + libc::SYS_copy_file_range, + fd_in, + off_in, + fd_out.as_fd().as_raw_fd(), + off_out, + len, + 0, + ) + }; + } + } Errno::result(ret).map(|r| r as usize) } @@ -566,18 +698,29 @@ pub fn splice( .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()) }; + 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 { +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: &[IoVec<&[u8]>], flags: SpliceFFlags) -> Result { +pub fn vmsplice( + fd: RawFd, + iov: &[std::io::IoSlice<'_>], + flags: SpliceFFlags, +) -> Result { let ret = unsafe { libc::vmsplice( fd, @@ -588,10 +731,13 @@ pub fn vmsplice(fd: RawFd, iov: &[IoVec<&[u8]>], flags: SpliceFFlags) -> Result< }; Errno::result(ret).map(|r| r as usize) } +} -#[cfg(any(target_os = "linux"))] +#[cfg(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. /// @@ -620,11 +766,15 @@ libc_bitflags!( } ); +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(target_os = "linux")] +#[cfg(feature = "fs")] pub fn fallocate( fd: RawFd, mode: FallocateFlags, @@ -635,23 +785,161 @@ pub fn fallocate( 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", - any(target_os = "wasi", target_env = "wasi"), + 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; + use std::os::unix::io::RawFd; + #[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, @@ -662,6 +950,8 @@ mod posix_fadvise { } } + feature! { + #![feature = "fs"] pub fn posix_fadvise( fd: RawFd, offset: libc::off_t, @@ -676,17 +966,23 @@ mod posix_fadvise { Err(Errno::from_i32(res)) } } + } } #[cfg(any( target_os = "linux", target_os = "android", + target_os = "dragonfly", target_os = "emscripten", target_os = "fuchsia", - any(target_os = "wasi", target_env = "wasi"), + target_os = "wasi", target_os = "freebsd" ))] -pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Result<()> { +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), @@ -694,3 +990,4 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu Ok(errno) => Err(Errno::from_i32(errno)), } } +} diff --git a/src/features.rs b/src/features.rs index ed80fd714b..9e292cbf5d 100644 --- a/src/features.rs +++ b/src/features.rs @@ -4,6 +4,9 @@ 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; + use std::sync::atomic::{AtomicUsize, Ordering}; // Features: // * atomic cloexec on socket: 2.6.27 @@ -11,10 +14,10 @@ mod os { // * 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; + 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) { @@ -22,15 +25,15 @@ mod os { *dst += (b - b'0') as usize; } - fn parse_kernel_version() -> usize { - let u = uname(); + fn parse_kernel_version() -> Result { + let u = uname()?; - let mut curr: usize = 0; + 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().bytes() { + for &b in u.release().as_bytes() { if curr >= 3 { break; } @@ -39,18 +42,16 @@ mod os { b'.' | b'-' => { curr += 1; } - b'0'..=b'9' => { - match curr { - 0 => digit(&mut major, b), - 1 => digit(&mut minor, b), - _ => digit(&mut patch, b), - } - } + b'0'..=b'9' => match curr { + 0 => digit(&mut major, b), + 1 => digit(&mut minor, b), + _ => digit(&mut patch, b), + }, _ => break, } } - if major >= 3 { + Ok(if major >= 3 { VERS_3 } else if major >= 2 { if minor >= 7 { @@ -68,29 +69,31 @@ mod os { } } else { VERS_UNKNOWN - } + }) } - fn kernel_version() -> usize { - static mut KERNEL_VERS: usize = 0; - - unsafe { - if KERNEL_VERS == 0 { - KERNEL_VERS = parse_kernel_version(); - } + fn kernel_version() -> Result { + static KERNEL_VERS: AtomicUsize = AtomicUsize::new(0); + let mut kernel_vers = KERNEL_VERS.load(Ordering::Relaxed); - KERNEL_VERS + if kernel_vers == 0 { + kernel_vers = parse_kernel_version()?; + KERNEL_VERS.store(kernel_vers, Ordering::Relaxed); } + + Ok(kernel_vers) } /// Check if the OS supports atomic close-on-exec for sockets pub fn socket_atomic_cloexec() -> bool { - kernel_version() >= VERS_2_6_27 + kernel_version() + .map(|version| version >= VERS_2_6_27) + .unwrap_or(false) } #[test] pub fn test_parsing_kernel_version() { - assert!(kernel_version() > 0); + assert!(kernel_version().unwrap() > 0); } } @@ -109,10 +112,14 @@ mod os { } } -#[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "fuchsia", - target_os = "solaris"))] +#[cfg(any( + target_os = "aix", + 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 { diff --git a/src/ifaddrs.rs b/src/ifaddrs.rs index ed6328f3ef..70b50b01eb 100644 --- a/src/ifaddrs.rs +++ b/src/ifaddrs.rs @@ -4,14 +4,16 @@ //! 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::{Result, Errno}; -use crate::sys::socket::SockAddr; 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)] @@ -21,13 +23,13 @@ pub struct InterfaceAddress { /// Flags as from `SIOCGIFFLAGS` ioctl pub flags: InterfaceFlags, /// Network address of this interface - pub address: Option, + pub address: Option, /// Netmask of this interface - pub netmask: Option, + pub netmask: Option, /// Broadcast address of this interface, if applicable - pub broadcast: Option, + pub broadcast: Option, /// Point-to-point destination address - pub destination: Option, + pub destination: Option, } cfg_if! { @@ -42,12 +44,52 @@ cfg_if! { } } +/// 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 { SockAddr::from_libc_sockaddr(info.ifa_addr) }; - let netmask = unsafe { SockAddr::from_libc_sockaddr(info.ifa_netmask) }; + 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), @@ -59,9 +101,9 @@ impl InterfaceAddress { let ifu = get_ifu_from_sockaddr(info); if addr.flags.contains(InterfaceFlags::IFF_POINTOPOINT) { - addr.destination = unsafe { SockAddr::from_libc_sockaddr(ifu) }; + addr.destination = unsafe { SockaddrStorage::from_raw(ifu, None) }; } else if addr.flags.contains(InterfaceFlags::IFF_BROADCAST) { - addr.broadcast = unsafe { SockAddr::from_libc_sockaddr(ifu) }; + addr.broadcast = unsafe { SockaddrStorage::from_raw(ifu, None) }; } addr @@ -103,9 +145,9 @@ impl Iterator for InterfaceAddressIterator { /// 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 `SockAddr` returned for the fields `address`, -/// `netmask`, `broadcast`, and `destination`. For any entry not supported, -/// the returned list will contain a `None` entry. +/// 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 /// ``` @@ -144,4 +186,28 @@ mod tests { 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 index c42068c70a..d3725c3f8a 100644 --- a/src/kmod.rs +++ b/src/kmod.rs @@ -3,7 +3,7 @@ //! For more details see use std::ffi::CStr; -use std::os::unix::io::AsRawFd; +use std::os::unix::io::{AsFd, AsRawFd}; use crate::errno::Errno; use crate::Result; @@ -79,11 +79,15 @@ libc_bitflags!( /// ``` /// /// 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<()> { +pub fn finit_module( + fd: Fd, + param_values: &CStr, + flags: ModuleInitFlags, +) -> Result<()> { let res = unsafe { libc::syscall( libc::SYS_finit_module, - fd.as_raw_fd(), + fd.as_fd().as_raw_fd(), param_values.as_ptr(), flags.bits(), ) @@ -116,7 +120,9 @@ libc_bitflags!( /// /// 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()) }; + 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 index 3a2b63ab0b..af0c67b0f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,88 +2,204 @@ //! //! 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 many 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(all( + feature = "acct", + feature = "aio", + feature = "dir", + feature = "env", + feature = "event", + feature = "feature", + feature = "fs", + feature = "hostname", + feature = "inotify", + feature = "ioctl", + feature = "kmod", + feature = "mman", + feature = "mount", + feature = "mqueue", + feature = "net", + feature = "personality", + feature = "poll", + feature = "process", + feature = "pthread", + feature = "ptrace", + feature = "quota", + feature = "reboot", + feature = "resource", + feature = "sched", + feature = "socket", + feature = "signal", + feature = "term", + feature = "time", + feature = "ucontext", + feature = "uio", + feature = "user", + feature = "zerocopy", + )), + 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; +#[macro_use] +mod macros; // Public crates #[cfg(not(target_os = "redox"))] -#[allow(missing_docs)] -pub mod dir; -pub mod env; +feature! { + #![feature = "dir"] + pub mod dir; +} +feature! { + #![feature = "env"] + pub mod env; +} #[allow(missing_docs)] pub mod errno; -pub mod features; +feature! { + #![feature = "feature"] + + #[deny(missing_docs)] + pub mod features; +} #[allow(missing_docs)] pub mod fcntl; -#[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"))] -pub mod ifaddrs; -#[cfg(any(target_os = "android", - target_os = "linux"))] -#[allow(missing_docs)] -pub mod kmod; -#[cfg(any(target_os = "android", - target_os = "freebsd", - target_os = "linux"))] -pub mod mount; -#[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "fushsia", - target_os = "linux", - target_os = "netbsd"))] -#[allow(missing_docs)] -pub mod mqueue; -#[cfg(not(target_os = "redox"))] -pub mod net; -pub mod poll; +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")))] -pub mod pty; -pub mod sched; +feature! { + #![feature = "term"] + #[deny(missing_docs)] + pub mod pty; +} +feature! { + #![feature = "sched"] + pub mod sched; +} pub mod sys; -#[allow(missing_docs)] -pub mod time; +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 = "x86", target_arch = "x86_64")))] -#[allow(missing_docs)] -pub mod ucontext; +#[cfg(all( + target_os = "linux", + any( + target_arch = "aarch64", + 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; -/* - * - * ===== Result / Error ===== - * - */ - -use libc::{c_char, PATH_MAX}; - -use std::{ptr, result}; -use std::ffi::{CStr, OsStr}; +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; @@ -114,7 +230,8 @@ pub trait NixPath { /// /// Mostly used internally by Nix. fn with_nix_path(&self, f: F) -> Result - where F: FnOnce(&CStr) -> T; + where + F: FnOnce(&CStr) -> T; } impl NixPath for str { @@ -127,9 +244,11 @@ impl NixPath for str { } fn with_nix_path(&self, f: F) -> Result - where F: FnOnce(&CStr) -> T { - OsStr::new(self).with_nix_path(f) - } + where + F: FnOnce(&CStr) -> T, + { + OsStr::new(self).with_nix_path(f) + } } impl NixPath for OsStr { @@ -142,9 +261,11 @@ impl NixPath for OsStr { } fn with_nix_path(&self, f: F) -> Result - where F: FnOnce(&CStr) -> T { - self.as_bytes().with_nix_path(f) - } + where + F: FnOnce(&CStr) -> T, + { + self.as_bytes().with_nix_path(f) + } } impl NixPath for CStr { @@ -157,12 +278,9 @@ impl NixPath for CStr { } fn with_nix_path(&self, f: F) -> Result - where F: FnOnce(&CStr) -> T { - // Equivalence with the [u8] impl. - if self.len() >= PATH_MAX as usize { - return Err(Errno::ENAMETOOLONG) - } - + where + F: FnOnce(&CStr) -> T, + { Ok(f(self)) } } @@ -177,27 +295,50 @@ impl NixPath for [u8] { } fn with_nix_path(&self, f: F) -> Result - where F: FnOnce(&CStr) -> T { - let mut buf = [0u8; PATH_MAX as usize]; - - if self.len() >= PATH_MAX as usize { - return Err(Errno::ENAMETOOLONG) + 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); } - match self.iter().position(|b| *b == 0) { - Some(_) => Err(Errno::EINVAL), - None => { - unsafe { - // TODO: Replace with bytes::copy_memory. rust-lang/rust#24028 - ptr::copy_nonoverlapping(self.as_ptr(), buf.as_mut_ptr(), self.len()); - Ok(f(CStr::from_ptr(buf.as_ptr() as *const c_char))) - } + 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()) @@ -207,7 +348,10 @@ impl NixPath for Path { NixPath::len(self.as_os_str()) } - fn with_nix_path(&self, f: F) -> Result where F: FnOnce(&CStr) -> T { + fn with_nix_path(&self, f: F) -> Result + where + F: FnOnce(&CStr) -> T, + { self.as_os_str().with_nix_path(f) } } @@ -221,7 +365,10 @@ impl NixPath for PathBuf { NixPath::len(self.as_os_str()) } - fn with_nix_path(&self, f: F) -> Result where F: FnOnce(&CStr) -> T { + 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 index 3ccbfdd43b..adff2bc6be 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,3 +1,17 @@ +// 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. @@ -5,7 +19,7 @@ /// The `libc` crate must be in scope with the name `libc`. /// /// # Example -/// ``` +/// ```ignore /// libc_bitflags!{ /// pub struct ProtFlags: libc::c_int { /// PROT_NONE; @@ -25,7 +39,7 @@ /// 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; @@ -49,6 +63,8 @@ macro_rules! libc_bitflags { } ) => { ::bitflags::bitflags! { + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[repr(transparent)] $(#[$outer])* pub struct $BitFlags: $T { $( @@ -66,7 +82,7 @@ macro_rules! libc_bitflags { /// The `libc` crate must be in scope with the name `libc`. /// /// # Example -/// ``` +/// ```ignore /// libc_enum!{ /// pub enum ProtFlags { /// PROT_NONE, @@ -80,6 +96,8 @@ macro_rules! libc_bitflags { /// } /// } /// ``` +// Some targets don't use all rules. +#[allow(unused_macro_rules)] macro_rules! libc_enum { // Exit rule. (@make_enum @@ -116,6 +134,8 @@ macro_rules! libc_enum { impl ::std::convert::TryFrom<$repr> for $BitFlags { type Error = $crate::Error; #[allow(unused_doc_comments)] + #[allow(deprecated)] + #[allow(unused_attributes)] fn try_from(x: $repr) -> $crate::Result { match x { $($try_froms)* diff --git a/src/mount/bsd.rs b/src/mount/bsd.rs index 627bfa5ec1..6ed2dc7fbf 100644 --- a/src/mount/bsd.rs +++ b/src/mount/bsd.rs @@ -1,48 +1,52 @@ -use crate::{ - Error, - Errno, - NixPath, - Result, - sys::uio::IoVec -}; -use libc::{c_char, c_int, c_uint, c_void}; +#[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::{CString, CStr}, - fmt, - io, - ptr + 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; @@ -52,6 +56,7 @@ libc_bitflags!( 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; @@ -62,6 +67,7 @@ libc_bitflags!( /// /// 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( @@ -70,10 +76,12 @@ libc_bitflags!( 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; @@ -83,27 +91,30 @@ libc_bitflags!( 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 + errmsg: Option, } +#[cfg(target_os = "freebsd")] impl NmountError { /// Returns the additional error string sometimes generated by `nmount(2)`. pub fn errmsg(&self) -> Option<&str> { @@ -118,13 +129,15 @@ impl NmountError { fn new(error: Error, errmsg: Option<&CStr>) -> Self { Self { errno: error, - errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned) + 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 { @@ -135,6 +148,7 @@ impl fmt::Display for NmountError { } } +#[cfg(target_os = "freebsd")] impl From for io::Error { fn from(err: NmountError) -> Self { err.errno.into() @@ -142,6 +156,7 @@ impl From for io::Error { } /// Result type of [`Nmount::nmount`]. +#[cfg(target_os = "freebsd")] pub type NmountResult = std::result::Result<(), NmountError>; /// Mount a FreeBSD file system. @@ -156,8 +171,9 @@ pub type NmountResult = std::result::Result<(), NmountError>; /// To mount `target` onto `mountpoint` with `nullfs`: /// ``` /// # use nix::unistd::Uid; -/// # use ::sysctl::CtlValue; -/// # if !Uid::current().is_root() && CtlValue::Int(0) == ::sysctl::value("vfs.usermount").unwrap() { +/// # 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}; @@ -174,7 +190,7 @@ pub type NmountResult = std::result::Result<(), NmountError>; /// .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(); /// ``` /// @@ -182,14 +198,54 @@ pub type NmountResult = std::result::Result<(), NmountError>; /// * [`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>{ - iov: Vec>, +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 @@ -221,13 +277,10 @@ impl<'a> Nmount<'a> { &mut self, name: &'a CStr, val: *mut c_void, - len: usize - ) -> &mut Self - { - self.iov.push(IoVec::from_slice(name.to_bytes_with_nul())); - self.is_owned.push(false); - self.iov.push(IoVec::from_raw_parts(val, len)); - self.is_owned.push(false); + len: usize, + ) -> &mut Self { + self.push_slice(name.to_bytes_with_nul(), false); + self.push_pointer_and_length(val.cast(), len, false); self } @@ -243,10 +296,8 @@ impl<'a> Nmount<'a> { /// .null_opt(&read_only); /// ``` pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self { - self.iov.push(IoVec::from_slice(name.to_bytes_with_nul())); - self.is_owned.push(false); - self.iov.push(IoVec::from_raw_parts(ptr::null_mut(), 0)); - self.is_owned.push(false); + self.push_slice(name.to_bytes_with_nul(), false); + self.push_slice(&[], false); self } @@ -266,19 +317,12 @@ impl<'a> Nmount<'a> { /// let mut nmount: Nmount<'static> = Nmount::new(); /// nmount.null_opt_owned(read_only); /// ``` - pub fn null_opt_owned(&mut self, name: &P) -> &mut Self - { - name.with_nix_path(|s| { - let len = s.to_bytes_with_nul().len(); - self.iov.push(IoVec::from_raw_parts( - // Must free it later - s.to_owned().into_raw() as *mut c_void, - len - )); - self.is_owned.push(true); - }).unwrap(); - self.iov.push(IoVec::from_raw_parts(ptr::null_mut(), 0)); - self.is_owned.push(false); + pub fn null_opt_owned( + &mut self, + name: &P, + ) -> &mut Self { + self.push_nix_path(name); + self.push_slice(&[], false); self } @@ -294,16 +338,9 @@ impl<'a> Nmount<'a> { /// Nmount::new() /// .str_opt(&fstype, &nullfs); /// ``` - pub fn str_opt( - &mut self, - name: &'a CStr, - val: &'a CStr - ) -> &mut Self - { - self.iov.push(IoVec::from_slice(name.to_bytes_with_nul())); - self.is_owned.push(false); - self.iov.push(IoVec::from_slice(val.to_bytes_with_nul())); - self.is_owned.push(false); + 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 } @@ -323,27 +360,12 @@ impl<'a> Nmount<'a> { /// .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 + where + P1: ?Sized + NixPath, + P2: ?Sized + NixPath, { - name.with_nix_path(|s| { - let len = s.to_bytes_with_nul().len(); - self.iov.push(IoVec::from_raw_parts( - // Must free it later - s.to_owned().into_raw() as *mut c_void, - len - )); - self.is_owned.push(true); - }).unwrap(); - val.with_nix_path(|s| { - let len = s.to_bytes_with_nul().len(); - self.iov.push(IoVec::from_raw_parts( - // Must free it later - s.to_owned().into_raw() as *mut c_void, - len - )); - self.is_owned.push(true); - }).unwrap(); + self.push_nix_path(name); + self.push_nix_path(val); self } @@ -354,24 +376,23 @@ impl<'a> Nmount<'a> { /// Actually mount the file system. pub fn nmount(&mut self, flags: MntFlags) -> NmountResult { - // nmount can return extra error information via a "errmsg" return - // argument. const ERRMSG_NAME: &[u8] = b"errmsg\0"; let mut errmsg = vec![0u8; 255]; - self.iov.push(IoVec::from_raw_parts( - ERRMSG_NAME.as_ptr() as *mut c_void, - ERRMSG_NAME.len() - )); - self.iov.push(IoVec::from_raw_parts( - errmsg.as_mut_ptr() as *mut c_void, - errmsg.len() - )); + + // 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) - }; + let iovp = self.iov.as_mut_ptr(); + let res = unsafe { libc::nmount(iovp, niov, flags.bits()) }; match Errno::result(res) { Ok(_) => Ok(()), Err(error) => { @@ -397,7 +418,7 @@ impl<'a> Drop for Nmount<'a> { // Free the owned string. Safe because we recorded ownership, // and Nmount does not implement Clone. unsafe { - drop(CString::from_raw(iov.0.iov_base as *mut c_char)); + drop(CString::from_raw(iov.iov_base as *mut c_char)); } } } @@ -408,18 +429,24 @@ impl<'a> Drop for Nmount<'a> { /// /// Useful flags include /// * `MNT_FORCE` - Unmount even if still in use. -/// * `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`. +#[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 +where + P: ?Sized + NixPath, { - let res = mountpoint.with_nix_path(|cstr| { - unsafe { libc::unmount(cstr.as_ptr(), flags.bits) } + 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 index 4cb2fa5490..e987603786 100644 --- a/src/mount/linux.rs +++ b/src/mount/linux.rs @@ -1,9 +1,9 @@ -#![allow(missing_docs)] -use libc::{self, c_ulong, c_int}; -use crate::{Result, NixPath}; use crate::errno::Errno; +use crate::{NixPath, Result}; +use libc::{self, c_int, c_ulong}; libc_bitflags!( + /// Used with [`mount`]. pub struct MsFlags: c_ulong { /// Mount read-only MS_RDONLY; @@ -27,65 +27,114 @@ libc_bitflags!( MS_NODIRATIME; /// Linux 2.4.0 - Bind directory at different place MS_BIND; + /// Move an existing mount to a new location MS_MOVE; + /// Used to create a recursive bind mount. MS_REC; + /// Suppress the display of certain kernel warning messages. MS_SILENT; + /// VFS does not apply the umask MS_POSIXACL; + /// The resulting mount cannot subsequently be bind mounted. MS_UNBINDABLE; + /// Make this mount point private. MS_PRIVATE; + /// If this is a shared mount point that is a member of a peer group + /// that contains other members, convert it to a slave mount. MS_SLAVE; + /// Make this mount point shared. MS_SHARED; + /// When a file on this filesystem is accessed, update the file's + /// last access time (atime) only if the current value of atime is + /// less than or equal to the file's last modification time (mtime) or + /// last status change time (ctime). MS_RELATIME; + /// Mount request came from within the kernel + #[deprecated(since = "0.27.0", note = "Should only be used in-kernel")] MS_KERNMOUNT; + /// Update inode I_version field MS_I_VERSION; + /// Always update the last access time (atime) when files on this + /// filesystem are accessed. MS_STRICTATIME; + /// Reduce on-disk updates of inode timestamps (atime, mtime, ctime) by + /// maintaining these changes only in memory. MS_LAZYTIME; + #[deprecated(since = "0.27.0", note = "Should only be used in-kernel")] + #[allow(missing_docs)] // Not documented in Linux MS_ACTIVE; + #[deprecated(since = "0.27.0", note = "Should only be used in-kernel")] + #[allow(missing_docs)] // Not documented in Linux MS_NOUSER; + #[allow(missing_docs)] // Not documented in Linux; possibly kernel-only MS_RMT_MASK; + #[allow(missing_docs)] // Not documented in Linux; possibly kernel-only MS_MGC_VAL; + #[allow(missing_docs)] // Not documented in Linux; possibly kernel-only MS_MGC_MSK; } ); libc_bitflags!( + /// Used with [`umount2]. pub struct MntFlags: c_int { + /// Attempt to unmount even if still in use, aborting pending requests. MNT_FORCE; + /// Lazy unmount. Disconnect the file system immediately, but don't + /// actually unmount it until it ceases to be busy. MNT_DETACH; + /// Mark the mount point as expired. MNT_EXPIRE; + /// Don't dereference `target` if it is a symlink. + UMOUNT_NOFOLLOW; } ); -pub fn mount( - source: Option<&P1>, - target: &P2, - fstype: Option<&P3>, - flags: MsFlags, - data: Option<&P4>) -> Result<()> { - +/// Mount a file system. +/// +/// # Arguments +/// - `source` - Specifies the file system. e.g. `/dev/sd0`. +/// - `target` - Specifies the destination. e.g. `/mnt`. +/// - `fstype` - The file system type, e.g. `ext4`. +/// - `flags` - Optional flags controlling the mount. +/// - `data` - Optional file system specific data. +/// +/// # See Also +/// [`mount`](https://man7.org/linux/man-pages/man2/mount.2.html) +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 + 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())) + 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 - ) - } + with_opt_nix_path(data, |d| unsafe { + libc::mount( + s, + t.as_ptr(), + ty, + flags.bits(), + d as *const libc::c_void, + ) }) }) }) @@ -94,17 +143,20 @@ pub fn mount(target: &P) -> Result<()> { - let res = target.with_nix_path(|cstr| { - unsafe { libc::umount(cstr.as_ptr()) } - })?; + let res = + target.with_nix_path(|cstr| unsafe { libc::umount(cstr.as_ptr()) })?; Errno::result(res).map(drop) } +/// Unmount the file system mounted at `target`. +/// +/// See also [`umount`](https://man7.org/linux/man-pages/man2/umount.2.html) pub fn umount2(target: &P, flags: MntFlags) -> Result<()> { - let res = target.with_nix_path(|cstr| { - unsafe { libc::umount2(cstr.as_ptr(), flags.bits) } + 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 index 14bf2a9636..e98b49c343 100644 --- a/src/mount/mod.rs +++ b/src/mount/mod.rs @@ -1,21 +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(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" - ))] +#[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 index 34fd802785..fb07d2accb 100644 --- a/src/mqueue.rs +++ b/src/mqueue.rs @@ -1,53 +1,115 @@ //! 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= "/a_nix_test_queue"; +//! +//! 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::Result; use crate::errno::Errno; +use crate::NixPath; +use crate::Result; -use libc::{self, c_char, mqd_t, size_t}; -use std::ffi::CString; use crate::sys::stat::Mode; +use libc::{self, c_char, mqd_t, size_t}; use std::mem; +#[cfg(any( + target_os = "linux", + target_os = "netbsd", + target_os = "dragonfly" +))] +use std::os::unix::io::{ + AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd, +}; -libc_bitflags!{ +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; } } -libc_bitflags!{ - pub struct FdFlag: libc::c_int { - FD_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 { - 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 - { + /// 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(); @@ -55,77 +117,139 @@ impl MqAttr { (*p).mq_maxmsg = mq_maxmsg; (*p).mq_msgsize = mq_msgsize; (*p).mq_curmsgs = mq_curmsgs; - MqAttr { mq_attr: attr.assume_init() } + 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 +// The mode.bits() cast is only lossless on some OSes #[allow(clippy::cast_lossless)] -pub fn mq_open(name: &CString, - oflag: MQ_OFlag, - mode: Mode, - attr: Option<&MqAttr>) - -> Result { - let res = match attr { +pub fn mq_open

    ( + name: &P, + oflag: MQ_OFlag, + mode: Mode, + attr: Option<&MqAttr>, +) -> Result +where + P: ?Sized + NixPath, +{ + let res = name.with_nix_path(|cstr| 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) + libc::mq_open( + cstr.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) + None => unsafe { libc::mq_open(cstr.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: &CString) -> Result<()> { - let res = unsafe { libc::mq_unlink(name.as_ptr()) }; +pub fn mq_unlink

    (name: &P) -> Result<()> +where + P: ?Sized + NixPath, +{ + let res = + name.with_nix_path(|cstr| unsafe { libc::mq_unlink(cstr.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: mqd_t) -> Result<()> { - let res = unsafe { libc::mq_close(mqdes) }; +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: mqd_t, message: &mut [u8], msg_prio: &mut u32) -> Result { +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, - message.as_mut_ptr() as *mut c_char, - len, - msg_prio as *mut u32) + 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) } +feature! { + #![feature = "time"] + use crate::sys::time::TimeSpec; + /// Receive a message from a message queue with a timeout + /// + /// See also ['mq_timedreceive(2)'](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_receive.html) + pub fn mq_timedreceive( + mqdes: &MqdT, + message: &mut [u8], + msg_prio: &mut u32, + abstime: &TimeSpec, + ) -> Result { + let len = message.len() as size_t; + let res = unsafe { + libc::mq_timedreceive( + mqdes.0, + message.as_mut_ptr() as *mut c_char, + len, + msg_prio as *mut u32, + abstime.as_ref(), + ) + }; + 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: mqd_t, message: &[u8], msq_prio: u32) -> Result<()> { +pub fn mq_send(mqdes: &MqdT, message: &[u8], msq_prio: u32) -> Result<()> { let res = unsafe { - libc::mq_send(mqdes, - message.as_ptr() as *const c_char, - message.len(), - msq_prio) + libc::mq_send( + mqdes.0, + message.as_ptr() as *const c_char, + message.len(), + msq_prio, + ) }; Errno::result(res).map(drop) } @@ -133,10 +257,14 @@ pub fn mq_send(mqdes: mqd_t, message: &[u8], msq_prio: u32) -> Result<()> { /// Get message queue attributes /// /// See also [`mq_getattr(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_getattr.html) -pub fn mq_getattr(mqd: mqd_t) -> Result { +pub fn mq_getattr(mqd: &MqdT) -> Result { let mut attr = mem::MaybeUninit::::uninit(); - let res = unsafe { libc::mq_getattr(mqd, attr.as_mut_ptr()) }; - Errno::result(res).map(|_| unsafe{MqAttr { mq_attr: attr.assume_init() }}) + 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 @@ -144,35 +272,87 @@ pub fn mq_getattr(mqd: mqd_t) -> Result { /// 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: mqd_t, newattr: &MqAttr) -> Result { +pub fn mq_setattr(mqd: &MqdT, newattr: &MqAttr) -> Result { let mut attr = mem::MaybeUninit::::uninit(); let res = unsafe { - libc::mq_setattr(mqd, &newattr.mq_attr as *const libc::mq_attr, attr.as_mut_ptr()) + 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() }}) + 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: mqd_t) -> Result { +#[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); + 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: mqd_t) -> Result { +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); + let newattr = MqAttr::new( + 0, + oldattr.mq_attr.mq_maxmsg, + oldattr.mq_attr.mq_msgsize, + oldattr.mq_attr.mq_curmsgs, + ); mq_setattr(mqd, &newattr) } + +#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "dragonfly"))] +impl AsFd for MqdT { + /// Borrow the underlying message queue descriptor. + fn as_fd(&self) -> BorrowedFd { + // SAFETY: [MqdT] will only contain a valid fd by construction. + unsafe { BorrowedFd::borrow_raw(self.0) } + } +} + +#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "dragonfly"))] +impl AsRawFd for MqdT { + /// Return the underlying message queue descriptor. + /// + /// Returned descriptor is a "shallow copy" of the descriptor, so it refers + /// to the same underlying kernel object as `self`. + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "dragonfly"))] +impl FromRawFd for MqdT { + /// Construct an [MqdT] from [RawFd]. + /// + /// # Safety + /// The `fd` given must be a valid and open file descriptor for a message + /// queue. + unsafe fn from_raw_fd(fd: RawFd) -> MqdT { + MqdT(fd) + } +} + +#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "dragonfly"))] +impl IntoRawFd for MqdT { + /// Consume this [MqdT] and return a [RawFd]. + fn into_raw_fd(self) -> RawFd { + self.0 + } +} diff --git a/src/net/if_.rs b/src/net/if_.rs index bc00a4328b..ec46260714 100644 --- a/src/net/if_.rs +++ b/src/net/if_.rs @@ -8,7 +8,8 @@ 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()) })?; + let if_index = name + .with_nix_path(|name| unsafe { libc::if_nametoindex(name.as_ptr()) })?; if if_index == 0 { Err(Error::last()) @@ -28,6 +29,7 @@ libc_bitflags!( 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)) @@ -45,9 +47,11 @@ libc_bitflags!( 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)) @@ -62,6 +66,7 @@ libc_bitflags!( 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)) @@ -75,6 +80,7 @@ libc_bitflags!( /// 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", @@ -82,21 +88,24 @@ libc_bitflags!( 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", - target_os = "osx"))] + 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)) @@ -108,13 +117,16 @@ libc_bitflags!( 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", @@ -123,13 +135,16 @@ libc_bitflags!( 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", @@ -138,132 +153,174 @@ libc_bitflags!( 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; } ); @@ -277,7 +334,9 @@ libc_bitflags!( target_os = "macos", target_os = "netbsd", target_os = "openbsd", + target_os = "illumos", ))] +#[cfg_attr(docsrs, doc(cfg(all())))] mod if_nameindex { use super::*; @@ -407,5 +466,6 @@ mod if_nameindex { target_os = "macos", target_os = "netbsd", target_os = "openbsd", + target_os = "illumos", ))] pub use if_nameindex::*; diff --git a/src/poll.rs b/src/poll.rs index 8556c1bb74..9181bf7f30 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -1,12 +1,8 @@ //! Wait for events to trigger on specific file descriptors -#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] -use crate::sys::time::TimeSpec; -#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] -use crate::sys::signal::SigSet; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; -use crate::Result; use crate::errno::Errno; +use crate::Result; /// This is a wrapper around `libc::pollfd`. /// @@ -14,33 +10,69 @@ use crate::errno::Errno; /// [`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 occured can be +/// 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 { +pub struct PollFd<'fd> { pollfd: libc::pollfd, + _fd: std::marker::PhantomData>, } -impl PollFd { +impl<'fd> PollFd<'fd> { /// Creates a new `PollFd` specifying the events of interest /// for a given file descriptor. - pub const fn new(fd: RawFd, events: PollFlags) -> PollFd { + // + // Different from other I/O-safe interfaces, here, we have to take `AsFd` + // by reference to prevent the case where the `fd` is closed but it is + // still in use. For example: + // + // ```rust + // let (reader, _) = pipe().unwrap(); + // + // // If `PollFd::new()` takes `AsFd` by value, then `reader` will be consumed, + // // but the file descriptor of `reader` will still be in use. + // let pollfd = PollFd::new(reader, flag); + // + // // Do something with `pollfd`, which uses the CLOSED fd. + // ``` + pub fn new(fd: &'fd Fd, events: PollFlags) -> PollFd<'fd> { PollFd { pollfd: libc::pollfd { - fd, + fd: fd.as_fd().as_raw_fd(), events: events.bits(), revents: PollFlags::empty().bits(), }, + _fd: std::marker::PhantomData, } } - /// Returns the events that occured in the last call to `poll` or `ppoll`. Will only return + /// 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() @@ -52,9 +84,29 @@ impl PollFd { } } -impl AsRawFd for PollFd { - fn as_raw_fd(&self) -> RawFd { - self.pollfd.fd +impl<'fd> AsFd for PollFd<'fd> { + fn as_fd(&self) -> BorrowedFd<'_> { + // Safety: + // + // BorrowedFd::borrow_raw(RawFd) requires that the raw fd being passed + // must remain open for the duration of the returned BorrowedFd, this is + // guaranteed as the returned BorrowedFd has the lifetime parameter same + // as `self`: + // "fn as_fd<'self>(&'self self) -> BorrowedFd<'self>" + // which means that `self` (PollFd) is guaranteed to outlive the returned + // BorrowedFd. (Lifetime: PollFd > BorrowedFd) + // + // And the lifetime parameter of PollFd::new(fd, ...) ensures that `fd` + // (an owned file descriptor) must outlive the returned PollFd: + // "pub fn new(fd: &'fd Fd, events: PollFlags) -> PollFd<'fd>" + // (Lifetime: Owned fd > PollFd) + // + // With two above relationships, we can conclude that the `Owned file + // descriptor` will outlive the returned BorrowedFd, + // (Lifetime: Owned fd > BorrowedFd) + // i.e., the raw fd being passed will remain valid for the lifetime of + // the returned BorrowedFd. + unsafe { BorrowedFd::borrow_raw(self.pollfd.fd) } } } @@ -81,15 +133,19 @@ libc_bitflags! { 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); @@ -134,14 +190,18 @@ libc_bitflags! { /// 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) + 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)) @@ -149,15 +209,25 @@ pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result { /// `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: SigSet) -> Result { +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.as_ref()) + sigmask) }; Errno::result(res) } +} diff --git a/src/pty.rs b/src/pty.rs index facc9aaf40..455828b703 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -5,86 +5,88 @@ pub use libc::winsize as Winsize; use std::ffi::CStr; use std::io; +#[cfg(not(target_os = "aix"))] use std::mem; use std::os::unix::prelude::*; -use crate::sys::termios::Termios; -use crate::unistd::{self, ForkResult, Pid}; -use crate::{Result, fcntl}; use crate::errno::Errno; +#[cfg(not(target_os = "aix"))] +use crate::sys::termios::Termios; +#[cfg(feature = "process")] +use crate::unistd::ForkResult; +#[cfg(all(feature = "process", not(target_os = "aix")))] +use crate::unistd::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)] +/// This is returned by [`openpty`]. +#[derive(Debug)] pub struct OpenptyResult { /// The master port in a virtual pty pair - pub master: RawFd, + pub master: OwnedFd, /// The slave port in a virtual pty pair - pub slave: RawFd, + pub slave: OwnedFd, } +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)] +/// This is returned by [`forkpty`]. +#[derive(Debug)] pub struct ForkptyResult { /// The master port in a virtual pty pair - pub master: RawFd, + pub master: OwnedFd, /// 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); +/// While this datatype is a thin wrapper around `OwnedFd`, it enforces that the available PTY +/// functions are given the correct file descriptor. +#[derive(Debug)] +pub struct PtyMaster(OwnedFd); impl AsRawFd for PtyMaster { fn as_raw_fd(&self) -> RawFd { - self.0 + self.0.as_raw_fd() } } impl IntoRawFd for PtyMaster { fn into_raw_fd(self) -> RawFd { let fd = self.0; - mem::forget(self); - fd + fd.into_raw_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.as_raw_fd(), buf).map_err(io::Error::from) } } -impl io::Read for PtyMaster { +impl io::Write for PtyMaster { + fn write(&mut self, buf: &[u8]) -> io::Result { + unistd::write(self.0.as_raw_fd(), 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) + unistd::read(self.0.as_raw_fd(), buf).map_err(io::Error::from) } } -impl io::Write for PtyMaster { +impl io::Write for &PtyMaster { fn write(&mut self, buf: &[u8]) -> io::Result { - unistd::write(self.0, buf).map_err(io::Error::from) + unistd::write(self.0.as_raw_fd(), buf).map_err(io::Error::from) } fn flush(&mut self) -> io::Result<()> { Ok(()) @@ -108,7 +110,7 @@ pub fn grantpt(fd: &PtyMaster) -> Result<()> { /// 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 pseuterminal master device. +/// `posix_openpt()` returns a file descriptor to an existing unused pseudoterminal master device. /// /// # Examples /// @@ -140,15 +142,13 @@ pub fn grantpt(fd: &PtyMaster) -> Result<()> { /// ``` #[inline] pub fn posix_openpt(flags: fcntl::OFlag) -> Result { - let fd = unsafe { - libc::posix_openpt(flags.bits()) - }; + let fd = unsafe { libc::posix_openpt(flags.bits()) }; if fd < 0 { return Err(Errno::last()); } - Ok(PtyMaster(fd)) + Ok(PtyMaster(unsafe { OwnedFd::from_raw_fd(fd) })) } /// Get the name of the slave pseudoterminal (see @@ -188,6 +188,7 @@ pub unsafe fn ptsname(fd: &PtyMaster) -> Result { /// 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); @@ -209,7 +210,7 @@ pub fn ptsname_r(fd: &PtyMaster) -> Result { /// /// `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 -/// pseuoterminal. +/// pseudoterminal. #[inline] pub fn unlockpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { @@ -219,7 +220,6 @@ pub fn unlockpt(fd: &PtyMaster) -> Result<()> { 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)). @@ -228,7 +228,16 @@ pub fn unlockpt(fd: &PtyMaster) -> Result<()> { /// 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 { +#[cfg(not(target_os = "aix"))] +pub fn openpty< + 'a, + 'b, + T: Into>, + U: Into>, +>( + winsize: T, + termios: U, +) -> Result { use std::ptr; let mut slave = mem::MaybeUninit::::uninit(); @@ -247,17 +256,15 @@ pub fn openpty<'a, 'b, T: Into>, U: Into ) } } - (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 _, - ) - } - } + (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 { @@ -270,17 +277,15 @@ pub fn openpty<'a, 'b, T: Into>, U: Into ) } } - (None, None) => { - unsafe { - libc::openpty( - master.as_mut_ptr(), - slave.as_mut_ptr(), - ptr::null_mut(), - ptr::null_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(), + ) + }, } }; @@ -288,12 +293,14 @@ pub fn openpty<'a, 'b, T: Into>, U: Into unsafe { Ok(OpenptyResult { - master: master.assume_init(), - slave: slave.assume_init(), + master: OwnedFd::from_raw_fd(master.assume_init()), + slave: OwnedFd::from_raw_fd(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)). @@ -313,6 +320,7 @@ pub fn openpty<'a, 'b, T: Into>, U: Into /// 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 +#[cfg(not(target_os = "aix"))] pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into>>( winsize: T, termios: U, @@ -342,7 +350,8 @@ pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into = 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. + /// + /// # Safety + /// + /// Because `clone` creates a child process with its stack located in + /// `stack` without specifying the size of the stack, special care must be + /// taken to ensure that the child process does not overflow the provided + /// stack space. + /// + /// See [`fork`](crate::unistd::fork) for additional safety concerns related + /// to executing child processes. + pub unsafe 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 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); + let res = 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: Fd, nstype: CloneFlags) -> Result<()> { + let res = unsafe { libc::setns(fd.as_fd().as_raw_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(C)] + #[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 { @@ -121,7 +209,9 @@ mod sched_linux_like { if field >= CpuSet::count() { Err(Errno::EINVAL) } else { - unsafe { libc::CPU_SET(field, &mut self.cpu_set); } + unsafe { + libc::CPU_SET(field, &mut self.cpu_set); + } Ok(()) } } @@ -132,14 +222,21 @@ mod sched_linux_like { if field >= CpuSet::count() { Err(Errno::EINVAL) } else { - unsafe { libc::CPU_CLR(field, &mut self.cpu_set);} + unsafe { + libc::CPU_CLR(field, &mut self.cpu_set); + } Ok(()) } } /// Return the maximum number of CPU in CpuSet pub const fn count() -> usize { - 8 * mem::size_of::() + #[cfg(not(target_os = "freebsd"))] + let bytes = mem::size_of::(); + #[cfg(target_os = "freebsd")] + let bytes = mem::size_of::(); + + 8 * bytes } } @@ -167,8 +264,8 @@ mod sched_linux_like { /// use nix::unistd::Pid; /// /// let mut cpu_set = CpuSet::new(); - /// cpu_set.set(0); - /// sched_setaffinity(Pid::from_raw(0), &cpu_set); + /// 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 { @@ -217,58 +314,11 @@ mod sched_linux_like { Errno::result(res).and(Ok(cpuset)) } - /// `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) - } + /// Determines the CPU on which the calling thread is running. + pub fn sched_getcpu() -> Result { + let res = unsafe { libc::sched_getcpu() }; - /// 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) + Errno::result(res).map(|int| int as usize) } } diff --git a/src/sys/aio.rs b/src/sys/aio.rs index e64a2a823a..5471177e3e 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -2,9 +2,12 @@ //! POSIX Asynchronous I/O //! //! The POSIX AIO interface is used for asynchronous I/O on files and disk-like -//! devices. It supports [`read`](struct.AioCb.html#method.read), -//! [`write`](struct.AioCb.html#method.write), and -//! [`fsync`](struct.AioCb.html#method.fsync) operations. Completion +//! 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 @@ -17,23 +20,29 @@ //! that they will be executed atomically. //! //! Outstanding operations may be cancelled with -//! [`cancel`](struct.AioCb.html#method.cancel) or +//! [`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 crate::Result; -use crate::errno::Errno; -use std::os::unix::io::RawFd; -use libc::{c_void, off_t, size_t}; -use std::fmt; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::mem; -use std::pin::Pin; -use std::ptr::{null, null_mut}; -use crate::sys::signal::*; -use std::thread; -use crate::sys::time::TimeSpec; +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 @@ -49,24 +58,10 @@ libc_enum! { target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] O_DSYNC } -} - -libc_enum! { - /// When used with [`lio_listio`](fn.lio_listio.html), determines whether a - /// given `aiocb` should be used for a read operation, a write operation, or - /// ignored. Has no effect for any other aio functions. - #[repr(i32)] - #[non_exhaustive] - pub enum LioOpcode { - /// No operation - LIO_NOP, - /// Write data as if by a call to [`AioCb::write`] - LIO_WRITE, - /// Write data as if by a call to [`AioCb::read`] - LIO_READ, - } + impl TryFrom } libc_enum! { @@ -102,354 +97,133 @@ struct LibcAiocb(libc::aiocb); unsafe impl Send for LibcAiocb {} unsafe impl Sync for LibcAiocb {} -/// AIO Control Block. -/// -/// The basic structure used by all aio functions. Each `AioCb` represents one -/// I/O request. -pub struct AioCb<'a> { +/// 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, - /// Tracks whether the buffer pointed to by `libc::aiocb.aio_buf` is mutable - mutable: bool, /// 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, - _buffer: std::marker::PhantomData<&'a [u8]>, - _pin: std::marker::PhantomPinned } -impl<'a> AioCb<'a> { - /// Returns the underlying file descriptor associated with the `AioCb` - pub fn fd(&self) -> RawFd { - self.aiocb.0.aio_fildes - } +impl AioCb { + pin_utils::unsafe_unpinned!(aiocb: LibcAiocb); - /// Constructs a new `AioCb` with no associated buffer. - /// - /// The resulting `AioCb` structure is suitable for use with `AioCb::fsync`. - /// - /// # Parameters - /// - /// * `fd`: File descriptor. Required for all aio functions. - /// * `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. - /// - /// # Examples - /// - /// Create an `AioCb` from a raw file descriptor and use it for an - /// [`fsync`](#method.fsync) operation. - /// - /// ``` - /// # 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 aiocb = AioCb::from_fd( f.as_raw_fd(), 0, SigevNone); - /// aiocb.fsync(AioFsyncMode::O_SYNC).expect("aio_fsync failed early"); - /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { - /// thread::sleep(time::Duration::from_millis(10)); - /// } - /// aiocb.aio_return().expect("aio_fsync failed late"); - /// ``` - pub fn from_fd(fd: RawFd, prio: libc::c_int, - sigev_notify: SigevNotify) -> Pin>> { - let mut a = AioCb::common_init(fd, prio, sigev_notify); - a.0.aio_offset = 0; - a.0.aio_nbytes = 0; - a.0.aio_buf = null_mut(); - - Box::pin(AioCb { - aiocb: a, - mutable: false, - in_progress: false, - _buffer: PhantomData, - _pin: std::marker::PhantomPinned - }) + 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) } - // Private helper - #[cfg(not(any(target_os = "ios", target_os = "macos")))] - fn from_mut_slice_unpinned(fd: RawFd, offs: off_t, buf: &'a mut [u8], - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> AioCb<'a> - { - let mut a = AioCb::common_init(fd, prio, sigev_notify); - a.0.aio_offset = offs; - a.0.aio_nbytes = buf.len() as size_t; - a.0.aio_buf = buf.as_ptr() as *mut c_void; - a.0.aio_lio_opcode = opcode as libc::c_int; + 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: a, - mutable: true, + aiocb: LibcAiocb(a), in_progress: false, - _buffer: PhantomData, - _pin: std::marker::PhantomPinned } } - /// Constructs a new `AioCb` from a mutable slice. - /// - /// The resulting `AioCb` will be suitable for both read and write - /// operations, but only if the borrow checker can guarantee that the slice - /// will outlive the `AioCb`. That will usually be the case if the `AioCb` - /// is stack-allocated. - /// - /// # Parameters - /// - /// * `fd`: File descriptor. Required for all aio functions. - /// * `offs`: File offset - /// * `buf`: A memory buffer - /// * `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. - /// * `opcode`: This field is only used for `lio_listio`. It - /// determines which operation to use for this individual - /// aiocb - /// - /// # Examples - /// - /// Create an `AioCb` from a mutable slice and read into it. - /// - /// ``` - /// # 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 aiocb = AioCb::from_mut_slice( f.as_raw_fd(), - /// 2, //offset - /// &mut rbuf, - /// 0, //priority - /// SigevNotify::SigevNone, - /// LioOpcode::LIO_NOP); - /// aiocb.read().unwrap(); - /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { - /// thread::sleep(time::Duration::from_millis(10)); - /// } - /// assert_eq!(aiocb.aio_return().unwrap() as usize, LEN); - /// } - /// assert_eq!(rbuf, b"cdef"); - /// ``` - pub fn from_mut_slice(fd: RawFd, offs: off_t, buf: &'a mut [u8], - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> Pin>> { - let mut a = AioCb::common_init(fd, prio, sigev_notify); - a.0.aio_offset = offs; - a.0.aio_nbytes = buf.len() as size_t; - a.0.aio_buf = buf.as_ptr() as *mut c_void; - a.0.aio_lio_opcode = opcode as libc::c_int; - - Box::pin(AioCb { - aiocb: a, - mutable: true, - in_progress: false, - _buffer: PhantomData, - _pin: std::marker::PhantomPinned - }) + 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:?}"), + } } - /// Constructs a new `AioCb` from a mutable raw pointer - /// - /// Unlike `from_mut_slice`, this method returns a structure suitable for - /// placement on the heap. It may be used for both reads and writes. Due - /// to its unsafety, this method is not recommended. It is most useful when - /// heap allocation is required. - /// - /// # Parameters - /// - /// * `fd`: File descriptor. Required for all aio functions. - /// * `offs`: File offset - /// * `buf`: Pointer to the memory buffer - /// * `len`: Length of the buffer pointed to by `buf` - /// * `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. - /// * `opcode`: This field is only used for `lio_listio`. It - /// determines which operation to use for this individual - /// aiocb - /// - /// # Safety - /// - /// The caller must ensure that the storage pointed to by `buf` outlives the - /// `AioCb`. The lifetime checker can't help here. - pub unsafe fn from_mut_ptr(fd: RawFd, offs: off_t, - buf: *mut c_void, len: usize, - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> Pin>> { - let mut a = AioCb::common_init(fd, prio, sigev_notify); - a.0.aio_offset = offs; - a.0.aio_nbytes = len; - a.0.aio_buf = buf; - a.0.aio_lio_opcode = opcode as libc::c_int; - - Box::pin(AioCb { - aiocb: a, - mutable: true, - in_progress: false, - _buffer: PhantomData, - _pin: std::marker::PhantomPinned, - }) + fn in_progress(&self) -> bool { + self.in_progress } - /// Constructs a new `AioCb` from a raw pointer. - /// - /// Unlike `from_slice`, this method returns a structure suitable for - /// placement on the heap. Due to its unsafety, this method is not - /// recommended. It is most useful when heap allocation is required. - /// - /// # Parameters - /// - /// * `fd`: File descriptor. Required for all aio functions. - /// * `offs`: File offset - /// * `buf`: Pointer to the memory buffer - /// * `len`: Length of the buffer pointed to by `buf` - /// * `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. - /// * `opcode`: This field is only used for `lio_listio`. It - /// determines which operation to use for this individual - /// aiocb - /// - /// # Safety - /// - /// The caller must ensure that the storage pointed to by `buf` outlives the - /// `AioCb`. The lifetime checker can't help here. - pub unsafe fn from_ptr(fd: RawFd, offs: off_t, - buf: *const c_void, len: usize, - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> Pin>> { - let mut a = AioCb::common_init(fd, prio, sigev_notify); - a.0.aio_offset = offs; - a.0.aio_nbytes = len; - // casting a const ptr to a mutable ptr here is ok, because we set the - // AioCb's mutable field to false - a.0.aio_buf = buf as *mut c_void; - a.0.aio_lio_opcode = opcode as libc::c_int; - - Box::pin(AioCb { - aiocb: a, - mutable: false, - in_progress: false, - _buffer: PhantomData, - _pin: std::marker::PhantomPinned - }) + fn set_in_progress(mut self: Pin<&mut Self>) { + self.as_mut().in_progress = true; } - // Private helper - fn from_slice_unpinned(fd: RawFd, offs: off_t, buf: &'a [u8], - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> AioCb - { - let mut a = AioCb::common_init(fd, prio, sigev_notify); - a.0.aio_offset = offs; - a.0.aio_nbytes = buf.len() as size_t; - // casting an immutable buffer to a mutable pointer looks unsafe, - // but technically its only unsafe to dereference it, not to create - // it. - a.0.aio_buf = buf.as_ptr() as *mut c_void; - assert!(opcode != LioOpcode::LIO_READ, "Can't read into an immutable buffer"); - a.0.aio_lio_opcode = opcode as libc::c_int; - - AioCb { - aiocb: a, - mutable: false, - in_progress: false, - _buffer: PhantomData, - _pin: std::marker::PhantomPinned - } + /// 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(); } +} - /// Like [`AioCb::from_mut_slice`], but works on constant slices rather than - /// mutable slices. - /// - /// An `AioCb` created this way cannot be used with `read`, and its - /// `LioOpcode` cannot be set to `LIO_READ`. This method is useful when - /// writing a const buffer with `AioCb::write`, since `from_mut_slice` can't - /// work with const buffers. - /// - /// # Examples - /// - /// Construct an `AioCb` from a slice and use it for writing. - /// - /// ``` - /// # 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 = AioCb::from_slice( f.as_raw_fd(), - /// 2, //offset - /// WBUF, - /// 0, //priority - /// SigevNotify::SigevNone, - /// LioOpcode::LIO_NOP); - /// aiocb.write().unwrap(); - /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { - /// thread::sleep(time::Duration::from_millis(10)); - /// } - /// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); - /// ``` - // Note: another solution to the problem of writing const buffers would be - // to genericize AioCb for both &mut [u8] and &[u8] buffers. AioCb::read - // could take the former and AioCb::write could take the latter. However, - // then lio_listio wouldn't work, because that function needs a slice of - // AioCb, and they must all be of the same type. - pub fn from_slice(fd: RawFd, offs: off_t, buf: &'a [u8], - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> Pin> - { - Box::pin(AioCb::from_slice_unpinned(fd, offs, buf, prio, sigev_notify, - opcode)) +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() } +} - fn common_init(fd: RawFd, prio: libc::c_int, - sigev_notify: SigevNotify) -> LibcAiocb { - // 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(); - LibcAiocb(a) +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" + ); } +} - /// Update the notification settings for an existing `aiocb` - pub fn set_sigev_notify(self: &mut Pin>, - sigev_notify: SigevNotify) - { - // Safe because we don't move any of the data - let selfp = unsafe { - self.as_mut().get_unchecked_mut() - }; - selfp.aiocb.0.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); - } +/// 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. /// @@ -476,51 +250,26 @@ impl<'a> AioCb<'a> { /// # use tempfile::tempfile; /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); - /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), + /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), /// 2, //offset /// &wbuf[..], /// 0, //priority - /// SigevNotify::SigevNone, - /// LioOpcode::LIO_NOP); - /// aiocb.write().unwrap(); - /// let cs = aiocb.cancel().unwrap(); + /// SigevNotify::SigevNone)); + /// aiocb.as_mut().submit().unwrap(); + /// let cs = aiocb.as_mut().cancel().unwrap(); /// if cs == AioCancelStat::AioNotCanceled { - /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { + /// 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.aio_return(); + /// let _ = aiocb.as_mut().aio_return(); /// ``` /// /// # References /// /// [aio_cancel](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) - pub fn cancel(self: &mut Pin>) -> Result { - let r = unsafe { - let selfp = self.as_mut().get_unchecked_mut(); - libc::aio_cancel(selfp.aiocb.0.aio_fildes, &mut selfp.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 error_unpinned(&mut self) -> Result<()> { - let r = unsafe { - libc::aio_error(&mut self.aiocb.0 as *mut libc::aiocb) - }; - 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 cancel(self: Pin<&mut Self>) -> Result; /// Retrieve error status of an asynchronous operation. /// @@ -542,155 +291,222 @@ impl<'a> AioCb<'a> { /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); - /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), + /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), /// 2, //offset /// WBUF, /// 0, //priority - /// SigevNotify::SigevNone, - /// LioOpcode::LIO_NOP); - /// aiocb.write().unwrap(); - /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { + /// 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.aio_return().unwrap() as usize, WBUF.len()); + /// assert_eq!(aiocb.as_mut().aio_return().unwrap(), WBUF.len()); /// ``` /// /// # References /// /// [aio_error](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_error.html) - pub fn error(self: &mut Pin>) -> Result<()> { - // Safe because error_unpinned doesn't move the data - let selfp = unsafe { - self.as_mut().get_unchecked_mut() - }; - selfp.error_unpinned() - } + fn error(self: Pin<&mut Self>) -> Result<()>; - /// An asynchronous version of `fsync(2)`. + /// Returns the underlying file descriptor associated with the operation. + fn fd(&self) -> RawFd; + + /// Does this operation currently have any in-kernel state? /// - /// # References + /// Dropping an operation that does have in-kernel state constitutes a + /// resource leak. /// - /// [aio_fsync](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_fsync.html) - pub fn fsync(self: &mut Pin>, mode: AioFsyncMode) -> Result<()> { - // Safe because we don't move the libc::aiocb - unsafe { - let selfp = self.as_mut().get_unchecked_mut(); - Errno::result({ - let p: *mut libc::aiocb = &mut selfp.aiocb.0; - libc::aio_fsync(mode as libc::c_int, p) - }).map(|_| { - selfp.in_progress = true; - }) - } - } - - /// Returns the `aiocb`'s `LioOpcode` field + /// # Examples /// - /// If the value cannot be represented as an `LioOpcode`, returns `None` - /// instead. - pub fn lio_opcode(&self) -> Option { - match self.aiocb.0.aio_lio_opcode { - libc::LIO_READ => Some(LioOpcode::LIO_READ), - libc::LIO_WRITE => Some(LioOpcode::LIO_WRITE), - libc::LIO_NOP => Some(LioOpcode::LIO_NOP), - _ => None - } - } + /// ``` + /// # 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 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.0.aio_nbytes - } + /// Returns the priority of the `AioCb` + fn priority(&self) -> i32; - /// Returns the file offset stored in the `AioCb` - pub fn offset(&self) -> off_t { - self.aiocb.0.aio_offset - } + /// 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 priority of the `AioCb` - pub fn priority(&self) -> libc::c_int { - self.aiocb.0.aio_reqprio - } + /// Returns the `SigEvent` that will be used for notification. + fn sigevent(&self) -> SigEvent; - /// Asynchronously reads from a file descriptor into a buffer + /// Actually start the I/O operation. /// - /// # References - /// - /// [aio_read](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_read.html) - pub fn read(self: &mut Pin>) -> Result<()> { - assert!(self.mutable, "Can't read into an immutable buffer"); - // Safe because we don't move anything - let selfp = unsafe { - self.as_mut().get_unchecked_mut() - }; - Errno::result({ - let p: *mut libc::aiocb = &mut selfp.aiocb.0; - unsafe { libc::aio_read(p) } - }).map(|_| { - selfp.in_progress = true; - }) - } + /// 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<()>; +} - /// Returns the `SigEvent` stored in the `AioCb` - pub fn sigevent(&self) -> SigEvent { - SigEvent::from(&self.aiocb.0.aio_sigevent) - } +macro_rules! aio_methods { + () => { + fn cancel(self: Pin<&mut Self>) -> Result { + self.aiocb().cancel() + } - fn aio_return_unpinned(&mut self) -> Result { - unsafe { - let p: *mut libc::aiocb = &mut self.aiocb.0; - self.in_progress = false; - Errno::result(libc::aio_return(p)) + fn error(self: Pin<&mut Self>) -> Result<()> { + self.aiocb().error() } - } - /// Retrieve return status of an asynchronous operation. - /// - /// Should only be called once for each `AioCb`, after `AioCb::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) - // Note: this should be just `return`, but that's a reserved word - pub fn aio_return(self: &mut Pin>) -> Result { - // Safe because aio_return_unpinned does not move the data - let selfp = unsafe { - self.as_mut().get_unchecked_mut() - }; - selfp.aio_return_unpinned() - } + fn fd(&self) -> RawFd { + self.aiocb.aiocb.0.aio_fildes + } - /// Asynchronously writes from a buffer to a file descriptor - /// - /// # References - /// - /// [aio_write](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_write.html) - pub fn write(self: &mut Pin>) -> Result<()> { - // Safe because we don't move anything - let selfp = unsafe { - self.as_mut().get_unchecked_mut() - }; - Errno::result({ - let p: *mut libc::aiocb = &mut selfp.aiocb.0; - unsafe{ libc::aio_write(p) } - }).map(|_| { - selfp.in_progress = true; - }) - } -} + fn in_progress(&self) -> bool { + self.aiocb.in_progress() + } -/// Cancels outstanding AIO requests for a given file descriptor. + 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 /// -/// Issue an aio operation, then cancel all outstanding operations on that file -/// descriptor. /// /// ``` /// # use nix::errno::Errno; @@ -701,422 +517,729 @@ impl<'a> AioCb<'a> { /// # use std::io::Write; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; -/// let wbuf = b"CDEF"; +/// const INITIAL: &[u8] = b"abcdef123456"; +/// const LEN: usize = 4; +/// let mut rbuf = vec![0; LEN]; /// let mut f = tempfile().unwrap(); -/// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), -/// 2, //offset -/// &wbuf[..], -/// 0, //priority -/// SigevNotify::SigevNone, -/// LioOpcode::LIO_NOP); -/// aiocb.write().unwrap(); -/// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); -/// if cs == AioCancelStat::AioNotCanceled { -/// while (aiocb.error() == Err(Errno::EINPROGRESS)) { +/// 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); /// } -/// // Must call `aio_return`, but ignore the result -/// let _ = aiocb.aio_return(); +/// assert_eq!(rbuf, b"cdef"); /// ``` -/// -/// # 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, 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") +#[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 } } -/// Suspends the calling process until at least one of the specified `AioCb`s -/// has completed, a signal is delivered, or the timeout has passed. +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. /// -/// If `timeout` is `None`, `aio_suspend` will block indefinitely. +/// # References +/// +/// [aio_readv](https://www.freebsd.org/cgi/man.cgi?query=aio_readv) /// /// # Examples /// -/// Use `aio_suspend` to block until an aio operation completes. /// -/// ``` +#[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 WBUF: &[u8] = b"abcdef123456"; +/// 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(); -/// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), -/// 2, //offset -/// WBUF, -/// 0, //priority -/// SigevNotify::SigevNone, -/// LioOpcode::LIO_NOP); -/// aiocb.write().unwrap(); -/// aio_suspend(&[aiocb.as_ref()], None).expect("aio_suspend failed"); -/// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); +/// 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"); /// ``` -/// # References -/// -/// [`aio_suspend`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_suspend.html) -pub fn aio_suspend(list: &[Pin<&AioCb>], timeout: Option) -> Result<()> { - let plist = list as *const [Pin<&AioCb>] as *const [*const libc::aiocb]; - let p = plist as *const *const libc::aiocb; - let timep = match timeout { - None => null::(), - Some(x) => x.as_ref() as *const libc::timespec - }; - Errno::result(unsafe { - libc::aio_suspend(p, list.len() as i32, timep) - }).map(drop) +#[cfg(target_os = "freebsd")] +#[derive(Debug)] +#[repr(transparent)] +pub struct AioReadv<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [&'a [u8]]>, + _pin: PhantomPinned, } -impl<'a> Debug for AioCb<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("AioCb") - .field("aiocb", &self.aiocb.0) - .field("mutable", &self.mutable) - .field("in_progress", &self.in_progress) - .finish() +#[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 } -} -impl<'a> Drop for AioCb<'a> { - /// 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"); + /// 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 } } -/// LIO Control Block. -/// -/// The basic structure used to issue multiple AIO operations simultaneously. -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -pub struct LioCb<'a> { - /// A collection of [`AioCb`]s. All of these will be issued simultaneously - /// by the [`listio`] method. - /// - /// [`AioCb`]: struct.AioCb.html - /// [`listio`]: #method.listio - // Their locations in memory must be fixed once they are passed to the - // kernel. So this field must be non-public so the user can't swap. - aiocbs: Box<[AioCb<'a>]>, +#[cfg(target_os = "freebsd")] +impl<'a> Aio for AioReadv<'a> { + type Output = usize; - /// The actual list passed to `libc::lio_listio`. - /// - /// It must live for as long as any of the operations are still being - /// processesed, because the aio subsystem uses its address as a unique - /// identifier. - list: Vec<*mut libc::aiocb>, - - /// A partial set of results. This field will get populated by - /// `listio_resubmit` when an `LioCb` is resubmitted after an error - results: Vec>> + aio_methods!(aio_readv); } -/// LioCb can't automatically impl Send and Sync just because of the raw -/// pointers in list. But that's stupid. There's no reason that raw pointers -/// should automatically be non-Send -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -unsafe impl<'a> Send for LioCb<'a> {} -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -unsafe impl<'a> Sync for LioCb<'a> {} - -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -impl<'a> LioCb<'a> { - /// Are no [`AioCb`]s contained? - pub fn is_empty(&self) -> bool { - self.aiocbs.is_empty() +#[cfg(target_os = "freebsd")] +impl<'a> AsMut for AioReadv<'a> { + fn as_mut(&mut self) -> &mut libc::aiocb { + &mut self.aiocb.aiocb.0 } +} - /// Return the number of individual [`AioCb`]s contained. - pub fn len(&self) -> usize { - self.aiocbs.len() +#[cfg(target_os = "freebsd")] +impl<'a> AsRef for AioReadv<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 } +} - /// 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, writes, and - /// fsyncs may be freely mixed. - /// - /// This function is useful for reducing the context-switch overhead of - /// submitting many AIO operations. It can also be used with - /// `LioMode::LIO_WAIT` to block on the result of several independent - /// operations. Used that way, it is often useful in programs that - /// otherwise make little use of AIO. - /// - /// # Examples - /// - /// Use `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 - /// [`AioCb::error`] to poll. - /// - /// ``` - /// # 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 liocb = LioCbBuilder::with_capacity(1) - /// .emplace_slice( - /// f.as_raw_fd(), - /// 2, //offset - /// WBUF, - /// 0, //priority - /// SigevNotify::SigevNone, - /// LioOpcode::LIO_WRITE - /// ).finish(); - /// liocb.listio(LioMode::LIO_WAIT, - /// SigevNotify::SigevNone).unwrap(); - /// assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - /// ``` - /// - /// # References - /// - /// [`lio_listio`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lio_listio.html) +/// 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 /// - /// [`aio_suspend`]: fn.aio_suspend.html - /// [`AioCb::error`]: struct.AioCb.html#method.error - pub fn listio(&mut self, mode: LioMode, - sigev_notify: SigevNotify) -> Result<()> { - let sigev = SigEvent::new(sigev_notify); - let sigevp = &mut sigev.sigevent() as *mut libc::sigevent; - self.list.clear(); - for a in &mut self.aiocbs.iter_mut() { - a.in_progress = true; - self.list.push(a as *mut AioCb<'a> - as *mut libc::aiocb); - } - let p = self.list.as_ptr(); - Errno::result(unsafe { - libc::lio_listio(mode as i32, p, self.list.len() as i32, sigevp) - }).map(drop) + /// 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 } - /// Resubmits any incomplete operations with [`lio_listio`]. - /// - /// Sometimes, due to system resource limitations, an `lio_listio` call will - /// return `EIO`, or `EAGAIN`. Or, if a signal is received, it may return - /// `EINTR`. In any of these cases, only a subset of its constituent - /// operations will actually have been initiated. `listio_resubmit` will - /// resubmit any operations that are still uninitiated. - /// - /// After calling `listio_resubmit`, results should be collected by - /// [`LioCb::aio_return`]. - /// - /// # Examples - /// ```no_run - /// # use nix::Error; - /// # use nix::errno::Errno; - /// # use nix::sys::aio::*; - /// # use nix::sys::signal::SigevNotify; - /// # use std::os::unix::io::AsRawFd; - /// # use std::{thread, time}; - /// # use tempfile::tempfile; - /// const WBUF: &[u8] = b"abcdef123456"; - /// let mut f = tempfile().unwrap(); - /// let mut liocb = LioCbBuilder::with_capacity(1) - /// .emplace_slice( - /// f.as_raw_fd(), - /// 2, //offset - /// WBUF, - /// 0, //priority - /// SigevNotify::SigevNone, - /// LioOpcode::LIO_WRITE - /// ).finish(); - /// let mut err = liocb.listio(LioMode::LIO_WAIT, SigevNotify::SigevNone); - /// while err == Err(Errno::EIO) || - /// err == Err(Errno::EAGAIN) { - /// thread::sleep(time::Duration::from_millis(10)); - /// err = liocb.listio_resubmit(LioMode::LIO_WAIT, SigevNotify::SigevNone); - /// } - /// assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - /// ``` - /// - /// # References + /// Construct a new `AioWrite`. /// - /// [`lio_listio`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lio_listio.html) + /// # Arguments /// - /// [`lio_listio`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/lio_listio.html - /// [`LioCb::aio_return`]: struct.LioCb.html#method.aio_return - // Note: the addresses of any EINPROGRESS or EOK aiocbs _must_ not be - // changed by this method, because the kernel relies on their addresses - // being stable. - // Note: aiocbs that are Ok(()) must be finalized by aio_return, or else the - // sigev_notify will immediately refire. - pub fn listio_resubmit(&mut self, mode:LioMode, - sigev_notify: SigevNotify) -> Result<()> { - let sigev = SigEvent::new(sigev_notify); - let sigevp = &mut sigev.sigevent() as *mut libc::sigevent; - self.list.clear(); - - while self.results.len() < self.aiocbs.len() { - self.results.push(None); - } - - for (i, a) in self.aiocbs.iter_mut().enumerate() { - if self.results[i].is_some() { - // Already collected final status for this operation - continue; - } - match a.error_unpinned() { - Ok(()) => { - // aiocb is complete; collect its status and don't resubmit - self.results[i] = Some(a.aio_return_unpinned()); - }, - Err(Errno::EAGAIN) => { - self.list.push(a as *mut AioCb<'a> as *mut libc::aiocb); - }, - Err(Errno::EINPROGRESS) => { - // aiocb is was successfully queued; no need to do anything - }, - Err(Errno::EINVAL) => panic!( - "AioCb was never submitted, or already finalized"), - _ => unreachable!() - } + /// * `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, } - let p = self.list.as_ptr(); - Errno::result(unsafe { - libc::lio_listio(mode as i32, p, self.list.len() as i32, sigevp) - }).map(drop) } - /// Collect final status for an individual `AioCb` submitted as part of an - /// `LioCb`. - /// - /// This is just like [`AioCb::aio_return`], except it takes into account - /// operations that were restarted by [`LioCb::listio_resubmit`] - /// - /// [`AioCb::aio_return`]: struct.AioCb.html#method.aio_return - /// [`LioCb::listio_resubmit`]: #method.listio_resubmit - pub fn aio_return(&mut self, i: usize) -> Result { - if i >= self.results.len() || self.results[i].is_none() { - self.aiocbs[i].aio_return_unpinned() - } else { - self.results[i].unwrap() - } + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset } +} - /// Retrieve error status of an individual `AioCb` submitted as part of an - /// `LioCb`. - /// - /// This is just like [`AioCb::error`], except it takes into account - /// operations that were restarted by [`LioCb::listio_resubmit`] - /// - /// [`AioCb::error`]: struct.AioCb.html#method.error - /// [`LioCb::listio_resubmit`]: #method.listio_resubmit - pub fn error(&mut self, i: usize) -> Result<()> { - if i >= self.results.len() || self.results[i].is_none() { - self.aiocbs[i].error_unpinned() - } else { - Ok(()) - } +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 } } -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -impl<'a> Debug for LioCb<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("LioCb") - .field("aiocbs", &self.aiocbs) - .finish() +impl<'a> AsRef for AioWrite<'a> { + fn as_ref(&self) -> &libc::aiocb { + &self.aiocb.aiocb.0 } } -/// Used to construct `LioCb` -// This must be a separate class from LioCb due to pinning constraints. LioCb -// must use a boxed slice of AioCbs so they will have stable storage, but -// LioCbBuilder must use a Vec to make construction possible when the final size -// is unknown. -#[cfg(not(any(target_os = "ios", target_os = "macos")))] +/// 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)] -pub struct LioCbBuilder<'a> { - /// A collection of [`AioCb`]s. - /// - /// [`AioCb`]: struct.AioCb.html - pub aiocbs: Vec>, +#[repr(transparent)] +pub struct AioWritev<'a> { + aiocb: AioCb, + _data: PhantomData<&'a [&'a [u8]]>, + _pin: PhantomPinned, } -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -impl<'a> LioCbBuilder<'a> { - /// Initialize an empty `LioCb` - pub fn with_capacity(capacity: usize) -> LioCbBuilder<'a> { - LioCbBuilder { - aiocbs: Vec::with_capacity(capacity), - } +#[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 } - /// Add a new operation on an immutable slice to the [`LioCb`] under - /// construction. + /// Construct a new `AioWritev`. /// - /// Arguments are the same as for [`AioCb::from_slice`] + /// # Arguments /// - /// [`LioCb`]: struct.LioCb.html - /// [`AioCb::from_slice`]: struct.AioCb.html#method.from_slice - pub fn emplace_slice(mut self, fd: RawFd, offs: off_t, buf: &'a [u8], - prio: libc::c_int, sigev_notify: SigevNotify, - opcode: LioOpcode) -> Self - { - self.aiocbs.push(AioCb::from_slice_unpinned(fd, offs, buf, prio, - sigev_notify, opcode)); - self + /// * `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, + } } - /// Add a new operation on a mutable slice to the [`LioCb`] under - /// construction. - /// - /// Arguments are the same as for [`AioCb::from_mut_slice`] - /// - /// [`LioCb`]: struct.LioCb.html - /// [`AioCb::from_mut_slice`]: struct.AioCb.html#method.from_mut_slice - pub fn emplace_mut_slice(mut self, fd: RawFd, offs: off_t, - buf: &'a mut [u8], prio: libc::c_int, - sigev_notify: SigevNotify, opcode: LioOpcode) - -> Self - { - self.aiocbs.push(AioCb::from_mut_slice_unpinned(fd, offs, buf, prio, - sigev_notify, opcode)); - self + /// Returns the file offset of the operation. + pub fn offset(&self) -> off_t { + self.aiocb.aiocb.0.aio_offset } +} - /// Finalize this [`LioCb`]. - /// - /// Afterwards it will be possible to issue the operations with - /// [`LioCb::listio`]. Conversely, it will no longer be possible to add new - /// operations with [`LioCbBuilder::emplace_slice`] or - /// [`LioCbBuilder::emplace_mut_slice`]. - /// - /// [`LioCb::listio`]: struct.LioCb.html#method.listio - /// [`LioCb::from_mut_slice`]: struct.LioCb.html#method.from_mut_slice - /// [`LioCb::from_slice`]: struct.LioCb.html#method.from_slice - pub fn finish(self) -> LioCb<'a> { - let len = self.aiocbs.len(); - LioCb { - aiocbs: self.aiocbs.into(), - list: Vec::with_capacity(len), - results: Vec::with_capacity(len) - } +#[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"), } } -#[cfg(not(any(target_os = "ios", target_os = "macos")))] +/// 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<()> { + // Note that this allocation could be eliminated by making the argument + // generic, and accepting arguments like &[AioWrite]. But that would + // prevent using aio_suspend to wait on a heterogeneous list of mixed + // operations. + let v = list.iter() + .map(|x| x.as_ref() as *const libc::aiocb) + .collect::>(); + let p = v.as_ptr(); + 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 nix::errno::Errno; +/// # use nix::sys::aio::*; +/// # use nix::sys::signal::*; +/// # use tempfile::tempfile; +/// pub static 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()); +/// ``` +#[deprecated(since = "0.27.0", note = "https://github.com/nix-rust/nix/issues/2017")] +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::*; - // It's important that `LioCb` be `UnPin`. The tokio-file crate relies on - // it. + /// aio_suspend relies on casting Rust Aio* struct pointers to libc::aiocb + /// pointers. This test ensures that such casts are valid. #[test] - fn liocb_is_unpin() { - use assert_impl::assert_impl; + 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 + ); - assert_impl!(Unpin: LioCb); + 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 index 6bc2a2539e..36f9c17d0e 100644 --- a/src/sys/epoll.rs +++ b/src/sys/epoll.rs @@ -1,9 +1,8 @@ -use crate::Result; use crate::errno::Errno; +use crate::Result; use libc::{self, c_int}; -use std::os::unix::io::RawFd; -use std::ptr; use std::mem; +use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}; libc_bitflags!( pub struct EpollFlags: c_int { @@ -18,7 +17,6 @@ libc_bitflags!( EPOLLERR; EPOLLHUP; EPOLLRDHUP; - #[cfg(target_os = "linux")] // Added in 4.5; not in Android. EPOLLEXCLUSIVE; #[cfg(not(target_arch = "mips"))] EPOLLWAKEUP; @@ -36,7 +34,7 @@ pub enum EpollOp { EpollCtlMod = libc::EPOLL_CTL_MOD, } -libc_bitflags!{ +libc_bitflags! { pub struct EpollCreateFlags: c_int { EPOLL_CLOEXEC; } @@ -50,7 +48,12 @@ pub struct EpollEvent { impl EpollEvent { pub fn new(events: EpollFlags, data: u64) -> Self { - EpollEvent { event: libc::epoll_event { events: events.bits() as u32, u64: data } } + EpollEvent { + event: libc::epoll_event { + events: events.bits() as u32, + u64: data, + }, + } } pub fn empty() -> Self { @@ -66,6 +69,126 @@ impl EpollEvent { } } +/// A safe wrapper around [`epoll`](https://man7.org/linux/man-pages/man7/epoll.7.html). +/// ``` +/// # use nix::sys::{epoll::{Epoll, EpollEvent, EpollFlags, EpollCreateFlags}, eventfd::{eventfd, EfdFlags}}; +/// # use nix::unistd::write; +/// # use std::os::unix::io::{OwnedFd, FromRawFd, AsRawFd, AsFd}; +/// # use std::time::{Instant, Duration}; +/// # fn main() -> nix::Result<()> { +/// const DATA: u64 = 17; +/// const MILLIS: u64 = 100; +/// +/// // Create epoll +/// let epoll = Epoll::new(EpollCreateFlags::empty())?; +/// +/// // Create eventfd & Add event +/// let eventfd = eventfd(0, EfdFlags::empty())?; +/// epoll.add(&eventfd, EpollEvent::new(EpollFlags::EPOLLIN,DATA))?; +/// +/// // Arm eventfd & Time wait +/// write(eventfd.as_raw_fd(), &1u64.to_ne_bytes())?; +/// let now = Instant::now(); +/// +/// // Wait on event +/// let mut events = [EpollEvent::empty()]; +/// epoll.wait(&mut events, MILLIS as isize)?; +/// +/// // Assert data correct & timeout didn't occur +/// assert_eq!(events[0].data(), DATA); +/// assert!(now.elapsed() < Duration::from_millis(MILLIS)); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug)] +pub struct Epoll(pub OwnedFd); +impl Epoll { + /// Creates a new epoll instance and returns a file descriptor referring to that instance. + /// + /// [`epoll_create1`](https://man7.org/linux/man-pages/man2/epoll_create1.2.html). + pub fn new(flags: EpollCreateFlags) -> Result { + let res = unsafe { libc::epoll_create1(flags.bits()) }; + let fd = Errno::result(res)?; + let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) }; + Ok(Self(owned_fd)) + } + /// Add an entry to the interest list of the epoll file descriptor for + /// specified in events. + /// + /// [`epoll_ctl`](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) with `EPOLL_CTL_ADD`. + pub fn add(&self, fd: Fd, mut event: EpollEvent) -> Result<()> { + self.epoll_ctl(EpollOp::EpollCtlAdd, fd, &mut event) + } + /// Remove (deregister) the target file descriptor `fd` from the interest list. + /// + /// [`epoll_ctl`](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) with `EPOLL_CTL_DEL` . + pub fn delete(&self, fd: Fd) -> Result<()> { + self.epoll_ctl(EpollOp::EpollCtlDel, fd, None) + } + /// Change the settings associated with `fd` in the interest list to the new settings specified + /// in `event`. + /// + /// [`epoll_ctl`](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) with `EPOLL_CTL_MOD`. + pub fn modify( + &self, + fd: Fd, + event: &mut EpollEvent, + ) -> Result<()> { + self.epoll_ctl(EpollOp::EpollCtlMod, fd, event) + } + /// Waits for I/O events, blocking the calling thread if no events are currently available. + /// (This can be thought of as fetching items from the ready list of the epoll instance.) + /// + /// [`epoll_wait`](https://man7.org/linux/man-pages/man2/epoll_wait.2.html) + pub fn wait( + &self, + events: &mut [EpollEvent], + timeout: isize, + ) -> Result { + let res = unsafe { + libc::epoll_wait( + self.0.as_raw_fd(), + events.as_mut_ptr() as *mut libc::epoll_event, + events.len() as c_int, + timeout as c_int, + ) + }; + + Errno::result(res).map(|r| r as usize) + } + /// This system call is used to add, modify, or remove entries in the interest list of the epoll + /// instance referred to by `self`. It requests that the operation `op` be performed for the + /// target file descriptor, `fd`. + /// + /// When possible prefer [`Epoll::add`], [`Epoll::delete`] and [`Epoll::modify`]. + /// + /// [`epoll_ctl`](https://man7.org/linux/man-pages/man2/epoll_ctl.2.html) + fn epoll_ctl<'a, Fd: AsFd, T>( + &self, + op: EpollOp, + fd: Fd, + event: T, + ) -> Result<()> + where + T: Into>, + { + let event: Option<&mut EpollEvent> = event.into(); + let ptr = event + .map(|x| &mut x.event as *mut libc::epoll_event) + .unwrap_or(std::ptr::null_mut()); + unsafe { + Errno::result(libc::epoll_ctl( + self.0.as_raw_fd(), + op as c_int, + fd.as_fd().as_raw_fd(), + ptr, + )) + .map(drop) + } + } +} + +#[deprecated(since = "0.27.0", note = "Use Epoll::new() instead")] #[inline] pub fn epoll_create() -> Result { let res = unsafe { libc::epoll_create(1024) }; @@ -73,6 +196,7 @@ pub fn epoll_create() -> Result { Errno::result(res) } +#[deprecated(since = "0.27.0", note = "Use Epoll::new() instead")] #[inline] pub fn epoll_create1(flags: EpollCreateFlags) -> Result { let res = unsafe { libc::epoll_create1(flags.bits()) }; @@ -80,9 +204,16 @@ pub fn epoll_create1(flags: EpollCreateFlags) -> Result { Errno::result(res) } +#[deprecated(since = "0.27.0", note = "Use Epoll::epoll_ctl() instead")] #[inline] -pub fn epoll_ctl<'a, T>(epfd: RawFd, op: EpollOp, fd: RawFd, event: T) -> Result<()> - where T: Into> +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 { @@ -92,17 +223,27 @@ pub fn epoll_ctl<'a, T>(epfd: RawFd, op: EpollOp, fd: RawFd, event: T) -> Result 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()) + libc::epoll_ctl(epfd, op as c_int, fd, std::ptr::null_mut()) } }; Errno::result(res).map(drop) } } +#[deprecated(since = "0.27.0", note = "Use Epoll::wait() instead")] #[inline] -pub fn epoll_wait(epfd: RawFd, events: &mut [EpollEvent], timeout_ms: isize) -> Result { +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) + 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 index c648f5ebc8..ec7f7e277a 100644 --- a/src/sys/event.rs +++ b/src/sys/event.rs @@ -1,33 +1,93 @@ -/* TOOD: Implement for other kqueue based systems - */ +//! Kernel event notification mechanism +//! +//! # See Also +//! [kqueue(2)](https://www.freebsd.org/cgi/man.cgi?query=kqueue) use crate::{Errno, Result}; #[cfg(not(target_os = "netbsd"))] -use libc::{timespec, time_t, c_int, c_long, intptr_t, uintptr_t}; +use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t}; #[cfg(target_os = "netbsd")] -use libc::{timespec, time_t, c_long, intptr_t, uintptr_t, size_t}; +use libc::{c_long, intptr_t, size_t, time_t, timespec, uintptr_t}; use std::convert::TryInto; -use std::os::unix::io::RawFd; +use std::mem; +use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd}; use std::ptr; -// Redefine kevent in terms of programmer-friendly enums and bitfields. +/// A kernel event queue. Used to notify a process of various asynchronous +/// events. #[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"))] +/// A kernel event queue. +/// +/// Used by the kernel to notify the process of various types of asynchronous +/// events. +#[repr(transparent)] +#[derive(Debug)] +pub struct Kqueue(OwnedFd); + +impl Kqueue { + /// Create a new kernel event queue. + pub fn new() -> Result { + let res = unsafe { libc::kqueue() }; + + Errno::result(res).map(|fd| unsafe { Self(OwnedFd::from_raw_fd(fd)) }) + } + + /// Register new events with the kqueue, and return any pending events to + /// the user. + /// + /// This method will block until either the timeout expires, or a registered + /// event triggers a notification. + /// + /// # Arguments + /// - `changelist` - Any new kevents to register for notifications. + /// - `eventlist` - Storage space for the kernel to return notifications. + /// - `timeout` - An optional timeout. + /// + /// # Returns + /// Returns the number of events placed in the `eventlist`. If an error + /// occurs while processing an element of the `changelist` and there is + /// enough room in the `eventlist`, then the event will be placed in the + /// `eventlist` with `EV_ERROR` set in `flags` and the system error in + /// `data`. + pub fn kevent( + &self, + changelist: &[KEvent], + eventlist: &mut [KEvent], + timeout_opt: Option, + ) -> Result { + let res = unsafe { + libc::kevent( + self.0.as_raw_fd(), + 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) + } +} + +#[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 = "dragonfly", target_os = "freebsd", - target_os = "ios", target_os = "macos"))] -type type_of_data = intptr_t; -#[cfg(any(target_os = "netbsd"))] +#[cfg(target_os = "netbsd")] type type_of_udata = intptr_t; -#[cfg(any(target_os = "netbsd", target_os = "openbsd"))] -type type_of_data = i64; #[cfg(target_os = "netbsd")] type type_of_event_filter = u32; @@ -37,22 +97,34 @@ libc_enum! { #[cfg_attr(target_os = "netbsd", repr(u32))] #[cfg_attr(not(target_os = "netbsd"), repr(i16))] #[non_exhaustive] + /// Kqueue filter types. These are all the different types of event that a + /// kqueue can notify for. pub enum EventFilter { + /// Notifies on the completion of a POSIX AIO operation. EVFILT_AIO, - /// Returns whenever there is no remaining data in the write buffer #[cfg(target_os = "freebsd")] + /// Returns whenever there is no remaining data in the write buffer EVFILT_EMPTY, #[cfg(target_os = "dragonfly")] + /// Takes a descriptor as the identifier, and returns whenever one of + /// the specified exceptional conditions has occurred on the descriptor. EVFILT_EXCEPT, #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] + /// Establishes a file system monitor. EVFILT_FS, #[cfg(target_os = "freebsd")] + /// Notify for completion of a list of POSIX AIO operations. + /// # See Also + /// [lio_listio(2)](https://www.freebsd.org/cgi/man.cgi?query=lio_listio) EVFILT_LIO, #[cfg(any(target_os = "ios", target_os = "macos"))] + /// Mach portsets EVFILT_MACHPORT, + /// Notifies when a process performs one or more of the requested + /// events. EVFILT_PROC, /// Returns events associated with the process referenced by a given /// process descriptor, created by `pdfork()`. The events to monitor are: @@ -60,289 +132,394 @@ libc_enum! { /// - NOTE_EXIT: the process has exited. The exit status will be stored in data. #[cfg(target_os = "freebsd")] EVFILT_PROCDESC, + /// Takes a file descriptor as the identifier, and notifies whenever + /// there is data available to read. EVFILT_READ, - /// Returns whenever an asynchronous `sendfile()` call completes. #[cfg(target_os = "freebsd")] + #[doc(hidden)] + #[deprecated(since = "0.27.0", note = "Never fully implemented by the OS")] EVFILT_SENDFILE, + /// Takes a signal number to monitor as the identifier and notifies when + /// the given signal is delivered to the process. EVFILT_SIGNAL, + /// Establishes a timer and notifies when the timer expires. EVFILT_TIMER, #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] + /// Notifies only when explicitly requested by the user. EVFILT_USER, #[cfg(any(target_os = "ios", target_os = "macos"))] + /// Virtual memory events EVFILT_VM, + /// Notifies when a requested event happens on a specified file. EVFILT_VNODE, + /// Takes a file descriptor as the identifier, and notifies whenever + /// it is possible to write to the file without blocking. EVFILT_WRITE, } impl TryFrom } -#[cfg(any(target_os = "dragonfly", target_os = "freebsd", - target_os = "ios", target_os = "macos", - target_os = "openbsd"))] +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "openbsd" +))] +#[doc(hidden)] pub type type_of_event_flag = u16; -#[cfg(any(target_os = "netbsd"))] +#[cfg(target_os = "netbsd")] +#[doc(hidden)] pub type type_of_event_flag = u32; -libc_bitflags!{ +libc_bitflags! { + /// Event flags. See the man page for details. + // There's no useful documentation we can write for the individual flags + // that wouldn't simply be repeating the man page. pub struct EventFlag: type_of_event_flag { + #[allow(missing_docs)] EV_ADD; + #[allow(missing_docs)] EV_CLEAR; + #[allow(missing_docs)] EV_DELETE; + #[allow(missing_docs)] EV_DISABLE; #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[allow(missing_docs)] EV_DISPATCH; #[cfg(target_os = "freebsd")] + #[allow(missing_docs)] EV_DROP; + #[allow(missing_docs)] EV_ENABLE; + #[allow(missing_docs)] EV_EOF; + #[allow(missing_docs)] EV_ERROR; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] EV_FLAG0; + #[allow(missing_docs)] EV_FLAG1; #[cfg(target_os = "dragonfly")] + #[allow(missing_docs)] EV_NODATA; + #[allow(missing_docs)] EV_ONESHOT; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] EV_OOBAND; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] EV_POLL; #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[allow(missing_docs)] EV_RECEIPT; - EV_SYSFLAGS; } } libc_bitflags!( + /// Filter-specific flags. See the man page for details. + // There's no useful documentation we can write for the individual flags + // that wouldn't simply be repeating the man page. + #[allow(missing_docs)] pub struct FilterFlag: u32 { #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_ABSOLUTE; + #[allow(missing_docs)] NOTE_ATTRIB; + #[allow(missing_docs)] NOTE_CHILD; + #[allow(missing_docs)] NOTE_DELETE; #[cfg(target_os = "openbsd")] + #[allow(missing_docs)] NOTE_EOF; + #[allow(missing_docs)] NOTE_EXEC; + #[allow(missing_docs)] NOTE_EXIT; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_EXITSTATUS; + #[allow(missing_docs)] NOTE_EXTEND; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_FFAND; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_FFCOPY; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_FFCTRLMASK; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_FFLAGSMASK; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_FFNOP; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_FFOR; + #[allow(missing_docs)] NOTE_FORK; + #[allow(missing_docs)] NOTE_LINK; + #[allow(missing_docs)] NOTE_LOWAT; #[cfg(target_os = "freebsd")] + #[allow(missing_docs)] NOTE_MSECONDS; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_NONE; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + #[allow(missing_docs)] NOTE_NSECONDS; #[cfg(target_os = "dragonfly")] + #[allow(missing_docs)] NOTE_OOB; + #[allow(missing_docs)] NOTE_PCTRLMASK; + #[allow(missing_docs)] NOTE_PDATAMASK; + #[allow(missing_docs)] NOTE_RENAME; + #[allow(missing_docs)] NOTE_REVOKE; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + #[allow(missing_docs)] NOTE_SECONDS; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_SIGNAL; + #[allow(missing_docs)] NOTE_TRACK; + #[allow(missing_docs)] NOTE_TRACKERR; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly"))] + #[allow(missing_docs)] NOTE_TRIGGER; #[cfg(target_os = "openbsd")] + #[allow(missing_docs)] NOTE_TRUNCATE; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + #[allow(missing_docs)] NOTE_USECONDS; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_VM_ERROR; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_VM_PRESSURE; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_VM_PRESSURE_SUDDEN_TERMINATE; #[cfg(any(target_os = "macos", target_os = "ios"))] + #[allow(missing_docs)] NOTE_VM_PRESSURE_TERMINATE; + #[allow(missing_docs)] NOTE_WRITE; } ); -pub fn kqueue() -> Result { - let res = unsafe { libc::kqueue() }; - - Errno::result(res) +#[allow(missing_docs)] +#[deprecated(since = "0.27.0", note = "Use KEvent::new instead")] +pub fn kqueue() -> Result { + Kqueue::new() } - // 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 { -} +unsafe impl Send for KEvent {} impl KEvent { - 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: data as type_of_data, - udata: udata as type_of_udata - } } + #[allow(clippy::needless_update)] // Not needless on all platforms. + /// Construct a new `KEvent` suitable for submission to the kernel via the + /// `changelist` argument of [`Kqueue::kevent`]. + 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() } + }, + } } + /// Value used to identify this event. The exact interpretation is + /// determined by the attached filter, but often is a raw file descriptor. pub fn ident(&self) -> uintptr_t { self.kevent.ident } + /// Identifies the kernel filter used to process this event. + /// + /// Will only return an error if the kernel reports an event via a filter + /// that is unknown to Nix. pub fn filter(&self) -> Result { self.kevent.filter.try_into() } + /// Flags control what the kernel will do when this event is added with + /// [`Kqueue::kevent`]. pub fn flags(&self) -> EventFlag { EventFlag::from_bits(self.kevent.flags).unwrap() } + /// Filter-specific flags. pub fn fflags(&self) -> FilterFlag { FilterFlag::from_bits(self.kevent.fflags).unwrap() } + /// Filter-specific data value. pub fn data(&self) -> intptr_t { self.kevent.data as intptr_t } + /// Opaque user-defined value passed through the kernel unchanged. 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 { - +#[allow(missing_docs)] +#[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] +pub fn kevent( + kq: &Kqueue, + 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 + tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long, }; - kevent_ts(kq, changelist, eventlist, Some(timeout)) + kq.kevent(changelist, eventlist, Some(timeout)) } -#[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd"))] +#[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) +#[allow(missing_docs)] +#[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] +pub fn kevent_ts( + kq: &Kqueue, + changelist: &[KEvent], + eventlist: &mut [KEvent], + timeout_opt: Option, +) -> Result { + kq.kevent(changelist, eventlist, timeout_opt) } +/// Modify an existing [`KEvent`]. +// Probably should deprecate. Would anybody ever use it over `KEvent::new`? +#[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] #[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; +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.flags = flags.bits(); ev.kevent.fflags = fflags.bits(); - ev.kevent.data = 0; - ev.kevent.udata = udata as type_of_udata; + 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 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); + 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() as type_of_data); + 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 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); + 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 index c54f952f09..f1723519cf 100644 --- a/src/sys/eventfd.rs +++ b/src/sys/eventfd.rs @@ -1,6 +1,6 @@ -use std::os::unix::io::RawFd; -use crate::Result; use crate::errno::Errno; +use crate::Result; +use std::os::unix::io::{FromRawFd, OwnedFd}; libc_bitflags! { pub struct EfdFlags: libc::c_int { @@ -10,8 +10,8 @@ libc_bitflags! { } } -pub fn eventfd(initval: libc::c_uint, flags: EfdFlags) -> Result { +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) + Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r) }) } diff --git a/src/sys/inotify.rs b/src/sys/inotify.rs index 3f5ae22abc..e5fe930f49 100644 --- a/src/sys/inotify.rs +++ b/src/sys/inotify.rs @@ -23,48 +23,68 @@ //! } //! ``` -use libc::{ - c_char, - c_int, -}; -use std::ffi::{OsString,OsStr,CStr}; -use std::os::unix::ffi::OsStrExt; -use std::mem::{MaybeUninit, size_of}; -use std::os::unix::io::{RawFd,AsRawFd,FromRawFd}; -use std::ptr; +use crate::errno::Errno; use crate::unistd::read; -use crate::Result; use crate::NixPath; -use crate::errno::Errno; +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::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, 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; } } @@ -72,16 +92,18 @@ libc_bitflags! { 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)] +#[derive(Debug)] pub struct Inotify { - fd: RawFd + fd: OwnedFd, } /// This object is returned when you create a new watch on an inotify instance. @@ -89,7 +111,7 @@ pub struct Inotify { /// know which watch triggered which event. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct WatchDescriptor { - wd: i32 + wd: i32, } /// A single inotify event. @@ -109,7 +131,7 @@ pub struct InotifyEvent { pub cookie: u32, /// Filename. This field exists only if the event was triggered for a file /// inside the watched directory. - pub name: Option + pub name: Option, } impl Inotify { @@ -119,11 +141,9 @@ impl Inotify { /// /// 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()) - }); + let res = Errno::result(unsafe { libc::inotify_init1(flags.bits()) }); - res.map(|fd| Inotify { fd }) + res.map(|fd| Inotify { fd: unsafe { OwnedFd::from_raw_fd(fd) } }) } /// Adds a new watch on the target file or directory. @@ -131,15 +151,13 @@ impl Inotify { /// 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()) - } + pub fn add_watch( + &self, + path: &P, + mask: AddWatchFlags, + ) -> Result { + let res = path.with_nix_path(|cstr| unsafe { + libc::inotify_add_watch(self.fd.as_raw_fd(), cstr.as_ptr(), mask.bits()) })?; Errno::result(res).map(|wd| WatchDescriptor { wd }) @@ -151,16 +169,15 @@ impl Inotify { /// 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). - #[cfg(target_os = "linux")] - pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> { - let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd) }; - - Errno::result(res).map(drop) - } - - #[cfg(target_os = "android")] - pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> { - let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd as u32) }; + 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.as_raw_fd(), arg) }; Errno::result(res).map(drop) } @@ -171,14 +188,14 @@ impl Inotify { /// /// 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> { + 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)?; + let nread = read(self.fd.as_raw_fd(), &mut buffer)?; while (nread - offset) >= header_size { let event = unsafe { @@ -186,7 +203,7 @@ impl Inotify { ptr::copy_nonoverlapping( buffer.as_ptr().add(offset), event.as_mut_ptr() as *mut u8, - (BUFSIZ - offset).min(header_size) + (BUFSIZ - offset).min(header_size), ); event.assume_init() }; @@ -195,9 +212,7 @@ impl Inotify { 0 => None, _ => { let ptr = unsafe { - buffer - .as_ptr() - .add(offset + header_size) + buffer.as_ptr().add(offset + header_size) as *const c_char }; let cstr = unsafe { CStr::from_ptr(ptr) }; @@ -210,7 +225,7 @@ impl Inotify { wd: WatchDescriptor { wd: event.wd }, mask: AddWatchFlags::from_bits_truncate(event.mask), cookie: event.cookie, - name + name, }); offset += header_size + event.len as usize; @@ -220,14 +235,14 @@ impl Inotify { } } -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: OwnedFd::from_raw_fd(fd) } } } -impl FromRawFd for Inotify { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Inotify { fd } +impl AsFd for Inotify { + fn as_fd(&'_ self) -> BorrowedFd<'_> { + self.fd.as_fd() } } diff --git a/src/sys/ioctl/bsd.rs b/src/sys/ioctl/bsd.rs index 4ce4d332a8..307994cb96 100644 --- a/src/sys/ioctl/bsd.rs +++ b/src/sys/ioctl/bsd.rs @@ -21,7 +21,7 @@ mod consts { #[allow(overflowing_literals)] pub const IN: ioctl_num_type = 0x8000_0000; #[doc(hidden)] - pub const INOUT: ioctl_num_type = IN|OUT; + pub const INOUT: ioctl_num_type = IN | OUT; #[doc(hidden)] pub const IOCPARM_MASK: ioctl_num_type = 0x1fff; } @@ -31,9 +31,14 @@ 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) - ) + ($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. @@ -53,7 +58,9 @@ macro_rules! ioc { /// ``` #[macro_export(local_inner_macros)] macro_rules! request_code_none { - ($g:expr, $n:expr) => (ioc!($crate::sys::ioctl::VOID, $g, $n, 0)) + ($g:expr, $n:expr) => { + ioc!($crate::sys::ioctl::VOID, $g, $n, 0) + }; } /// Generate an ioctl request code for a command that passes an integer @@ -64,7 +71,14 @@ macro_rules! request_code_none { /// 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>())) + ($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. @@ -79,7 +93,9 @@ macro_rules! request_code_write_int { /// 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)) + ($g:expr, $n:expr, $len:expr) => { + ioc!($crate::sys::ioctl::OUT, $g, $n, $len) + }; } /// Generate an ioctl request code for a command that writes. @@ -94,7 +110,9 @@ macro_rules! request_code_read { /// 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)) + ($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. @@ -105,5 +123,7 @@ macro_rules! request_code_write { /// 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)) + ($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 index 68ebaba9bf..610b8ddac0 100644 --- a/src/sys/ioctl/linux.rs +++ b/src/sys/ioctl/linux.rs @@ -1,3 +1,5 @@ +use cfg_if::cfg_if; + /// The datatype used for the ioctl number #[cfg(any(target_os = "android", target_env = "musl"))] #[doc(hidden)] @@ -14,38 +16,41 @@ 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 = "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; +cfg_if! { + if #[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; + } + } else { + // "Generic" ioctl protocol + 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::*; @@ -72,11 +77,20 @@ pub const DIRMASK: ioctl_num_type = (1 << DIRBITS) - 1; #[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)) + ($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. @@ -96,7 +110,9 @@ macro_rules! ioc { /// ``` #[macro_export(local_inner_macros)] macro_rules! request_code_none { - ($ty:expr, $nr:expr) => (ioc!($crate::sys::ioctl::NONE, $ty, $nr, 0)) + ($ty:expr, $nr:expr) => { + ioc!($crate::sys::ioctl::NONE, $ty, $nr, 0) + }; } /// Generate an ioctl request code for a command that reads. @@ -111,7 +127,9 @@ macro_rules! request_code_none { /// 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)) + ($ty:expr, $nr:expr, $sz:expr) => { + ioc!($crate::sys::ioctl::READ, $ty, $nr, $sz) + }; } /// Generate an ioctl request code for a command that writes. @@ -126,7 +144,9 @@ macro_rules! request_code_read { /// 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)) + ($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. @@ -137,5 +157,12 @@ macro_rules! request_code_write { /// 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)) + ($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 index 203b7d06f3..0b3fe3e769 100644 --- a/src/sys/ioctl/mod.rs +++ b/src/sys/ioctl/mod.rs @@ -227,37 +227,45 @@ use cfg_if::cfg_if; #[macro_use] mod linux; -#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] +#[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 = "openbsd"))] +#[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 = "openbsd"))] +#[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) - } - ); + ($w:expr) => {{ + $crate::errno::Errno::result($w) + }}; } /// Generates a wrapper function for an ioctl that passes no data to the kernel. @@ -489,7 +497,7 @@ macro_rules! ioctl_write_ptr_bad { ) } -cfg_if!{ +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. /// @@ -704,7 +712,7 @@ macro_rules! ioctl_read_buf { 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)) + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, ::std::mem::size_of_val(data)) as $crate::sys::ioctl::ioctl_num_type, data)) } ) } @@ -743,7 +751,7 @@ macro_rules! ioctl_write_buf { 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)) + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of_val(data)) as $crate::sys::ioctl::ioctl_num_type, data)) } ) } @@ -772,7 +780,7 @@ macro_rules! ioctl_readwrite_buf { 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)) + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, ::std::mem::size_of_val(data)) as $crate::sys::ioctl::ioctl_num_type, data)) } ) } diff --git a/src/sys/memfd.rs b/src/sys/memfd.rs index 642676b431..516ffd3262 100644 --- a/src/sys/memfd.rs +++ b/src/sys/memfd.rs @@ -1,8 +1,10 @@ //! Interfaces for managing memory-backed files. -use std::os::unix::io::RawFd; -use crate::Result; +use cfg_if::cfg_if; +use std::os::unix::io::{FromRawFd, OwnedFd, RawFd}; + use crate::errno::Errno; +use crate::Result; use std::ffi::CStr; libc_bitflags!( @@ -38,10 +40,26 @@ libc_bitflags!( /// 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 { +#[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined +pub fn memfd_create(name: &CStr, flags: MemFdCreateFlag) -> Result { let res = unsafe { - libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags.bits()) + 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) + Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r as RawFd) }) } diff --git a/src/sys/mman.rs b/src/sys/mman.rs index 0ef1ca8a4f..8cfd6d6d54 100644 --- a/src/sys/mman.rs +++ b/src/sys/mman.rs @@ -1,17 +1,16 @@ //! Memory management declarations. -use crate::Result; -#[cfg(not(target_os = "android"))] -use crate::NixPath; use crate::errno::Errno; #[cfg(not(target_os = "android"))] -use crate::fcntl::OFlag; -use libc::{self, c_int, c_void, size_t, off_t}; +use crate::NixPath; +use crate::Result; #[cfg(not(target_os = "android"))] -use crate::sys::stat::Mode; -use std::os::unix::io::RawFd; +#[cfg(feature = "fs")] +use crate::{fcntl::OFlag, sys::stat::Mode}; +use libc::{self, c_int, c_void, off_t, size_t}; +use std::{num::NonZeroUsize, os::unix::io::{AsRawFd, AsFd}}; -libc_bitflags!{ +libc_bitflags! { /// Desired memory protection of a memory mapping. pub struct ProtFlags: c_int { /// Pages cannot be accessed. @@ -24,14 +23,16 @@ libc_bitflags!{ 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!{ +libc_bitflags! { /// Additional parameters for [`mmap`]. pub struct MapFlags: c_int { /// Compatibility flag. Ignored. @@ -42,9 +43,14 @@ libc_bitflags!{ 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; @@ -55,122 +61,156 @@ libc_bitflags!{ 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(not(any(target_os = "dragonfly", target_os = "freebsd", target_os = "aix")))] + #[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!{ +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; - /// Permits to use the old and new address as hints to relocate the mapping. + /// 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!{ +libc_enum! { /// Usage information for a range of memory to allow for performance optimizations by the kernel. /// /// Used by [`madvise`]. @@ -189,23 +229,29 @@ libc_enum!{ 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", @@ -221,51 +267,73 @@ libc_enum!{ 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. + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] 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!{ +libc_bitflags! { /// Configuration flags for [`msync`]. pub struct MsFlags: c_int { /// Schedule an update but return immediately. @@ -274,16 +342,19 @@ libc_bitflags!{ 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; } } -libc_bitflags!{ +#[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. @@ -325,6 +396,7 @@ pub unsafe fn munlock(addr: *const c_void, length: size_t) -> Result<()> { /// 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) } @@ -334,6 +406,7 @@ pub fn mlockall(flags: MlockAllFlags) -> Result<()> { /// 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) } @@ -345,8 +418,20 @@ pub fn munlockall() -> Result<()> { /// 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: *mut c_void, length: size_t, prot: ProtFlags, flags: MapFlags, fd: RawFd, offset: off_t) -> Result<*mut c_void> { - let ret = libc::mmap(addr, length, prot.bits(), flags.bits(), fd, offset); +pub unsafe fn mmap( + addr: Option, + length: NonZeroUsize, + prot: ProtFlags, + flags: MapFlags, + f: Option, + 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 fd = f.map(|f| f.as_fd().as_raw_fd()).unwrap_or(-1); + let ret = + libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset); if ret == libc::MAP_FAILED { Err(Errno::last()) @@ -368,10 +453,16 @@ pub unsafe fn mremap( old_size: size_t, new_size: size_t, flags: MRemapFlags, - new_address: Option<* mut c_void>, + 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())); + 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, @@ -379,7 +470,7 @@ pub unsafe fn mremap( new_address.unwrap_or(std::ptr::null_mut()), new_size, flags.bits(), - ); + ); if ret == libc::MAP_FAILED { Err(Errno::last()) @@ -408,7 +499,11 @@ pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> { /// [`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<()> { +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) } @@ -426,10 +521,12 @@ pub unsafe fn madvise(addr: *mut c_void, length: size_t, advise: MmapAdvise) -> /// # use nix::libc::size_t; /// # use nix::sys::mman::{mmap, mprotect, MapFlags, ProtFlags}; /// # use std::ptr; +/// # use std::os::unix::io::BorrowedFd; /// 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(ptr::null_mut(), ONE_K, ProtFlags::PROT_NONE, -/// MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, -1, 0).unwrap(); +/// let mem = mmap::(None, one_k_non_zero, ProtFlags::PROT_NONE, +/// MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, None, 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) /// }; @@ -437,7 +534,11 @@ pub unsafe fn madvise(addr: *mut c_void, length: size_t, advise: MmapAdvise) -> /// slice[0] = 0xFF; /// assert_eq!(slice[0], 0xFF); /// ``` -pub unsafe fn mprotect(addr: *mut c_void, length: size_t, prot: ProtFlags) -> Result<()> { +pub unsafe fn mprotect( + addr: *mut c_void, + length: size_t, + prot: ProtFlags, +) -> Result<()> { Errno::result(libc::mprotect(addr, length, prot.bits())).map(drop) } @@ -449,17 +550,31 @@ pub unsafe fn mprotect(addr: *mut c_void, length: size_t, prot: ProtFlags) -> Re /// 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<()> { +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 -#[cfg(not(target_os = "android"))] -pub fn shm_open(name: &P, flag: OFlag, mode: Mode) -> Result { +pub fn shm_open

    ( + name: &P, + flag: OFlag, + mode: Mode + ) -> Result + where P: ?Sized + NixPath +{ + use std::os::unix::io::{FromRawFd, OwnedFd}; + let ret = name.with_nix_path(|cstr| { #[cfg(any(target_os = "macos", target_os = "ios"))] unsafe { @@ -471,7 +586,11 @@ pub fn shm_open(name: &P, flag: OFlag, mode: Mode) -> Resul } })?; - Errno::result(ret) + match ret { + -1 => Err(Errno::last()), + fd => Ok(unsafe{ OwnedFd::from_raw_fd(fd) }) + } +} } /// Performs the converse of [`shm_open`], removing an object previously created. @@ -481,9 +600,8 @@ pub fn shm_open(name: &P, flag: OFlag, mode: Mode) -> Resul /// [`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()) } - })?; + 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 index 156b0d9d1c..bf047b3dda 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -1,130 +1,231 @@ //! Mostly platform-specific functionality -#[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd"))] -pub mod aio; - -#[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(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"))] + 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(target_os = "linux")] -pub mod memfd; +#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +feature! { + #![feature = "fs"] + pub mod memfd; +} #[cfg(not(target_os = "redox"))] -#[allow(missing_docs)] -pub mod mman; +feature! { + #![feature = "mman"] + pub mod mman; +} #[cfg(target_os = "linux")] -#[allow(missing_docs)] -pub mod personality; - -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"))] -#[allow(missing_docs)] -pub mod ptrace; +feature! { + #![feature = "personality"] + pub mod personality; +} #[cfg(target_os = "linux")] -pub mod quota; - -#[cfg(any(target_os = "linux"))] -#[allow(missing_docs)] -pub mod reboot; - -#[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "illumos")))] -pub mod resource; +feature! { + #![feature = "process"] + pub mod prctl; +} + +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(not(target_os = "redox"))] -pub mod select; +#[cfg(target_os = "linux")] +feature! { + #![feature = "quota"] + pub mod quota; +} -#[cfg(any(target_os = "android", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] -pub mod sendfile; +#[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; +} + +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"))] -#[allow(missing_docs)] -pub mod signalfd; - -#[cfg(not(target_os = "redox"))] -#[allow(missing_docs)] -pub mod socket; - -#[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 = "signal"] + #[allow(missing_docs)] + pub mod signalfd; +} + +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" ))] -pub mod statfs; +feature! { + #![feature = "fs"] + pub mod statfs; +} -pub mod statvfs; +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; -#[allow(missing_docs)] -pub mod termios; +feature! { + #![feature = "term"] + #[allow(missing_docs)] + pub mod termios; +} #[allow(missing_docs)] pub mod time; -pub mod uio; +feature! { + #![feature = "uio"] + pub mod uio; +} -pub mod utsname; +feature! { + #![feature = "feature"] + pub mod utsname; +} -pub mod wait; +feature! { + #![feature = "process"] + pub mod wait; +} #[cfg(any(target_os = "android", target_os = "linux"))] -#[allow(missing_docs)] -pub mod inotify; +feature! { + #![feature = "inotify"] + pub mod inotify; +} #[cfg(any(target_os = "android", target_os = "linux"))] -#[allow(missing_docs)] -pub mod timerfd; +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 index b15956c469..30231dd7b8 100644 --- a/src/sys/personality.rs +++ b/src/sys/personality.rs @@ -1,5 +1,6 @@ -use crate::Result; +//! Process execution domains use crate::errno::Errno; +use crate::Result; use libc::{self, c_int, c_ulong}; @@ -7,18 +8,44 @@ 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; - #[cfg(not(target_env = "musl"))] + /// 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; - #[cfg(not(target_env = "musl"))] + /// 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; } } @@ -35,9 +62,7 @@ libc_bitflags! { /// assert!(!pers.contains(Persona::WHOLE_SECONDS)); /// ``` pub fn get() -> Result { - let res = unsafe { - libc::personality(0xFFFFFFFF) - }; + let res = unsafe { libc::personality(0xFFFFFFFF) }; Errno::result(res).map(Persona::from_bits_truncate) } @@ -55,16 +80,17 @@ pub fn get() -> Result { /// /// Example: /// -/// ``` +// Disable test on aarch64 until we know why it fails. +// https://github.com/nix-rust/nix/issues/2060 +#[cfg_attr(target_arch = "aarch64", doc = " ```no_run")] +#[cfg_attr(not(target_arch = "aarch64"), doc = " ```")] /// # 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); +/// personality::set(pers | Persona::ADDR_NO_RANDOMIZE).unwrap(); /// ``` pub fn set(persona: Persona) -> Result { - let res = unsafe { - libc::personality(persona.bits() as c_ulong) - }; + let res = unsafe { libc::personality(persona.bits() as c_ulong) }; Errno::result(res).map(Persona::from_bits_truncate) } diff --git a/src/sys/prctl.rs b/src/sys/prctl.rs new file mode 100644 index 0000000000..995382cb0c --- /dev/null +++ b/src/sys/prctl.rs @@ -0,0 +1,208 @@ +//! prctl is a Linux-only API for performing operations on a process or thread. +//! +//! Note that careless use of some prctl() operations can confuse the user-space run-time +//! environment, so these operations should be used with care. +//! +//! For more documentation, please read [prctl(2)](https://man7.org/linux/man-pages/man2/prctl.2.html). + +use crate::errno::Errno; +use crate::sys::signal::Signal; +use crate::Result; + +use libc::{c_int, c_ulong}; +use std::convert::TryFrom; +use std::ffi::{CStr, CString}; + +libc_enum! { + /// The type of hardware memory corruption kill policy for the thread. + + #[repr(i32)] + #[non_exhaustive] + #[allow(non_camel_case_types)] + pub enum PrctlMCEKillPolicy { + /// The thread will receive SIGBUS as soon as a memory corruption is detected. + PR_MCE_KILL_EARLY, + /// The process is killed only when it accesses a corrupted page. + PR_MCE_KILL_LATE, + /// Uses the system-wide default. + PR_MCE_KILL_DEFAULT, + } + impl TryFrom +} + +fn prctl_set_bool(option: c_int, status: bool) -> Result<()> { + let res = unsafe { libc::prctl(option, status as c_ulong, 0, 0, 0) }; + Errno::result(res).map(drop) +} + +fn prctl_get_bool(option: c_int) -> Result { + let res = unsafe { libc::prctl(option, 0, 0, 0, 0) }; + Errno::result(res).map(|res| res != 0) +} + +/// Set the "child subreaper" attribute for this process +pub fn set_child_subreaper(attribute: bool) -> Result<()> { + prctl_set_bool(libc::PR_SET_CHILD_SUBREAPER, attribute) +} + +/// Get the "child subreaper" attribute for this process +pub fn get_child_subreaper() -> Result { + // prctl writes into this var + let mut subreaper: c_int = 0; + + let res = unsafe { libc::prctl(libc::PR_GET_CHILD_SUBREAPER, &mut subreaper, 0, 0, 0) }; + + Errno::result(res).map(|_| subreaper != 0) +} + +/// Set the dumpable attribute which determines if core dumps are created for this process. +pub fn set_dumpable(attribute: bool) -> Result<()> { + prctl_set_bool(libc::PR_SET_DUMPABLE, attribute) +} + +/// Get the dumpable attribute for this process. +pub fn get_dumpable() -> Result { + prctl_get_bool(libc::PR_GET_DUMPABLE) +} + +/// Set the "keep capabilities" attribute for this process. This causes the thread to retain +/// capabilities even if it switches its UID to a nonzero value. +pub fn set_keepcaps(attribute: bool) -> Result<()> { + prctl_set_bool(libc::PR_SET_KEEPCAPS, attribute) +} + +/// Get the "keep capabilities" attribute for this process +pub fn get_keepcaps() -> Result { + prctl_get_bool(libc::PR_GET_KEEPCAPS) +} + +/// Clear the thread memory corruption kill policy and use the system-wide default +pub fn clear_mce_kill() -> Result<()> { + let res = unsafe { libc::prctl(libc::PR_MCE_KILL, libc::PR_MCE_KILL_CLEAR, 0, 0, 0) }; + + Errno::result(res).map(drop) +} + +/// Set the thread memory corruption kill policy +pub fn set_mce_kill(policy: PrctlMCEKillPolicy) -> Result<()> { + let res = unsafe { + libc::prctl( + libc::PR_MCE_KILL, + libc::PR_MCE_KILL_SET, + policy as c_ulong, + 0, + 0, + ) + }; + + Errno::result(res).map(drop) +} + +/// Get the thread memory corruption kill policy +pub fn get_mce_kill() -> Result { + let res = unsafe { libc::prctl(libc::PR_MCE_KILL_GET, 0, 0, 0, 0) }; + + match Errno::result(res) { + Ok(val) => Ok(PrctlMCEKillPolicy::try_from(val)?), + Err(e) => Err(e), + } +} + +/// Set the parent-death signal of the calling process. This is the signal that the calling process +/// will get when its parent dies. +pub fn set_pdeathsig>>(signal: T) -> Result<()> { + let sig = match signal.into() { + Some(s) => s as c_int, + None => 0, + }; + + let res = unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, sig, 0, 0, 0) }; + + Errno::result(res).map(drop) +} + +/// Returns the current parent-death signal +pub fn get_pdeathsig() -> Result> { + // prctl writes into this var + let mut sig: c_int = 0; + + let res = unsafe { libc::prctl(libc::PR_GET_PDEATHSIG, &mut sig, 0, 0, 0) }; + + match Errno::result(res) { + Ok(_) => Ok(match sig { + 0 => None, + _ => Some(Signal::try_from(sig)?), + }), + Err(e) => Err(e), + } +} + +/// Set the name of the calling thread. Strings longer than 15 bytes will be truncated. +pub fn set_name(name: &CStr) -> Result<()> { + let res = unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr(), 0, 0, 0) }; + + Errno::result(res).map(drop) +} + +/// Return the name of the calling thread +pub fn get_name() -> Result { + // Size of buffer determined by linux/sched.h TASK_COMM_LEN + let buf = [0u8; 16]; + + let res = unsafe { libc::prctl(libc::PR_GET_NAME, &buf, 0, 0, 0) }; + + let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len()); + let name = CStr::from_bytes_with_nul(&buf[..=len]).map_err(|_| Errno::EINVAL)?; + + Errno::result(res).map(|_| name.to_owned()) +} + +/// Sets the timer slack value for the calling thread. Timer slack is used by the kernel to group +/// timer expirations and make them the supplied amount of nanoseconds late. +pub fn set_timerslack(ns: u64) -> Result<()> { + let res = unsafe { libc::prctl(libc::PR_SET_TIMERSLACK, ns, 0, 0, 0) }; + + Errno::result(res).map(drop) +} + +/// Get the timerslack for the calling thread. +pub fn get_timerslack() -> Result { + let res = unsafe { libc::prctl(libc::PR_GET_TIMERSLACK, 0, 0, 0, 0) }; + + Errno::result(res) +} + +/// Disable all performance counters attached to the calling process. +pub fn task_perf_events_disable() -> Result<()> { + let res = unsafe { libc::prctl(libc::PR_TASK_PERF_EVENTS_DISABLE, 0, 0, 0, 0) }; + + Errno::result(res).map(drop) +} + +/// Enable all performance counters attached to the calling process. +pub fn task_perf_events_enable() -> Result<()> { + let res = unsafe { libc::prctl(libc::PR_TASK_PERF_EVENTS_ENABLE, 0, 0, 0, 0) }; + + Errno::result(res).map(drop) +} + +/// Set the calling threads "no new privs" attribute. Once set this option can not be unset. +pub fn set_no_new_privs() -> Result<()> { + prctl_set_bool(libc::PR_SET_NO_NEW_PRIVS, true) // Cannot be unset +} + +/// Get the "no new privs" attribute for the calling thread. +pub fn get_no_new_privs() -> Result { + prctl_get_bool(libc::PR_GET_NO_NEW_PRIVS) +} + +/// Set the state of the "THP disable" flag for the calling thread. Setting this disables +/// transparent huge pages. +pub fn set_thp_disable(flag: bool) -> Result<()> { + prctl_set_bool(libc::PR_SET_THP_DISABLE, flag) +} + +/// Get the "THP disable" flag for the calling thread. +pub fn get_thp_disable() -> Result { + prctl_get_bool(libc::PR_GET_THP_DISABLE) +} diff --git a/src/sys/pthread.rs b/src/sys/pthread.rs index d42e45d13d..6bad03a4d4 100644 --- a/src/sys/pthread.rs +++ b/src/sys/pthread.rs @@ -4,8 +4,6 @@ use crate::errno::Errno; #[cfg(not(target_os = "redox"))] use crate::Result; -#[cfg(not(target_os = "redox"))] -use crate::sys::signal::Signal; use libc::{self, pthread_t}; /// Identifies an individual thread. @@ -21,14 +19,20 @@ 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<()> { +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, @@ -36,3 +40,4 @@ pub fn pthread_kill>>(thread: Pthread, signal: T) -> Resu 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 index a62881ef34..ba267c6577 100644 --- a/src/sys/ptrace/bsd.rs +++ b/src/sys/ptrace/bsd.rs @@ -1,16 +1,16 @@ -use cfg_if::cfg_if; use crate::errno::Errno; -use libc::{self, c_int}; -use std::ptr; 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", + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", target_os = "macos", target_os = "openbsd"))] { #[doc(hidden)] @@ -30,10 +30,12 @@ libc_enum! { 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, @@ -47,10 +49,13 @@ libc_enum! { 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 } } @@ -66,7 +71,8 @@ unsafe fn ptrace_other( libc::pid_t::from(pid), addr, data, - )).map(|_| 0) + )) + .map(|_| 0) } /// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)` @@ -74,14 +80,19 @@ unsafe fn ptrace_other( /// 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) } + 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) } + unsafe { + ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) + } } /// Detaches the current running process, as with `ptrace(PT_DETACH, ...)` @@ -109,13 +120,14 @@ pub fn cont>>(pid: Pid, sig: T) -> Result<()> { }; unsafe { // Ignore the useless return value - ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(drop) + 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);` +/// 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) @@ -144,24 +156,28 @@ pub fn kill(pid: Pid) -> Result<()> { /// _ => {}, /// } /// ``` -#[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") - ) +#[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) } + 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 @@ -171,6 +187,9 @@ pub fn read(pid: Pid, addr: AddressType) -> Result { } /// 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 index 37236790b4..8c134cf7ee 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -1,26 +1,31 @@ //! For detailed description of the ptrace requests, consult `man ptrace`. -use cfg_if::cfg_if; -use std::{mem, ptr}; -use crate::Result; use crate::errno::Errno; -use libc::{self, c_void, c_long, siginfo_t}; -use crate::unistd::Pid; 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")) + 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")))] { + all(target_os = "linux", target_env = "gnu"), + target_env = "uclibc"))] { #[doc(hidden)] pub type RequestType = ::libc::c_uint; } else { @@ -29,9 +34,9 @@ cfg_if! { } } -libc_enum!{ - #[cfg_attr(not(any(target_env = "musl", target_os = "android")), repr(u32))] - #[cfg_attr(any(target_env = "musl", target_os = "android"), repr(i32))] +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 { @@ -99,8 +104,10 @@ libc_enum!{ 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"))))] @@ -117,7 +124,7 @@ libc_enum!{ } } -libc_enum!{ +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 @@ -171,12 +178,16 @@ libc_bitflags! { 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. - #[cfg(any(target_os = "android", target_os = "linux"))] PTRACE_O_EXITKILL; } } -fn ptrace_peek(request: Request, pid: Pid, addr: AddressType, data: *mut c_void) -> Result { +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) @@ -190,9 +201,13 @@ fn ptrace_peek(request: Request, pid: Pid, addr: AddressType, data: *mut c_void) /// 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")) + 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) @@ -201,16 +216,22 @@ pub fn getregs(pid: Pid) -> Result { /// 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")) + 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) + 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) } @@ -222,48 +243,65 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { 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) + 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() }) + 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) +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,...)`. +/// 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) + 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,...)` +/// 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,...)` +/// 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,...)` +/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO, ...)` pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { - let ret = unsafe{ + 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) + 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(()), @@ -282,7 +320,8 @@ pub fn traceme() -> Result<()> { Pid::from_raw(0), ptr::null_mut(), ptr::null_mut(), - ).map(drop) // ignore the useless return value + ) + .map(drop) // ignore the useless return value } } @@ -296,12 +335,8 @@ pub fn syscall>>(pid: Pid, sig: T) -> Result<()> { None => ptr::null_mut(), }; unsafe { - ptrace_other( - Request::PTRACE_SYSCALL, - pid, - ptr::null_mut(), - data, - ).map(drop) // ignore the useless return value + ptrace_other(Request::PTRACE_SYSCALL, pid, ptr::null_mut(), data) + .map(drop) // ignore the useless return value } } @@ -310,14 +345,19 @@ pub fn syscall>>(pid: Pid, sig: T) -> Result<()> { /// 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")))] +#[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) + ptrace_other(Request::PTRACE_SYSEMU, pid, ptr::null_mut(), data) + .map(drop) // ignore the useless return value } } @@ -332,7 +372,8 @@ pub fn attach(pid: Pid) -> Result<()> { pid, ptr::null_mut(), ptr::null_mut(), - ).map(drop) // ignore the useless return value + ) + .map(drop) // ignore the useless return value } } @@ -340,6 +381,7 @@ pub fn attach(pid: Pid) -> Result<()> { /// /// 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( @@ -347,7 +389,8 @@ pub fn seize(pid: Pid, options: Options) -> Result<()> { pid, ptr::null_mut(), options.bits() as *mut c_void, - ).map(drop) // ignore the useless return value + ) + .map(drop) // ignore the useless return value } } @@ -361,12 +404,8 @@ pub fn detach>>(pid: Pid, sig: T) -> Result<()> { None => ptr::null_mut(), }; unsafe { - ptrace_other( - Request::PTRACE_DETACH, - pid, - ptr::null_mut(), - data - ).map(drop) + ptrace_other(Request::PTRACE_DETACH, pid, ptr::null_mut(), data) + .map(drop) } } @@ -380,7 +419,8 @@ pub fn cont>>(pid: Pid, sig: T) -> Result<()> { None => ptr::null_mut(), }; unsafe { - ptrace_other(Request::PTRACE_CONT, pid, ptr::null_mut(), data).map(drop) // ignore the useless return value + ptrace_other(Request::PTRACE_CONT, pid, ptr::null_mut(), data).map(drop) + // ignore the useless return value } } @@ -388,9 +428,16 @@ pub fn cont>>(pid: Pid, sig: T) -> Result<()> { /// /// 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) + ptrace_other( + Request::PTRACE_INTERRUPT, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) } } @@ -399,7 +446,13 @@ pub fn interrupt(pid: Pid) -> Result<()> { /// 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) + ptrace_other( + Request::PTRACE_KILL, + pid, + ptr::null_mut(), + ptr::null_mut(), + ) + .map(drop) } } @@ -432,7 +485,8 @@ pub fn step>>(pid: Pid, sig: T) -> Result<()> { None => ptr::null_mut(), }; unsafe { - ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data).map(drop) + ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data) + .map(drop) } } @@ -442,7 +496,11 @@ pub fn step>>(pid: Pid, sig: T) -> Result<()> { /// 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")))] +#[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, @@ -459,12 +517,14 @@ pub fn sysemu_step>>(pid: Pid, sig: T) -> Result<()> { } } -/// Reads a word from a processes memory at the given address +/// Reads a word from a processes memory at the given address, as with +/// ptrace(PTRACE_PEEKDATA, ...) 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 +/// Writes a word into the processes memory at the given address, as with +/// ptrace(PTRACE_POKEDATA, ...) /// /// # Safety /// @@ -473,7 +533,28 @@ pub fn read(pid: Pid, addr: AddressType) -> Result { pub unsafe fn write( pid: Pid, addr: AddressType, - data: *mut c_void) -> Result<()> -{ + data: *mut c_void, +) -> Result<()> { ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(drop) } + +/// Reads a word from a user area at `offset`, as with ptrace(PTRACE_PEEKUSER, ...). +/// 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`, as with ptrace(PTRACE_POKEUSER, ...). +/// 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 index 782c30409b..88648acabc 100644 --- a/src/sys/ptrace/mod.rs +++ b/src/sys/ptrace/mod.rs @@ -1,4 +1,4 @@ -///! Provides helpers for making ptrace system calls +//! Provides helpers for making ptrace system calls #[cfg(any(target_os = "android", target_os = "linux"))] mod linux; @@ -6,17 +6,20 @@ 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(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" - ))] +#[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 index 6e34e38d2b..a32d07aa1e 100644 --- a/src/sys/quota.rs +++ b/src/sys/quota.rs @@ -6,29 +6,28 @@ //! //! ```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"); +//! 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); +//! 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}; -use libc::{self, c_int, c_char}; -use crate::{Result, NixPath}; -use crate::errno::Errno; 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) } + libc::QCMD(self.0 as i32, self.1 as i32) } } // linux quota version >= 2 -libc_enum!{ +libc_enum! { #[repr(i32)] enum QuotaSubCmd { Q_SYNC, @@ -39,7 +38,7 @@ libc_enum!{ } } -libc_enum!{ +libc_enum! { /// The scope of the quota. #[repr(i32)] #[non_exhaustive] @@ -51,7 +50,7 @@ libc_enum!{ } } -libc_enum!{ +libc_enum! { /// The type of quota format to use. #[repr(i32)] #[non_exhaustive] @@ -120,7 +119,8 @@ impl Default for Dqblk { 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); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) { Some(self.0.dqb_bhardlimit) } else { @@ -135,7 +135,8 @@ impl Dqblk { /// Preferred limit on disk quota blocks pub fn blocks_soft_limit(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) { Some(self.0.dqb_bsoftlimit) } else { @@ -150,7 +151,8 @@ impl Dqblk { /// Current occupied space (bytes). pub fn occupied_space(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_SPACE) { Some(self.0.dqb_curspace) } else { @@ -160,7 +162,8 @@ impl Dqblk { /// Maximum number of allocated inodes. pub fn inodes_hard_limit(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) { Some(self.0.dqb_ihardlimit) } else { @@ -175,7 +178,8 @@ impl Dqblk { /// Preferred inode limit pub fn inodes_soft_limit(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) { Some(self.0.dqb_isoftlimit) } else { @@ -190,7 +194,8 @@ impl Dqblk { /// Current number of allocated inodes. pub fn allocated_inodes(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_INODES) { Some(self.0.dqb_curinodes) } else { @@ -200,7 +205,8 @@ impl Dqblk { /// Time limit for excessive disk use. pub fn block_time_limit(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_BTIME) { Some(self.0.dqb_btime) } else { @@ -215,7 +221,8 @@ impl Dqblk { /// Time limit for excessive files. pub fn inode_time_limit(&self) -> Option { - let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); + let valid_fields = + QuotaValidFlags::from_bits_truncate(self.0.dqb_valid); if valid_fields.contains(QuotaValidFlags::QIF_ITIME) { Some(self.0.dqb_itime) } else { @@ -229,11 +236,18 @@ impl Dqblk { } } -fn quotactl(cmd: QuotaCmd, special: Option<&P>, id: c_int, addr: *mut c_char) -> Result<()> { +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)), + 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)), }?; @@ -242,36 +256,82 @@ fn quotactl(cmd: QuotaCmd, special: Option<&P>, id: c_int, } /// Turn on disk quotas for a block device. -pub fn quotactl_on(which: QuotaType, special: &P, format: QuotaFmt, quota_file: &P) -> Result<()> { +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) + 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()) +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()) +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 { +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())}) + 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<()> { +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) + 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 index 46ab68b632..02d98162bd 100644 --- a/src/sys/reboot.rs +++ b/src/sys/reboot.rs @@ -1,7 +1,7 @@ //! Reboot/shutdown or enable/disable Ctrl-Alt-Delete. -use crate::Result; use crate::errno::Errno; +use crate::Result; use std::convert::Infallible; use std::mem::drop; @@ -13,19 +13,24 @@ libc_enum! { #[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, + // 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) - }; + unsafe { libc::reboot(how as libc::c_int) }; Err(Errno::last()) } @@ -38,8 +43,6 @@ pub fn set_cad_enabled(enable: bool) -> Result<()> { } else { libc::RB_DISABLE_CAD }; - let res = unsafe { - libc::reboot(cmd) - }; + let res = unsafe { libc::reboot(cmd) }; Errno::result(res).map(drop) } diff --git a/src/sys/resource.rs b/src/sys/resource.rs index f3bfb67194..f42d32e3ca 100644 --- a/src/sys/resource.rs +++ b/src/sys/resource.rs @@ -1,15 +1,18 @@ //! 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", target_env = "gnu"))]{ - use libc::{__rlimit_resource_t, rlimit, RLIM_INFINITY}; - }else if #[cfg(any( + 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", @@ -17,26 +20,30 @@ cfg_if! { target_os = "ios", target_os = "android", target_os = "dragonfly", + target_os = "aix", all(target_os = "linux", not(target_env = "gnu")) ))]{ - use libc::{c_int, rlimit, RLIM_INFINITY}; + 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 has been provided for - /// earier reference (non-exhaustive). + /// 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", target_env = "gnu"), repr(u32))] + #[cfg_attr(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), repr(u32))] #[cfg_attr(any( target_os = "freebsd", target_os = "openbsd", @@ -45,15 +52,13 @@ libc_enum! { target_os = "ios", target_os = "android", target_os = "dragonfly", - all(target_os = "linux", not(target_env = "gnu")) + target_os = "aix", + 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(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, @@ -72,69 +77,102 @@ libc_enum! { 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"))] + #[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 = "openbsd", target_os = "linux"))] + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "linux", + target_os = "aix", + ))] + #[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 = "openbsd", target_os = "linux"))] + #[cfg(any(target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "linux", + target_os = "aix", + ))] + #[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, } @@ -142,8 +180,8 @@ libc_enum! { /// Get the current processes resource limits /// -/// A value of `None` indicates the value equals to `RLIM_INFINITY` which means -/// there is no limit. +/// The special value [`RLIM_INFINITY`] indicates that no limit will be +/// enforced. /// /// # Parameters /// @@ -155,8 +193,8 @@ libc_enum! { /// # 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); +/// println!("current soft_limit: {}", soft_limit); +/// println!("current hard_limit: {}", hard_limit); /// ``` /// /// # References @@ -164,20 +202,20 @@ libc_enum! { /// [getrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215) /// /// [`Resource`]: enum.Resource.html -pub fn getrlimit(resource: Resource) -> Result<(Option, Option)> { +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", target_env = "gnu"))]{ + 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{ + } 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() }; - (Some(rlim_cur), Some(rlim_max)) + (rlim_cur, rlim_max) }) } @@ -187,21 +225,20 @@ pub fn getrlimit(resource: Resource) -> Result<(Option, Option)> /// /// * `resource`: The [`Resource`] that we want to set the limits of. /// * `soft_limit`: The value that the kernel enforces for the corresponding -/// resource. Note: `None` input will be replaced by constant `RLIM_INFINITY`. +/// resource. /// * `hard_limit`: The ceiling for the soft limit. Must be lower or equal to -/// the current hard limit for non-root users. Note: `None` input will be -/// replaced by constant `RLIM_INFINITY`. +/// the current hard limit for non-root users. /// -/// > Note: for some os (linux_gnu), setting hard_limit to `RLIM_INFINITY` can -/// > results `EPERM` Error. So you will need to set the number explicitly. +/// The special value [`RLIM_INFINITY`] indicates that no limit will be +/// enforced. /// /// # Examples /// /// ``` /// # use nix::sys::resource::{setrlimit, Resource}; /// -/// let soft_limit = Some(512); -/// let hard_limit = Some(1024); +/// let soft_limit = 512; +/// let hard_limit = 1024; /// setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); /// ``` /// @@ -214,15 +251,15 @@ pub fn getrlimit(resource: Resource) -> Result<(Option, Option)> /// Note: `setrlimit` provides a safe wrapper to libc's `setrlimit`. pub fn setrlimit( resource: Resource, - soft_limit: Option, - hard_limit: Option, + soft_limit: rlim_t, + hard_limit: rlim_t, ) -> Result<()> { let new_rlim = rlimit { - rlim_cur: soft_limit.unwrap_or(RLIM_INFINITY), - rlim_max: hard_limit.unwrap_or(RLIM_INFINITY), + rlim_cur: soft_limit, + rlim_max: hard_limit, }; cfg_if! { - if #[cfg(all(target_os = "linux", target_env = "gnu"))]{ + 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) }; @@ -231,3 +268,180 @@ pub fn setrlimit( 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 index 4d7576a58a..0e2193b130 100644 --- a/src/sys/select.rs +++ b/src/sys/select.rs @@ -1,22 +1,24 @@ //! 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::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::ptr::{null, null_mut}; -use libc::{self, c_int}; -use crate::Result; -use crate::errno::Errno; -use crate::sys::signal::SigSet; -use crate::sys::time::{TimeSpec, TimeVal}; 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); +pub struct FdSet<'fd> { + set: libc::fd_set, + _fd: std::marker::PhantomData>, +} fn assert_fd_valid(fd: RawFd) { assert!( @@ -25,37 +27,40 @@ fn assert_fd_valid(fd: RawFd) { ); } -impl FdSet { +impl<'fd> FdSet<'fd> { /// Create an empty `FdSet` - pub fn new() -> FdSet { + pub fn new() -> FdSet<'fd> { let mut fdset = mem::MaybeUninit::uninit(); unsafe { libc::FD_ZERO(fdset.as_mut_ptr()); - FdSet(fdset.assume_init()) + Self { + set: fdset.assume_init(), + _fd: std::marker::PhantomData, + } } } /// 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) }; + pub fn insert(&mut self, fd: &'fd Fd) { + assert_fd_valid(fd.as_fd().as_raw_fd()); + unsafe { libc::FD_SET(fd.as_fd().as_raw_fd(), &mut self.set) }; } /// 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) }; + pub fn remove(&mut self, fd: &'fd Fd) { + assert_fd_valid(fd.as_fd().as_raw_fd()); + unsafe { libc::FD_CLR(fd.as_fd().as_raw_fd(), &mut self.set) }; } /// 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) } + pub fn contains(&self, fd: &'fd Fd) -> bool { + assert_fd_valid(fd.as_fd().as_raw_fd()); + unsafe { libc::FD_ISSET(fd.as_fd().as_raw_fd(), &self.set) } } /// Remove all file descriptors from this `FdSet`. pub fn clear(&mut self) { - unsafe { libc::FD_ZERO(&mut self.0) }; + unsafe { libc::FD_ZERO(&mut self.set) }; } /// Finds the highest file descriptor in the set. @@ -67,15 +72,18 @@ impl FdSet { /// # Example /// /// ``` + /// # use std::os::unix::io::{AsRawFd, BorrowedFd}; /// # use nix::sys::select::FdSet; + /// let fd_four = unsafe {BorrowedFd::borrow_raw(4)}; + /// let fd_nine = unsafe {BorrowedFd::borrow_raw(9)}; /// let mut set = FdSet::new(); - /// set.insert(4); - /// set.insert(9); - /// assert_eq!(set.highest(), Some(9)); + /// set.insert(&fd_four); + /// set.insert(&fd_nine); + /// assert_eq!(set.highest().map(|borrowed_fd|borrowed_fd.as_raw_fd()), Some(9)); /// ``` /// /// [`select`]: fn.select.html - pub fn highest(&self) -> Option { + pub fn highest(&self) -> Option> { self.fds(None).next_back() } @@ -89,11 +97,13 @@ impl FdSet { /// /// ``` /// # use nix::sys::select::FdSet; - /// # use std::os::unix::io::RawFd; + /// # use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd}; /// let mut set = FdSet::new(); - /// set.insert(4); - /// set.insert(9); - /// let fds: Vec = set.fds(None).collect(); + /// let fd_four = unsafe {BorrowedFd::borrow_raw(4)}; + /// let fd_nine = unsafe {BorrowedFd::borrow_raw(9)}; + /// set.insert(&fd_four); + /// set.insert(&fd_nine); + /// let fds: Vec = set.fds(None).map(|borrowed_fd|borrowed_fd.as_raw_fd()).collect(); /// assert_eq!(fds, vec![4, 9]); /// ``` #[inline] @@ -105,7 +115,7 @@ impl FdSet { } } -impl Default for FdSet { +impl<'fd> Default for FdSet<'fd> { fn default() -> Self { Self::new() } @@ -113,18 +123,19 @@ impl Default for FdSet { /// Iterator over `FdSet`. #[derive(Debug)] -pub struct Fds<'a> { - set: &'a FdSet, +pub struct Fds<'a, 'fd> { + set: &'a FdSet<'fd>, range: Range, } -impl<'a> Iterator for Fds<'a> { - type Item = RawFd; +impl<'a, 'fd> Iterator for Fds<'a, 'fd> { + type Item = BorrowedFd<'fd>; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { for i in &mut self.range { - if self.set.contains(i as RawFd) { - return Some(i as RawFd); + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + if self.set.contains(&borrowed_i) { + return Some(borrowed_i); } } None @@ -137,19 +148,20 @@ impl<'a> Iterator for Fds<'a> { } } -impl<'a> DoubleEndedIterator for Fds<'a> { +impl<'a, 'fd> DoubleEndedIterator for Fds<'a, 'fd> { #[inline] - fn next_back(&mut self) -> Option { + 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); + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + if self.set.contains(&borrowed_i) { + return Some(borrowed_i); } } None } } -impl<'a> FusedIterator for Fds<'a> {} +impl<'a, 'fd> FusedIterator for Fds<'a, 'fd> {} /// Monitors file descriptors for readiness /// @@ -174,16 +186,19 @@ impl<'a> FusedIterator for Fds<'a> {} /// [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, +pub fn select<'a, 'fd, N, R, W, E, T>( + nfds: N, readfds: R, writefds: W, errorfds: E, - timeout: T) -> Result + timeout: T, +) -> Result where + 'fd: 'a, N: Into>, - R: Into>, - W: Into>, - E: Into>, + R: Into>>, + W: Into>>, + E: Into>>, T: Into>, { let mut readfds = readfds.into(); @@ -192,27 +207,44 @@ where let timeout = timeout.into(); let nfds = nfds.into().unwrap_or_else(|| { - readfds.iter_mut() + readfds + .iter_mut() .chain(writefds.iter_mut()) .chain(errorfds.iter_mut()) - .map(|set| set.highest().unwrap_or(-1)) + .map(|set| { + set.highest() + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .unwrap_or(-1) + }) .max() - .unwrap_or(-1) + 1 + .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) + 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) - }; + 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 @@ -242,17 +274,18 @@ where /// [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, +pub fn pselect<'a, 'fd, N, R, W, E, T, S>(nfds: N, readfds: R, writefds: W, errorfds: E, timeout: T, sigmask: S) -> Result where + 'fd: 'a, N: Into>, - R: Into>, - W: Into>, - E: Into>, + R: Into>>, + W: Into>>, + E: Into>>, T: Into>, S: Into>, { @@ -266,7 +299,7 @@ where readfds.iter_mut() .chain(writefds.iter_mut()) .chain(errorfds.iter_mut()) - .map(|set| set.highest().unwrap_or(-1)) + .map(|set| set.highest().map(|borrowed_fd|borrowed_fd.as_raw_fd()).unwrap_or(-1)) .max() .unwrap_or(-1) + 1 }); @@ -283,26 +316,28 @@ where Errno::result(res) } - +} #[cfg(test)] mod tests { use super::*; - use std::os::unix::io::RawFd; use crate::sys::time::{TimeVal, TimeValLike}; - use crate::unistd::{write, pipe}; + use crate::unistd::{close, pipe, write}; + use std::os::unix::io::{FromRawFd, OwnedFd, RawFd}; #[test] fn fdset_insert() { let mut fd_set = FdSet::new(); for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + assert!(!fd_set.contains(&borrowed_i)); } - fd_set.insert(7); + let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; + fd_set.insert(&fd_seven); - assert!(fd_set.contains(7)); + assert!(fd_set.contains(&fd_seven)); } #[test] @@ -310,102 +345,183 @@ mod tests { let mut fd_set = FdSet::new(); for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + assert!(!fd_set.contains(&borrowed_i)); } - fd_set.insert(7); - fd_set.remove(7); + let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; + fd_set.insert(&fd_seven); + fd_set.remove(&fd_seven); for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + assert!(!fd_set.contains(&borrowed_i)); } } #[test] + #[allow(non_snake_case)] 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); + let fd_one = unsafe { BorrowedFd::borrow_raw(1) }; + let fd_FD_SETSIZE_devided_by_two = + unsafe { BorrowedFd::borrow_raw((FD_SETSIZE / 2) as RawFd) }; + let fd_FD_SETSIZE_minus_one = + unsafe { BorrowedFd::borrow_raw((FD_SETSIZE - 1) as RawFd) }; + fd_set.insert(&fd_one); + fd_set.insert(&fd_FD_SETSIZE_devided_by_two); + fd_set.insert(&fd_FD_SETSIZE_minus_one); fd_set.clear(); for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + assert!(!fd_set.contains(&borrowed_i)); } } #[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)); + assert_eq!( + set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), + None + ); + let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; + let fd_ninety = unsafe { BorrowedFd::borrow_raw(90) }; + set.insert(&fd_zero); + assert_eq!( + set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), + Some(0) + ); + set.insert(&fd_ninety); + assert_eq!( + set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), + Some(90) + ); + set.remove(&fd_zero); + assert_eq!( + set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), + Some(90) + ); + set.remove(&fd_ninety); + assert_eq!( + set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), + None + ); + + let fd_four = unsafe { BorrowedFd::borrow_raw(4) }; + let fd_five = unsafe { BorrowedFd::borrow_raw(5) }; + let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; + set.insert(&fd_four); + set.insert(&fd_five); + set.insert(&fd_seven); + assert_eq!( + set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), + 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]); + let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; + let fd_ninety = unsafe { BorrowedFd::borrow_raw(90) }; + assert_eq!( + set.fds(None) + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .collect::>(), + vec![] + ); + set.insert(&fd_zero); + assert_eq!( + set.fds(None) + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .collect::>(), + vec![0] + ); + set.insert(&fd_ninety); + assert_eq!( + set.fds(None) + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .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]); + assert_eq!( + set.fds(Some(89)) + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .collect::>(), + vec![0] + ); + assert_eq!( + set.fds(Some(90)) + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .collect::>(), + vec![0, 90] + ); } #[test] fn test_select() { let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); + let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; + let w1 = unsafe { OwnedFd::from_raw_fd(w1) }; let (r2, _w2) = pipe().unwrap(); + let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; + write(w1.as_raw_fd(), b"hi!").unwrap(); let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + 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)); + assert_eq!( + 1, + select(None, &mut fd_set, None, None, &mut timeout).unwrap() + ); + assert!(fd_set.contains(&r1)); + assert!(!fd_set.contains(&r2)); + close(_w2).unwrap(); } #[test] fn test_select_nfds() { let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); let (r2, _w2) = pipe().unwrap(); + let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; + let w1 = unsafe { OwnedFd::from_raw_fd(w1) }; + let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; + write(w1.as_raw_fd(), b"hi!").unwrap(); let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + 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)); + { + assert_eq!( + 1, + select( + Some( + fd_set + .highest() + .map(|borrowed_fd| borrowed_fd.as_raw_fd()) + .unwrap() + + 1 + ), + &mut fd_set, + None, + None, + &mut timeout + ) + .unwrap() + ); + } + assert!(fd_set.contains(&r1)); + assert!(!fd_set.contains(&r2)); + close(_w2).unwrap(); } #[test] @@ -413,18 +529,26 @@ mod tests { let (r1, w1) = pipe().unwrap(); write(w1, b"hi!").unwrap(); let (r2, _w2) = pipe().unwrap(); - + let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; + let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + fd_set.insert(&r1); + fd_set.insert(&r2); let mut timeout = TimeVal::seconds(10); - assert_eq!(1, select(::std::cmp::max(r1, r2) + 1, + assert_eq!( + 1, + select( + std::cmp::max(r1.as_raw_fd(), r2.as_raw_fd()) + 1, &mut fd_set, None, None, - &mut timeout).unwrap()); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); + &mut timeout + ) + .unwrap() + ); + assert!(fd_set.contains(&r1)); + assert!(!fd_set.contains(&r2)); + close(_w2).unwrap(); } } diff --git a/src/sys/sendfile.rs b/src/sys/sendfile.rs index 7a210c6fc3..9f3c333f97 100644 --- a/src/sys/sendfile.rs +++ b/src/sys/sendfile.rs @@ -1,13 +1,13 @@ //! Send data from a file to a socket, bypassing userland. use cfg_if::cfg_if; -use std::os::unix::io::RawFd; +use std::os::unix::io::{AsFd, AsRawFd}; use std::ptr; use libc::{self, off_t}; -use crate::Result; use crate::errno::Errno; +use crate::Result; /// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`. /// @@ -22,16 +22,24 @@ use crate::errno::Errno; /// /// 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"))] -pub fn sendfile( - out_fd: RawFd, - in_fd: RawFd, +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn sendfile( + out_fd: F1, + in_fd: F2, 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) }; + let ret = unsafe { + libc::sendfile( + out_fd.as_fd().as_raw_fd(), + in_fd.as_fd().as_raw_fd(), + offset, + count, + ) + }; Errno::result(ret).map(|r| r as usize) } @@ -48,30 +56,39 @@ pub fn sendfile( /// /// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) #[cfg(target_os = "linux")] -pub fn sendfile64( - out_fd: RawFd, - in_fd: RawFd, +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn sendfile64( + out_fd: F1, + in_fd: F2, 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) }; + let ret = unsafe { + libc::sendfile64( + out_fd.as_fd().as_raw_fd(), + in_fd.as_fd().as_raw_fd(), + offset, + count, + ) + }; Errno::result(ret).map(|r| r as usize) } cfg_if! { - if #[cfg(any(target_os = "freebsd", + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", target_os = "ios", target_os = "macos"))] { - use crate::sys::uio::IoVec; + use std::io::IoSlice; - #[derive(Clone, Debug, Eq, Hash, PartialEq)] + #[derive(Clone, Debug)] struct SendfileHeaderTrailer<'a>( libc::sf_hdtr, - Option>>, - Option>>, + Option>>, + Option>>, ); impl<'a> SendfileHeaderTrailer<'a> { @@ -79,10 +96,10 @@ cfg_if! { headers: Option<&'a [&'a [u8]]>, trailers: Option<&'a [&'a [u8]]> ) -> SendfileHeaderTrailer<'a> { - let header_iovecs: Option>> = - headers.map(|s| s.iter().map(|b| IoVec::from_slice(b)).collect()); - let trailer_iovecs: Option>> = - trailers.map(|s| s.iter().map(|b| IoVec::from_slice(b)).collect()); + 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: { @@ -153,9 +170,9 @@ cfg_if! { /// 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, + pub fn sendfile( + in_fd: F1, + out_sock: F2, offset: off_t, count: Option, headers: Option<&[&[u8]]>, @@ -172,8 +189,8 @@ cfg_if! { 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, + libc::sendfile(in_fd.as_fd().as_raw_fd(), + out_sock.as_fd().as_raw_fd(), offset, count.unwrap_or(0), hdtr_ptr as *mut libc::sf_hdtr, @@ -182,6 +199,49 @@ cfg_if! { }; (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: F1, + out_sock: F2, + 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.as_fd().as_raw_fd(), + out_sock.as_fd().as_raw_fd(), + 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`. @@ -206,9 +266,9 @@ cfg_if! { /// /// 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, + pub fn sendfile( + in_fd: F1, + out_sock: F2, offset: off_t, count: Option, headers: Option<&[&[u8]]>, @@ -218,8 +278,8 @@ cfg_if! { 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, + libc::sendfile(in_fd.as_fd().as_raw_fd(), + out_sock.as_fd().as_raw_fd(), offset, &mut len as *mut off_t, hdtr_ptr as *mut libc::sf_hdtr, diff --git a/src/sys/signal.rs b/src/sys/signal.rs index 61bdc74aef..c946e4a0b1 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -3,20 +3,26 @@ //! Operating system signals. -use crate::{Error, Result}; use crate::errno::Errno; -use crate::unistd::Pid; -use std::mem; +use crate::{Error, Result}; +use cfg_if::cfg_if; use std::fmt; -use std::str::FromStr; +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(not(any( + target_os = "fuchsia", + target_os = "openbsd", + target_os = "redox" +)))] +#[cfg(any(feature = "aio", feature = "signal"))] pub use self::sigevent::*; -libc_enum!{ +#[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. @@ -24,6 +30,7 @@ libc_enum!{ // this is not (yet) possible. #[repr(i32)] #[non_exhaustive] + #[cfg_attr(docsrs, doc(cfg(any(feature = "aio", feature = "signal"))))] pub enum Signal { /// Hangup SIGHUP, @@ -86,27 +93,35 @@ libc_enum!{ /// 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"))] + target_os = "fuchsia", target_os = "linux", + target_os = "aix"))] + #[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 = "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 = "redox", target_os = "haiku", + target_os = "aix")))] + #[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 { @@ -126,10 +141,19 @@ impl FromStr for Signal { "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"))))] + #[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, @@ -143,24 +167,41 @@ impl FromStr for Signal { "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"))] + #[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")))] + #[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")))] + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "aix", + target_os = "haiku" + )))] "SIGINFO" => Signal::SIGINFO, _ => return Err(Errno::EINVAL), }) } } +#[cfg(feature = "signal")] impl Signal { /// Returns name of signal. /// @@ -184,9 +225,19 @@ impl Signal { 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"))))] + #[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", @@ -200,174 +251,136 @@ impl Signal { 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"))] + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "aix", + 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")))] + #[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")))] + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + target_os = "aix", + 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(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"))))] + 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")))] + 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(target_os = "aix")] +#[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")))] + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGEMT, SIGFPE, SIGKILL, SIGSEGV, + SIGSYS, SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, SIGPWR, SIGWINCH, + SIGURG, SIGPOLL, SIGIO, SIGSTOP, SIGTSTP, SIGCONT, SIGTTIN, SIGTTOU, + SIGVTALRM, SIGPROF, SIGXCPU, SIGXFSZ, SIGTRAP, +]; +#[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "aix", + 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]; + 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 @@ -399,17 +412,26 @@ impl Signal { /// 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(not(target_os = "redox"))] -type SaFlags_t = libc::c_int; -#[cfg(target_os = "redox")] -type SaFlags_t = libc::c_ulong; +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; + } +} +} -libc_bitflags!{ +#[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 @@ -435,10 +457,12 @@ libc_bitflags!{ } } +#[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, @@ -450,15 +474,26 @@ libc_enum! { } } +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()) }; @@ -467,6 +502,7 @@ impl SigSet { } /// 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()) }; @@ -475,21 +511,25 @@ impl SigSet { } /// 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) }; @@ -500,14 +540,9 @@ impl SigSet { } } - /// Merge all of `other`'s signals into this set. - // TODO: use libc::sigorset on supported operating systems. - pub fn extend(&mut self, other: &SigSet) { - for signal in Signal::iterator() { - if other.contains(signal) { - self.add(signal); - } - } + /// 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. @@ -542,6 +577,7 @@ impl SigSet { /// 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; @@ -552,6 +588,19 @@ impl SigSet { 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 { @@ -560,8 +609,56 @@ impl AsRef for 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. @@ -573,6 +670,7 @@ pub enum SigHandler { /// 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)) } @@ -589,22 +687,24 @@ impl SigAction { /// 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 { - #[cfg(target_os = "redox")] + #[cfg(not(target_os = "aix"))] unsafe fn install_sig(p: *mut libc::sigaction, handler: SigHandler) { - (*p).sa_handler = match handler { + (*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, }; } - #[cfg(not(target_os = "redox"))] + #[cfg(target_os = "aix")] 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, - SigHandler::SigAction(f) => f as *const extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void) as usize, + (*p).sa_union.__su_sigaction = match handler { + SigHandler::SigDfl => mem::transmute::(libc::SIG_DFL), + SigHandler::SigIgn => mem::transmute::(libc::SIG_IGN), + SigHandler::Handler(f) => mem::transmute::(f), + SigHandler::SigAction(f) => f, }; } @@ -635,11 +735,12 @@ impl SigAction { } /// Returns the action's handler. - #[cfg(not(target_os = "redox"))] + #[cfg(not(target_os = "aix"))] 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: @@ -669,24 +770,23 @@ impl SigAction { } /// Returns the action's handler. - #[cfg(target_os = "redox")] + #[cfg(target_os = "aix")] pub fn handler(&self) -> SigHandler { - match self.sigaction.sa_handler { + unsafe { + match self.sigaction.sa_union.__su_sigaction as usize { libc::SIG_DFL => SigHandler::SigDfl, libc::SIG_IGN => SigHandler::SigIgn, + p if self.flags().contains(SaFlags::SA_SIGINFO) => + SigHandler::SigAction( + *(&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)), } + } } } @@ -741,13 +841,10 @@ pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result, oldset: Option<&mut SigSet>) -> Result<()> { if set.is_none() && oldset.is_none() { @@ -860,10 +957,11 @@ pub fn sigprocmask(how: SigmaskHow, set: Option<&SigSet>, oldset: Option<&mut Si /// # Arguments /// /// * `pid` - Specifies which processes should receive the signal. -/// - If positive, specifies an individual process +/// - 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 -/// variant of [`killpg`]. +#[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 @@ -912,20 +1010,23 @@ pub fn raise(signal: Signal) -> Result<()> { 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")] +#[cfg(any(target_env = "gnu", target_env = "uclibc"))] 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")))] +#[cfg(not(any(target_os = "fuchsia", target_os = "openbsd", target_os = "redox")))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SigevNotify { /// No notification will be delivered @@ -938,18 +1039,35 @@ pub enum SigevNotify { /// 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. + // Note: SIGEV_THREAD is not implemented, but could be if desired. /// 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 an event to a kqueue, with optional event flags set + #[cfg(target_os = "freebsd")] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(feature = "event")] + SigevKeventFlags { + /// File descriptor of the kqueue to notify. + kq: RawFd, + /// Will be contained in the kevent's `udata` field. + udata: libc::intptr_t, + /// Flags that will be set on the delivered event. See `kevent(2)`. + flags: crate::sys::event::EventFlag + }, /// Notify by delivering a signal to a thread. - #[cfg(any(target_os = "freebsd", target_os = "linux"))] + #[cfg(any( + target_os = "freebsd", + target_env = "gnu", + target_env = "uclibc", + ))] + #[cfg_attr(docsrs, doc(cfg(all())))] SigevThreadId { /// Signal to send signal: Signal, @@ -960,21 +1078,152 @@ pub enum SigevNotify { si_value: libc::intptr_t }, } +} -#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[cfg(not(any( + target_os = "fuchsia", + 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; + + #[cfg(target_os = "freebsd")] + pub(crate) use ffi::sigevent as libc_sigevent; + #[cfg(not(target_os = "freebsd"))] + pub(crate) use libc::sigevent as libc_sigevent; + + // For FreeBSD only, we define the C structure here. Because the structure + // defined in libc isn't correct. The real sigevent contains union fields, + // but libc could not represent those when sigevent was originally added, so + // instead libc simply defined the most useful field. Now that Rust can + // represent unions, there's a PR to libc to fix it. However, it's stuck + // forever due to backwards compatibility concerns. Even though there's a + // workaround, libc refuses to merge it. I think it's just too complicated + // for them to want to think about right now, because that project is + // short-staffed. So we define it here instead, so we won't have to wait on + // libc. + // https://github.com/rust-lang/libc/pull/2813 + #[cfg(target_os = "freebsd")] + mod ffi { + use std::{fmt, hash}; + + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(C)] + pub struct __c_anonymous_sigev_thread { + pub _function: *mut libc::c_void, // Actually a function pointer + pub _attribute: *mut libc::pthread_attr_t, + } + #[derive(Clone, Copy)] + // This will never be used on its own, and its parent has a Debug impl, + // so it doesn't need one. + #[allow(missing_debug_implementations)] + #[repr(C)] + pub union __c_anonymous_sigev_un { + pub _threadid: libc::__lwpid_t, + pub _sigev_thread: __c_anonymous_sigev_thread, + pub _kevent_flags: libc::c_ushort, + __spare__: [libc::c_long; 8], + } + + #[derive(Clone, Copy)] + #[repr(C)] + pub struct sigevent { + pub sigev_notify: libc::c_int, + pub sigev_signo: libc::c_int, + pub sigev_value: libc::sigval, + pub _sigev_un: __c_anonymous_sigev_un, + } + + impl fmt::Debug for sigevent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut ds = f.debug_struct("sigevent"); + ds.field("sigev_notify", &self.sigev_notify) + .field("sigev_signo", &self.sigev_signo) + .field("sigev_value", &self.sigev_value); + // Safe because we check the sigev_notify discriminant + unsafe { + match self.sigev_notify { + libc::SIGEV_KEVENT => { + ds.field("sigev_notify_kevent_flags", &self._sigev_un._kevent_flags); + } + libc::SIGEV_THREAD_ID => { + ds.field("sigev_notify_thread_id", &self._sigev_un._threadid); + } + libc::SIGEV_THREAD => { + ds.field("sigev_notify_function", &self._sigev_un._sigev_thread._function); + ds.field("sigev_notify_attributes", &self._sigev_un._sigev_thread._attribute); + } + _ => () + }; + } + ds.finish() + } + } + + impl PartialEq for sigevent { + fn eq(&self, other: &Self) -> bool { + let mut equals = self.sigev_notify == other.sigev_notify; + equals &= self.sigev_signo == other.sigev_signo; + equals &= self.sigev_value == other.sigev_value; + // Safe because we check the sigev_notify discriminant + unsafe { + match self.sigev_notify { + libc::SIGEV_KEVENT => { + equals &= self._sigev_un._kevent_flags == other._sigev_un._kevent_flags; + } + libc::SIGEV_THREAD_ID => { + equals &= self._sigev_un._threadid == other._sigev_un._threadid; + } + libc::SIGEV_THREAD => { + equals &= self._sigev_un._sigev_thread == other._sigev_un._sigev_thread; + } + _ => /* The union field is don't care */ () + } + } + equals + } + } + + impl Eq for sigevent {} + + impl hash::Hash for sigevent { + fn hash(&self, s: &mut H) { + self.sigev_notify.hash(s); + self.sigev_signo.hash(s); + self.sigev_value.hash(s); + // Safe because we check the sigev_notify discriminant + unsafe { + match self.sigev_notify { + libc::SIGEV_KEVENT => { + self._sigev_un._kevent_flags.hash(s); + } + libc::SIGEV_THREAD_ID => { + self._sigev_un._threadid.hash(s); + } + libc::SIGEV_THREAD => { + self._sigev_un._sigev_thread.hash(s); + } + _ => /* The union field is don't care */ () + } + } + } + } + } /// 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)] + #[derive(Clone, Debug, Eq, Hash, PartialEq)] + // It can't be Copy on all platforms. + #[allow(missing_copy_implementations)] pub struct SigEvent { - sigevent: libc::sigevent + sigevent: libc_sigevent } impl SigEvent { @@ -991,73 +1240,103 @@ mod sigevent { /// 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, + let mut sev: libc_sigevent = unsafe { mem::zeroed() }; + match sigev_notify { + SigevNotify::SigevNone => { + sev.sigev_notify = libc::SIGEV_NONE; + }, + SigevNotify::SigevSignal{signal, si_value} => { + sev.sigev_notify = libc::SIGEV_SIGNAL; + sev.sigev_signo = signal as libc::c_int; + sev.sigev_value.sival_ptr = si_value as *mut libc::c_void + }, #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT, + SigevNotify::SigevKevent{kq, udata} => { + sev.sigev_notify = libc::SIGEV_KEVENT; + sev.sigev_signo = kq; + sev.sigev_value.sival_ptr = udata as *mut libc::c_void; + }, #[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(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); + #[cfg(feature = "event")] + SigevNotify::SigevKeventFlags{kq, udata, flags} => { + sev.sigev_notify = libc::SIGEV_KEVENT; + sev.sigev_signo = kq; + sev.sigev_value.sival_ptr = udata as *mut libc::c_void; + sev._sigev_un._kevent_flags = flags.bits(); + }, + #[cfg(target_os = "freebsd")] + SigevNotify::SigevThreadId{signal, thread_id, si_value} => { + sev.sigev_notify = libc::SIGEV_THREAD_ID; + sev.sigev_signo = signal as libc::c_int; + sev.sigev_value.sival_ptr = si_value as *mut libc::c_void; + sev._sigev_un._threadid = thread_id; + } + #[cfg(any(target_env = "gnu", target_env = "uclibc"))] + SigevNotify::SigevThreadId{signal, thread_id, si_value} => { + sev.sigev_notify = libc::SIGEV_THREAD_ID; + sev.sigev_signo = signal as libc::c_int; + sev.sigev_value.sival_ptr = si_value as *mut libc::c_void; + sev.sigev_notify_thread_id = thread_id; + } + } 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 + #[cfg(target_os = "freebsd")] + pub fn sigevent(&self) -> libc::sigevent { + // Safe because they're really the same structure. See + // https://github.com/rust-lang/libc/pull/2813 + unsafe { + mem::transmute::(self.sigevent) + } } /// Return a copy of the inner structure + #[cfg(not(target_os = "freebsd"))] pub fn sigevent(&self) -> libc::sigevent { self.sigevent } + + /// Returns a mutable pointer to the `sigevent` wrapped by `self` + #[cfg(target_os = "freebsd")] + pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent { + // Safe because they're really the same structure. See + // https://github.com/rust-lang/libc/pull/2813 + &mut self.sigevent as *mut libc_sigevent as *mut libc::sigevent + } + + /// Returns a mutable pointer to the `sigevent` wrapped by `self` + #[cfg(not(target_os = "freebsd"))] + pub fn as_mut_ptr(&mut self) -> *mut libc::sigevent { + &mut self.sigevent + } } impl<'a> From<&'a libc::sigevent> for SigEvent { + #[cfg(target_os = "freebsd")] + fn from(sigevent: &libc::sigevent) -> Self { + // Safe because they're really the same structure. See + // https://github.com/rust-lang/libc/pull/2813 + let sigevent = unsafe { + mem::transmute::(*sigevent) + }; + SigEvent{ sigevent } + } + #[cfg(not(target_os = "freebsd"))] fn from(sigevent: &libc::sigevent) -> Self { SigEvent{ sigevent: *sigevent } } } + } } #[cfg(test)] mod tests { + use super::*; #[cfg(not(target_os = "redox"))] use std::thread; - use super::*; #[test] fn test_contains() { @@ -1120,15 +1399,19 @@ mod tests { let mut test_mask = prev_mask; test_mask.add(SIGUSR1); - assert!(test_mask.thread_set_mask().is_ok()); - let new_mask = SigSet::thread_get_mask() - .expect("Failed to get new mask!"); + 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(); + prev_mask + .thread_set_mask() + .expect("Failed to revert signal mask!"); + }) + .join() + .unwrap(); } #[test] @@ -1138,10 +1421,12 @@ mod tests { let mut mask = SigSet::empty(); mask.add(SIGUSR1); - assert!(mask.thread_block().is_ok()); + mask.thread_block().expect("assertion failed"); assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); - }).join().unwrap(); + }) + .join() + .unwrap(); } #[test] @@ -1151,10 +1436,12 @@ mod tests { let mut mask = SigSet::empty(); mask.add(SIGUSR1); - assert!(mask.thread_unblock().is_ok()); + mask.thread_unblock().expect("assertion failed"); assert!(!SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); - }).join().unwrap(); + }) + .join() + .unwrap(); } #[test] @@ -1170,36 +1457,51 @@ mod tests { let mut mask2 = SigSet::empty(); mask2.add(SIGUSR2); - let oldmask = mask2.thread_swap_mask(SigmaskHow::SIG_SETMASK) - .unwrap(); + 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(); + }) + .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 fn test_sigaction_handler(_: libc::c_int) {} - extern fn test_sigaction_action(_: libc::c_int, - _: *mut libc::siginfo_t, _: *mut libc::c_void) {} + 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 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.flags(), + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART + ); assert_eq!(action_sig.handler(), handler_sig); mask = action_sig.mask(); @@ -1215,7 +1517,9 @@ mod tests { let action_ign = SigAction::new(SigHandler::SigIgn, flags, mask); assert_eq!(action_ign.handler(), SigHandler::SigIgn); - }).join().unwrap(); + }) + .join() + .unwrap(); } #[test] @@ -1229,6 +1533,25 @@ mod tests { raise(SIGUSR1).unwrap(); assert_eq!(mask.wait().unwrap(), SIGUSR1); - }).join().unwrap(); + }) + .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 index bc4a452243..2b80ea643f 100644 --- a/src/sys/signalfd.rs +++ b/src/sys/signalfd.rs @@ -15,24 +15,21 @@ //! //! Please note that signal discarding is not specific to `signalfd`, but also happens with regular //! signal handlers. -use crate::unistd; -use crate::Result; use crate::errno::Errno; pub use crate::sys::signal::{self, SigSet}; +use crate::Result; pub use libc::signalfd_siginfo as siginfo; -use std::os::unix::io::{RawFd, AsRawFd}; use std::mem; +use std::os::unix::io::{AsRawFd, RawFd, FromRawFd, OwnedFd, AsFd, BorrowedFd}; - -libc_bitflags!{ +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::(); @@ -47,9 +44,19 @@ pub const SIGNALFD_SIGINFO_SIZE: usize = mem::size_of::(); /// 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 { +#[deprecated(since = "0.27.0", note = "Use SignalFd instead")] +pub fn signalfd(fd: Option, mask: &SigSet, flags: SfdFlags) -> Result { + _signalfd(fd, mask, flags) +} + +fn _signalfd(fd: Option, mask: &SigSet, flags: SfdFlags) -> Result { + let raw_fd = fd.map_or(-1, |x|x.as_fd().as_raw_fd()); unsafe { - Errno::result(libc::signalfd(fd as libc::c_int, mask.as_ref(), flags.bits())) + Errno::result(libc::signalfd( + raw_fd, + mask.as_ref(), + flags.bits(), + )).map(|raw_fd|FromRawFd::from_raw_fd(raw_fd)) } } @@ -79,8 +86,8 @@ pub fn signalfd(fd: RawFd, mask: &SigSet, flags: SfdFlags) -> Result { /// Err(err) => (), // some error happend /// } /// ``` -#[derive(Debug, Eq, Hash, PartialEq)] -pub struct SignalFd(RawFd); +#[derive(Debug)] +pub struct SignalFd(OwnedFd); impl SignalFd { pub fn new(mask: &SigSet) -> Result { @@ -88,13 +95,13 @@ impl SignalFd { } pub fn with_flags(mask: &SigSet, flags: SfdFlags) -> Result { - let fd = signalfd(SIGNALFD_NEW, mask, flags)?; + let fd = _signalfd(None::, mask, flags)?; Ok(SignalFd(fd)) } pub fn set_mask(&mut self, mask: &SigSet) -> Result<()> { - signalfd(self.0, mask, SfdFlags::empty()).map(drop) + _signalfd(Some(self.0.as_fd()), mask, SfdFlags::empty()).map(drop) } pub fn read_signal(&mut self) -> Result> { @@ -102,29 +109,26 @@ impl SignalFd { 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); + libc::read(self.0.as_raw_fd(), 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) + 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 AsFd for SignalFd { + fn as_fd(&self) -> BorrowedFd { + self.0.as_fd() } } - impl AsRawFd for SignalFd { fn as_raw_fd(&self) -> RawFd { - self.0 + self.0.as_raw_fd() } } @@ -139,7 +143,6 @@ impl Iterator for SignalFd { } } - #[cfg(test)] mod tests { use super::*; @@ -147,21 +150,24 @@ mod tests { #[test] fn create_signalfd() { let mask = SigSet::empty(); - let fd = SignalFd::new(&mask); - assert!(fd.is_ok()); + SignalFd::new(&mask).unwrap(); } #[test] fn create_signalfd_with_opts() { let mask = SigSet::empty(); - let fd = SignalFd::with_flags(&mask, SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK); - assert!(fd.is_ok()); + 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 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 index b119642b3f..1783531d49 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -1,36 +1,65 @@ +#[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", + target_os = "aix", +))] +#[cfg(feature = "net")] +pub use self::datalink::LinkAddr; +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub use self::vsock::VsockAddr; use super::sa_family_t; -use crate::{Result, NixPath}; 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::{fmt, mem, net, ptr, slice}; +use std::convert::TryInto; use std::ffi::OsStr; use std::hash::{Hash, Hasher}; -use std::path::Path; use std::os::unix::ffi::OsStrExt; -#[cfg(any(target_os = "android", target_os = "linux"))] -use crate::sys::socket::addr::netlink::NetlinkAddr; -#[cfg(any(target_os = "android", target_os = "linux"))] -use crate::sys::socket::addr::alg::AlgAddr; -#[cfg(any(target_os = "ios", target_os = "macos"))] -use std::os::unix::io::RawFd; -#[cfg(any(target_os = "ios", target_os = "macos"))] -use crate::sys::socket::addr::sys_control::SysControlAddr; -#[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 = "fuchsia"))] -pub use self::datalink::LinkAddr; -#[cfg(any(target_os = "android", target_os = "linux"))] -pub use self::vsock::VsockAddr; +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 { + libc::in_addr { + s_addr: u32::from_ne_bytes(addr.octets()) + } +} + +/// 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 { + libc::in6_addr { + s6_addr: addr.octets() + } +} /// 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)] @@ -43,191 +72,345 @@ pub enum AddressFamily { 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, + /// Kernel interface for interacting with the routing table + #[cfg(not(any( + target_os = "redox", + target_os = "linux", + target_os = "android" + )))] + Route = libc::PF_ROUTE, /// 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(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 + #[cfg(not(any(target_os = "aix", target_os = "redox")))] + #[cfg_attr(docsrs, doc(cfg(all())))] Ipx = libc::AF_IPX, /// AppleTalk + #[cfg(not(target_os = "redox"))] 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(any(target_os = "haiku", target_os = "redox")))] 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(any(target_os = "haiku", target_os = "redox")))] 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, - #[cfg(target_os = "linux")] + /// InfiniBand native addressing + #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] + #[cfg_attr(docsrs, doc(cfg(all())))] Ib = libc::AF_IB, - #[cfg(target_os = "linux")] + /// 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, - #[cfg(not(any(target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "solaris")))] + /// Bluetooth low-level socket protocol + #[cfg(not(any( + target_os = "aix", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "solaris", + target_os = "redox", + )))] + #[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, - #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] + /// New "modular ISDN" driver interface protocol + #[cfg(not(any( + target_os = "aix", + target_os = "illumos", + target_os = "solaris", + target_os = "haiku", + target_os = "redox", + )))] + #[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, - #[cfg(any(target_os = "android", target_os = "linux"))] + /// VMWare VSockets protocol for hypervisor-guest interaction. + #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] + #[cfg_attr(docsrs, doc(cfg(all())))] Vsock = libc::AF_VSOCK, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + #[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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + #[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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "illumos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] + /// 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, } @@ -246,282 +429,48 @@ impl AddressFamily { libc::AF_NETLINK => Some(AddressFamily::Netlink), #[cfg(any(target_os = "macos", target_os = "macos"))] libc::AF_SYSTEM => Some(AddressFamily::System), + #[cfg(not(any( + target_os = "redox", + target_os = "linux", + target_os = "android" + )))] + libc::PF_ROUTE => Some(AddressFamily::Route), #[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"))] + #[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"))] + #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] libc::AF_VSOCK => Some(AddressFamily::Vsock), - _ => None - } - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum InetAddr { - V4(libc::sockaddr_in), - V6(libc::sockaddr_in6), -} - -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 { - 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 { - 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) - } -} - -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 ===== - * - */ -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum IpAddr { - V4(Ipv4Addr), - V6(Ipv6Addr), -} - -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()), - } - } -} - -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) + _ => None, } } } -/* - * - * ===== Ipv4Addr ===== - * - */ - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct Ipv4Addr(pub libc::in_addr); - -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]) - } -} - -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 ===== - * - */ - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -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,)*] - } -} - -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]) - } -} - -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 & path_len are valid as defined by docs for from_raw_parts + // INVARIANT: sun & sun_len are valid as defined by docs for from_raw_parts sun: libc::sockaddr_un, - path_len: usize, + /// 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", + target_os = "redox", + ))] + sun_len: u8, } // linux man page unix(7) says there are 3 kinds of unix socket: @@ -538,97 +487,169 @@ enum UnixAddrKind<'a> { Abstract(&'a [u8]), } impl<'a> UnixAddrKind<'a> { - /// Safety: sun & path_len must be valid - unsafe fn get(sun: &'a libc::sockaddr_un, path_len: usize) -> Self { + /// Safety: sun & sun_len must be valid + #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms + 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); + 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 - 1); - Self::Pathname(Path::new(OsStr::from_bytes(pathname))) + 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. + #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms 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(); + path.with_nix_path(|cstr| unsafe { + let mut ret = libc::sockaddr_un { + sun_family: AddressFamily::Unix as sa_family_t, + ..mem::zeroed() + }; - if bytes.len() >= ret.sun_path.len() { - return Err(Errno::ENAMETOOLONG); - } + let bytes = cstr.to_bytes(); - ptr::copy_nonoverlapping(bytes.as_ptr(), - ret.sun_path.as_mut_ptr() as *mut u8, - bytes.len()); + if bytes.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); + } - Ok(UnixAddr::from_raw_parts(ret, bytes.len() + 1)) + 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 null byte for the abstract namespace is automatically added; - /// thus the input `path` is expected to be the bare name, not null-prefixed. + /// 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())))] + #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms pub fn new_abstract(path: &[u8]) -> Result { unsafe { let mut ret = libc::sockaddr_un { sun_family: AddressFamily::Unix as sa_family_t, - .. mem::zeroed() + ..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()); + 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, path.len() + 1)) + Ok(UnixAddr::from_raw_parts(ret, sun_len)) } } - /// Create a UnixAddr from a raw `sockaddr_un` struct and a size. `path_len` is the "addrlen" - /// of this address, but minus `offsetof(struct sockaddr_un, sun_path)`. Basically the length - /// of the data in `sun_path`. + /// 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 & path_len must be a valid unix addr, which means: - /// - path_len <= sockaddr_un.sun_path.len() - /// - if this is a unix addr with a pathname, sun.sun_path is a nul-terminated fs path and - /// sun.sun_path[path_len - 1] == 0 || sun.sun_path[path_len] == 0 - pub(crate) unsafe fn from_raw_parts(sun: libc::sockaddr_un, mut path_len: usize) -> UnixAddr { - if let UnixAddrKind::Pathname(_) = UnixAddrKind::get(&sun, path_len) { - if sun.sun_path[path_len - 1] != 0 { - assert_eq!(sun.sun_path[path_len], 0); - path_len += 1 + /// 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", + target_os = "redox", + ))] + { + UnixAddr { sun, sun_len } + } else { + assert_eq!(sun_len, sun.sun_len); + UnixAddr {sun} } } - UnixAddr { sun, path_len } } fn kind(&self) -> UnixAddrKind<'_> { // SAFETY: our sockaddr is always valid because of the invariant on the struct - unsafe { UnixAddrKind::get(&self.sun, self.path_len) } + unsafe { UnixAddrKind::get(&self.sun, self.sun_len()) } } /// If this address represents a filesystem path, return that path. @@ -642,8 +663,9 @@ impl UnixAddr { /// If this address represents an abstract socket, return its name. /// /// For abstract sockets only the bare name is returned, without the - /// leading null byte. `None` is returned for unnamed or path-backed sockets. + /// 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), @@ -651,10 +673,18 @@ impl UnixAddr { } } + /// 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.path_len + self.sun_len() as usize - offset_of!(libc::sockaddr_un, sun_path) } /// Returns a pointer to the raw `sockaddr_un` struct #[inline] @@ -666,6 +696,101 @@ impl UnixAddr { 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", + target_os = "redox", + ))] + { + 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", + target_os = "redox", + ))] { + 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 + } + + unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> { + // `new_length` is only used on some platforms, so it must be provided even when not used + #![allow(unused_variables)] + cfg_if! { + if #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "redox", + ))] { + self.sun_len = new_length as u8; + } + }; + Ok(()) + } +} + +impl AsRef for UnixAddr { + fn as_ref(&self) -> &libc::sockaddr_un { + &self.sun + } } #[cfg(any(target_os = "android", target_os = "linux"))] @@ -695,283 +820,957 @@ 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::*; + /// # use std::os::unix::io::AsRawFd; + /// let fd = socket(AddressFamily::Inet, SockType::Stream, + /// SockFlag::empty(), None).unwrap(); + /// let ss: SockaddrStorage = getsockname(fd.as_raw_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 + } + + /// Set the length of this socket address + /// + /// This method may only be called on socket addresses whose lengths are dynamic, and it + /// returns an error if called on a type whose length is static. + /// + /// # Safety + /// + /// `new_length` must be a valid length for this type of address. Specifically, reads of that + /// length from `self` must be valid. + #[doc(hidden)] + unsafe fn set_length(&mut self, _new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> { + Err(SocketAddressLengthNotDynamic) + } +} + +/// The error returned by [`SockaddrLike::set_length`] on an address whose length is statically +/// fixed. +#[derive(Copy, Clone, Debug)] +pub struct SocketAddressLengthNotDynamic; +impl fmt::Display for SocketAddressLengthNotDynamic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Attempted to set length on socket whose length is statically fixed") + } +} +impl std::error::Error for SocketAddressLengthNotDynamic {} + +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 +#[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 = "aix", + 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; +/// # use std::os::unix::io::AsRawFd; +/// let localhost = SockaddrIn::from_str("127.0.0.1:8081").unwrap(); +/// let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), +/// None).unwrap(); +/// bind(fd.as_raw_fd(), &localhost).expect("bind"); +/// let ss: SockaddrStorage = getsockname(fd.as_raw_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(all(feature = "net", not(target_os = "redox")))] + #[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", target_os = "macos" ))] + #[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", target_os = "macos" ))] + libc::AF_VSOCK => { + VsockAddr::from_raw(addr, l).map(|vsock| Self { vsock }) + } + _ => None, + } + } + } -impl Eq for UnixAddr {} + #[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, + } + } -impl Hash for UnixAddr { - fn hash(&self, s: &mut H) { - self.kind().hash(s) + unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> { + match self.as_unix_addr_mut() { + Some(addr) => { + addr.set_length(new_length) + }, + None => Err(SocketAddressLengthNotDynamic), + } } } -/// Represents a socket address -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] -pub enum SockAddr { - Inet(InetAddr), - Unix(UnixAddr), - #[cfg(any(target_os = "android", target_os = "linux"))] - Netlink(NetlinkAddr), - #[cfg(any(target_os = "android", target_os = "linux"))] - Alg(AlgAddr), - #[cfg(any(target_os = "ios", target_os = "macos"))] - 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"))] - Link(LinkAddr), - #[cfg(any(target_os = "android", target_os = "linux"))] - Vsock(VsockAddr), +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 SockAddr { - pub fn new_inet(addr: InetAddr) -> SockAddr { - SockAddr::Inet(addr) +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 }) + } } - pub fn new_unix(path: &P) -> Result { - Ok(SockAddr::Unix(UnixAddr::new(path)?)) + /// 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"))] - pub fn new_netlink(pid: u32, groups: u32) -> SockAddr { - SockAddr::Netlink(NetlinkAddr::new(pid, groups)) - } + 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"))] - pub fn new_alg(alg_type: &str, alg_name: &str) -> SockAddr { - SockAddr::Alg(AlgAddr::new(alg_type, alg_name)) + 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", target_os = "macos"))] + #[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() } +} - #[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) +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", target_os = "macos"))] + libc::AF_VSOCK => self.vsock.fmt(f), + _ => "

    ".fmt(f), + } + } } +} - #[cfg(any(target_os = "android", target_os = "linux"))] - pub fn new_vsock(cid: u32, port: u32) -> SockAddr { - SockAddr::Vsock(VsockAddr::new(cid, port)) +#[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 + } } +} - pub fn family(&self) -> AddressFamily { - match *self { - SockAddr::Inet(InetAddr::V4(..)) => AddressFamily::Inet, - 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(any(target_os = "ios", target_os = "macos"))] - SockAddr::SysControl(..) => AddressFamily::System, - #[cfg(any(target_os = "android", target_os = "linux"))] - 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"))] - SockAddr::Link(..) => AddressFamily::Link, - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Vsock(..) => AddressFamily::Vsock, +#[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 } } +} - #[deprecated(since = "0.23.0", note = "use .to_string() instead")] - pub fn to_str(&self) -> String { - format!("{}", self) +#[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), + } } +} - /// 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"))] - 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, - Some(AddressFamily::Inet) => Some(SockAddr::Inet( - InetAddr::V4(*(addr as *const libc::sockaddr_in)))), - Some(AddressFamily::Inet6) => Some(SockAddr::Inet( - InetAddr::V6(*(addr as *const libc::sockaddr_in6)))), - #[cfg(any(target_os = "android", target_os = "linux"))] - Some(AddressFamily::Netlink) => Some(SockAddr::Netlink( - NetlinkAddr(*(addr as *const libc::sockaddr_nl)))), - #[cfg(any(target_os = "ios", target_os = "macos"))] - Some(AddressFamily::System) => Some(SockAddr::SysControl( - SysControlAddr(*(addr as *const libc::sockaddr_ctl)))), +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"))] - Some(AddressFamily::Packet) => Some(SockAddr::Link( - LinkAddr(*(addr as *const libc::sockaddr_ll)))), - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd"))] - Some(AddressFamily::Link) => { - let ether_addr = LinkAddr(*(addr as *const libc::sockaddr_dl)); - if ether_addr.is_empty() { - None - } else { - Some(SockAddr::Link(ether_addr)) - } - }, + 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"))] - Some(AddressFamily::Vsock) => Some(SockAddr::Vsock( - VsockAddr(*(addr as *const libc::sockaddr_vm)))), - // Other address families are currently not supported and simply yield a None - // entry instead of a proper conversion to a `SockAddr`. - Some(_) | None => None, + 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", target_os = "macos"))] + libc::AF_VSOCK => self.vsock.hash(s), + _ => self.ss.hash(s), } } } +} - /// 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 { - 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 - ), - 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(UnixAddr { ref sun, path_len }) => ( - // This cast is always allowed in C - unsafe { - &*(sun as *const libc::sockaddr_un as *const libc::sockaddr) - }, - (path_len + offset_of!(libc::sockaddr_un, sun_path)) 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(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"))] - 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"))] - 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 - ), +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", target_os = "macos"))] + (libc::AF_VSOCK, libc::AF_VSOCK) => self.vsock == other.vsock, + _ => false, + } } } } -impl fmt::Display for SockAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - 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(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"))] - SockAddr::Link(ref ether_addr) => ether_addr.fmt(f), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Vsock(ref svm) => svm.fmt(f), +pub(super) 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 } } } #[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)] - pub struct NetlinkAddr(pub sockaddr_nl); + #[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; @@ -981,15 +1780,44 @@ pub mod netlink { 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()) @@ -998,21 +1826,64 @@ pub mod netlink { } #[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(docsrs, doc(cfg(all())))] pub mod alg { - use libc::{AF_ALG, sockaddr_alg, c_char}; - use std::{fmt, mem, str}; - use std::hash::{Hash, Hasher}; + 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)] - pub struct AlgAddr(pub sockaddr_alg); + #[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[..]) + ( + 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[..], + ) } } @@ -1021,35 +1892,53 @@ pub mod alg { 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); + ( + 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()); + 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) } + 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) } + 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()) + write!( + f, + "type: {} alg: {}", + self.alg_name().to_string_lossy(), + self.alg_type().to_string_lossy() + ) } } @@ -1060,13 +1949,16 @@ pub mod alg { } } +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}; + 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)] @@ -1083,11 +1975,41 @@ pub mod sys_control { ioctl_readwrite!(ctl_info, CTL_IOC_MAGIC, CTL_IOC_INFO, ctl_ioc_info); - #[repr(C)] + /// Apple system control socket + /// + /// # References + /// + /// #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] - pub struct SysControlAddr(pub libc::sockaddr_ctl); + #[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, @@ -1101,6 +2023,8 @@ pub mod sys_control { 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); @@ -1115,10 +2039,12 @@ pub mod sys_control { 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 } @@ -1130,23 +2056,21 @@ pub mod sys_control { } } } - +} #[cfg(any(target_os = "android", target_os = "linux", target_os = "fuchsia"))] +#[cfg_attr(docsrs, doc(cfg(all())))] mod datalink { - use super::{fmt, AddressFamily}; + feature! { + #![feature = "net"] + use super::{fmt, mem, private, ptr, SockaddrLike}; /// Hardware Address #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] - pub struct LinkAddr(pub libc::sockaddr_ll); + #[repr(transparent)] + pub struct LinkAddr(pub(in super::super) libc::sockaddr_ll); impl LinkAddr { - /// Always AF_PACKET - pub fn family(&self) -> AddressFamily { - assert_eq!(self.0.sll_family as i32, libc::AF_PACKET); - AddressFamily::Packet - } - /// Physical-layer protocol pub fn protocol(&self) -> u16 { self.0.sll_protocol @@ -1173,70 +2097,97 @@ mod datalink { } /// Physical-layer address (MAC) - pub fn addr(&self) -> [u8; 6] { - [ - self.0.sll_addr[0] as u8, - self.0.sll_addr[1] as u8, - self.0.sll_addr[2] as u8, - self.0.sll_addr[3] as u8, - self.0.sll_addr[4] as u8, - self.0.sll_addr[5] as u8, - ] + // 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 { - let addr = self.addr(); - write!(f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - addr[0], - addr[1], - addr[2], - addr[3], - addr[4], - addr[5]) + 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 = "openbsd"))] +#[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 = "aix", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] mod datalink { - use super::{fmt, AddressFamily}; + feature! { + #![feature = "net"] + use super::{fmt, mem, private, ptr, SockaddrLike}; /// Hardware Address #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] - pub struct LinkAddr(pub libc::sockaddr_dl); + #[repr(transparent)] + pub struct LinkAddr(pub(in super::super) libc::sockaddr_dl); impl LinkAddr { - /// Total length of sockaddr - #[cfg(not(target_os = "illumos"))] - pub fn len(&self) -> usize { - self.0.sdl_len as usize - } - - /// always == AF_LINK - pub fn family(&self) -> AddressFamily { - assert_eq!(i32::from(self.0.sdl_family), libc::AF_LINK); - AddressFamily::Link - } - /// 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 + /// MAC address start position pub fn nlen(&self) -> usize { self.0.sdl_nlen as usize } @@ -1247,6 +2198,7 @@ mod datalink { } /// link layer selector length + #[cfg(not(target_os = "haiku"))] pub fn slen(&self) -> usize { self.0.sdl_slen as usize } @@ -1262,62 +2214,141 @@ mod datalink { } /// Physical-layer address (MAC) - pub fn addr(&self) -> [u8; 6] { + // 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; - assert!(!self.is_empty()); - - [ - 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, - ] + 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 { - let addr = self.addr(); - write!(f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - addr[0], - addr[1], - addr[2], - addr[3], - addr[4], - addr[5]) + 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(any(target_os = "android", target_os = "linux", target_os = "macos"))] +#[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::{fmt, mem}; 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)] - pub struct VsockAddr(pub sockaddr_vm); + #[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 { + #[cfg(any(target_os = "android", target_os = "linux"))] 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) + (inner.svm_family, inner.svm_cid, inner.svm_port) + == (other.svm_family, other.svm_cid, other.svm_port) + } + #[cfg(target_os = "macos")] + fn eq(&self, other: &Self) -> bool { + let (inner, other) = (self.0, other.0); + (inner.svm_family, inner.svm_cid, inner.svm_port, inner.svm_len) + == (other.svm_family, other.svm_cid, other.svm_port, inner.svm_len) } } impl Eq for VsockAddr {} impl Hash for VsockAddr { + #[cfg(any(target_os = "android", target_os = "linux"))] fn hash(&self, s: &mut H) { let inner = self.0; (inner.svm_family, inner.svm_cid, inner.svm_port).hash(s); } + #[cfg(target_os = "macos")] + fn hash(&self, s: &mut H) { + let inner = self.0; + (inner.svm_family, inner.svm_cid, inner.svm_port, inner.svm_len).hash(s); + } } /// VSOCK Address @@ -1325,12 +2356,17 @@ pub mod vsock { /// 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; + #[cfg(target_os = "macos")] + { + addr.svm_len = std::mem::size_of::() as u8; + } VsockAddr(addr) } @@ -1360,88 +2396,290 @@ pub mod vsock { #[cfg(test)] mod tests { - #[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"))] use super::*; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[test] - fn test_macos_loopback_datalink_addr() { - 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 _sock_addr = unsafe { SockAddr::from_libc_sockaddr(sa) }; - assert!(_sock_addr.is_none()); - } - - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[test] - fn test_macos_tap_datalink_addr() { - 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 _sock_addr = unsafe { SockAddr::from_libc_sockaddr(sa) }; - - assert!(_sock_addr.is_some()); - - let sock_addr = _sock_addr.unwrap(); - - assert_eq!(sock_addr.family(), AddressFamily::Link); - - match sock_addr { - SockAddr::Link(ether_addr) => { - assert_eq!(ether_addr.addr(), [24u8, 101, 144, 221, 76, 176]); - }, - _ => { unreachable!() } - }; + 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] + ); + } } - #[cfg(target_os = "illumos")] - #[test] - fn test_illumos_tap_datalink_addr() { - 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 _sock_addr = unsafe { SockAddr::from_libc_sockaddr(sa) }; + #[cfg(not(target_os = "redox"))] + 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"), + } + } - assert!(_sock_addr.is_some()); + #[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) }; - let sock_addr = _sock_addr.unwrap(); + assert!(_sock_addr.is_some()); - assert_eq!(sock_addr.family(), AddressFamily::Link); + let sock_addr = _sock_addr.unwrap(); - match sock_addr { - SockAddr::Link(ether_addr) => { - assert_eq!(ether_addr.addr(), [24u8, 101, 144, 221, 76, 176]); - }, - _ => { unreachable!() } - }; + 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 = "aix", + 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); + } } - #[cfg(any(target_os = "android", target_os = "linux"))] - #[test] - fn test_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); + 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 index 97eea3dcb1..78dd617c55 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -1,18 +1,27 @@ //! Socket interface functions //! //! [Further reading](https://man7.org/linux/man-pages/man7/socket.7.html) -use cfg_if::cfg_if; -use crate::{Result, errno::Errno}; -use libc::{self, c_void, c_int, iovec, socklen_t, size_t, - CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA, CMSG_LEN}; -use memoffset::offset_of; -use std::{mem, ptr, slice}; -use std::os::unix::io::RawFd; -#[cfg(all(target_os = "linux"))] +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "uio")] use crate::sys::time::TimeSpec; +#[cfg(not(target_os = "redox"))] +#[cfg(feature = "uio")] use crate::sys::time::TimeVal; -use crate::sys::uio::IoVec; +use crate::{errno::Errno, Result}; +use cfg_if::cfg_if; +use libc::{self, c_int, c_void, size_t, socklen_t}; +#[cfg(all(feature = "uio", not(target_os = "redox")))] +use libc::{ + iovec, CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_NXTHDR, CMSG_SPACE, +}; +#[cfg(not(target_os = "redox"))] +use std::io::{IoSlice, IoSliceMut}; +#[cfg(feature = "net")] +use std::net; +use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, RawFd, OwnedFd}; +use std::{mem, ptr}; +#[deny(missing_docs)] mod addr; #[deny(missing_docs)] pub mod sockopt; @@ -23,49 +32,47 @@ pub mod sockopt; * */ -#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] -pub use self::addr::{ - AddressFamily, - SockAddr, - InetAddr, - UnixAddr, - IpAddr, - Ipv4Addr, - Ipv6Addr, - LinkAddr, -}; +pub use self::addr::{SockaddrLike, SockaddrStorage}; + #[cfg(any(target_os = "illumos", target_os = "solaris"))] -pub use self::addr::{ - AddressFamily, - SockAddr, - InetAddr, - UnixAddr, - IpAddr, - Ipv4Addr, - Ipv6Addr, -}; +pub use self::addr::{AddressFamily, UnixAddr}; +#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] +pub use self::addr::{AddressFamily, UnixAddr}; +#[cfg(not(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku", + target_os = "redox", +)))] +#[cfg(feature = "net")] +pub use self::addr::{LinkAddr, SockaddrIn, SockaddrIn6}; +#[cfg(any( + target_os = "illumos", + target_os = "solaris", + target_os = "haiku", + target_os = "redox", +))] +#[cfg(feature = "net")] +pub use self::addr::{SockaddrIn, SockaddrIn6}; -#[cfg(any(target_os = "android", target_os = "linux"))] -pub use crate::sys::socket::addr::netlink::NetlinkAddr; #[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", target_os = "macos"))] pub use crate::sys::socket::addr::vsock::VsockAddr; -pub use libc::{ - cmsghdr, - msghdr, - sa_family_t, - sockaddr, - sockaddr_in, - sockaddr_in6, - sockaddr_storage, - sockaddr_un, -}; +#[cfg(all(feature = "uio", not(target_os = "redox")))] +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) @@ -86,11 +93,32 @@ pub enum SockType { /// entire packet with each input system call. SeqPacket = libc::SOCK_SEQPACKET, /// Provides raw network protocol access. + #[cfg(not(target_os = "redox"))] Raw = libc::SOCK_RAW, /// Provides a reliable datagram layer that does not /// guarantee ordering. + #[cfg(not(any(target_os = "haiku", target_os = "redox")))] 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), + #[cfg(not(target_os = "redox"))] + libc::SOCK_RAW => Ok(Self::Raw), + #[cfg(not(any(target_os = "haiku", target_os = "redox")))] + 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. @@ -102,75 +130,149 @@ pub enum SockProtocol { 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, + /// Netfilter/iptables ULOG. + /// ([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())))] + NetlinkNFLOG = libc::NETLINK_NFLOG, /// 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, + /// Generic netlink family for simplified netlink usage. + /// ([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())))] + NetlinkGeneric = libc::NETLINK_GENERIC, /// 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 as u16).to_be() as i32, + /// The Controller Area Network raw socket protocol + /// ([ref](https://docs.kernel.org/networking/can.html#how-to-use-socketcan)) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + CanRaw = libc::CAN_RAW, +} + +impl SockProtocol { + /// The Controller Area Network broadcast manager protocol + /// ([ref](https://docs.kernel.org/networking/can.html#how-to-use-socketcan)) + #[cfg(target_os = "linux")] + #[cfg_attr(docsrs, doc(cfg(all())))] + #[allow(non_upper_case_globals)] + pub const CanBcm: SockProtocol = SockProtocol::NetlinkUserSock; // Matches libc::CAN_BCM +} +#[cfg(any(target_os = "android", 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: libc::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 transmitting timestamps as reported by hardware + SOF_TIMESTAMPING_TX_HARDWARE; + /// Collect transmitting 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; + /// Generate a unique identifier along with each transmitted packet + SOF_TIMESTAMPING_OPT_ID; + /// Return transmit timestamps alongside an empty packet instead of the original packet + SOF_TIMESTAMPING_OPT_TSONLY; + } } -libc_bitflags!{ +libc_bitflags! { /// Additional socket options pub struct SockFlag: c_int { /// Set non-blocking mode on the new socket @@ -181,6 +283,7 @@ libc_bitflags!{ 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", @@ -190,18 +293,21 @@ libc_bitflags!{ 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!{ +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 @@ -226,6 +332,8 @@ libc_bitflags!{ /// 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. + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] MSG_DONTWAIT; /// Receive flags: Control Data was discarded (buffer too small) MSG_CTRUNC; @@ -245,6 +353,7 @@ libc_bitflags!{ /// 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())))] 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 @@ -259,7 +368,33 @@ libc_bitflags!{ target_os = "linux", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] 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())))] + MSG_NOSIGNAL; + /// Turns on [`MSG_DONTWAIT`] after the first message has been received (only for + /// `recvmmsg()`). + #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "linux", + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] + MSG_WAITFORONE; } } @@ -276,11 +411,14 @@ cfg_if! { impl UnixCredentials { /// Creates a new instance with the credentials of the current process pub fn new() -> Self { - UnixCredentials(libc::ucred { - pid: crate::unistd::getpid().as_raw(), - uid: crate::unistd::getuid().as_raw(), - gid: crate::unistd::getgid().as_raw(), - }) + // 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 @@ -347,7 +485,12 @@ cfg_if! { /// 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 _) } + unsafe { + std::slice::from_raw_parts( + self.0.cmcred_groups.as_ptr() as *const libc::gid_t, + self.0.cmcred_ngroups as _ + ) + } } } @@ -359,7 +502,7 @@ cfg_if! { } } -cfg_if!{ +cfg_if! { if #[cfg(any( target_os = "dragonfly", target_os = "freebsd", @@ -391,6 +534,8 @@ cfg_if!{ } } +feature! { +#![feature = "net"] /// Request for multicast socket operations /// /// This is a wrapper type around `ip_mreq`. @@ -402,10 +547,16 @@ impl IpMembershipRequest { /// Instantiate a new `IpMembershipRequest` /// /// If `interface` is `None`, then `Ipv4Addr::any()` will be used for the interface. - pub fn new(group: Ipv4Addr, interface: Option) -> Self { + 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: group.0, - imr_interface: interface.unwrap_or_else(Ipv4Addr::any).0, + imr_multiaddr: ipv4addr_to_libc(group), + imr_interface: ipv4addr_to_libc(imr_addr) }) } } @@ -419,13 +570,18 @@ pub struct Ipv6MembershipRequest(libc::ipv6_mreq); impl Ipv6MembershipRequest { /// Instantiate a new `Ipv6MembershipRequest` - pub const fn new(group: Ipv6Addr) -> Self { + pub const fn new(group: net::Ipv6Addr) -> Self { Ipv6MembershipRequest(libc::ipv6_mreq { - ipv6mr_multiaddr: group.0, + ipv6mr_multiaddr: ipv6addr_to_libc(&group), ipv6mr_interface: 0, }) } } +} + +#[cfg(not(target_os = "redox"))] +feature! { +#![feature = "uio"] /// Create a buffer large enough for storing some control messages as returned /// by [`recvmsg`](fn.recvmsg.html). @@ -453,28 +609,34 @@ impl Ipv6MembershipRequest { 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; - )* + let space = 0 $(+ $crate::sys::socket::cmsg_space::<$x>())*; Vec::::with_capacity(space) } } } +#[inline] +#[doc(hidden)] +pub fn cmsg_space() -> usize { + // SAFETY: CMSG_SPACE is always safe + unsafe { libc::CMSG_SPACE(mem::size_of::() as libc::c_uint) as usize } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RecvMsg<'a> { +/// 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 address: Option, pub flags: MsgFlags, + iobufs: std::marker::PhantomData<& 's()>, mhdr: msghdr, } -impl<'a> RecvMsg<'a> { +impl<'a, S> RecvMsg<'a, '_, S> { /// Iterate over the valid control messages pointed to by this /// msghdr. pub fn cmsgs(&self) -> CmsgIterator { @@ -531,9 +693,11 @@ pub enum ControlMessageOwned { 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. @@ -546,9 +710,11 @@ pub enum ControlMessageOwned { /// ``` /// # #[macro_use] extern crate nix; /// # use nix::sys::socket::*; - /// # use nix::sys::uio::IoVec; /// # use nix::sys::time::*; + /// # use std::io::{IoSlice, IoSliceMut}; /// # use std::time::*; + /// # use std::str::FromStr; + /// # use std::os::unix::io::AsRawFd; /// # fn main() { /// // Set up /// let message = "Ohayō!".as_bytes(); @@ -557,22 +723,23 @@ pub enum ControlMessageOwned { /// SockType::Datagram, /// SockFlag::empty(), /// None).unwrap(); - /// setsockopt(in_socket, sockopt::ReceiveTimestamp, &true).unwrap(); - /// let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); - /// bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); - /// let address = getsockname(in_socket).unwrap(); + /// setsockopt(&in_socket, sockopt::ReceiveTimestamp, &true).unwrap(); + /// let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + /// bind(in_socket.as_raw_fd(), &localhost).unwrap(); + /// let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); /// // Get initial time /// let time0 = SystemTime::now(); /// // Send the message - /// let iov = [IoVec::from_slice(message)]; + /// let iov = [IoSlice::new(message)]; /// let flags = MsgFlags::empty(); - /// let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + /// let l = sendmsg(in_socket.as_raw_fd(), &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 iov = [IoVec::from_mut_slice(&mut buffer)]; - /// let r = recvmsg(in_socket, &iov, Some(&mut cmsgspace), flags).unwrap(); + /// let mut iov = [IoSliceMut::new(&mut buffer)]; + /// let r = recvmsg::(in_socket.as_raw_fd(), &mut iov, Some(&mut cmsgspace), flags) + /// .unwrap(); /// let rtime = match r.cmsgs().next() { /// Some(ControlMessageOwned::ScmTimestamp(rtime)) => rtime, /// Some(_) => panic!("Unexpected control message"), @@ -587,14 +754,19 @@ pub enum ControlMessageOwned { /// 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(any(target_os = "android", 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(any(target_os = "android", target_os = "linux"))] + #[cfg_attr(docsrs, doc(cfg(all())))] ScmTimestampns(TimeSpec), #[cfg(any( target_os = "android", @@ -603,6 +775,8 @@ pub enum ControlMessageOwned { 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", @@ -614,6 +788,8 @@ pub enum ControlMessageOwned { 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", @@ -622,6 +798,8 @@ pub enum ControlMessageOwned { 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", @@ -630,7 +808,17 @@ pub enum ControlMessageOwned { 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. @@ -641,6 +829,8 @@ pub enum ControlMessageOwned { /// `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 @@ -652,13 +842,18 @@ pub enum ControlMessageOwned { /// `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. @@ -666,6 +861,18 @@ pub enum ControlMessageOwned { Unknown(UnknownCmsg), } +/// For representing packet timestamps via `SO_TIMESTAMPING` interface +#[cfg(any(target_os = "android", 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. /// @@ -679,6 +886,8 @@ impl ControlMessageOwned { 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) { @@ -701,15 +910,28 @@ impl ControlMessageOwned { let cred: libc::cmsgcred = ptr::read_unaligned(p as *const _); ControlMessageOwned::ScmCreds(cred.into()) } + #[cfg(not(any(target_os = "aix", 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"))] + #[cfg(any(target_os = "android", 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(any(target_os = "android", 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", @@ -717,6 +939,7 @@ impl ControlMessageOwned { 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) @@ -728,6 +951,7 @@ impl ControlMessageOwned { 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) @@ -739,6 +963,7 @@ impl ControlMessageOwned { 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) @@ -750,11 +975,19 @@ impl ControlMessageOwned { 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) @@ -765,17 +998,25 @@ impl ControlMessageOwned { 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 sl = std::slice::from_raw_parts(p, len); let ucmsg = UnknownCmsg(*header, Vec::::from(sl)); ControlMessageOwned::Unknown(ucmsg) } @@ -783,6 +1024,8 @@ impl ControlMessageOwned { } #[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); @@ -832,6 +1075,7 @@ pub enum ControlMessage<'a> { /// 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. @@ -846,6 +1090,7 @@ pub enum ControlMessage<'a> { /// 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. @@ -856,6 +1101,7 @@ pub enum ControlMessage<'a> { 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` @@ -866,6 +1112,7 @@ pub enum ControlMessage<'a> { 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. @@ -876,6 +1123,7 @@ pub enum ControlMessage<'a> { 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 @@ -887,6 +1135,8 @@ pub enum ControlMessage<'a> { /// 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 @@ -898,6 +1148,8 @@ pub enum ControlMessage<'a> { 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 @@ -910,15 +1162,37 @@ pub enum ControlMessage<'a> { 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 @@ -998,21 +1272,32 @@ impl<'a> ControlMessage<'a> { 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( @@ -1039,7 +1324,7 @@ impl<'a> ControlMessage<'a> { } #[cfg(any(target_os = "android", target_os = "linux"))] ControlMessage::AlgSetIv(iv) => { - mem::size_of_val(&iv) + iv.len() + mem::size_of::<&[u8]>() + iv.len() }, #[cfg(any(target_os = "android", target_os = "linux"))] ControlMessage::AlgSetOp(op) => { @@ -1050,21 +1335,32 @@ impl<'a> ControlMessage<'a> { 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) + }, } } @@ -1080,17 +1376,26 @@ impl<'a> ControlMessage<'a> { 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, } } @@ -1115,21 +1420,32 @@ impl<'a> ControlMessage<'a> { 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 + }, } } @@ -1149,8 +1465,44 @@ impl<'a> ControlMessage<'a> { /// as with sendto. /// /// Allocates if cmsgs is nonempty. -pub fn sendmsg(fd: RawFd, iov: &[IoVec<&[u8]>], cmsgs: &[ControlMessage], - flags: MsgFlags, addr: Option<&SockAddr>) -> Result +/// +/// # 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; +/// # use std::os::unix::io::AsRawFd; +/// 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.as_raw_fd(), &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; +/// # use std::os::unix::io::AsRawFd; +/// 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.as_raw_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(); @@ -1158,30 +1510,13 @@ pub fn sendmsg(fd: RawFd, iov: &[IoVec<&[u8]>], cmsgs: &[ControlMessage], // 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 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) } -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", -))] -#[derive(Debug)] -pub struct SendMmsgData<'a, I, C> - where - I: AsRef<[IoVec<&'a [u8]>]>, - C: AsRef<[ControlMessage<'a>]> -{ - pub iov: I, - pub cmsgs: C, - pub addr: Option, - pub _lt: std::marker::PhantomData<&'a I>, -} /// An extension of `sendmsg` that allows the caller to transmit multiple /// messages on a socket using a single system call. This has performance @@ -1206,50 +1541,66 @@ pub struct SendMmsgData<'a, I, C> target_os = "freebsd", target_os = "netbsd", ))] -pub fn sendmmsg<'a, I, C>( +pub fn sendmmsg<'a, XS, AS, C, I, S>( fd: RawFd, - data: impl std::iter::IntoIterator>, + data: &'a mut MultiHeaders, + slices: XS, + // one address per group of slices + addrs: AS, + // shared across all the messages + cmsgs: C, flags: MsgFlags -) -> Result> +) -> crate::Result> where - I: AsRef<[IoVec<&'a [u8]>]> + 'a, + XS: IntoIterator, + AS: AsRef<[Option]>, + I: AsRef<[IoSlice<'a>]> + 'a, C: AsRef<[ControlMessage<'a>]> + 'a, + S: SockaddrLike + 'a { - let iter = data.into_iter(); - let size_hint = iter.size_hint(); - let reserve_items = size_hint.1.unwrap_or(size_hint.0); + let mut count = 0; - let mut output = Vec::::with_capacity(reserve_items); - let mut cmsgs_buffers = Vec::>::with_capacity(reserve_items); - - for d in iter { - let capacity: usize = d.cmsgs.as_ref().iter().map(|c| c.space()).sum(); - let mut cmsgs_buffer = vec![0u8; capacity]; - - output.push(libc::mmsghdr { - msg_hdr: pack_mhdr_to_send( - &mut cmsgs_buffer, - &d.iov, - &d.cmsgs, - d.addr.as_ref() - ), - msg_len: 0, - }); - cmsgs_buffers.push(cmsgs_buffer); - }; + for (i, ((slice, addr), mmsghdr)) in slices.into_iter().zip(addrs.as_ref()).zip(data.items.iter_mut() ).enumerate() { + let 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 _; - let ret = unsafe { libc::sendmmsg(fd, output.as_mut_ptr(), output.len() as _, flags.bits() 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 _; - let sent_messages = Errno::result(ret)? as usize; - let mut sent_bytes = Vec::with_capacity(sent_messages); + // 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) }; + } - for item in &output { - sent_bytes.push(item.msg_len as usize); + count = i+1; } - Ok(sent_bytes) + 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 + }) + } @@ -1260,131 +1611,347 @@ pub fn sendmmsg<'a, I, C>( target_os = "netbsd", ))] #[derive(Debug)] -pub struct RecvMmsgData<'a, I> +/// 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 + _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 - I: AsRef<[IoVec<&'a mut [u8]>]> + 'a, -{ - pub iov: I, - pub cmsg_buffer: Option<&'a mut Vec>, + 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 mut 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 &mut cmsg_buffers { + Some(v) => ((&mut v[ix * msg_controllen] as *mut u8), msg_controllen), + None => (std::ptr::null_mut(), 0), + }; + let msg_hdr = unsafe { pack_mhdr_to_receive(std::ptr::null_mut(), 0, ptr, cap, address.as_mut_ptr()) }; + libc::mmsghdr { + msg_hdr, + msg_len: 0, + } + }) + .collect::>(); + + Self { + items: items.into_boxed_slice(), + addresses, + _cmsg_buffers: 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. -/// -/// `iov` and `cmsg_buffer` should be constructed similarly to `recvmsg` +/// An extension of recvmsg that allows the caller to receive multiple messages from a socket using a single system call. /// -/// Multiple allocations are performed +/// This has performance benefits for some applications. /// -/// # Arguments +/// This method performs no allocations. /// -/// * `fd`: Socket file descriptor -/// * `data`: Struct that implements `IntoIterator` with `RecvMmsgData` items -/// * `flags`: Optional flags passed directly to the operating system. -/// -/// # RecvMmsgData -/// -/// * `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) +/// 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]. /// -/// # Returns -/// A `Vec` with multiple `RecvMsg`, one per received message +/// # 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. /// -/// # References -/// - [`recvmsg`](fn.recvmsg.html) -/// - [`RecvMsg`](struct.RecvMsg.html) +/// 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", ))] -#[allow(clippy::needless_collect)] // Complicated false positive -pub fn recvmmsg<'a, I>( +pub fn recvmmsg<'a, XS, S, I>( fd: RawFd, - data: impl std::iter::IntoIterator, - IntoIter=impl ExactSizeIterator + Iterator>>, + data: &'a mut MultiHeaders, + slices: XS, flags: MsgFlags, - timeout: Option -) -> Result>> - where - I: AsRef<[IoVec<&'a mut [u8]>]> + 'a, + mut timeout: Option, +) -> crate::Result> +where + XS: IntoIterator, + I: AsRef<[IoSliceMut<'a>]> + 'a, { - let iter = data.into_iter(); + let mut count = 0; + for (i, (slice, mmsghdr)) in slices.into_iter().zip(data.items.iter_mut()).enumerate() { + let 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 num_messages = iter.len(); + let timeout_ptr = timeout + .as_mut() + .map_or_else(std::ptr::null_mut, |t| t as *mut _ as *mut libc::timespec); - let mut output: Vec = Vec::with_capacity(num_messages); + 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, + }) +} - // Addresses should be pre-allocated. pack_mhdr_to_receive will store them - // as raw pointers, so we may not move them. Turn the vec into a boxed - // slice so we won't inadvertently reallocate the vec. - let mut addresses = vec![mem::MaybeUninit::uninit(); num_messages] - .into_boxed_slice(); +#[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, +} - let results: Vec<_> = iter.enumerate().map(|(i, d)| { - let (msg_controllen, mhdr) = unsafe { - pack_mhdr_to_receive( - d.iov.as_ref(), - &mut d.cmsg_buffer, - addresses[i].as_mut_ptr(), +#[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; + use std::os::unix::io::AsRawFd; + + #[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}; - output.push( - libc::mmsghdr { - msg_hdr: mhdr, - msg_len: 0, - } - ); + let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); - (msg_controllen as usize, &mut d.cmsg_buffer) - }).collect(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + )?; - let timeout = if let Some(mut t) = timeout { - t.as_mut() as *mut libc::timespec - } else { - ptr::null_mut() - }; + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::SOCK_NONBLOCK, + None, + )?; + + crate::sys::socket::bind(rsock.as_raw_fd(), &sock_addr)?; - let ret = unsafe { libc::recvmmsg(fd, output.as_mut_ptr(), output.len() as _, flags.bits() as _, timeout) }; - - let _ = Errno::result(ret)?; - - Ok(output - .into_iter() - .take(ret as usize) - .zip(addresses.iter().map(|addr| unsafe{addr.assume_init()})) - .zip(results.into_iter()) - .map(|((mmsghdr, address), (msg_controllen, cmsg_buffer))| { - unsafe { - read_mhdr( - mmsghdr.msg_hdr, - mmsghdr.msg_len as isize, - msg_controllen, - address, - cmsg_buffer - ) + 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.as_raw_fd(), &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.as_raw_fd(), &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; + } + } } - }) - .collect()) -} -unsafe fn read_mhdr<'a, 'b>( + #[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: sockaddr_storage, - cmsg_buffer: &'a mut Option<&'b mut Vec> -) -> RecvMsg<'b> { + mut 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 { - // got control message(s) - cmsg_buffer - .as_mut() - .unwrap() - .set_len(mhdr.msg_controllen as usize); debug_assert!(!mhdr.msg_control.is_null()); debug_assert!(msg_controllen >= mhdr.msg_controllen as usize); CMSG_FIRSTHDR(&mhdr as *const msghdr) @@ -1393,74 +1960,72 @@ unsafe fn read_mhdr<'a, 'b>( }.as_ref() }; - let address = sockaddr_storage_to_addr( - &address , - mhdr.msg_namelen as usize - ).ok(); + // Ignore errors if this socket address has statically-known length + // + // This is to ensure that unix socket addresses have their length set appropriately. + let _ = address.set_length(mhdr.msg_namelen as usize); RecvMsg { bytes: r as usize, cmsghdr, - address, + address: Some(address), flags: MsgFlags::from_bits_truncate(mhdr.msg_flags), mhdr, + iobufs: std::marker::PhantomData, } } -unsafe fn pack_mhdr_to_receive<'a, I>( - iov: I, - cmsg_buffer: &mut Option<&mut Vec>, - address: *mut sockaddr_storage, -) -> (usize, msghdr) +/// 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: *mut IoSliceMut, + iov_buffer_len: usize, + cmsg_buffer: *mut u8, + cmsg_capacity: usize, + address: *mut S, +) -> msghdr where - I: AsRef<[IoVec<&'a mut [u8]>]> + 'a, + S: SockaddrLike { - let (msg_control, msg_controllen) = cmsg_buffer.as_mut() - .map(|v| (v.as_mut_ptr(), v.capacity())) - .unwrap_or((ptr::null_mut(), 0)); - - let mhdr = { - // 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 c_void; - (*p).msg_namelen = mem::size_of::() as socklen_t; - (*p).msg_iov = iov.as_ref().as_ptr() as *mut iovec; - (*p).msg_iovlen = iov.as_ref().len() as _; - (*p).msg_control = msg_control as *mut c_void; - (*p).msg_controllen = msg_controllen as _; - (*p).msg_flags = 0; - mhdr.assume_init() - }; - - (msg_controllen, mhdr) + // 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 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>( +fn pack_mhdr_to_send<'a, I, C, S>( cmsg_buffer: &mut [u8], iov: I, cmsgs: C, - addr: Option<&SockAddr> + addr: Option<&S> ) -> msghdr where - I: AsRef<[IoVec<&'a [u8]>]>, - C: AsRef<[ControlMessage<'a>]> + I: AsRef<[IoSlice<'a>]>, + C: AsRef<[ControlMessage<'a>]>, + S: SockaddrLike + 'a { let capacity = cmsg_buffer.len(); - // Next encode the sending address, if provided - let (name, namelen) = match addr { - Some(addr) => { - let (x, y) = addr.as_ffi_pair(); - (x as *const _, y) - }, - None => (ptr::null(), 0), - }; - // The message header must be initialized before the individual cmsgs. let cmsg_ptr = if capacity > 0 { - cmsg_buffer.as_ptr() as *mut c_void + cmsg_buffer.as_mut_ptr() as *mut c_void } else { ptr::null_mut() }; @@ -1470,8 +2035,8 @@ fn pack_mhdr_to_send<'a, I, C>( // initialize it. let mut mhdr = mem::MaybeUninit::::zeroed(); let p = mhdr.as_mut_ptr(); - (*p).msg_name = name as *mut _; - (*p).msg_namelen = namelen; + (*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 _; @@ -1507,28 +2072,33 @@ fn pack_mhdr_to_send<'a, I, C>( /// * `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) +/// [`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>(fd: RawFd, iov: &[IoVec<&mut [u8]>], +pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'inner>], mut cmsg_buffer: Option<&'a mut Vec>, - flags: MsgFlags) -> Result> + flags: MsgFlags) -> Result> + where S: SockaddrLike + 'a, + 'inner: 'outer { let mut address = mem::MaybeUninit::uninit(); - let (msg_controllen, mut mhdr) = unsafe { - pack_mhdr_to_receive(&iov, &mut cmsg_buffer, address.as_mut_ptr()) + 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_mut().as_mut_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(), &mut cmsg_buffer) }) + Ok(unsafe { read_mhdr(mhdr, r, msg_controllen, address.assume_init()) }) +} } - /// Create an endpoint for communication /// @@ -1540,7 +2110,12 @@ pub fn recvmsg<'a>(fd: RawFd, iov: &[IoVec<&mut [u8]>], /// 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 { +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, @@ -1554,14 +2129,24 @@ pub fn socket>>(domain: AddressFamily, ty: SockType let res = unsafe { libc::socket(domain as c_int, ty, protocol) }; - Errno::result(res) + match res { + -1 => Err(Errno::last()), + fd => { + // Safe because libc::socket returned success + unsafe { Ok(OwnedFd::from_raw_fd(fd)) } + } + } } /// 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)> { +pub fn socketpair>>( + domain: AddressFamily, + ty: SockType, + protocol: T, + flags: SockFlag, +) -> Result<(OwnedFd, OwnedFd)> { let protocol = match protocol.into() { None => 0, Some(p) => p as c_int, @@ -1575,17 +2160,23 @@ pub fn socketpair>>(domain: AddressFamily, ty: Sock let mut fds = [-1, -1]; - let res = unsafe { libc::socketpair(domain as c_int, ty, protocol, fds.as_mut_ptr()) }; + let res = unsafe { + libc::socketpair(domain as c_int, ty, protocol, fds.as_mut_ptr()) + }; Errno::result(res)?; - Ok((fds[0], fds[1])) + // Safe because socketpair returned success. + unsafe { + Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(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) }; +pub fn listen(sock: &F, backlog: usize) -> Result<()> { + let fd = sock.as_fd().as_raw_fd(); + let res = unsafe { libc::listen(fd, backlog as c_int) }; Errno::result(res).map(drop) } @@ -1593,11 +2184,8 @@ pub fn listen(sockfd: RawFd, backlog: usize) -> Result<()> { /// Bind a name to a socket /// /// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html) -pub fn bind(fd: RawFd, addr: &SockAddr) -> Result<()> { - let res = unsafe { - let (ptr, len) = addr.as_ffi_pair(); - libc::bind(fd, ptr, len) - }; +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) } @@ -1614,19 +2202,28 @@ pub fn accept(sockfd: RawFd) -> Result { /// 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 = "freebsd", - target_os = "linux", - target_os = "openbsd"))] +#[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()) }; + let res = unsafe { + libc::accept4(sockfd, ptr::null_mut(), ptr::null_mut(), flags.bits()) + }; Errno::result(res) } @@ -1634,11 +2231,8 @@ pub fn accept4(sockfd: RawFd, flags: SockFlag) -> Result { /// Initiate a connection on a socket /// /// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html) -pub fn connect(fd: RawFd, addr: &SockAddr) -> Result<()> { - let res = unsafe { - let (ptr, len) = addr.as_ffi_pair(); - libc::connect(fd, ptr, len) - }; +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) } @@ -1651,9 +2245,10 @@ 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.as_mut_ptr() as *mut c_void, buf.len() as size_t, - flags.bits()); + flags.bits(), + ); Errno::result(ret).map(|r| r as usize) } @@ -1664,36 +2259,51 @@ pub fn recv(sockfd: RawFd, buf: &mut [u8], flags: MsgFlags) -> Result { /// 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)> -{ +pub fn recvfrom( + sockfd: RawFd, + buf: &mut [u8], +) -> Result<(usize, Option)> { unsafe { - let mut addr: sockaddr_storage = mem::zeroed(); - let mut len = mem::size_of::() as socklen_t; + 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.as_mut_ptr() as *mut c_void, buf.len() as size_t, 0, - &mut addr as *mut libc::sockaddr_storage as *mut libc::sockaddr, - &mut len as *mut socklen_t))? as usize; - - match sockaddr_storage_to_addr(&addr, len as usize) { - Err(Errno::ENOTCONN) => Ok((ret, None)), - Ok(addr) => Ok((ret, Some(addr))), - Err(e) => Err(e) - } + addr.as_mut_ptr() as *mut sockaddr, + &mut len as *mut socklen_t, + ))? as usize; + + Ok(( + ret, + T::from_raw( + addr.assume_init().as_ptr(), + 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: &SockAddr, flags: MsgFlags) -> Result { +pub fn sendto( + fd: RawFd, + buf: &[u8], + addr: &dyn SockaddrLike, + flags: MsgFlags, +) -> Result { let ret = unsafe { - let (ptr, len) = addr.as_ffi_pair(); - libc::sendto(fd, buf.as_ptr() as *const c_void, buf.len() as size_t, flags.bits(), ptr, len) + 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) @@ -1704,7 +2314,12 @@ pub fn sendto(fd: RawFd, buf: &[u8], addr: &SockAddr, flags: MsgFlags) -> Result /// [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()) + 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) @@ -1717,25 +2332,25 @@ pub fn send(fd: RawFd, buf: &[u8], flags: MsgFlags) -> Result { */ /// Represents a socket option that can be retrieved. -pub trait GetSockOpt : Copy { +pub trait GetSockOpt: Copy { type Val; /// Look up the value of this socket option on the given socket. - fn get(&self, fd: RawFd) -> Result; + fn get(&self, fd: &F) -> Result; } /// Represents a socket option that can be set. -pub trait SetSockOpt : Clone { +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<()>; + fn set(&self, fd: &F, 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 { +pub fn getsockopt(fd: &F, opt: O) -> Result { opt.get(fd) } @@ -1749,137 +2364,54 @@ pub fn getsockopt(fd: RawFd, opt: O) -> Result { /// 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); +/// let fd = listener; +/// let res = setsockopt(&fd, KeepAlive, &true); /// assert!(res.is_ok()); /// ``` -pub fn setsockopt(fd: RawFd, opt: O, val: &O::Val) -> Result<()> { +pub fn setsockopt( + fd: &F, + 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 { +pub fn getpeername(fd: RawFd) -> Result { unsafe { - let mut addr = mem::MaybeUninit::uninit(); - let mut len = mem::size_of::() as socklen_t; + 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 - ); + let ret = + libc::getpeername(fd, addr.as_mut_ptr() as *mut sockaddr, &mut len); Errno::result(ret)?; - sockaddr_storage_to_addr(&addr.assume_init(), len as usize) + 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 { +pub fn getsockname(fd: RawFd) -> Result { unsafe { - let mut addr = mem::MaybeUninit::uninit(); - let mut len = mem::size_of::() as socklen_t; + 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 - ); + let ret = + libc::getsockname(fd, addr.as_mut_ptr() as *mut sockaddr, &mut len); Errno::result(ret)?; - sockaddr_storage_to_addr(&addr.assume_init(), len as usize) - } -} - -/// 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. -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) { - libc::AF_INET => { - assert!(len as usize >= mem::size_of::()); - let sin = unsafe { - *(addr as *const sockaddr_storage as *const sockaddr_in) - }; - Ok(SockAddr::Inet(InetAddr::V4(sin))) - } - libc::AF_INET6 => { - assert!(len as usize >= mem::size_of::()); - let sin6 = unsafe { - *(addr as *const _ as *const sockaddr_in6) - }; - Ok(SockAddr::Inet(InetAddr::V6(sin6))) - } - libc::AF_UNIX => { - let pathlen = len - offset_of!(sockaddr_un, sun_path); - unsafe { - let sun = *(addr as *const _ as *const sockaddr_un); - Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, pathlen))) - } - } - #[cfg(any(target_os = "android", target_os = "linux"))] - 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), + T::from_raw(addr.assume_init().as_ptr(), Some(len)).ok_or(Errno::EINVAL) } } - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Shutdown { /// Further receptions will be disallowed. @@ -1898,9 +2430,9 @@ pub fn shutdown(df: RawFd, how: Shutdown) -> Result<()> { use libc::shutdown; let how = match how { - Shutdown::Read => libc::SHUT_RD, + Shutdown::Read => libc::SHUT_RD, Shutdown::Write => libc::SHUT_WR, - Shutdown::Both => libc::SHUT_RDWR, + Shutdown::Both => libc::SHUT_RDWR, }; Errno::result(shutdown(df, how)).map(drop) @@ -1909,8 +2441,25 @@ pub fn shutdown(df: RawFd, how: Shutdown) -> Result<()> { #[cfg(test)] mod tests { + #[cfg(not(target_os = "redox"))] #[test] fn can_use_cmsg_space() { let _ = cmsg_space!(u8); } + + #[cfg(not(any( + target_os = "redox", + target_os = "linux", + target_os = "android" + )))] + #[test] + fn can_open_routing_socket() { + let _ = super::socket( + super::AddressFamily::Route, + super::SockType::Raw, + super::SockFlag::empty(), + None, + ) + .expect("Failed to open routing socket"); + } } diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index fcb4be81be..44f3ebbc1d 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -1,22 +1,20 @@ //! Socket options as used by `setsockopt` and `getsockopt`. -use cfg_if::cfg_if; use super::{GetSockOpt, SetSockOpt}; -use crate::Result; 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::mem::{ - self, - MaybeUninit -}; -use std::os::unix::io::RawFd; use std::ffi::{OsStr, OsString}; +use std::mem::{self, MaybeUninit}; #[cfg(target_family = "unix")] use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::{AsFd, AsRawFd}; // Constants // TCP_CA_NAME_MAX isn't defined in user space include files -#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[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 @@ -46,18 +44,22 @@ macro_rules! setsockopt_impl { impl SetSockOpt for $name { type Val = $ty; - fn set(&self, fd: RawFd, val: &$ty) -> Result<()> { + fn set(&self, fd: &F, val: &$ty) -> Result<()> { unsafe { let setter: $setter = Set::new(val); - let res = libc::setsockopt(fd, $level, $flag, - setter.ffi_ptr(), - setter.ffi_len()); + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + $level, + $flag, + setter.ffi_ptr(), + setter.ffi_len(), + ); Errno::result(res).map(drop) } } } - } + }; } /// Helper for implementing `GetSockOpt` for a given socket option. See @@ -87,20 +89,27 @@ macro_rules! getsockopt_impl { impl GetSockOpt for $name { type Val = $ty; - fn get(&self, fd: RawFd) -> Result<$ty> { + fn get(&self, fd: &F) -> Result<$ty> { unsafe { let mut getter: $getter = Get::uninit(); - let res = libc::getsockopt(fd, $level, $flag, - getter.ffi_ptr(), - getter.ffi_len()); + let res = libc::getsockopt( + fd.as_fd().as_raw_fd(), + $level, + $flag, + getter.ffi_ptr(), + getter.ffi_len(), + ); Errno::result(res)?; - Ok(getter.assume_init()) + match <$ty>::try_from(getter.assume_init()) { + Err(_) => Err(Errno::EINVAL), + Ok(r) => Ok(r), + } } } } - } + }; } /// Helper to generate the sockopt accessors. See @@ -128,6 +137,8 @@ macro_rules! getsockopt_impl { /// * `$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(unused_macro_rules)] macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* @@ -244,14 +255,25 @@ macro_rules! sockopt_impl { sockopt_impl!( /// Enables local address reuse - ReuseAddr, Both, libc::SOL_SOCKET, libc::SO_REUSEADDR, bool + 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); + 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 @@ -259,26 +281,52 @@ sockopt_impl!( /// 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); + 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); + 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); + 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); + 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", @@ -289,185 +337,497 @@ cfg_if! { 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + KeepAlive, + Both, + libc::SOL_SOCKET, + libc::SO_KEEPALIVE, + bool +); #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "ios" + 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); + LocalPeerCred, + GetOnly, + 0, + libc::LOCAL_PEERCRED, + super::XuCred +); +#[cfg(any(target_os = "macos", target_os = "ios"))] +sockopt_impl!( + /// Get the PID of the peer process of a connected unix domain socket. + LocalPeerPid, + GetOnly, + 0, + libc::LOCAL_PEERPID, + libc::c_int +); #[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"))] + 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", - target_os = "nacl"))] + 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); + 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 { + } else if #[cfg(not(target_os = "redox"))] { sockopt_impl!( /// The maximum segment size for outgoing TCP packets. TcpMaxSeg, GetOnly, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32); } } -#[cfg(not(target_os = "openbsd"))] +#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "redox")))] +#[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"))] + 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(target_os = "openbsd"))] + TcpRepair, + Both, + libc::IPPROTO_TCP, + libc::TCP_REPAIR, + u32 +); +#[cfg(not(any(target_os = "openbsd", target_os = "haiku", target_os = "redox")))] +#[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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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]>); + 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); + 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); -sockopt_impl!( + Ip6tOriginalDst, + GetOnly, + libc::SOL_IPV6, + libc::IP6T_SO_ORIGINAL_DST, + libc::sockaddr_in6 +); +#[cfg(any(target_os = "android", 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(any(target_os = "aix", target_os = "haiku", target_os = "redox")))] +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"))] + ReceiveTimestamp, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMP, + bool +); +#[cfg(any(target_os = "android", 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); + ReceiveTimestampns, + Both, + libc::SOL_SOCKET, + libc::SO_TIMESTAMPNS, + bool +); +#[cfg(target_os = "freebsd")] +sockopt_impl!( + /// Sets a specific timestamp format instead of the classic `SCM_TIMESTAMP`, + /// to follow up after `SO_TIMESTAMP` is set. + TsClock, + Both, + libc::SOL_SOCKET, + libc::SO_TS_CLOCK, + i32 +); #[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); + 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); + 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); + BindAny, + Both, + libc::IPPROTO_IP, + libc::IP_BINDANY, + bool +); +#[cfg(target_os = "freebsd")] +sockopt_impl!( + /// Set the route table (FIB) for this socket up to the `net.fibs` OID limit + /// (more specific than the setfib command line/call which are process based). + Fib, + SetOnly, + libc::SOL_SOCKET, + libc::SO_SETFIB, + i32 +); +#[cfg(target_os = "freebsd")] +sockopt_impl!( + /// Set `so_user_cookie` for this socket allowing network traffic based + /// upon it, similar to Linux's netfilter MARK. + UserCookie, + SetOnly, + libc::SOL_SOCKET, + libc::SO_USER_COOKIE, + u32 +); +#[cfg(target_os = "openbsd")] +sockopt_impl!( + /// Set the route table for this socket, needs a privileged user if + /// the process/socket had been set to the non default route. + Rtable, + SetOnly, + libc::SOL_SOCKET, + libc::SO_RTABLE, + i32 +); +#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] +sockopt_impl!( + /// Get/set a filter on this socket before accepting connections similarly + /// to Linux's TCP_DEFER_ACCEPT but after the listen's call. + AcceptFilter, + Both, + libc::SOL_SOCKET, + libc::SO_ACCEPTFILTER, + libc::accept_filter_arg +); #[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); + 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"))] + 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]>); + TcpCongestion, + Both, + libc::IPPROTO_TCP, + libc::TCP_CONGESTION, + OsString<[u8; TCP_CA_NAME_MAX]> +); #[cfg(any( target_os = "android", target_os = "ios", @@ -475,10 +835,17 @@ sockopt_impl!( 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); + Ipv4PacketInfo, + Both, + libc::IPPROTO_IP, + libc::IP_PKTINFO, + bool +); #[cfg(any( target_os = "android", target_os = "freebsd", @@ -488,10 +855,17 @@ sockopt_impl!( 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); + Ipv6RecvPacketInfo, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVPKTINFO, + bool +); #[cfg(any( target_os = "freebsd", target_os = "ios", @@ -499,10 +873,17 @@ sockopt_impl!( 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); + Ipv4RecvIf, + Both, + libc::IPPROTO_IP, + libc::IP_RECVIF, + bool +); #[cfg(any( target_os = "freebsd", target_os = "ios", @@ -510,46 +891,165 @@ sockopt_impl!( 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. - Ipv4RecvDstAddr, Both, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, bool); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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! @@ -563,13 +1063,15 @@ pub struct AlgSetAeadAuthSize; impl SetSockOpt for AlgSetAeadAuthSize { type Val = usize; - fn set(&self, fd: RawFd, val: &usize) -> Result<()> { + fn set(&self, fd: &F, 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); + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_ALG, + libc::ALG_SET_AEAD_AUTHSIZE, + ::std::ptr::null(), + *val as libc::socklen_t, + ); Errno::result(res).map(drop) } } @@ -589,16 +1091,21 @@ impl Default for AlgSetKey { } #[cfg(any(target_os = "android", target_os = "linux"))] -impl SetSockOpt for AlgSetKey where T: AsRef<[u8]> + Clone { +impl SetSockOpt for AlgSetKey +where + T: AsRef<[u8]> + Clone, +{ type Val = T; - fn set(&self, fd: RawFd, val: &T) -> Result<()> { + fn set(&self, fd: &F, 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); + let res = libc::setsockopt( + fd.as_fd().as_raw_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) } } @@ -659,7 +1166,11 @@ impl Get for GetStruct { } unsafe fn assume_init(self) -> T { - assert_eq!(self.len as usize, mem::size_of::(), "invalid getsockopt implementation"); + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); self.val.assume_init() } } @@ -706,7 +1217,11 @@ impl Get for GetBool { } unsafe fn assume_init(self) -> bool { - assert_eq!(self.len as usize, mem::size_of::(), "invalid getsockopt implementation"); + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); self.val.assume_init() != 0 } } @@ -718,7 +1233,9 @@ struct SetBool { impl<'a> Set<'a, bool> for SetBool { fn new(val: &'a bool) -> SetBool { - SetBool { val: if *val { 1 } else { 0 } } + SetBool { + val: i32::from(*val), + } } fn ffi_ptr(&self) -> *const c_void { @@ -753,7 +1270,11 @@ impl Get for GetU8 { } unsafe fn assume_init(self) -> u8 { - assert_eq!(self.len as usize, mem::size_of::(), "invalid getsockopt implementation"); + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); self.val.assume_init() } } @@ -765,7 +1286,7 @@ struct SetU8 { impl<'a> Set<'a, u8> for SetU8 { fn new(val: &'a u8) -> SetU8 { - SetU8 { val: *val as u8 } + SetU8 { val: *val } } fn ffi_ptr(&self) -> *const c_void { @@ -800,7 +1321,11 @@ impl Get for GetUsize { } unsafe fn assume_init(self) -> usize { - assert_eq!(self.len as usize, mem::size_of::(), "invalid getsockopt implementation"); + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); self.val.assume_init() as usize } } @@ -860,7 +1385,9 @@ struct SetOsString<'a> { impl<'a> Set<'a, OsString> for SetOsString<'a> { fn new(val: &'a OsString) -> SetOsString { - SetOsString { val: val.as_os_str() } + SetOsString { + val: val.as_os_str(), + } } fn ffi_ptr(&self) -> *const c_void { @@ -872,7 +1399,6 @@ impl<'a> Set<'a, OsString> for SetOsString<'a> { } } - #[cfg(test)] mod test { #[cfg(any(target_os = "android", target_os = "linux"))] @@ -880,51 +1406,65 @@ mod 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(); + 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!(a_cred.pid() != 0); + 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(); + 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(); + 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", - target_os = "nacl"))] + #[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(); + 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(); + 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 index c8f10419c3..7e51c03a3f 100644 --- a/src/sys/stat.rs +++ b/src/sys/stat.rs @@ -1,12 +1,20 @@ -pub use libc::{dev_t, mode_t}; +#[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}; -use crate::{Result, NixPath, errno::Errno}; #[cfg(not(target_os = "redox"))] -use crate::fcntl::{AtFlags, at_rawfd}; +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; -use crate::sys::time::{TimeSpec, TimeVal}; libc_bitflags!( /// "File type" flags for `mknod` and related functions. @@ -25,35 +33,164 @@ libc_bitflags!( 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<()> { +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) + 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")))] +#[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, @@ -62,30 +199,36 @@ pub fn mknodat( 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) + 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) + ((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) + ((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) + ((major & 0xffff_f000) << 32) + | ((major & 0x0000_0fff) << 8) + | ((minor & 0xffff_ff00) << 12) + | (minor & 0x0000_00ff) } pub fn umask(mode: Mode) -> Mode { @@ -95,28 +238,24 @@ pub fn umask(mode: Mode) -> 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()) - } + 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()}) + 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()) - } + 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()}) + Ok(unsafe { dst.assume_init() }) } pub fn fstat(fd: RawFd) -> Result { @@ -125,19 +264,29 @@ pub fn fstat(fd: RawFd) -> Result { Errno::result(res)?; - Ok(unsafe{dst.assume_init()}) + Ok(unsafe { dst.assume_init() }) } #[cfg(not(target_os = "redox"))] -pub fn fstatat(dirfd: RawFd, pathname: &P, f: AtFlags) -> Result { +#[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) } + 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()}) + Ok(unsafe { dst.assume_init() }) } /// Change the file permission bits of the file specified by a file descriptor. @@ -175,17 +324,17 @@ pub enum FchmodatFlags { /// /// [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 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), @@ -208,7 +357,11 @@ pub fn fchmodat( /// # References /// /// [utimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html). -pub fn utimes(path: &P, atime: &TimeVal, mtime: &TimeVal) -> Result<()> { +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]) @@ -227,13 +380,20 @@ pub fn utimes(path: &P, atime: &TimeVal, mtime: &TimeVal) - /// # 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"))] -pub fn lutimes(path: &P, atime: &TimeVal, mtime: &TimeVal) -> Result<()> { +#[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]) @@ -280,18 +440,18 @@ pub enum UtimensatFlags { /// /// [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 + flag: UtimensatFlags, ) -> Result<()> { - let atflag = - match flag { - UtimensatFlags::FollowSymlink => AtFlags::empty(), - UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, - }; + 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( @@ -306,9 +466,14 @@ pub fn utimensat( } #[cfg(not(target_os = "redox"))] -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) } +#[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 index 829be57f63..5111df2e6e 100644 --- a/src/sys/statfs.rs +++ b/src/sys/statfs.rs @@ -1,25 +1,62 @@ //! Get filesystem statistics, non-portably //! //! See [`statvfs`](crate::sys::statvfs) for a portable alternative. -use std::fmt::{self, Debug}; -use std::mem; -use std::os::unix::io::AsRawFd; #[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::{AsFd, AsRawFd}; + +use cfg_if::cfg_if; -use crate::{NixPath, Result, errno::Errno}; +#[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(libc::statfs); +pub struct Statfs(type_of_statfs); #[cfg(target_os = "freebsd")] type fs_type_t = u32; @@ -29,7 +66,16 @@ type fs_type_t = libc::c_ulong; 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", not(any(target_arch = "s390x", target_env = "musl"))))] +#[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. @@ -38,101 +84,218 @@ type fs_type_t = libc::__fsword_t; 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"))), + 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(all(target_os = "linux", not(target_env = "musl")))] +#[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 ADFS_SUPER_MAGIC: FsType = FsType(libc::ADFS_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 AFFS_SUPER_MAGIC: FsType = FsType(libc::AFFS_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 CODA_SUPER_MAGIC: FsType = FsType(libc::CODA_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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(all(target_os = "linux", not(target_env = "musl")))] +#[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(all(target_os = "linux", not(target_env = "musl")))] +#[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 EXT2_SUPER_MAGIC: FsType = FsType(libc::EXT2_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 EXT3_SUPER_MAGIC: FsType = FsType(libc::EXT3_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 EXT4_SUPER_MAGIC: FsType = FsType(libc::EXT4_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 HPFS_SUPER_MAGIC: FsType = FsType(libc::HPFS_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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(all(target_os = "linux", not(target_env = "musl")))] +#[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 ISOFS_SUPER_MAGIC: FsType = FsType(libc::ISOFS_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 JFFS2_SUPER_MAGIC: FsType = FsType(libc::JFFS2_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 MINIX_SUPER_MAGIC: FsType = FsType(libc::MINIX_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 MINIX_SUPER_MAGIC2: FsType = FsType(libc::MINIX_SUPER_MAGIC2 as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 MINIX2_SUPER_MAGIC: FsType = FsType(libc::MINIX2_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 MINIX2_SUPER_MAGIC2: FsType = FsType(libc::MINIX2_SUPER_MAGIC2 as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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(all(target_os = "linux", not(target_env = "musl")))] +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(all(target_os = "linux", not(target_env = "musl")))] +#[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(all(target_os = "linux", not(target_env = "musl")))] +#[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 OPENPROM_SUPER_MAGIC: FsType = FsType(libc::OPENPROM_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 OVERLAYFS_SUPER_MAGIC: FsType = FsType(libc::OVERLAYFS_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 PROC_SUPER_MAGIC: FsType = FsType(libc::PROC_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 QNX4_SUPER_MAGIC: FsType = FsType(libc::QNX4_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 REISERFS_SUPER_MAGIC: FsType = FsType(libc::REISERFS_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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(all(target_os = "linux", not(target_env = "musl")))] +#[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(all(target_os = "linux", not(target_env = "musl")))] +#[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(all(target_os = "linux", not(target_env = "musl")))] +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 CGROUP_SUPER_MAGIC: FsType = FsType(libc::CGROUP_SUPER_MAGIC as fs_type_t); -#[cfg(all(target_os = "linux", not(target_env = "musl")))] +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 CGROUP2_SUPER_MAGIC: FsType = FsType(libc::CGROUP2_SUPER_MAGIC as fs_type_t); - +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 @@ -142,12 +305,14 @@ impl Statfs { 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() @@ -155,18 +320,21 @@ impl Statfs { /// 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 } @@ -176,30 +344,49 @@ impl Statfs { 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"))))] + #[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 } @@ -207,6 +394,7 @@ impl Statfs { /// 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 } @@ -214,61 +402,126 @@ impl Statfs { /// 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", not(any(target_arch = "s390x", target_env = "musl"))))] + #[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", not(any(target_arch = "s390x", target_env = "musl"))))] + #[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 } @@ -279,35 +532,26 @@ impl Statfs { 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(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] - pub fn blocks(&self) -> u64 { - self.0.f_blocks - } - - /// Total data blocks in filesystem - #[cfg(not(any( - target_os = "ios", - target_os = "macos", - target_os = "android", - target_os = "freebsd", - target_os = "openbsd", - target_os = "dragonfly", - all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) - )))] - pub fn blocks(&self) -> libc::c_ulong { + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks(&self) -> u32 { self.0.f_blocks } @@ -317,73 +561,60 @@ impl Statfs { 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(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] - pub fn blocks_free(&self) -> u64 { + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_free(&self) -> u32 { self.0.f_bfree } - /// Free blocks in filesystem - #[cfg(not(any( + /// Free blocks available to unprivileged user + #[cfg(any( target_os = "ios", target_os = "macos", target_os = "android", - target_os = "freebsd", - target_os = "openbsd", - target_os = "dragonfly", - all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) - )))] - pub fn blocks_free(&self) -> libc::c_ulong { - 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(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] - pub fn blocks_available(&self) -> u64 { - self.0.f_bavail - } - - /// Free blocks available to unprivileged user - #[cfg(not(any( - target_os = "ios", - target_os = "macos", - target_os = "android", - target_os = "freebsd", - target_os = "openbsd", - target_os = "dragonfly", - all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) - )))] - pub fn blocks_available(&self) -> libc::c_ulong { + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn blocks_available(&self) -> u32 { self.0.f_bavail } @@ -393,78 +624,61 @@ impl Statfs { 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(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] - pub fn files(&self) -> libc::fsfilcnt_t { + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files(&self) -> u32 { self.0.f_files } - /// Total file nodes in filesystem - #[cfg(not(any( + /// Free 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 = "dragonfly", - all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) - )))] - pub fn files(&self) -> libc::c_ulong { - self.0.f_files - } - - /// Free file nodes in filesystem - #[cfg(any( - target_os = "android", - target_os = "ios", - target_os = "macos", - 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(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] - pub fn files_free(&self) -> libc::fsfilcnt_t { - self.0.f_ffree - } - - /// Free file nodes in filesystem - #[cfg(not(any( - target_os = "ios", - target_os = "macos", - target_os = "android", - target_os = "freebsd", - target_os = "openbsd", - target_os = "dragonfly", - all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) - )))] - pub fn files_free(&self) -> libc::c_ulong { + #[cfg(target_os = "emscripten")] + #[cfg_attr(docsrs, doc(cfg(all())))] + pub fn files_free(&self) -> u32 { self.0.f_ffree } @@ -476,22 +690,33 @@ impl Statfs { impl Debug for Statfs { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Statfs") - .field("optimal_transfer_size", &self.optimal_transfer_size()) - .field("block_size", &self.block_size()) - .field("blocks", &self.blocks()) - .field("blocks_free", &self.blocks_free()) - .field("blocks_available", &self.blocks_available()) - .field("files", &self.files()) - .field("files_free", &self.files_free()) - .field("filesystem_id", &self.filesystem_id()) - .finish() + 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 portabable alternative, see +/// The result is OS-dependent. For a portable alternative, see /// [`statvfs`](crate::sys::statvfs::statvfs). /// /// # Arguments @@ -499,24 +724,26 @@ impl Debug for Statfs { /// `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()))?; + 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 portabable alternative, see +/// 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 { +pub fn fstatfs(fd: Fd) -> Result { unsafe { - let mut stat = mem::MaybeUninit::::uninit(); - Errno::result(libc::fstatfs(fd.as_raw_fd(), stat.as_mut_ptr())) + let mut stat = mem::MaybeUninit::::uninit(); + Errno::result(LIBC_FSTATFS(fd.as_fd().as_raw_fd(), stat.as_mut_ptr())) .map(|_| Statfs(stat.assume_init())) } } @@ -564,6 +791,8 @@ mod test { 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); @@ -611,6 +840,8 @@ mod test { 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); diff --git a/src/sys/statvfs.rs b/src/sys/statvfs.rs index 15e7a7d4ab..35424e5e27 100644 --- a/src/sys/statvfs.rs +++ b/src/sys/statvfs.rs @@ -3,51 +3,62 @@ //! 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 std::os::unix::io::{AsFd, AsRawFd}; use libc::{self, c_ulong}; -use crate::{Result, NixPath, errno::Errno}; +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; } ); @@ -109,6 +120,7 @@ impl Statvfs { /// 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) } @@ -117,7 +129,6 @@ impl Statvfs { pub fn name_max(&self) -> c_ulong { self.0.f_namemax } - } /// Return a `Statvfs` object with information about the `path` @@ -125,28 +136,28 @@ pub fn statvfs(path: &P) -> Result { unsafe { Errno::clear(); let mut stat = mem::MaybeUninit::::uninit(); - let res = path.with_nix_path(|path| + 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 { +pub fn fstatvfs(fd: Fd) -> Result { unsafe { Errno::clear(); let mut stat = mem::MaybeUninit::::uninit(); - Errno::result(libc::fstatvfs(fd.as_raw_fd(), stat.as_mut_ptr())) + Errno::result(libc::fstatvfs(fd.as_fd().as_raw_fd(), stat.as_mut_ptr())) .map(|_| Statvfs(stat.assume_init())) } } #[cfg(test)] mod test { - use std::fs::File; use crate::sys::statvfs::*; + use std::fs::File; #[test] fn statvfs_call() { diff --git a/src/sys/sysinfo.rs b/src/sys/sysinfo.rs index dc943c1adc..e8aa00b00d 100644 --- a/src/sys/sysinfo.rs +++ b/src/sys/sysinfo.rs @@ -1,9 +1,9 @@ use libc::{self, SI_LOAD_SHIFT}; -use std::{cmp, mem}; use std::time::Duration; +use std::{cmp, mem}; -use crate::Result; use crate::errno::Errno; +use crate::Result; /// System info structure returned by `sysinfo`. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] @@ -30,6 +30,8 @@ impl SysInfo { } /// 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) @@ -64,6 +66,8 @@ impl SysInfo { 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 } @@ -75,5 +79,5 @@ impl SysInfo { 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()) }) + Errno::result(res).map(|_| unsafe { SysInfo(info.assume_init()) }) } diff --git a/src/sys/termios.rs b/src/sys/termios.rs index 01d4608039..ecaa3eaf8f 100644 --- a/src/sys/termios.rs +++ b/src/sys/termios.rs @@ -64,9 +64,9 @@ //! # use nix::sys::termios::{BaudRate, cfsetispeed, cfsetospeed, cfsetspeed, Termios}; //! # fn main() { //! # let mut t: Termios = unsafe { std::mem::zeroed() }; -//! cfsetispeed(&mut t, BaudRate::B9600); -//! cfsetospeed(&mut t, BaudRate::B9600); -//! cfsetspeed(&mut t, BaudRate::B9600); +//! cfsetispeed(&mut t, BaudRate::B9600).unwrap(); +//! cfsetospeed(&mut t, BaudRate::B9600).unwrap(); +//! cfsetspeed(&mut t, BaudRate::B9600).unwrap(); //! # } //! ``` //! @@ -76,21 +76,37 @@ //! # 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); +//! # cfsetspeed(&mut t, BaudRate::B9600).unwrap(); //! let speed = cfgetispeed(&t); //! assert_eq!(speed, cfgetospeed(&t)); -//! cfsetispeed(&mut t, speed); +//! 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")] +#![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() }; @@ -102,12 +118,28 @@ //! //! 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")] +#![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() }; @@ -119,12 +151,28 @@ //! //! 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")] +#![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() }; @@ -137,12 +185,28 @@ //! 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")] +#![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() }; @@ -151,15 +215,16 @@ //! cfsetspeed(&mut t, 9600u32); //! # } //! ``` -use cfg_if::cfg_if; -use crate::Result; 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; +use std::os::unix::io::{AsFd, AsRawFd}; +#[cfg(feature = "process")] use crate::unistd::Pid; /// Stores settings for the termios API @@ -180,6 +245,12 @@ pub struct Termios { 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 { @@ -195,6 +266,14 @@ impl Termios { 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() } @@ -213,6 +292,14 @@ impl Termios { 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() } @@ -222,9 +309,17 @@ impl Termios { 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.control_flags = ControlFlags::from_bits_retain(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; + } } } @@ -237,6 +332,12 @@ impl From for Termios { 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, } } } @@ -247,15 +348,16 @@ impl From for libc::termios { } } -libc_enum!{ +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(target_os = "haiku", 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_pointer_width = "64")), repr(u32))] + #[cfg_attr(all(not(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64")), not(target_os = "haiku")), repr(u32))] #[non_exhaustive] pub enum BaudRate { B0, @@ -276,6 +378,7 @@ libc_enum!{ target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B7200, B9600, #[cfg(any(target_os = "dragonfly", @@ -283,6 +386,7 @@ libc_enum!{ target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B14400, B19200, #[cfg(any(target_os = "dragonfly", @@ -290,20 +394,30 @@ libc_enum!{ target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B28800, B38400, + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] 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, + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B115200, #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B153600, + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B230400, #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] B307200, #[cfg(any(target_os = "android", target_os = "freebsd", @@ -311,10 +425,13 @@ libc_enum!{ 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", @@ -322,39 +439,57 @@ libc_enum!{ 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"))] +#[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. @@ -407,11 +542,14 @@ libc_enum! { } // 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 { + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] VDISCARD, #[cfg(any(target_os = "dragonfly", target_os = "freebsd", @@ -419,7 +557,9 @@ libc_enum! { target_os = "macos", target_os = "netbsd", target_os = "openbsd", + target_os = "aix", target_os = "solaris"))] + #[cfg_attr(docsrs, doc(cfg(all())))] VDSUSP, VEOF, VEOL, @@ -429,12 +569,14 @@ libc_enum! { 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")))] + target_os = "illumos", target_os = "solaris", target_os = "aix")))] + #[cfg_attr(docsrs, doc(cfg(all())))] VMIN, VQUIT, VREPRINT, @@ -446,36 +588,52 @@ libc_enum! { 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")))] + target_os = "illumos", target_os = "solaris", target_os = "aix")))] + #[cfg_attr(docsrs, doc(cfg(all())))] VTIME, + #[cfg(not(target_os = "aix"))] + #[cfg_attr(docsrs, doc(cfg(all())))] 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"))] +#[cfg(any( + all(target_os = "linux", target_arch = "sparc64"), + target_os = "illumos", + target_os = "solaris", + target_os = "aix", +))] impl SpecialCharacterIndices { pub const VMIN: SpecialCharacterIndices = SpecialCharacterIndices::VEOF; pub const VTIME: SpecialCharacterIndices = SpecialCharacterIndices::VEOL; } pub use libc::NCCS; -#[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "aix", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" +))] +#[cfg_attr(docsrs, doc(cfg(all())))] pub use libc::_POSIX_VDISABLE; libc_bitflags! { @@ -493,10 +651,13 @@ libc_bitflags! { IXON; IXOFF; #[cfg(not(target_os = "redox"))] + #[cfg_attr(docsrs, doc(cfg(all())))] IXANY; - #[cfg(not(target_os = "redox"))] + #[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; } } @@ -509,6 +670,7 @@ libc_bitflags! { target_os = "haiku", target_os = "linux", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] OLCUC; ONLCR; OCRNL as tcflag_t; @@ -519,48 +681,56 @@ libc_bitflags! { 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", @@ -568,18 +738,21 @@ libc_bitflags! { 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", @@ -587,44 +760,52 @@ libc_bitflags! { 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", @@ -632,12 +813,14 @@ libc_bitflags! { 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 @@ -649,12 +832,14 @@ libc_bitflags! { 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", @@ -662,24 +847,28 @@ libc_bitflags! { 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; } } @@ -693,6 +882,7 @@ libc_bitflags! { target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] CIGNORE; CS5; CS6; @@ -704,44 +894,55 @@ libc_bitflags! { PARODD; HUPCL; CLOCAL; - #[cfg(not(target_os = "redox"))] + #[cfg(not(any(target_os = "redox", target_os = "aix")))] + #[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 @@ -756,14 +957,17 @@ 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; @@ -773,12 +977,15 @@ libc_bitflags! { target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] + #[cfg_attr(docsrs, doc(cfg(all())))] ALTWERASE; IEXTEN; - #[cfg(not(target_os = "redox"))] + #[cfg(not(any(target_os = "redox", target_os = "haiku", target_os = "aix")))] + #[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", @@ -786,14 +993,16 @@ libc_bitflags! { 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!{ +cfg_if! { if #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios", @@ -804,6 +1013,8 @@ cfg_if!{ /// [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 } @@ -813,6 +1024,8 @@ cfg_if!{ /// [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 } @@ -899,6 +1112,7 @@ cfg_if!{ /// /// `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) }; @@ -927,6 +1141,7 @@ pub fn cfmakeraw(termios: &mut Termios) { /// /// 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 { @@ -941,10 +1156,12 @@ pub fn cfmakesane(termios: &mut Termios) { /// `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 { +pub fn tcgetattr(fd: Fd) -> Result { let mut termios = mem::MaybeUninit::uninit(); - let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; + let res = unsafe { + libc::tcgetattr(fd.as_fd().as_raw_fd(), termios.as_mut_ptr()) + }; Errno::result(res)?; @@ -957,15 +1174,26 @@ pub fn tcgetattr(fd: RawFd) -> Result { /// `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<()> { +pub fn tcsetattr( + fd: Fd, + 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) + Errno::result(unsafe { + libc::tcsetattr( + fd.as_fd().as_raw_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) +pub fn tcdrain(fd: Fd) -> Result<()> { + Errno::result(unsafe { libc::tcdrain(fd.as_fd().as_raw_fd()) }).map(drop) } /// Suspend or resume the transmission or reception of data (see @@ -973,8 +1201,11 @@ pub fn tcdrain(fd: RawFd) -> Result<()> { /// /// `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) +pub fn tcflow(fd: Fd, action: FlowArg) -> Result<()> { + Errno::result(unsafe { + libc::tcflow(fd.as_fd().as_raw_fd(), action as c_int) + }) + .map(drop) } /// Discard data in the output or input queue (see @@ -982,8 +1213,11 @@ pub fn tcflow(fd: RawFd, action: FlowArg) -> Result<()> { /// /// `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) +pub fn tcflush(fd: Fd, action: FlushArg) -> Result<()> { + Errno::result(unsafe { + libc::tcflush(fd.as_fd().as_raw_fd(), action as c_int) + }) + .map(drop) } /// Send a break for a specific duration (see @@ -991,17 +1225,23 @@ pub fn tcflush(fd: RawFd, action: FlushArg) -> Result<()> { /// /// 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) +pub fn tcsendbreak(fd: Fd, duration: c_int) -> Result<()> { + Errno::result(unsafe { + libc::tcsendbreak(fd.as_fd().as_raw_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) }; +pub fn tcgetsid(fd: Fd) -> Result { + let res = unsafe { libc::tcgetsid(fd.as_fd().as_raw_fd()) }; Errno::result(res).map(Pid::from_raw) } +} #[cfg(test)] mod test { @@ -1011,6 +1251,9 @@ mod test { #[test] fn try_from() { assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0)); - assert!(BaudRate::try_from(999999999).is_err()); + #[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 index ac4247180d..a0160e21ff 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -1,9 +1,147 @@ -use std::{cmp, fmt, ops}; -use std::time::Duration; -use std::convert::From; +#[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}; -#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 -pub use libc::{time_t, suseconds_t}; +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. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct TimerSetTimeFlags: libc::c_int { + const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; + const TFD_TIMER_CANCEL_ON_SET = libc::TFD_TIMER_CANCEL_ON_SET; + } + } + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "illumos" + ))] + bitflags! { + /// Flags that are used for arming the timer. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + 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] @@ -13,14 +151,16 @@ pub trait TimeValLike: Sized { #[inline] fn hours(hours: i64) -> Self { - let secs = hours.checked_mul(SECS_PER_HOUR) + 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) + let secs = minutes + .checked_mul(SECS_PER_MINUTE) .expect("TimeValLike::minutes out of bounds"); Self::seconds(secs) } @@ -55,10 +195,10 @@ const SECS_PER_MINUTE: i64 = 60; const SECS_PER_HOUR: i64 = 3600; #[cfg(target_pointer_width = "64")] -const TS_MAX_SECONDS: i64 = (::std::i64::MAX / NANOS_PER_SEC) - 1; +const TS_MAX_SECONDS: i64 = (i64::MAX / NANOS_PER_SEC) - 1; #[cfg(target_pointer_width = "32")] -const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64; +const TS_MAX_SECONDS: i64 = isize::MAX as i64; const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS; @@ -119,16 +259,22 @@ impl PartialOrd for TimeSpec { 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!(seconds >= TS_MIN_SECONDS && seconds <= TS_MAX_SECONDS, - "TimeSpec out of bounds; seconds={}", seconds); - #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 - TimeSpec(timespec {tv_sec: seconds as time_t, tv_nsec: 0 }) + 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) + let nanoseconds = milliseconds + .checked_mul(1_000_000) .expect("TimeSpec::milliseconds out of bounds"); TimeSpec::nanoseconds(nanoseconds) @@ -137,7 +283,8 @@ impl TimeValLike for TimeSpec { /// Makes a new `TimeSpec` with given number of microseconds. #[inline] fn microseconds(microseconds: i64) -> TimeSpec { - let nanoseconds = microseconds.checked_mul(1_000) + let nanoseconds = microseconds + .checked_mul(1_000) .expect("TimeSpec::milliseconds out of bounds"); TimeSpec::nanoseconds(nanoseconds) @@ -145,15 +292,22 @@ impl TimeValLike for TimeSpec { /// 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!(secs >= TS_MIN_SECONDS && secs <= TS_MAX_SECONDS, - "TimeSpec out of bounds"); - #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 - TimeSpec(timespec {tv_sec: secs as time_t, - tv_nsec: nanos as timespec_tv_nsec_t }) - } - + 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 @@ -167,9 +321,11 @@ impl TimeValLike for TimeSpec { } fn num_microseconds(&self) -> i64 { - self.num_nanoseconds() / 1_000_000_000 + 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(); @@ -178,6 +334,15 @@ impl TimeValLike for TimeSpec { } 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 @@ -195,12 +360,13 @@ impl TimeSpec { 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 { - #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 - TimeSpec(timespec { - tv_sec: duration.as_secs() as time_t, - tv_nsec: duration.subsec_nanos() as timespec_tv_nsec_t - }) + 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 { @@ -220,8 +386,7 @@ impl ops::Add for TimeSpec { type Output = TimeSpec; fn add(self, rhs: TimeSpec) -> TimeSpec { - TimeSpec::nanoseconds( - self.num_nanoseconds() + rhs.num_nanoseconds()) + TimeSpec::nanoseconds(self.num_nanoseconds() + rhs.num_nanoseconds()) } } @@ -229,8 +394,7 @@ impl ops::Sub for TimeSpec { type Output = TimeSpec; fn sub(self, rhs: TimeSpec) -> TimeSpec { - TimeSpec::nanoseconds( - self.num_nanoseconds() - rhs.num_nanoseconds()) + TimeSpec::nanoseconds(self.num_nanoseconds() - rhs.num_nanoseconds()) } } @@ -238,7 +402,9 @@ impl ops::Mul for TimeSpec { type Output = TimeSpec; fn mul(self, rhs: i32) -> TimeSpec { - let usec = self.num_nanoseconds().checked_mul(i64::from(rhs)) + let usec = self + .num_nanoseconds() + .checked_mul(i64::from(rhs)) .expect("TimeSpec multiply out of bounds"); TimeSpec::nanoseconds(usec) @@ -264,28 +430,26 @@ impl fmt::Display for TimeSpec { let sec = abs.tv_sec(); - write!(f, "{}", sign)?; + write!(f, "{sign}")?; if abs.tv_nsec() == 0 { - if abs.tv_sec() == 1 { - write!(f, "{} second", sec)?; + if sec == 1 { + write!(f, "1 second")?; } else { - write!(f, "{} seconds", sec)?; + write!(f, "{sec} seconds")?; } } else if abs.tv_nsec() % 1_000_000 == 0 { - write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)?; + write!(f, "{sec}.{:03} seconds", abs.tv_nsec() / 1_000_000)?; } else if abs.tv_nsec() % 1_000 == 0 { - write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)?; + write!(f, "{sec}.{:06} seconds", abs.tv_nsec() / 1_000)?; } else { - write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())?; + write!(f, "{sec}.{:09} seconds", abs.tv_nsec())?; } Ok(()) } } - - #[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct TimeVal(timeval); @@ -293,10 +457,10 @@ pub struct TimeVal(timeval); const MICROS_PER_SEC: i64 = 1_000_000; #[cfg(target_pointer_width = "64")] -const TV_MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1; +const TV_MAX_SECONDS: i64 = (i64::MAX / MICROS_PER_SEC) - 1; #[cfg(target_pointer_width = "32")] -const TV_MAX_SECONDS: i64 = ::std::isize::MAX as i64; +const TV_MAX_SECONDS: i64 = isize::MAX as i64; const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS; @@ -333,15 +497,22 @@ impl PartialOrd for TimeVal { impl TimeValLike for TimeVal { #[inline] fn seconds(seconds: i64) -> TimeVal { - assert!(seconds >= TV_MIN_SECONDS && seconds <= TV_MAX_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 }) + 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) + let microseconds = milliseconds + .checked_mul(1_000) .expect("TimeVal::milliseconds out of bounds"); TimeVal::microseconds(microseconds) @@ -351,11 +522,16 @@ impl TimeValLike for TimeVal { #[inline] fn microseconds(microseconds: i64) -> TimeVal { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS, - "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 }) + 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 @@ -364,13 +540,20 @@ impl TimeValLike for TimeVal { fn nanoseconds(nanoseconds: i64) -> TimeVal { let microseconds = nanoseconds / 1000; let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS, - "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 }) + 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 @@ -383,6 +566,8 @@ impl TimeValLike for TimeVal { 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(); @@ -395,6 +580,15 @@ impl TimeValLike for TimeVal { } 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 @@ -425,8 +619,7 @@ impl ops::Add for TimeVal { type Output = TimeVal; fn add(self, rhs: TimeVal) -> TimeVal { - TimeVal::microseconds( - self.num_microseconds() + rhs.num_microseconds()) + TimeVal::microseconds(self.num_microseconds() + rhs.num_microseconds()) } } @@ -434,8 +627,7 @@ impl ops::Sub for TimeVal { type Output = TimeVal; fn sub(self, rhs: TimeVal) -> TimeVal { - TimeVal::microseconds( - self.num_microseconds() - rhs.num_microseconds()) + TimeVal::microseconds(self.num_microseconds() - rhs.num_microseconds()) } } @@ -443,7 +635,9 @@ impl ops::Mul for TimeVal { type Output = TimeVal; fn mul(self, rhs: i32) -> TimeVal { - let usec = self.num_microseconds().checked_mul(i64::from(rhs)) + let usec = self + .num_microseconds() + .checked_mul(i64::from(rhs)) .expect("TimeVal multiply out of bounds"); TimeVal::microseconds(usec) @@ -469,18 +663,18 @@ impl fmt::Display for TimeVal { let sec = abs.tv_sec(); - write!(f, "{}", sign)?; + write!(f, "{sign}")?; if abs.tv_usec() == 0 { - if abs.tv_sec() == 1 { - write!(f, "{} second", sec)?; + if sec == 1 { + write!(f, "1 second")?; } else { - write!(f, "{} seconds", sec)?; + write!(f, "{sec} seconds")?; } } else if abs.tv_usec() % 1000 == 0 { - write!(f, "{}.{:03} seconds", sec, abs.tv_usec() / 1000)?; + write!(f, "{sec}.{:03} seconds", abs.tv_usec() / 1000)?; } else { - write!(f, "{}.{:06} seconds", sec, abs.tv_usec())?; + write!(f, "{sec}.{:06} seconds", abs.tv_usec())?; } Ok(()) @@ -501,18 +695,16 @@ fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { #[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, + (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, + r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, + r => r, } } @@ -528,11 +720,15 @@ mod test { #[test] pub fn test_timespec() { - assert!(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)); + 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] @@ -554,7 +750,7 @@ mod test { #[test] pub fn test_timespec_ord() { - assert!(TimeSpec::seconds(1) == TimeSpec::nanoseconds(1_000_000_000)); + 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)); @@ -567,22 +763,29 @@ mod test { 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::nanoseconds(42).to_string(), + "0.000000042 seconds" + ); assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); } #[test] pub fn test_timeval() { - assert!(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)); + 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!(TimeVal::seconds(1) == TimeVal::microseconds(1_000_000)); + 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)); diff --git a/src/sys/timer.rs b/src/sys/timer.rs new file mode 100644 index 0000000000..e1a34051e8 --- /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 index 705a3c4d65..c4337c9dfa 100644 --- a/src/sys/timerfd.rs +++ b/src/sys/timerfd.rs @@ -28,29 +28,33 @@ //! // We wait for the timer to expire. //! timer.wait().unwrap(); //! ``` -use crate::sys::time::TimeSpec; +use crate::sys::time::timer::TimerSpec; +pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; use crate::unistd::read; use crate::{errno::Errno, Result}; -use bitflags::bitflags; use libc::c_int; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; /// A timerfd instance. This is also a file descriptor, you can feed it to -/// other interfaces consuming file descriptors, epoll for example. +/// other interfaces taking file descriptors as arguments, [`epoll`] for example. +/// +/// [`epoll`]: crate::sys::epoll #[derive(Debug)] pub struct TimerFd { - fd: RawFd, + fd: OwnedFd, } -impl AsRawFd for TimerFd { - fn as_raw_fd(&self) -> RawFd { - self.fd +impl AsFd for TimerFd { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() } } impl FromRawFd for TimerFd { unsafe fn from_raw_fd(fd: RawFd) -> Self { - TimerFd { fd } + TimerFd { + fd: OwnedFd::from_raw_fd(fd), + } } } @@ -60,10 +64,19 @@ libc_enum! { #[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, } } @@ -72,105 +85,25 @@ 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; } } -bitflags! { - /// Flags that are used for arming the timer. - pub struct TimerSetTimeFlags: libc::c_int { - const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME; - } -} - -#[derive(Debug, Clone, Copy)] -struct TimerSpec(libc::itimerspec); - -impl TimerSpec { - pub const fn none() -> Self { - Self(libc::itimerspec { - it_interval: libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }, - it_value: libc::timespec { - tv_sec: 0, - tv_nsec: 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: libc::timespec { - tv_sec: 0, - tv_nsec: 0, - }, - 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(), - }), - } - } -} - -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()) - } - } - } - } -} - -/// An enumeration allowing the definition of the expiration time of an alarm, -/// recurring or not. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Expiration { - OneShot(TimeSpec), - IntervalDelayed(TimeSpec, TimeSpec), - Interval(TimeSpec), -} - 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 }) + Errno::result(unsafe { + libc::timerfd_create(clockid as i32, flags.bits()) + }) + .map(|fd| Self { + fd: unsafe { OwnedFd::from_raw_fd(fd) }, + }) } /// Sets a new alarm on the timer. @@ -181,7 +114,7 @@ impl TimerFd { /// /// - 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 disables itself. + /// 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 @@ -202,6 +135,13 @@ impl TimerFd { /// Then the one shot TimeSpec and the delay TimeSpec of the delayed /// interval are going to be interpreted as absolute. /// + /// # Cancel on a clock change + /// + /// If you set a `TFD_TIMER_CANCEL_ON_SET` alongside `TFD_TIMER_ABSTIME` + /// and the clock for this timer is `CLOCK_REALTIME` or `CLOCK_REALTIME_ALARM`, + /// then this timer is marked as cancelable if the real-time clock undergoes + /// a discontinuous change. + /// /// # Disabling alarms /// /// Note: Only one alarm can be set for any given timer. Setting a new alarm @@ -209,11 +149,16 @@ impl TimerFd { /// /// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm /// altogether. - pub fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> { + #[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, + self.fd.as_fd().as_raw_fd(), flags.bits(), timerspec.as_ref(), std::ptr::null_mut(), @@ -223,15 +168,20 @@ impl TimerFd { } /// Get the parameters for the alarm currently set, if any. + #[doc(alias("timerfd_gettime"))] pub fn get(&self) -> Result> { let mut timerspec = TimerSpec::none(); - let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0; - - Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| { - if timerspec.0.it_interval.tv_sec == 0 - && timerspec.0.it_interval.tv_nsec == 0 - && timerspec.0.it_value.tv_sec == 0 - && timerspec.0.it_value.tv_nsec == 0 + Errno::result(unsafe { + libc::timerfd_gettime( + self.fd.as_fd().as_raw_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 { @@ -241,10 +191,11 @@ impl TimerFd { } /// Remove the alarm if any is set. + #[doc(alias("timerfd_settime"))] pub fn unset(&self) -> Result<()> { Errno::result(unsafe { libc::timerfd_settime( - self.fd, + self.fd.as_fd().as_raw_fd(), TimerSetTimeFlags::empty().bits(), TimerSpec::none().as_ref(), std::ptr::null_mut(), @@ -257,25 +208,15 @@ impl TimerFd { /// /// 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]) { + while let Err(e) = read(self.fd.as_fd().as_raw_fd(), &mut [0u8; 8]) { + if e == Errno::ECANCELED { + break; + } if e != Errno::EINTR { - return Err(e) + 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 index 3abcde24fe..eaf61edfd4 100644 --- a/src/sys/uio.rs +++ b/src/sys/uio.rs @@ -1,16 +1,25 @@ //! Vectored I/O -use crate::Result; use crate::errno::Errno; -use libc::{self, c_int, c_void, size_t, off_t}; -use std::marker::PhantomData; -use std::os::unix::io::RawFd; +use crate::Result; +use libc::{self, c_int, c_void, off_t, size_t}; +use std::io::{IoSlice, IoSliceMut}; +use std::os::unix::io::{AsFd, AsRawFd}; /// 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: &[IoVec<&[u8]>]) -> Result { - let res = unsafe { libc::writev(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) }; +pub fn writev(fd: Fd, 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.as_fd().as_raw_fd(), iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + }; Errno::result(res).map(|r| r as usize) } @@ -18,8 +27,14 @@ pub fn writev(fd: RawFd, iov: &[IoVec<&[u8]>]) -> Result { /// 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 [IoVec<&mut [u8]>]) -> Result { - let res = unsafe { libc::readv(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) }; +// Clippy doesn't know that we need to pass iov mutably only because the +// mutation happens after converting iov to a pointer +#[allow(clippy::needless_pass_by_ref_mut)] +pub fn readv(fd: Fd, iov: &mut [IoSliceMut<'_>]) -> Result { + // SAFETY: same as in writev(), IoSliceMut is ABI-compatible with iovec + let res = unsafe { + libc::readv(fd.as_fd().as_raw_fd(), iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + }; Errno::result(res).map(|r| r as usize) } @@ -30,11 +45,20 @@ pub fn readv(fd: RawFd, iov: &mut [IoVec<&mut [u8]>]) -> Result { /// or an error occurs. The file offset is not changed. /// /// See also: [`writev`](fn.writev.html) and [`pwrite`](fn.pwrite.html) -#[cfg(not(target_os = "redox"))] -pub fn pwritev(fd: RawFd, iov: &[IoVec<&[u8]>], - offset: off_t) -> Result { +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +pub fn pwritev(fd: Fd, 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) + libc::pwritev( + fd.as_fd().as_raw_fd(), + iov.as_ptr() as *const libc::iovec, + iov.len() as c_int, + offset, + ) }; Errno::result(res).map(|r| r as usize) @@ -47,11 +71,27 @@ pub fn pwritev(fd: RawFd, iov: &[IoVec<&[u8]>], /// changed. /// /// See also: [`readv`](fn.readv.html) and [`pread`](fn.pread.html) -#[cfg(not(target_os = "redox"))] -pub fn preadv(fd: RawFd, iov: &[IoVec<&mut [u8]>], - offset: off_t) -> Result { +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg_attr(docsrs, doc(cfg(all())))] +// Clippy doesn't know that we need to pass iov mutably only because the +// mutation happens after converting iov to a pointer +#[allow(clippy::needless_pass_by_ref_mut)] +pub fn preadv( + fd: Fd, + 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) + libc::preadv( + fd.as_fd().as_raw_fd(), + iov.as_ptr() as *const libc::iovec, + iov.len() as c_int, + offset, + ) }; Errno::result(res).map(|r| r as usize) @@ -61,23 +101,31 @@ pub fn preadv(fd: RawFd, iov: &[IoVec<&mut [u8]>], /// /// 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 { +pub fn pwrite(fd: Fd, 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) + libc::pwrite( + fd.as_fd().as_raw_fd(), + buf.as_ptr() as *const c_void, + buf.len() as size_t, + offset, + ) }; Errno::result(res).map(|r| r as usize) } -/// Low-level write to a file, with specified offset. +/// 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{ +pub fn pread(fd: Fd, 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) + libc::pread( + fd.as_fd().as_raw_fd(), + buf.as_mut_ptr() as *mut c_void, + buf.len() as size_t, + offset, + ) }; Errno::result(res).map(|r| r as usize) @@ -86,12 +134,13 @@ pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result{ /// 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 [`IoVec`](struct.IoVec.html), +/// 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 `IoVec` is. It +/// 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(target_os = "linux")] +#[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 { @@ -101,10 +150,13 @@ pub struct RemoteIoVec { pub len: usize, } +feature! { +#![feature = "process"] + /// Write data directly to another process's virtual memory /// (see [`process_vm_writev`(2)]). /// -/// `local_iov` is a list of [`IoVec`]s containing the data to be written, +/// `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 @@ -115,16 +167,16 @@ pub struct RemoteIoVec { /// `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. +/// 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 -/// [`IoVec`]: struct.IoVec.html +/// [`IoSlice`]: https://doc.rust-lang.org/std/io/struct.IoSlice.html /// [`RemoteIoVec`]: struct.RemoteIoVec.html -#[cfg(target_os = "linux")] +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] pub fn process_vm_writev( pid: crate::unistd::Pid, - local_iov: &[IoVec<&[u8]>], + local_iov: &[IoSlice<'_>], remote_iov: &[RemoteIoVec]) -> Result { let res = unsafe { @@ -139,7 +191,7 @@ pub fn process_vm_writev( /// Read data directly from another process's virtual memory /// (see [`process_vm_readv`(2)]). /// -/// `local_iov` is a list of [`IoVec`]s containing the buffer to copy +/// `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 @@ -150,16 +202,16 @@ pub fn process_vm_writev( /// `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. +/// 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 -/// [`IoVec`]: struct.IoVec.html +/// [`IoSliceMut`]: https://doc.rust-lang.org/std/io/struct.IoSliceMut.html /// [`RemoteIoVec`]: struct.RemoteIoVec.html -#[cfg(any(target_os = "linux"))] +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] pub fn process_vm_readv( pid: crate::unistd::Pid, - local_iov: &[IoVec<&mut [u8]>], + local_iov: &mut [IoSliceMut<'_>], remote_iov: &[RemoteIoVec]) -> Result { let res = unsafe { @@ -170,54 +222,4 @@ pub fn process_vm_readv( Errno::result(res).map(|r| r as 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. -#[repr(transparent)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct IoVec(pub(crate) libc::iovec, PhantomData); - -impl IoVec { - /// View the `IoVec` as a Rust slice. - #[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 as usize) - } - } -} - -impl<'a> IoVec<&'a [u8]> { - #[cfg(target_os = "freebsd")] - pub(crate) fn from_raw_parts(base: *mut c_void, len: usize) -> Self { - IoVec(libc::iovec { - iov_base: base, - iov_len: len - }, PhantomData) - } - - /// Create an `IoVec` from a Rust slice. - 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) - } -} - -impl<'a> IoVec<&'a mut [u8]> { - /// Create an `IoVec` from a mutable Rust slice. - 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) - } } diff --git a/src/sys/utsname.rs b/src/sys/utsname.rs index 98edee0428..b48ed9f45e 100644 --- a/src/sys/utsname.rs +++ b/src/sys/utsname.rs @@ -1,8 +1,9 @@ //! Get system identification +use crate::{Errno, Result}; +use libc::c_char; +use std::ffi::OsStr; use std::mem; -use libc::{self, c_char}; -use std::ffi::CStr; -use std::str::from_utf8_unchecked; +use std::os::unix::ffi::OsStrExt; /// Describes the running system. Return type of [`uname`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -10,47 +11,56 @@ use std::str::from_utf8_unchecked; pub struct UtsName(libc::utsname); impl UtsName { - /// Name of the operating system implementation - pub fn sysname(&self) -> &str { - to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char) + /// 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) -> &str { - to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char) + pub fn nodename(&self) -> &OsStr { + cast_and_trim(&self.0.nodename) } /// Release level of the operating system. - pub fn release(&self) -> &str { - to_str(&(&self.0.release as *const c_char ) as *const *const c_char) + pub fn release(&self) -> &OsStr { + cast_and_trim(&self.0.release) } /// Version level of the operating system. - pub fn version(&self) -> &str { - to_str(&(&self.0.version as *const c_char ) as *const *const c_char) + pub fn version(&self) -> &OsStr { + cast_and_trim(&self.0.version) } /// Machine hardware platform. - pub fn machine(&self) -> &str { - to_str(&(&self.0.machine as *const c_char ) as *const *const c_char) + 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() -> UtsName { +pub fn uname() -> Result { unsafe { - let mut ret = mem::MaybeUninit::uninit(); - libc::uname(ret.as_mut_ptr()); - UtsName(ret.assume_init()) + let mut ret = mem::MaybeUninit::zeroed(); + Errno::result(libc::uname(ret.as_mut_ptr()))?; + Ok(UtsName(ret.assume_init())) } } -#[inline] -fn to_str<'a>(s: *const *const c_char) -> &'a str { - unsafe { - let res = CStr::from_ptr(*s).to_bytes(); - from_utf8_unchecked(res) - } +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)] @@ -58,18 +68,18 @@ mod test { #[cfg(target_os = "linux")] #[test] pub fn test_uname_linux() { - assert_eq!(super::uname().sysname(), "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().sysname(), "Darwin"); + assert_eq!(super::uname().unwrap().sysname(), "Darwin"); } #[cfg(target_os = "freebsd")] #[test] pub fn test_uname_freebsd() { - assert_eq!(super::uname().sysname(), "FreeBSD"); + assert_eq!(super::uname().unwrap().sysname(), "FreeBSD"); } } diff --git a/src/sys/wait.rs b/src/sys/wait.rs index ee49e37dec..f7a63ffcd2 100644 --- a/src/sys/wait.rs +++ b/src/sys/wait.rs @@ -6,6 +6,11 @@ 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::{AsRawFd, BorrowedFd}; libc_bitflags!( /// Controls the behavior of [`waitpid`]. @@ -27,6 +32,7 @@ libc_bitflags!( 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 @@ -41,6 +47,7 @@ libc_bitflags!( 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", @@ -51,15 +58,19 @@ libc_bitflags!( 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; } ); @@ -97,6 +108,7 @@ pub enum WaitStatus { /// [`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 @@ -104,6 +116,7 @@ pub enum WaitStatus { /// /// [`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 @@ -122,7 +135,9 @@ impl WaitStatus { pub fn pid(&self) -> Option { use self::WaitStatus::*; match *self { - Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => Some(p), + 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), @@ -225,12 +240,72 @@ impl WaitStatus { 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 { +pub fn waitpid>>( + pid: P, + options: Option, +) -> Result { use self::WaitStatus::*; let mut status: i32 = 0; @@ -260,3 +335,59 @@ pub fn waitpid>>(pid: P, options: Option) -> Re 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)] +pub enum Id<'fd> { + /// 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(BorrowedFd<'fd>), + /// A helper variant to resolve the unused parameter (`'fd`) problem on platforms + /// other than Linux and Android. + #[doc(hidden)] + _Unreachable(std::marker::PhantomData<&'fd std::convert::Infallible>), +} + +/// 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_raw_fd() as libc::id_t), + Id::_Unreachable(_) => unreachable!("This variant could never be constructed"), + }; + + 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 index 6275b59c74..2e03c46cf4 100644 --- a/src/time.rs +++ b/src/time.rs @@ -6,6 +6,7 @@ use crate::sys::time::TimeSpec; target_os = "android", target_os = "emscripten", ))] +#[cfg(feature = "process")] use crate::unistd::Pid; use crate::{Errno, Result}; use libc::{self, clockid_t}; @@ -13,7 +14,7 @@ use std::mem::MaybeUninit; /// Clock identifier /// -/// Newtype pattern around `clockid_t` (which is just alias). It pervents bugs caused by +/// 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); @@ -24,6 +25,8 @@ impl ClockId { ClockId(clk_id) } + feature! { + #![feature = "process"] /// Returns `ClockId` of a `pid` CPU-time clock #[cfg(any( target_os = "freebsd", @@ -32,12 +35,15 @@ impl ClockId { 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) } @@ -51,11 +57,10 @@ impl ClockId { #[cfg(not(any( target_os = "macos", target_os = "ios", - all( - not(any(target_env = "uclibc", target_env = "newlibc")), - any(target_os = "redox", target_os = "hermit",), - ), + target_os = "redox", + target_os = "hermit", )))] + #[cfg_attr(docsrs, doc(cfg(all())))] pub fn set_time(self, timespec: TimeSpec) -> Result<()> { clock_settime(self, timespec) } @@ -66,122 +71,134 @@ impl ClockId { } #[cfg(any( + target_os = "android", + target_os = "emscripten", target_os = "fuchsia", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any(target_os = "linux", target_os = "android", target_os = "emscripten"), - ) + 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", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any(target_os = "linux", target_os = "android", target_os = "emscripten") - ) + target_os = "linux" ))] - pub const CLOCK_BOOTTIME_ALARM: ClockId = ClockId(libc::CLOCK_BOOTTIME_ALARM); + #[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", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any(target_os = "linux", target_os = "android", target_os = "emscripten") - ) + target_os = "linux" ))] - pub const CLOCK_MONOTONIC_COARSE: ClockId = ClockId(libc::CLOCK_MONOTONIC_COARSE); + #[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"))] - pub const CLOCK_MONOTONIC_FAST: ClockId = ClockId(libc::CLOCK_MONOTONIC_FAST); + #[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"))] - pub const CLOCK_MONOTONIC_PRECISE: ClockId = ClockId(libc::CLOCK_MONOTONIC_PRECISE); + #[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", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any(target_os = "linux", target_os = "android", target_os = "emscripten") - ) + 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_env = "uclibc", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly", - all( - not(target_env = "newlib"), - any(target_os = "linux", target_os = "android", target_os = "emscripten") - ) + target_os = "redox", + target_os = "linux" ))] - pub const CLOCK_PROCESS_CPUTIME_ID: ClockId = ClockId(libc::CLOCK_PROCESS_CPUTIME_ID); + #[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", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any(target_os = "linux", target_os = "android", target_os = "emscripten") - ) + target_os = "linux" ))] - pub const CLOCK_REALTIME_ALARM: ClockId = ClockId(libc::CLOCK_REALTIME_ALARM); + #[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", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any(target_os = "linux", target_os = "android", target_os = "emscripten") - ) + target_os = "linux" ))] - pub const CLOCK_REALTIME_COARSE: ClockId = ClockId(libc::CLOCK_REALTIME_COARSE); + #[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"))] - pub const CLOCK_REALTIME_PRECISE: ClockId = ClockId(libc::CLOCK_REALTIME_PRECISE); + #[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( - not(any(target_env = "uclibc", target_env = "newlib")), - any( - target_os = "emscripten", - all(target_os = "linux", target_env = "musl") - ) - ) + 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", - all( - not(any(target_env = "uclibc", target_env = "newlib")), - any( - target_os = "emscripten", - all(target_os = "linux", target_env = "musl") - ) - ) + target_os = "linux" ))] + #[cfg_attr(docsrs, doc(cfg(all())))] pub const CLOCK_TAI: ClockId = ClockId(libc::CLOCK_TAI); #[cfg(any( - target_env = "uclibc", + target_os = "android", + target_os = "emscripten", target_os = "fuchsia", target_os = "ios", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", - all( - not(target_env = "newlib"), - any(target_os = "linux", target_os = "android", target_os = "emscripten",), - ), + target_os = "linux" ))] - pub const CLOCK_THREAD_CPUTIME_ID: ClockId = ClockId(libc::CLOCK_THREAD_CPUTIME_ID); + #[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"))] - pub const CLOCK_UPTIME_PRECISE: ClockId = ClockId(libc::CLOCK_UPTIME_PRECISE); + #[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); } @@ -206,9 +223,11 @@ impl std::fmt::Display for ClockId { /// 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()) }; + 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)) @@ -218,7 +237,8 @@ pub fn clock_getres(clock_id: ClockId) -> Result { /// [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()) }; + 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)) @@ -229,13 +249,13 @@ pub fn clock_gettime(clock_id: ClockId) -> Result { #[cfg(not(any( target_os = "macos", target_os = "ios", - all( - not(any(target_env = "uclibc", target_env = "newlibc")), - any(target_os = "redox", target_os = "hermit",), - ), + 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()) }; + let ret = + unsafe { libc::clock_settime(clock_id.as_raw(), timespec.as_ref()) }; Errno::result(ret).map(drop) } @@ -248,9 +268,12 @@ pub fn clock_settime(clock_id: ClockId, timespec: TimeSpec) -> Result<()> { 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()) }; + 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)) diff --git a/src/ucontext.rs b/src/ucontext.rs index f2338bd426..b2a39f7699 100644 --- a/src/ucontext.rs +++ b/src/ucontext.rs @@ -1,10 +1,10 @@ #[cfg(not(target_env = "musl"))] -use crate::Result; -#[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; -use crate::sys::signal::SigSet; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct UContext { @@ -17,7 +17,9 @@ impl UContext { 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()} + UContext { + context: context.assume_init(), + } }) } @@ -31,13 +33,15 @@ impl UContext { pub fn sigmask_mut(&mut self) -> &mut SigSet { unsafe { - &mut *(&mut self.context.uc_sigmask as *mut libc::sigset_t as *mut SigSet) + &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) + &*(&self.context.uc_sigmask as *const libc::sigset_t + as *const SigSet) } } } diff --git a/src/unistd.rs b/src/unistd.rs index 2c89d77240..bb9f1c1f67 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1,37 +1,71 @@ //! Safe wrappers around functions found in libc "unistd.h" header -#[cfg(not(target_os = "redox"))] -use cfg_if::cfg_if; use crate::errno::{self, Errno}; -use crate::{Error, Result, NixPath}; #[cfg(not(target_os = "redox"))] -use crate::fcntl::{AtFlags, at_rawfd}; -use crate::fcntl::{FdFlag, OFlag, fcntl}; -use crate::fcntl::FcntlArg::F_SETFD; -use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t, - uid_t, gid_t, mode_t, PATH_MAX}; -use std::{fmt, mem, ptr}; +#[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}; -use std::os::unix::ffi::OsStringExt; #[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::os::unix::io::{AsFd, AsRawFd}; use std::path::PathBuf; -use crate::sys::stat::Mode; +use std::{fmt, mem, ptr}; -#[cfg(any(target_os = "android", target_os = "linux"))] -pub use self::pivot_root::*; +feature! { + #![feature = "fs"] + #[cfg(any(target_os = "android", target_os = "linux"))] + pub use self::pivot_root::*; +} -#[cfg(any(target_os = "android", target_os = "freebsd", - target_os = "linux", target_os = "openbsd"))] +#[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 = "linux"))] +#[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 @@ -46,11 +80,13 @@ impl 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() } @@ -72,6 +108,12 @@ impl From for uid_t { } } +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) @@ -95,11 +137,13 @@ impl 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() } @@ -116,12 +160,21 @@ impl From for gid_t { } } +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 @@ -136,11 +189,13 @@ impl 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() } @@ -163,7 +218,6 @@ impl fmt::Display for Pid { } } - /// Represents the successful result of calling `fork` /// /// When `fork` is called, the process continues execution in the parent process @@ -176,7 +230,6 @@ pub enum ForkResult { } impl ForkResult { - /// Return `true` if this is the child process of the `fork()` #[inline] pub fn is_child(self) -> bool { @@ -193,8 +246,8 @@ impl ForkResult { /// Create a new child process duplicating the parent process ([see /// fork(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html)). /// -/// After calling the fork system call (successfully) two processes will -/// be created that are identical with the exception of their pid and the +/// 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: /// /// ``` @@ -214,7 +267,7 @@ impl ForkResult { /// } /// ``` /// -/// This will print something like the following (order indeterministic). The +/// 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. /// @@ -303,8 +356,10 @@ 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)). /// @@ -325,8 +380,10 @@ 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)). /// @@ -352,11 +409,14 @@ pub fn getpgrp() -> Pid { 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 be have a new index but refer to the same +/// 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 @@ -417,9 +477,8 @@ fn dup3_polyfill(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result { /// 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()) } - })?; + let res = + path.with_nix_path(|cstr| unsafe { libc::chdir(cstr.as_ptr()) })?; Errno::result(res).map(drop) } @@ -466,8 +525,8 @@ pub fn fchdir(dirfd: RawFd) -> Result<()> { /// ``` #[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) } + let res = path.with_nix_path(|cstr| unsafe { + libc::mkdir(cstr.as_ptr(), mode.bits() as mode_t) })?; Errno::result(res).map(drop) @@ -505,8 +564,8 @@ pub fn mkdir(path: &P, mode: Mode) -> Result<()> { #[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) } + let res = path.with_nix_path(|cstr| unsafe { + libc::mkfifo(cstr.as_ptr(), mode.bits() as mode_t) })?; Errno::result(res).map(drop) @@ -524,9 +583,17 @@ pub fn mkfifo(path: &P, mode: Mode) -> Result<()> { // mkfifoat is not implemented in OSX or android #[inline] #[cfg(not(any( - target_os = "macos", target_os = "ios", - target_os = "android", target_os = "redox")))] -pub fn mkfifoat(dirfd: Option, path: &P, mode: Mode) -> Result<()> { + 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) })?; @@ -547,29 +614,29 @@ pub fn mkfifoat(dirfd: Option, path: &P, mode: Mode) 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() - ) - } - }) - })??; + 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) + return Err(Errno::ERANGE); } let capacity = min(buf.capacity() * 2, limit); @@ -578,6 +645,9 @@ fn reserve_double_buffer_size(buf: &mut Vec, limit: usize) -> Result<()> { 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 @@ -604,7 +674,9 @@ pub fn getcwd() -> Result { // 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(); + 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))); @@ -614,24 +686,30 @@ pub fn getcwd() -> Result { 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) { +fn chown_raw_ids(owner: Option, group: Option) -> (uid_t, 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) + let uid = owner + .map(Into::into) .unwrap_or_else(|| (0 as uid_t).wrapping_sub(1)); - let gid = group.map(Into::into) + let gid = group + .map(Into::into) .unwrap_or_else(|| (0 as gid_t).wrapping_sub(1)); (uid, gid) } @@ -644,7 +722,11 @@ fn chown_raw_ids(owner: Option, group: Option) -> (libc::uid_t, libc:: /// 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<()> { +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) } @@ -688,8 +770,8 @@ pub enum FchownatFlags { /// If `flag` is `FchownatFlags::NoFollowSymlink` and `path` names a symbolic link, /// then the mode of the symbolic link is changed. /// -/// `fchownat(None, path, mode, FchownatFlags::NoFollowSymlink)` is identical to -/// a call `libc::lchown(path, mode)`. That's why `lchmod` is unimplemented in +/// `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 @@ -703,23 +785,33 @@ pub fn fchownat( group: Option, flag: FchownatFlags, ) -> Result<()> { - let atflag = - match flag { - FchownatFlags::FollowSymlink => AtFlags::empty(), - FchownatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, - }; + 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) + 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() + args.iter() + .map(|s| s.as_ref().as_ptr()) + .chain(once(ptr::null())) + .collect() } /// Replace the current process image with a new one (see @@ -732,14 +824,11 @@ fn to_exec_array>(args: &[S]) -> Vec<*const c_char> { 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()) - }; + 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)). /// @@ -753,13 +842,15 @@ pub fn execv>(path: &CStr, argv: &[S]) -> Result { /// 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 { +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()) - }; + unsafe { libc::execve(path.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }; Err(Errno::last()) } @@ -774,12 +865,13 @@ pub fn execve, SE: AsRef>(path: &CStr, args: &[SA], env: & /// 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 { +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()) - }; + unsafe { libc::execvp(filename.as_ptr(), args_p.as_ptr()) }; Err(Errno::last()) } @@ -791,10 +883,12 @@ pub fn execvp>(filename: &CStr, args: &[S]) -> Result /// 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 { +#[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); @@ -815,20 +909,22 @@ pub fn execvpe, SE: AsRef>(filename: &CStr, args: &[SA], e /// /// This function is similar to `execve`, except that the program to be executed /// is referenced as a file descriptor instead of a path. -// Note for NetBSD and OpenBSD: although rust-lang/libc includes it (under -// unix/bsd/netbsdlike/) fexecve is not currently implemented on NetBSD nor on -// OpenBSD. -#[cfg(any(target_os = "android", - target_os = "linux", - target_os = "freebsd"))] +#[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 { +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()) - }; + unsafe { libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr()) }; Err(Errno::last()) } @@ -845,14 +941,25 @@ pub fn fexecve ,SE: AsRef>(fd: RawFd, args: &[SA], env: &[ /// 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 { +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); + libc::syscall( + libc::SYS_execveat, + dirfd, + pathname.as_ptr(), + args_p.as_ptr(), + env_p.as_ptr(), + flags, + ); }; Err(Errno::last()) @@ -883,26 +990,32 @@ pub fn execveat,SE: AsRef>(dirfd: RawFd, pathname: &CStr, /// 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"))] +#[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 return if the name is not valid or the current process does not have -/// permissions to update the host name. +/// 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. @@ -912,6 +1025,7 @@ pub fn sethostname>(name: S) -> Result<()> { target_os = "illumos", target_os = "ios", target_os = "macos", + target_os = "aix", target_os = "solaris", ))] { type sethostname_len_t = c_int; } else { @@ -925,36 +1039,37 @@ pub fn sethostname>(name: S) -> Result<()> { Errno::result(res).map(drop) } -/// Get the host name and store it in the provided buffer, returning a pointer -/// the `CStr` in that buffer on success (see +/// 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 a provided buffer. The buffer will be populated with bytes up -/// to the length of the provided slice including a NUL terminating byte. If -/// the hostname is longer than the length provided, no error will be provided. -/// The posix specification does not specify whether implementations will -/// null-terminate in this case, but the nix implementation will ensure that the -/// buffer is null terminated in this case. +/// store it in an internal buffer, returning it as an `OsString` if successful. /// /// ```no_run /// use nix::unistd; /// -/// let mut buf = [0u8; 64]; -/// let hostname_cstr = unistd::gethostname(&mut buf).expect("Failed getting hostname"); -/// let hostname = hostname_cstr.to_str().expect("Hostname wasn't valid UTF-8"); +/// 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(buffer: &mut [u8]) -> Result<&CStr> { +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.len() as size_t; + let len = buffer.capacity() as size_t; let res = unsafe { libc::gethostname(ptr, len) }; Errno::result(res).map(|_| { - buffer[len - 1] = 0; // ensure always null-terminated - unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) } + 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 /// @@ -990,7 +1105,9 @@ pub fn close(fd: RawFd) -> Result<()> { /// /// 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) }; + 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) } @@ -999,11 +1116,16 @@ pub fn read(fd: RawFd, buf: &mut [u8]) -> Result { /// /// 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) }; + 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 @@ -1020,23 +1142,27 @@ pub enum Whence { /// 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"))] + #[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 + #[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. @@ -1049,54 +1175,66 @@ pub fn lseek(fd: RawFd, offset: off_t, whence: Whence) -> Result { } #[cfg(any(target_os = "linux", target_os = "android"))] -pub fn lseek64(fd: RawFd, offset: libc::off64_t, whence: Whence) -> Result { +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> { - unsafe { - let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); + let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); - let res = libc::pipe(fds.as_mut_ptr() as *mut c_int); + let res = unsafe { libc::pipe(fds.as_mut_ptr() as *mut c_int) }; - Error::result(res)?; + Error::result(res)?; - Ok((fds.assume_init()[0], fds.assume_init()[1])) - } + 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`.")] +#[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"))] +#[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()) - }; + let res = + unsafe { libc::pipe2(fds.as_mut_ptr() as *mut c_int, flags.bits()) }; Errno::result(res)?; @@ -1109,11 +1247,8 @@ pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { /// [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) - } - })?; + let res = path + .with_nix_path(|cstr| unsafe { libc::truncate(cstr.as_ptr(), len) })?; Errno::result(res).map(drop) } @@ -1122,8 +1257,8 @@ pub fn truncate(path: &P, len: off_t) -> Result<()> { /// /// 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 ftruncate(fd: Fd, len: off_t) -> Result<()> { + Errno::result(unsafe { libc::ftruncate(fd.as_fd().as_raw_fd(), len) }).map(drop) } pub fn isatty(fd: RawFd) -> Result { @@ -1137,7 +1272,7 @@ pub fn isatty(fd: RawFd) -> Result { Errno::ENOTTY => Ok(false), err => Err(err), } - } + } } } @@ -1169,40 +1304,31 @@ pub fn linkat( newpath: &P, flag: LinkatFlags, ) -> Result<()> { + let atflag = match flag { + LinkatFlags::SymlinkFollow => AtFlags::AT_SYMLINK_FOLLOW, + LinkatFlags::NoSymlinkFollow => AtFlags::empty(), + }; - 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 - ) - } - }) - })??; + 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()) - } - })?; + let res = + path.with_nix_path(|cstr| unsafe { libc::unlink(cstr.as_ptr()) })?; Errno::result(res).map(drop) } @@ -1229,26 +1355,25 @@ pub fn unlinkat( 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) - } + 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()) } - })?; + let res = + path.with_nix_path(|cstr| unsafe { libc::chroot(cstr.as_ptr()) })?; Errno::result(res).map(drop) } @@ -1267,6 +1392,17 @@ 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) @@ -1281,19 +1417,27 @@ pub fn fsync(fd: RawFd) -> Result<()> { /// /// See also /// [fdatasync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html) -// `fdatasync(2) is in POSIX, but in libc it is only defined in `libc::notbsd`. -// TODO: exclude only Apple systems after https://github.com/rust-lang/libc/pull/211 -#[cfg(any(target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "illumos", - target_os = "solaris"))] +#[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 /// @@ -1374,7 +1518,10 @@ pub fn setgid(gid: Gid) -> Result<()> { 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. @@ -1396,6 +1543,10 @@ 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. /// @@ -1428,27 +1579,31 @@ pub fn getgroups() -> Result> { // 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); + 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) + 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) + } + Err(e) => return Err(e), } } } @@ -1472,7 +1627,7 @@ pub fn getgroups() -> Result> { /// # use std::error::Error; /// # use nix::unistd::*; /// # -/// # fn try_main() -> Result<(), Box> { +/// # fn try_main() -> Result<(), Box> { /// let uid = Uid::from_raw(33); /// let gid = Gid::from_raw(34); /// setgroups(&[gid])?; @@ -1484,17 +1639,23 @@ pub fn getgroups() -> Result> { /// # /// # try_main().unwrap(); /// ``` -#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] +#[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", + if #[cfg(any(target_os = "aix", + target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", target_os = "ios", target_os = "macos", target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd"))] { + target_os = "openbsd", + target_os = "solaris"))] { type setgroups_ngroups_t = c_int; } else { type setgroups_ngroups_t = size_t; @@ -1504,7 +1665,10 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { // 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) + libc::setgroups( + groups.len() as setgroups_ngroups_t, + groups.as_ptr() as *const gid_t, + ) }; Errno::result(res).map(drop) @@ -1530,10 +1694,13 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { /// 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")))] +#[cfg(not(any( + target_os = "aix", + 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, @@ -1552,10 +1719,12 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result> { 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) + 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. @@ -1598,7 +1767,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result> { /// # use std::ffi::CString; /// # use nix::unistd::*; /// # -/// # fn try_main() -> Result<(), Box> { +/// # fn try_main() -> Result<(), Box> { /// let user = CString::new("www-data").unwrap(); /// let uid = Uid::from_raw(33); /// let gid = Gid::from_raw(33); @@ -1611,7 +1780,12 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result> { /// # /// # try_main().unwrap(); /// ``` -#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] +#[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"))] { @@ -1621,10 +1795,15 @@ pub fn initgroups(user: &CStr, group: Gid) -> Result<()> { } } let gid: gid_t = group.into(); - let res = unsafe { libc::initgroups(user.as_ptr(), gid as initgroups_group_t) }; + 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. /// @@ -1661,8 +1840,8 @@ pub mod alarm { //! //! Scheduling an alarm and waiting for the signal: //! -#![cfg_attr(target_os = "redox", doc = " ```rust,ignore")] -#![cfg_attr(not(target_os = "redox"), doc = " ```rust")] + #![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}; @@ -1722,6 +1901,7 @@ pub mod alarm { } } } +} /// Suspend execution for an interval of time /// @@ -1732,19 +1912,21 @@ pub fn sleep(seconds: c_uint) -> c_uint { unsafe { libc::sleep(seconds) } } -#[cfg(not(target_os = "redox"))] +feature! { +#![feature = "acct"] + +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] pub mod acct { - use crate::{Result, NixPath}; use crate::errno::Errno; + use crate::{NixPath, Result}; 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()) } - })?; + let res = filename + .with_nix_path(|cstr| unsafe { libc::acct(cstr.as_ptr()) })?; Errno::result(res).map(drop) } @@ -1756,7 +1938,10 @@ pub mod acct { 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` @@ -1783,7 +1968,8 @@ pub mod acct { /// ``` #[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 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 @@ -1792,6 +1978,10 @@ pub fn mkstemp(template: &P) -> Result<(RawFd, PathBuf)> { Errno::result(fd)?; Ok((fd, PathBuf::from(pathname))) } +} + +feature! { +#![all(feature = "fs", feature = "feature")] /// Variable names for `pathconf` /// @@ -1813,10 +2003,17 @@ pub fn mkstemp(template: &P) -> Result<(RawFd, PathBuf)> { #[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"))] + #[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, @@ -1837,36 +2034,87 @@ pub enum PathconfVar { /// 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(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(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(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(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(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(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(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 @@ -1879,27 +2127,53 @@ pub enum PathconfVar { /// 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(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(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(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 + _POSIX_TIMESTAMP_RESOLUTION = libc::_PC_TIMESTAMP_RESOLUTION, } /// Like `pathconf`, but works with file descriptors instead of paths (see @@ -1955,12 +2229,13 @@ pub fn fpathconf(fd: RawFd, var: PathconfVar) -> Result> { /// - `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) - } +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 { @@ -1972,6 +2247,10 @@ pub fn pathconf(path: &P, var: PathconfVar) -> Result(path: &P, var: PathconfVar) -> Result. - #[cfg(not(target_os = "redox"))] + /// 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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(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(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(target_os = "redox"))] + #[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(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(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(target_os = "redox"))] + #[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(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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(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(target_os = "redox"))] + #[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(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(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(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(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(target_os = "redox"))] + #[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(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(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(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(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(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(target_os = "redox"))] + #[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(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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(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(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(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(target_os = "redox"))] + #[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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(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(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(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(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(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(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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(target_os = "redox"))] + #[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(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(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(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(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(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(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(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(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(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(target_os = "redox"))] + #[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(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(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(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 @@ -2472,19 +3199,25 @@ pub fn sysconf(var: SysconfVar) -> Result> { Ok(Some(raw)) } } +} #[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(feature = "fs")] mod pivot_root { - use crate::{Result, NixPath}; use crate::errno::Errno; + use crate::{NixPath, Result}; pub fn pivot_root( - new_root: &P1, put_old: &P2) -> Result<()> { + 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()) - } + put_old.with_nix_path(|put_old| unsafe { + libc::syscall( + libc::SYS_pivot_root, + new_root.as_ptr(), + put_old.as_ptr(), + ) }) })??; @@ -2492,12 +3225,20 @@ mod pivot_root { } } -#[cfg(any(target_os = "android", target_os = "freebsd", - target_os = "linux", target_os = "openbsd"))] +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] mod setres { - use crate::Result; + feature! { + #![feature = "user"] + + use super::{Gid, Uid}; use crate::errno::Errno; - use super::{Uid, Gid}; + use crate::Result; /// Sets the real, effective, and saved uid. /// ([see setresuid(2)](https://man7.org/linux/man-pages/man2/setresuid.2.html)) @@ -2510,7 +3251,8 @@ mod setres { /// 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()) }; + let res = + unsafe { libc::setresuid(ruid.into(), euid.into(), suid.into()) }; Errno::result(res).map(drop) } @@ -2526,24 +3268,35 @@ mod setres { /// 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()) }; + let res = + unsafe { libc::setresgid(rgid.into(), egid.into(), sgid.into()) }; Errno::result(res).map(drop) } + } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" +))] mod getres { - use crate::Result; + feature! { + #![feature = "user"] + + use super::{Gid, Uid}; use crate::errno::Errno; - use super::{Uid, Gid}; + use crate::Result; /// Real, effective and saved user IDs. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ResUid { pub real: Uid, pub effective: Uid, - pub saved: Uid + pub saved: Uid, } /// Real, effective and saved group IDs. @@ -2551,7 +3304,7 @@ mod getres { pub struct ResGid { pub real: Gid, pub effective: Gid, - pub saved: Gid + pub saved: Gid, } /// Gets the real, effective, and saved user IDs. @@ -2570,7 +3323,11 @@ mod getres { 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) }) + Errno::result(res).map(|_| ResUid { + real: Uid(ruid), + effective: Uid(euid), + saved: Uid(suid), + }) } /// Gets the real, effective, and saved group IDs. @@ -2589,12 +3346,19 @@ mod getres { 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) } ) + Errno::result(res).map(|_| ResGid { + real: Gid(rgid), + effective: Gid(egid), + saved: Gid(sgid), + }) + } } } -libc_bitflags!{ +#[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; @@ -2607,17 +3371,69 @@ libc_bitflags!{ } } +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) - } + 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 @@ -2625,11 +3441,11 @@ pub fn access(path: &P, amode: AccessFlags) -> Result<()> { /// 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, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct User { /// Username pub name: String, - /// User password (probably encrypted) + /// User password (probably hashed) pub passwd: CString, /// User ID pub uid: Uid, @@ -2643,59 +3459,116 @@ pub struct User { /// Path to shell pub shell: PathBuf, /// Login class - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + target_os = "aix", + 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 = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + target_os = "aix", + 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 = "illumos", - target_os = "linux", - target_os = "solaris")))] - pub expire: libc::time_t -} - -#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd + #[cfg(not(any( + target_os = "aix", + 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: CStr::from_ptr((*pw).pw_name).to_string_lossy().into_owned(), - passwd: CString::new(CStr::from_ptr((*pw).pw_passwd).to_bytes()).unwrap(), - #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] - gecos: CString::new(CStr::from_ptr((*pw).pw_gecos).to_bytes()).unwrap(), - dir: PathBuf::from(OsStr::from_bytes(CStr::from_ptr((*pw).pw_dir).to_bytes())), - shell: 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 = "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 = "illumos", - target_os = "linux", - target_os = "solaris")))] - change: (*pw).pw_change, - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] - expire: (*pw).pw_expire + 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 = "aix", + 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 = "aix", + 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 = "aix", + target_os = "android", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "solaris" + )))] + expire: pw.pw_expire, } } } @@ -2719,29 +3592,44 @@ impl From for libc::passwd { Self { pw_name: name, pw_passwd: u.passwd.into_raw(), - #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] + #[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 = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + target_os = "aix", + 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 = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + target_os = "aix", + 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 = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + target_os = "aix", + 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(), @@ -2755,12 +3643,19 @@ impl From for libc::passwd { #[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd impl User { - fn from_anything(f: F) -> Result> + /// # Safety + /// + /// If `f` writes to its `*mut *mut libc::passwd` parameter, then it must + /// also initialize the value pointed to by its `*mut libc::group` + /// parameter. + unsafe fn from_anything(f: F) -> Result> where - F: Fn(*mut libc::passwd, - *mut libc::c_char, - libc::size_t, - *mut *mut libc::passwd) -> libc::c_int + 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) { @@ -2773,12 +3668,19 @@ impl User { let mut res = ptr::null_mut(); loop { - let error = f(pwd.as_mut_ptr(), cbuf.as_mut_ptr(), cbuf.capacity(), &mut res); + 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() }; + // SAFETY: `f` guarantees that `pwd` is initialized if `res` + // is not null. + let pwd = pwd.assume_init(); return Ok(Some(User::from(&pwd))); } } else if Errno::last() == Errno::ERANGE { @@ -2801,18 +3703,22 @@ impl User { /// use nix::unistd::{Uid, User}; /// // Returns an Result>, thus the double unwrap. /// let res = User::from_uid(Uid::from_raw(0)).unwrap().unwrap(); - /// assert!(res.name == "root"); + /// 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) } - }) + // SAFETY: `getpwuid_r` will write to `res` if it initializes the value + // at `pwd`. + unsafe { + User::from_anything(|pwd, cbuf, cap, res| { + 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) + /// [getpwnam_r(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam_r.html) /// /// # Examples /// @@ -2820,19 +3726,26 @@ impl User { /// use nix::unistd::User; /// // Returns an Result>, thus the double unwrap. /// let res = User::from_name("root").unwrap().unwrap(); - /// assert!(res.name == "root"); + /// assert_eq!(res.name, "root"); /// ``` pub fn from_name(name: &str) -> Result> { - let name = CString::new(name).unwrap(); - User::from_anything(|pwd, cbuf, cap, res| { - unsafe { libc::getpwnam_r(name.as_ptr(), pwd, cbuf, cap, res) } - }) + let name = match CString::new(name) { + Ok(c_str) => c_str, + Err(_nul_error) => return Ok(None), + }; + // SAFETY: `getpwnam_r` will write to `res` if it initializes the value + // at `pwd`. + unsafe { + User::from_anything(|pwd, cbuf, cap, res| { + 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, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Group { /// Group name pub name: String, @@ -2841,7 +3754,7 @@ pub struct Group { /// Group ID pub gid: Gid, /// List of Group members - pub mem: Vec + pub mem: Vec, } #[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd @@ -2849,10 +3762,23 @@ 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) + name: if gr.gr_name.is_null() { + Default::default() + } else { + CStr::from_ptr(gr.gr_name).to_string_lossy().into_owned() + }, + passwd: if gr.gr_passwd.is_null() { + Default::default() + } else { + CString::new(CStr::from_ptr(gr.gr_passwd).to_bytes()) + .unwrap() + }, + gid: Gid::from_raw(gr.gr_gid), + mem: if gr.gr_mem.is_null() { + Default::default() + } else { + Group::members(gr.gr_mem) + }, } } } @@ -2876,12 +3802,19 @@ impl Group { ret } - fn from_anything(f: F) -> Result> + /// # Safety + /// + /// If `f` writes to its `*mut *mut libc::group` parameter, then it must + /// also initialize the value pointed to by its `*mut libc::group` + /// parameter. + unsafe fn from_anything(f: F) -> Result> where - F: Fn(*mut libc::group, - *mut libc::c_char, - libc::size_t, - *mut *mut libc::group) -> libc::c_int + 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) { @@ -2894,12 +3827,19 @@ impl Group { let mut res = ptr::null_mut(); loop { - let error = f(grp.as_mut_ptr(), cbuf.as_mut_ptr(), cbuf.capacity(), &mut res); + 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() }; + // SAFETY: `f` guarantees that `grp` is initialized if `res` + // is not null. + let grp = grp.assume_init(); return Ok(Some(Group::from(&grp))); } } else if Errno::last() == Errno::ERANGE { @@ -2927,9 +3867,13 @@ impl Group { /// 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) } - }) + // SAFETY: `getgrgid_r` will write to `res` if it initializes the value + // at `grp`. + unsafe { + Group::from_anything(|grp, cbuf, cap, res| { + libc::getgrgid_r(gid.0, grp, cbuf, cap, res) + }) + } } /// Get a group by name. @@ -2948,12 +3892,23 @@ impl Group { /// assert!(res.name == "root"); /// ``` pub fn from_name(name: &str) -> Result> { - let name = CString::new(name).unwrap(); - Group::from_anything(|grp, cbuf, cap, res| { - unsafe { libc::getgrnam_r(name.as_ptr(), grp, cbuf, cap, res) } - }) + let name = match CString::new(name) { + Ok(c_str) => c_str, + Err(_nul_error) => return Ok(None), + }; + // SAFETY: `getgrnam_r` will write to `res` if it initializes the value + // at `grp`. + unsafe { + Group::from_anything(|grp, cbuf, cap, res| { + 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)). @@ -2972,6 +3927,10 @@ pub fn ttyname(fd: RawFd) -> Result { 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. /// @@ -2992,3 +3951,27 @@ pub fn getpeereid(fd: RawFd) -> Result<(Uid, 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 index 84a0b4fa30..bb056aab87 100644 --- a/test/common/mod.rs +++ b/test/common/mod.rs @@ -1,6 +1,7 @@ use cfg_if::cfg_if; -#[macro_export] macro_rules! skip { +#[macro_export] +macro_rules! skip { ($($reason: expr),+) => { use ::std::io::{self, Write}; @@ -33,42 +34,49 @@ cfg_if! { /// Skip the test if we don't have the ability to mount file systems. #[cfg(target_os = "freebsd")] -#[macro_export] macro_rules! require_mount { +#[macro_export] +macro_rules! require_mount { ($name:expr) => { - use ::sysctl::CtlValue; + use ::sysctl::{CtlValue, Sysctl}; use nix::unistd::Uid; - if !Uid::current().is_root() && CtlValue::Int(0) == ::sysctl::value("vfs.usermount").unwrap() + 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); + 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 { +#[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 { +#[macro_export] +macro_rules! skip_if_jailed { ($name:expr) => { - use ::sysctl::CtlValue; + use ::sysctl::{CtlValue, Sysctl}; - if let CtlValue::Int(1) = ::sysctl::value("security.jail.jailed") - .unwrap() - { + 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 { +#[macro_export] +macro_rules! skip_if_not_root { ($name:expr) => { use nix::unistd::Uid; @@ -111,15 +119,15 @@ cfg_if! { let version_requirement = VersionReq::parse($version_requirement) .expect("Bad match_version provided"); - let uname = nix::sys::utsname::uname(); - println!("{}", uname.sysname()); - println!("{}", uname.nodename()); - println!("{}", uname.release()); - println!("{}", uname.version()); - println!("{}", uname.machine()); + 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_string() + 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("_", "-") diff --git a/test/sys/mod.rs b/test/sys/mod.rs index e73d9b1dc6..20312120a6 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -5,43 +5,56 @@ mod test_signal; // 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", - target_os = "linux", - target_os = "macos", - target_os = "netbsd"))] +#[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(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] mod test_socket; -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox")))] mod test_sockopt; -#[cfg(not(target_os = "redox"))] -mod test_select; +mod test_stat; #[cfg(any(target_os = "android", target_os = "linux"))] mod test_sysinfo; -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] mod test_termios; -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] -mod test_ioctl; -mod test_wait; mod test_uio; +mod test_wait; -#[cfg(target_os = "linux")] +#[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"))] +#[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 index 80cd053f8d..5035b5a08f 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -1,415 +1,514 @@ -use libc::{c_int, c_void}; -use nix::Result; -use nix::errno::*; -use nix::sys::aio::*; -use nix::sys::signal::{SaFlags, SigAction, sigaction, SigevNotify, SigHandler, Signal, SigSet}; -use nix::sys::time::{TimeSpec, TimeValLike}; -use std::io::{Write, Read, Seek, SeekFrom}; -use std::ops::Deref; -use std::os::unix::io::AsRawFd; -use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::{thread, time}; +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; -// Helper that polls an AioCb for completion or error -fn poll_aio(aiocb: &mut Pin>) -> Result<()> { - loop { - let err = aiocb.error(); - if err != Err(Errno::EINPROGRESS) { return err; }; - thread::sleep(time::Duration::from_millis(10)); - } -} +pub static SIGNALED: AtomicBool = AtomicBool::new(false); -// Helper that polls a component of an LioCb for completion or error -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -fn poll_lio(liocb: &mut LioCb, i: usize) -> Result<()> { - loop { - let err = liocb.error(i); - if err != Err(Errno::EINPROGRESS) { return err; }; - thread::sleep(time::Duration::from_millis(10)); - } +extern "C" fn sigfunc(_: c_int) { + SIGNALED.store(true, Ordering::Relaxed); } -#[test] -fn test_accessors() { - let mut rbuf = vec![0; 4]; - let aiocb = AioCb::from_mut_slice( 1001, - 2, //offset - &mut rbuf, - 42, //priority - SigevNotify::SigevSignal { - signal: Signal::SIGUSR2, - si_value: 99 - }, - LioOpcode::LIO_NOP); - assert_eq!(1001, aiocb.fd()); - assert_eq!(Some(LioOpcode::LIO_NOP), aiocb.lio_opcode()); - 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); +// 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)); + } + }; } -// Tests AioCb.cancel. We aren't trying to test the OS's implementation, only -// our bindings. So it's sufficient to check that AioCb.cancel returned any -// AioCancelStat value. -#[test] -#[cfg_attr(target_env = "musl", ignore)] -fn test_cancel() { - let wbuf: &[u8] = b"CDEF"; - - let f = tempfile().unwrap(); - let mut aiocb = AioCb::from_slice( f.as_raw_fd(), - 0, //offset - wbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.write().unwrap(); - let err = aiocb.error(); - assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); +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); + } - let cancelstat = aiocb.cancel(); - assert!(cancelstat.is_ok()); + /// `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"); + } - // Wait for aiocb to complete, but don't care whether it succeeded - let _ = poll_aio(&mut aiocb); - let _ = aiocb.aio_return(); + #[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(); + } } -// 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 = AioCb::from_slice(f.as_raw_fd(), - 0, //offset - wbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.write().unwrap(); - let err = aiocb.error(); - assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); +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); + } - let cancelstat = aio_cancel_all(f.as_raw_fd()); - assert!(cancelstat.is_ok()); + // 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(); + } - // Wait for aiocb to complete, but don't care whether it succeeded - let _ = poll_aio(&mut aiocb); - let _ = aiocb.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] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_fsync() { - const INITIAL: &[u8] = b"abcdef123456"; - let mut f = tempfile().unwrap(); - f.write_all(INITIAL).unwrap(); - let mut aiocb = AioCb::from_fd( f.as_raw_fd(), - 0, //priority - SigevNotify::SigevNone); - let err = aiocb.fsync(AioFsyncMode::O_SYNC); - assert!(err.is_ok()); - poll_aio(&mut aiocb).unwrap(); - aiocb.aio_return().unwrap(); -} + // 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(); -/// `AioCb::fsync` 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 test_fsync_error() { - use std::mem; + 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()); + } - 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 aiocb = AioCb::from_fd( f.as_raw_fd(), - 0, //priority - SigevNotify::SigevNone); - let err = aiocb.fsync(mode); - assert!(err.is_err()); + // 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()); + } } -#[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(); +#[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); + } - let mut wcb = AioCb::from_slice( f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_WRITE); - - let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(), - 8, //offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_READ); - wcb.write().unwrap(); - rcb.read().unwrap(); - loop { + #[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 cbbuf = [wcb.as_ref(), rcb.as_ref()]; - let r = aio_suspend(&cbbuf[..], Some(timeout)); - match r { - Err(Errno::EINTR) => continue, - Err(e) => panic!("aio_suspend returned {:?}", e), - Ok(_) => () - }; - } - if rcb.error() != Err(Errno::EINPROGRESS) && - wcb.error() != Err(Errno::EINPROGRESS) { - break + 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); } - - assert_eq!(wcb.aio_return().unwrap() as usize, WBUF.len()); - assert_eq!(rcb.aio_return().unwrap() as usize, rlen); } -// 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 test_read() { - 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 mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), - 2, //offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.read().unwrap(); - - let err = poll_aio(&mut aiocb); - assert_eq!(err, Ok(())); - assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len()); +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); } - assert_eq!(EXPECT, rbuf.deref().deref()); -} + // 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"; -/// `AioCb::read` 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 test_read_error() { - const INITIAL: &[u8] = b"abcdef123456"; - let mut rbuf = vec![0; 4]; - let mut f = tempfile().unwrap(); - f.write_all(INITIAL).unwrap(); - let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), - -1, //an invalid offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - assert!(aiocb.read().is_err()); -} + 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)); -// Tests from_mut_slice -#[test] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_read_into_mut_slice() { - 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 mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(), - 2, //offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.read().unwrap(); - - let err = poll_aio(&mut aiocb); - assert_eq!(err, Ok(())); - assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len()); + 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(); } - assert_eq!(rbuf, EXPECT); -} + // 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(); -// Tests from_ptr -#[test] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_read_into_pointer() { - 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(); - { - // Safety: ok because rbuf lives until after poll_aio - let mut aiocb = unsafe { - AioCb::from_mut_ptr( f.as_raw_fd(), - 2, //offset - rbuf.as_mut_ptr() as *mut c_void, - rbuf.len(), - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP) - }; - aiocb.read().unwrap(); - - let err = poll_aio(&mut aiocb); + let err = poll_aio!(&mut aiow); assert_eq!(err, Ok(())); - assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len()); - } + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); - assert_eq!(rbuf, EXPECT); -} + f.rewind().unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert_eq!(len, EXPECT.len()); + assert_eq!(rbuf, EXPECT); + } -// Test reading into an immutable buffer. It should fail -// FIXME: This test fails to panic on Linux/musl -#[test] -#[should_panic(expected = "Can't read into an immutable buffer")] -#[cfg_attr(target_env = "musl", ignore)] -fn test_read_immutable_buffer() { - let rbuf: &[u8] = b"CDEF"; - let f = tempfile().unwrap(); - let mut aiocb = AioCb::from_slice( f.as_raw_fd(), - 2, //offset - rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.read().unwrap(); -} + // 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()); -// Test a simple aio operation with no completion notification. We must poll -// for completion. Unlike test_aio_read, this test uses AioCb::from_slice -#[test] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_write() { - const INITIAL: &[u8] = b"abcdef123456"; - let wbuf = "CDEF".to_string().into_bytes(); - let mut rbuf = Vec::new(); - const EXPECT: &[u8] = b"abCDEF123456"; + f.rewind().unwrap(); + let len = f.read_to_end(&mut rbuf).unwrap(); + assert_eq!(len, EXPECT.len()); + assert_eq!(rbuf, EXPECT); + } - let mut f = tempfile().unwrap(); - f.write_all(INITIAL).unwrap(); - let mut aiocb = AioCb::from_slice( f.as_raw_fd(), - 2, //offset - &wbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.write().unwrap(); - - let err = poll_aio(&mut aiocb); - assert_eq!(err, Ok(())); - assert_eq!(aiocb.aio_return().unwrap() as usize, wbuf.len()); - - f.seek(SeekFrom::Start(0)).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 + } } -// Tests `AioCb::from_ptr` -#[test] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_write_from_pointer() { - 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(); - // Safety: ok because aiocb outlives poll_aio - let mut aiocb = unsafe { - AioCb::from_ptr( f.as_raw_fd(), - 2, //offset - wbuf.as_ptr() as *const c_void, - wbuf.len(), - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP) - }; - aiocb.write().unwrap(); - - let err = poll_aio(&mut aiocb); - assert_eq!(err, Ok(())); - assert_eq!(aiocb.aio_return().unwrap() as usize, wbuf.len()); - - f.seek(SeekFrom::Start(0)).unwrap(); - let len = f.read_to_end(&mut rbuf).unwrap(); - assert_eq!(len, EXPECT.len()); - assert_eq!(rbuf, EXPECT); -} +#[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); + } -/// `AioCb::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 test_write_error() { - let wbuf = "CDEF".to_string().into_bytes(); - let mut aiocb = AioCb::from_slice( 666, // An invalid file descriptor - 0, //offset - &wbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - assert!(aiocb.write().is_err()); -} + // 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(); -lazy_static! { - pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); -} + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen); -extern fn sigfunc(_: c_int) { - SIGNALED.store(true, Ordering::Relaxed); + 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 -// FIXME: This test is ignored on mips because of failures in qemu in CI #[test] -#[cfg_attr(any(all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips", target_arch = "mips64"), ignore)] -fn test_write_sigev_signal() { +#[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()); + let sa = SigAction::new( + SigHandler::Handler(sigfunc), + SaFlags::SA_RESETHAND, + SigSet::empty(), + ); SIGNALED.store(false, Ordering::Relaxed); unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); @@ -420,201 +519,106 @@ fn test_write_sigev_signal() { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiocb = AioCb::from_slice( f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevSignal { - signal: Signal::SIGUSR2, - si_value: 0 //TODO: validate in sigfunc - }, - LioOpcode::LIO_NOP); - aiocb.write().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!(aiocb.aio_return().unwrap() as usize, WBUF.len()); - f.seek(SeekFrom::Start(0)).unwrap(); + 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); } -// Test LioCb::listio with LIO_WAIT, so all AIO ops should be complete by the -// time listio returns. -#[test] -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_liocb_listio_wait() { - const INITIAL: &[u8] = b"abcdef123456"; - const WBUF: &[u8] = b"CDEF"; - let mut rbuf = vec![0; 4]; - let rlen = rbuf.len(); - let mut rbuf2 = Vec::new(); - const EXPECT: &[u8] = b"abCDEF123456"; - let mut f = tempfile().unwrap(); - - f.write_all(INITIAL).unwrap(); - - { - let mut liocb = LioCbBuilder::with_capacity(2) - .emplace_slice( - f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_WRITE - ).emplace_mut_slice( - f.as_raw_fd(), - 8, //offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_READ - ).finish(); - let err = liocb.listio(LioMode::LIO_WAIT, SigevNotify::SigevNone); - err.expect("lio_listio"); - - assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - assert_eq!(liocb.aio_return(1).unwrap() as usize, rlen); - } - assert_eq!(rbuf.deref().deref(), b"3456"); - - f.seek(SeekFrom::Start(0)).unwrap(); - let len = f.read_to_end(&mut rbuf2).unwrap(); - assert_eq!(len, EXPECT.len()); - assert_eq!(rbuf2, EXPECT); -} - -// Test LioCb::listio with LIO_NOWAIT and no SigEvent, so we must use some other -// mechanism to check for the individual AioCb's completion. +// Tests using aio_cancel_all for all outstanding IOs. #[test] -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)] -fn test_liocb_listio_nowait() { - const INITIAL: &[u8] = b"abcdef123456"; - const WBUF: &[u8] = b"CDEF"; - let mut rbuf = vec![0; 4]; - let rlen = rbuf.len(); - let mut rbuf2 = Vec::new(); - const EXPECT: &[u8] = b"abCDEF123456"; - let mut f = tempfile().unwrap(); +#[cfg_attr(target_env = "musl", ignore)] +fn test_aio_cancel_all() { + let wbuf: &[u8] = b"CDEF"; - f.write_all(INITIAL).unwrap(); + 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)); - { - let mut liocb = LioCbBuilder::with_capacity(2) - .emplace_slice( - f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_WRITE - ).emplace_mut_slice( - f.as_raw_fd(), - 8, //offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_READ - ).finish(); - let err = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone); - err.expect("lio_listio"); - - poll_lio(&mut liocb, 0).unwrap(); - poll_lio(&mut liocb, 1).unwrap(); - assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - assert_eq!(liocb.aio_return(1).unwrap() as usize, rlen); - } - assert_eq!(rbuf.deref().deref(), b"3456"); + aio_cancel_all(f.as_raw_fd()).unwrap(); - f.seek(SeekFrom::Start(0)).unwrap(); - let len = f.read_to_end(&mut rbuf2).unwrap(); - assert_eq!(len, EXPECT.len()); - assert_eq!(rbuf2, EXPECT); + // Wait for aiocb to complete, but don't care whether it succeeded + let _ = poll_aio!(&mut aiocb); + let _ = aiocb.as_mut().aio_return(); } -// Test LioCb::listio with LIO_NOWAIT and a SigEvent to indicate when all -// AioCb's are complete. -// FIXME: This test is ignored on mips/mips64 because of failures in qemu in CI. #[test] -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -#[cfg_attr(any(target_arch = "mips", target_arch = "mips64", target_env = "musl"), ignore)] -fn test_liocb_listio_signal() { - let _m = crate::SIGNAL_MTX.lock(); +// 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"CDEF"; + const WBUF: &[u8] = b"CDEFG"; + let timeout = TimeSpec::seconds(10); let mut rbuf = vec![0; 4]; let rlen = rbuf.len(); - let mut rbuf2 = Vec::new(); - const EXPECT: &[u8] = b"abCDEF123456"; let mut f = tempfile().unwrap(); - let sa = SigAction::new(SigHandler::Handler(sigfunc), - SaFlags::SA_RESETHAND, - SigSet::empty()); - let sigev_notify = SigevNotify::SigevSignal { signal: Signal::SIGUSR2, - si_value: 0 }; - f.write_all(INITIAL).unwrap(); - { - let mut liocb = LioCbBuilder::with_capacity(2) - .emplace_slice( - f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_WRITE - ).emplace_mut_slice( - f.as_raw_fd(), - 8, //offset - &mut rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_READ - ).finish(); - SIGNALED.store(false, Ordering::Relaxed); - unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap(); - let err = liocb.listio(LioMode::LIO_NOWAIT, sigev_notify); - err.expect("lio_listio"); - while !SIGNALED.load(Ordering::Relaxed) { - thread::sleep(time::Duration::from_millis(10)); + 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!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - assert_eq!(liocb.aio_return(1).unwrap() as usize, rlen); } - assert_eq!(rbuf.deref().deref(), b"3456"); - - f.seek(SeekFrom::Start(0)).unwrap(); - let len = f.read_to_end(&mut rbuf2).unwrap(); - assert_eq!(len, EXPECT.len()); - assert_eq!(rbuf2, EXPECT); -} -// Try to use LioCb::listio to read into an immutable buffer. It should fail -// FIXME: This test fails to panic on Linux/musl -#[test] -#[cfg(not(any(target_os = "ios", target_os = "macos")))] -#[should_panic(expected = "Can't read into an immutable buffer")] -#[cfg_attr(target_env = "musl", ignore)] -fn test_liocb_listio_read_immutable() { - let rbuf: &[u8] = b"abcd"; - let f = tempfile().unwrap(); - - - let mut liocb = LioCbBuilder::with_capacity(1) - .emplace_slice( - f.as_raw_fd(), - 2, //offset - rbuf, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_READ - ).finish(); - let _ = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone); + 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 index 71a2183bc1..bbe6623fd7 100644 --- a/test/sys/test_aio_drop.rs +++ b/test/sys/test_aio_drop.rs @@ -3,12 +3,17 @@ // the AIO subsystem and causes subsequent tests to fail #[test] #[should_panic(expected = "Dropped an in-progress AioCb")] -#[cfg(all(not(target_env = "musl"), - any(target_os = "linux", - target_os = "ios", - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd")))] +#[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::*; @@ -19,11 +24,12 @@ fn test_drop() { let f = tempfile().unwrap(); f.set_len(6).unwrap(); - let mut aiocb = AioCb::from_slice( f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_NOP); - aiocb.write().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 index 8d44cd08f0..84b100c1e9 100644 --- a/test/sys/test_epoll.rs +++ b/test/sys/test_epoll.rs @@ -1,23 +1,26 @@ -use nix::sys::epoll::{EpollCreateFlags, EpollFlags, EpollOp, EpollEvent}; -use nix::sys::epoll::{epoll_create1, epoll_ctl}; +#![allow(deprecated)] + 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); - assert!(result.is_err()); + result.expect_err("assertion failed"); assert_eq!(result.unwrap_err(), Errno::ENOENT); let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None); - assert!(result.is_err()); + 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); + 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 index 137816a352..bb5851a903 100644 --- a/test/sys/test_inotify.rs +++ b/test/sys/test_inotify.rs @@ -1,15 +1,16 @@ -use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify}; 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 instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); let tempdir = tempfile::tempdir().unwrap(); - instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); + instance + .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS) + .unwrap(); let events = instance.read_events(); assert_eq!(events.unwrap_err(), Errno::EAGAIN); @@ -22,11 +23,12 @@ pub fn test_inotify() { #[test] pub fn test_inotify_multi_events() { - let instance = Inotify::init(InitFlags::IN_NONBLOCK) - .unwrap(); + let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); let tempdir = tempfile::tempdir().unwrap(); - instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); + instance + .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS) + .unwrap(); let events = instance.read_events(); assert_eq!(events.unwrap_err(), Errno::EAGAIN); diff --git a/test/sys/test_ioctl.rs b/test/sys/test_ioctl.rs index 236d24268a..40f60cfdbc 100644 --- a/test/sys/test_ioctl.rs +++ b/test/sys/test_ioctl.rs @@ -30,9 +30,16 @@ ioctl_readwrite_buf!(readwritebuf_test, 0, 0, u32); #[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")){ + 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 { @@ -41,9 +48,16 @@ mod linux { } } + // 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")){ + 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 { @@ -55,19 +69,29 @@ mod linux { #[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); + 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); + 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")){ + 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 { @@ -79,15 +103,21 @@ mod linux { #[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); + 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); + 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); @@ -97,17 +127,21 @@ mod linux { #[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); + 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"))] +#[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() { @@ -164,8 +198,8 @@ mod linux_ioctls { use std::mem; use std::os::unix::io::AsRawFd; + use libc::{termios, TCGETS, TCSBRK, TCSETS, TIOCNXCL}; use tempfile::tempfile; - use libc::{TCGETS, TCSBRK, TCSETS, TIOCNXCL, termios}; use nix::errno::Errno; @@ -255,7 +289,7 @@ mod linux_ioctls { } // From linux/videodev2.h - ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio); + ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio); #[test] fn test_ioctl_readwrite() { let file = tempfile().unwrap(); @@ -281,7 +315,12 @@ mod linux_ioctls { } // From linux/spi/spidev.h - ioctl_write_buf!(spi_ioc_message, super::SPI_IOC_MAGIC, super::SPI_IOC_MESSAGE, spi_ioc_transfer); + 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(); @@ -298,8 +337,8 @@ mod freebsd_ioctls { use std::mem; use std::os::unix::io::AsRawFd; - use tempfile::tempfile; use libc::termios; + use tempfile::tempfile; use nix::errno::Errno; diff --git a/test/sys/test_lio_listio_resubmit.rs b/test/sys/test_lio_listio_resubmit.rs deleted file mode 100644 index c9077891cb..0000000000 --- a/test/sys/test_lio_listio_resubmit.rs +++ /dev/null @@ -1,106 +0,0 @@ -// vim: tw=80 - -// Annoyingly, Cargo is unable to conditionally build an entire test binary. So -// we must disable the test here rather than in Cargo.toml -#![cfg(target_os = "freebsd")] - -use nix::errno::*; -use nix::libc::off_t; -use nix::sys::aio::*; -use nix::sys::signal::SigevNotify; -use nix::unistd::{SysconfVar, sysconf}; -use std::os::unix::io::AsRawFd; -use std::{thread, time}; -use sysctl::CtlValue; -use tempfile::tempfile; - -const BYTES_PER_OP: usize = 512; - -/// Attempt to collect final status for all of `liocb`'s operations, freeing -/// system resources -fn finish_liocb(liocb: &mut LioCb) { - for j in 0..liocb.len() { - loop { - let e = liocb.error(j); - match e { - Ok(()) => break, - Err(Errno::EINPROGRESS) => - thread::sleep(time::Duration::from_millis(10)), - Err(x) => panic!("aio_error({:?})", x) - } - } - assert_eq!(liocb.aio_return(j).unwrap(), BYTES_PER_OP as isize); - } -} - -// Deliberately exceed system resource limits, causing lio_listio to return EIO. -// This test must run in its own process since it deliberately uses all AIO -// resources. ATM it is only enabled on FreeBSD, because I don't know how to -// check system AIO limits on other operating systems. -#[test] -fn test_lio_listio_resubmit() { - let mut resubmit_count = 0; - - // Lookup system resource limits - let alm = sysconf(SysconfVar::AIO_LISTIO_MAX) - .expect("sysconf").unwrap() as usize; - let maqpp = if let CtlValue::Int(x) = sysctl::value( - "vfs.aio.max_aio_queue_per_proc").unwrap(){ - x as usize - } else { - panic!("unknown sysctl"); - }; - - // Find lio_listio sizes that satisfy the AIO_LISTIO_MAX constraint and also - // result in a final lio_listio call that can only partially be queued - let target_ops = maqpp + alm / 2; - let num_listios = (target_ops + alm - 3) / (alm - 2); - let ops_per_listio = (target_ops + num_listios - 1) / num_listios; - assert!((num_listios - 1) * ops_per_listio < maqpp, - "the last lio_listio won't make any progress; fix the algorithm"); - println!("Using {:?} LioCbs of {:?} operations apiece", num_listios, - ops_per_listio); - - let f = tempfile().unwrap(); - let buffer_set = (0..num_listios).map(|_| { - (0..ops_per_listio).map(|_| { - vec![0u8; BYTES_PER_OP] - }).collect::>() - }).collect::>(); - - let mut liocbs = (0..num_listios).map(|i| { - let mut builder = LioCbBuilder::with_capacity(ops_per_listio); - for j in 0..ops_per_listio { - let offset = (BYTES_PER_OP * (i * ops_per_listio + j)) as off_t; - builder = builder.emplace_slice(f.as_raw_fd(), - offset, - &buffer_set[i][j][..], - 0, //priority - SigevNotify::SigevNone, - LioOpcode::LIO_WRITE); - } - let mut liocb = builder.finish(); - let mut err = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone); - while err == Err(Errno::EIO) || - err == Err(Errno::EAGAIN) || - err == Err(Errno::EINTR) { - // - thread::sleep(time::Duration::from_millis(10)); - resubmit_count += 1; - err = liocb.listio_resubmit(LioMode::LIO_NOWAIT, - SigevNotify::SigevNone); - } - liocb - }).collect::>(); - - // Ensure that every AioCb completed - for liocb in liocbs.iter_mut() { - finish_liocb(liocb); - } - - if resubmit_count > 0 { - println!("Resubmitted {:?} times, test passed", resubmit_count); - } else { - println!("Never resubmitted. Test ambiguous"); - } -} diff --git a/test/sys/test_mman.rs b/test/sys/test_mman.rs index a7ceedcbd1..b4674e53fa 100644 --- a/test/sys/test_mman.rs +++ b/test/sys/test_mman.rs @@ -1,55 +1,78 @@ use nix::sys::mman::{mmap, MapFlags, ProtFlags}; +use std::{num::NonZeroUsize, os::unix::io::BorrowedFd}; #[test] fn test_mmap_anonymous() { unsafe { - let ptr = mmap(std::ptr::null_mut(), 1, - ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, - MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, -1, 0) - .unwrap() as *mut u8; - assert_eq !(*ptr, 0x00u8); + let ptr = mmap::( + None, + NonZeroUsize::new(1).unwrap(), + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, + None, + 0, + ) + .unwrap() as *mut u8; + assert_eq!(*ptr, 0x00u8); *ptr = 0xffu8; - assert_eq !(*ptr, 0xffu8); + assert_eq!(*ptr, 0xffu8); } } #[test] #[cfg(any(target_os = "linux", target_os = "netbsd"))] fn test_mremap_grow() { - use nix::sys::mman::{mremap, MRemapFlags}; use nix::libc::{c_void, size_t}; + use nix::sys::mman::{mremap, MRemapFlags}; - const ONE_K : size_t = 1024; - let slice : &mut[u8] = unsafe { - let mem = mmap(std::ptr::null_mut(), 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) + 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, + None, + 0, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) }; - assert_eq !(slice[ONE_K - 1], 0x00); + assert_eq!(slice[ONE_K - 1], 0x00); slice[ONE_K - 1] = 0xFF; - assert_eq !(slice[ONE_K - 1], 0xFF); + assert_eq!(slice[ONE_K - 1], 0xFF); - let slice : &mut[u8] = unsafe { + 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(); + 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) + 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); + 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); + assert_eq!(slice[10 * ONE_K - 1], 0x00); slice[10 * ONE_K - 1] = 0xFF; - assert_eq !(slice[10 * ONE_K - 1], 0xFF); + assert_eq!(slice[10 * ONE_K - 1], 0xFF); } #[test] @@ -57,36 +80,43 @@ fn test_mremap_grow() { // 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::sys::mman::{mremap, MRemapFlags}; use nix::libc::{c_void, size_t}; + use nix::sys::mman::{mremap, MRemapFlags}; + use std::num::NonZeroUsize; - const ONE_K : size_t = 1024; - let slice : &mut[u8] = unsafe { - let mem = mmap(std::ptr::null_mut(), 10 * 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) + 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, + None, + 0, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) }; - assert_eq !(slice[ONE_K - 1], 0x00); + assert_eq!(slice[ONE_K - 1], 0x00); slice[ONE_K - 1] = 0xFF; - assert_eq !(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, 10 * ONE_K, ONE_K, - MRemapFlags::empty(), None) - .unwrap(); + 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. - #[cfg(target_os = "netbsd")] - let mem = mremap(slice.as_mut_ptr() as * mut c_void, 10 * ONE_K, ONE_K, - MRemapFlags::MAP_FIXED, None) - .unwrap(); - assert_eq !(mem, slice.as_mut_ptr() as * mut c_void); - std::slice::from_raw_parts_mut(mem as * mut u8, ONE_K) + 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); + assert_eq!(slice[ONE_K - 1], 0xFF); } diff --git a/test/sys/test_prctl.rs b/test/sys/test_prctl.rs new file mode 100644 index 0000000000..351213b7ef --- /dev/null +++ b/test/sys/test_prctl.rs @@ -0,0 +1,125 @@ +#[cfg(target_os = "linux")] +#[cfg(feature = "process")] +mod test_prctl { + use std::ffi::CStr; + + use nix::sys::prctl; + + #[cfg_attr(qemu, ignore)] + #[test] + fn test_get_set_subreaper() { + let original = prctl::get_child_subreaper().unwrap(); + + prctl::set_child_subreaper(true).unwrap(); + let subreaper = prctl::get_child_subreaper().unwrap(); + assert!(subreaper); + + prctl::set_child_subreaper(original).unwrap(); + } + + #[test] + fn test_get_set_dumpable() { + let original = prctl::get_dumpable().unwrap(); + + prctl::set_dumpable(false).unwrap(); + let dumpable = prctl::get_dumpable().unwrap(); + assert!(!dumpable); + + prctl::set_dumpable(original).unwrap(); + } + + #[test] + fn test_get_set_keepcaps() { + let original = prctl::get_keepcaps().unwrap(); + + prctl::set_keepcaps(true).unwrap(); + let keepcaps = prctl::get_keepcaps().unwrap(); + assert!(keepcaps); + + prctl::set_keepcaps(original).unwrap(); + } + + #[test] + fn test_get_set_clear_mce_kill() { + use prctl::PrctlMCEKillPolicy::*; + + prctl::set_mce_kill(PR_MCE_KILL_LATE).unwrap(); + let mce = prctl::get_mce_kill().unwrap(); + assert_eq!(mce, PR_MCE_KILL_LATE); + + prctl::clear_mce_kill().unwrap(); + let mce = prctl::get_mce_kill().unwrap(); + assert_eq!(mce, PR_MCE_KILL_DEFAULT); + } + + #[cfg_attr(qemu, ignore)] + #[test] + fn test_get_set_pdeathsig() { + use nix::sys::signal::Signal; + + let original = prctl::get_pdeathsig().unwrap(); + + prctl::set_pdeathsig(Signal::SIGUSR1).unwrap(); + let sig = prctl::get_pdeathsig().unwrap(); + assert_eq!(sig, Some(Signal::SIGUSR1)); + + prctl::set_pdeathsig(original).unwrap(); + } + + #[test] + fn test_get_set_name() { + let original = prctl::get_name().unwrap(); + + let long_name = + CStr::from_bytes_with_nul(b"0123456789abcdefghijklmn\0").unwrap(); + prctl::set_name(long_name).unwrap(); + let res = prctl::get_name().unwrap(); + + // name truncated by kernel to TASK_COMM_LEN + assert_eq!(&long_name.to_str().unwrap()[..15], res.to_str().unwrap()); + + let short_name = CStr::from_bytes_with_nul(b"01234567\0").unwrap(); + prctl::set_name(short_name).unwrap(); + let res = prctl::get_name().unwrap(); + assert_eq!(short_name.to_str().unwrap(), res.to_str().unwrap()); + + prctl::set_name(&original).unwrap(); + } + + #[cfg_attr(qemu, ignore)] + #[test] + fn test_get_set_timerslack() { + let original = prctl::get_timerslack().unwrap(); + + let slack = 60_000; + prctl::set_timerslack(slack).unwrap(); + let res = prctl::get_timerslack().unwrap(); + assert_eq!(slack, res as u64); + + prctl::set_timerslack(original as u64).unwrap(); + } + + #[test] + fn test_disable_enable_perf_events() { + prctl::task_perf_events_disable().unwrap(); + prctl::task_perf_events_enable().unwrap(); + } + + #[test] + fn test_get_set_no_new_privs() { + prctl::set_no_new_privs().unwrap(); + let no_new_privs = prctl::get_no_new_privs().unwrap(); + assert!(no_new_privs); + } + + #[test] + fn test_get_set_thp_disable() { + let original = prctl::get_thp_disable().unwrap(); + + prctl::set_thp_disable(true).unwrap(); + let thp_disable = prctl::get_thp_disable().unwrap(); + assert!(thp_disable); + + prctl::set_thp_disable(original).unwrap(); + } +} diff --git a/test/sys/test_pthread.rs b/test/sys/test_pthread.rs index fa9b510e85..ce048bae60 100644 --- a/test/sys/test_pthread.rs +++ b/test/sys/test_pthread.rs @@ -4,14 +4,14 @@ use nix::sys::pthread::*; #[test] fn test_pthread_self() { let tid = pthread_self(); - assert!(tid != ::std::ptr::null_mut()); + assert!(!tid.is_null()); } #[cfg(not(any(target_env = "musl", target_os = "redox")))] #[test] fn test_pthread_self() { let tid = pthread_self(); - assert!(tid != 0); + assert_ne!(tid, 0); } #[test] diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 83fff9a5b4..530560fe17 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -1,8 +1,14 @@ +#[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::unistd::getpid; 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; @@ -15,8 +21,9 @@ fn test_ptrace() { // 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); + assert!( + err == Errno::EPERM || err == Errno::EINVAL || err == Errno::ENOSYS + ); } // Just make sure ptrace_setoptions can be called at all, for now. @@ -24,8 +31,9 @@ fn test_ptrace() { #[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!(err != Errno::EOPNOTSUPP); + 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. @@ -34,7 +42,7 @@ fn test_ptrace_setoptions() { fn test_ptrace_getevent() { require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE); let err = ptrace::getevent(getpid()).unwrap_err(); - assert!(err != Errno::EOPNOTSUPP); + assert_ne!(err, Errno::EOPNOTSUPP); } // Just make sure ptrace_getsiginfo can be called at all, for now. @@ -58,7 +66,6 @@ fn test_ptrace_setsiginfo() { } } - #[test] fn test_ptrace_cont() { use nix::sys::ptrace; @@ -72,7 +79,7 @@ fn test_ptrace_cont() { let _m = crate::FORK_MTX.lock(); // FIXME: qemu-user doesn't implement ptrace on all architectures - // and retunrs ENOSYS in this case. + // 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 @@ -82,7 +89,7 @@ fn test_ptrace_cont() { return; } - match unsafe{fork()}.expect("Error: Fork Failed") { + match unsafe { fork() }.expect("Error: Fork Failed") { Child => { ptrace::traceme().unwrap(); // As recommended by ptrace(2), raise SIGTRAP to pause the child @@ -90,15 +97,22 @@ fn test_ptrace_cont() { loop { raise(Signal::SIGTRAP).unwrap(); } - - }, + } Parent { child } => { - assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))); + 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))); + 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 => { + 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 @@ -110,7 +124,7 @@ fn test_ptrace_cont() { } _ => panic!("The process should have been killed"), } - }, + } } } @@ -129,22 +143,28 @@ fn test_ptrace_interrupt() { let _m = crate::FORK_MTX.lock(); - match unsafe{fork()}.expect("Error: Fork Failed") { - Child => { - loop { - sleep(Duration::from_millis(1000)); - } - + 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::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD) + .unwrap(); ptrace::interrupt(child).unwrap(); - assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128))); + 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))); + 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 => { + 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)); @@ -152,19 +172,20 @@ fn test_ptrace_interrupt() { } _ => 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"))] +#[cfg(all( + target_os = "linux", + any(target_arch = "x86_64", target_arch = "x86"), + target_env = "gnu" +))] #[test] fn test_ptrace_syscall() { - use nix::sys::signal::kill; use nix::sys::ptrace; + use nix::sys::signal::kill; use nix::sys::signal::Signal; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::fork; @@ -175,45 +196,80 @@ fn test_ptrace_syscall() { let _m = crate::FORK_MTX.lock(); - match unsafe{fork()}.expect("Error: Fork Failed") { + 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); } - }, + unsafe { + ::libc::_exit(0); + } + } Parent { child } => { - assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP))); + 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(); + 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; + 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; + 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!( + 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!( + 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))); + 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))); - }, + 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 index 2f7396b189..79f75de3b4 100644 --- a/test/sys/test_select.rs +++ b/test/sys/test_select.rs @@ -1,7 +1,8 @@ use nix::sys::select::*; -use nix::unistd::{pipe, write}; use nix::sys::signal::SigSet; use nix::sys::time::{TimeSpec, TimeValLike}; +use nix::unistd::{pipe, write}; +use std::os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd}; #[test] pub fn test_pselect() { @@ -9,11 +10,13 @@ pub fn test_pselect() { let (r1, w1) = pipe().unwrap(); write(w1, b"hi!").unwrap(); + let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; let (r2, _w2) = pipe().unwrap(); + let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + fd_set.insert(&r1); + fd_set.insert(&r2); let timeout = TimeSpec::seconds(10); let sigmask = SigSet::empty(); @@ -21,34 +24,37 @@ pub fn test_pselect() { 1, pselect(None, &mut fd_set, None, None, &timeout, &sigmask).unwrap() ); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); + 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 r1 = unsafe { OwnedFd::from_raw_fd(r1) }; let (r2, _w2) = pipe().unwrap(); + let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + fd_set.insert(&r1); + fd_set.insert(&r2); let timeout = TimeSpec::seconds(10); assert_eq!( 1, pselect( - ::std::cmp::max(r1, r2) + 1, + std::cmp::max(r1.as_raw_fd(), r2.as_raw_fd()) + 1, &mut fd_set, None, None, &timeout, None - ).unwrap() + ) + .unwrap() ); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); + assert!(fd_set.contains(&r1)); + assert!(!fd_set.contains(&r2)); } macro_rules! generate_fdset_bad_fd_tests { @@ -57,17 +63,13 @@ macro_rules! generate_fdset_bad_fd_tests { #[test] #[should_panic] fn $method() { - FdSet::new().$method($fd); + let bad_fd = unsafe{BorrowedFd::borrow_raw($fd)}; + FdSet::new().$method(&bad_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; diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs index fdd2568df4..ca25ff9ab0 100644 --- a/test/sys/test_signal.rs +++ b/test/sys/test_signal.rs @@ -52,9 +52,11 @@ fn test_sigprocmask() { // 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); + assert!( + !old_signal_set.contains(SIGNAL), + "the {SIGNAL:?} signal is already blocked, please change to a \ + different one" + ); // Now block the signal. let mut signal_set = SigSet::empty(); @@ -66,25 +68,30 @@ fn test_sigprocmask() { 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); + assert!( + old_signal_set.contains(SIGNAL), + "expected the {SIGNAL:?} to be blocked" + ); // 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); -} +static SIGNALED: AtomicBool = AtomicBool::new(false); -extern fn test_sigaction_handler(signal: libc::c_int) { +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 fn test_sigaction_action(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) {} +extern "C" fn test_sigaction_action( + _: libc::c_int, + _: *mut libc::siginfo_t, + _: *mut libc::c_void, +) { +} #[test] #[cfg(not(target_os = "redox"))] @@ -92,7 +99,10 @@ 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); + assert_eq!( + unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(), + Errno::ENOTSUP + ); } #[test] @@ -101,20 +111,32 @@ fn test_signal() { unsafe { signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap(); raise(Signal::SIGINT).unwrap(); - assert_eq!(unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), SigHandler::SigIgn); + 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); + 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); + 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); + 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 index b6f748b46a..87153c9572 100644 --- a/test/sys/test_signalfd.rs +++ b/test/sys/test_signalfd.rs @@ -2,8 +2,8 @@ use std::convert::TryFrom; #[test] fn test_signalfd() { + use nix::sys::signal::{self, raise, SigSet, Signal}; use nix::sys::signalfd::SignalFd; - use nix::sys::signal::{self, raise, Signal, SigSet}; // Grab the mutex for altering signals so we don't interfere with other tests. let _m = crate::SIGNAL_MTX.lock(); diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 0f6fac6664..ed1686e87d 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -1,109 +1,75 @@ -use nix::sys::socket::{AddressFamily, InetAddr, SockAddr, UnixAddr, getsockname, sockaddr, sockaddr_in6, sockaddr_storage_to_addr}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use crate::*; +use libc::c_char; +use nix::sys::socket::{getsockname, AddressFamily, UnixAddr}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -use std::mem::{self, MaybeUninit}; -use std::net::{self, Ipv6Addr, SocketAddr, SocketAddrV6}; -use std::os::unix::io::RawFd; +use std::net::{SocketAddrV4, SocketAddrV6}; +use std::os::unix::io::{AsRawFd, RawFd}; use std::path::Path; use std::slice; use std::str::FromStr; -use libc::{c_char, sockaddr_storage}; -#[cfg(any(target_os = "linux", target_os= "android"))] -use crate::*; +#[cfg(target_os = "linux")] +#[cfg_attr(qemu, ignore)] #[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"), - } +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}; - assert_eq!(addr.to_string(), "127.0.0.1:3000"); + let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); - let inet = addr.to_std(); - assert_eq!(actual, inet); -} + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); -#[test] -pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() { - 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 rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); - 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); -} + setsockopt(&rsock, Timestamping, &TimestampingFlag::all()).unwrap(); -#[test] -pub fn test_inetv6_addr_to_sock_addr() { - 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); - - match addr { - InetAddr::V6(addr) => { - assert_eq!(addr.sin6_port, port.to_be()); - assert_eq!(addr.sin6_flowinfo, flowinfo); - assert_eq!(addr.sin6_scope_id, scope_id); + 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.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); + let recv = + recvmsg::<()>(rsock.as_raw_fd(), &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); } - _ => panic!("nope"), } - - assert_eq!(actual, addr.to_std()); -} -#[test] -pub fn test_inetv6_addr_roundtrip_sockaddr_storage_to_addr() { - 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 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 }; - - 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); + assert!(std::time::Duration::from(diff).as_secs() < 60); } #[test] @@ -144,7 +110,7 @@ pub fn test_addr_equality_path() { 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()); - assert!(addr.is_err()); + addr.expect_err("assertion failed"); } #[cfg(any(target_os = "android", target_os = "linux"))] @@ -163,7 +129,7 @@ pub fn test_addr_equality_abstract() { } // Test getting/setting abstract addresses (without unix socket creation) -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "android", target_os = "linux"))] #[test] pub fn test_abstract_uds_addr() { let empty = String::new(); @@ -174,7 +140,8 @@ pub fn test_abstract_uds_addr() { 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 + 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); @@ -183,46 +150,133 @@ pub fn test_abstract_uds_addr() { 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::{socket, AddressFamily, SockType, SockFlag}; - use nix::sys::socket::{bind, SockAddr}; + 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 = SockAddr::new_unix(&sockname).unwrap(); - bind(sock, &sockaddr).expect("bind failed"); - assert_eq!(sockaddr, getsockname(sock).expect("getsockname failed")); + let sock = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + let sockaddr = UnixAddr::new(&sockname).unwrap(); + bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); + assert_eq!( + sockaddr, + getsockname(sock.as_raw_fd()).expect("getsockname failed") + ); } #[test] pub fn test_socketpair() { + use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType}; use nix::unistd::{read, write}; - use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag}; - 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(); + let (fd1, fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + write(fd1.as_raw_fd(), b"hello").unwrap(); + let mut buf = [0; 5]; + read(fd2.as_raw_fd(), &mut buf).unwrap(); assert_eq!(&buf[..], b"hello"); } +#[test] +pub fn test_recvmsg_sockaddr_un() { + use nix::sys::socket::{ + self, bind, socket, AddressFamily, MsgFlags, SockFlag, SockType, + }; + + let tempdir = tempfile::tempdir().unwrap(); + let sockname = tempdir.path().join("sock"); + let sock = socket( + AddressFamily::Unix, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + let sockaddr = UnixAddr::new(&sockname).unwrap(); + bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); + + // Send a message + let send_buffer = "hello".as_bytes(); + if let Err(e) = socket::sendmsg( + sock.as_raw_fd(), + &[std::io::IoSlice::new(send_buffer)], + &[], + MsgFlags::empty(), + Some(&sockaddr), + ) { + crate::skip!("Couldn't send ({e:?}), so skipping test"); + } + + // Receive the message + let mut recv_buffer = [0u8; 32]; + let mut iov = [std::io::IoSliceMut::new(&mut recv_buffer)]; + let received = + socket::recvmsg(sock.as_raw_fd(), &mut iov, None, MsgFlags::empty()) + .unwrap(); + // Check the address in the received message + assert_eq!(sockaddr, received.address.unwrap()); +} + +#[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 nix::Result; + use super::*; use nix::sys::socket::*; + use nix::{errno::Errno, Result}; use std::thread; - use super::*; 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), + 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; @@ -248,43 +302,51 @@ mod recvfrom { #[test] pub fn stream() { - let (fd2, fd1) = socketpair(AddressFamily::Unix, SockType::Stream, - None, SockFlag::empty()).unwrap(); + let (fd2, fd1) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); // Ignore from for stream sockets - let _ = sendrecv(fd1, fd2, |s, m, flags| { - send(s, m, flags) - }, |_, _| {}); + let _ = sendrecv(fd1.as_raw_fd(), fd2.as_raw_fd(), send, |_, _| {}); } #[test] pub fn udp() { - let std_sa = SocketAddr::from_str("127.0.0.1:6789").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); - let rsock = socket(AddressFamily::Inet, + 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(); + None, + ) + .unwrap(); + bind(rsock.as_raw_fd(), &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) - },|_, _| {}); + ) + .expect("send socket failed"); + let from = sendrecv( + rsock.as_raw_fd(), + ssock.as_raw_fd(), + move |s, m, flags| sendto(s.as_raw_fd(), m, &sock_addr, flags), + |_, _| {}, + ); // UDP sockets should set the from address - assert_eq!(AddressFamily::Inet, from.unwrap().family()); + assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); } #[cfg(target_os = "linux")] mod udp_offload { use super::*; - use nix::sys::uio::IoVec; use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment}; + use std::io::IoSlice; #[test] // Disable the test under emulation because it fails in Cirrus-CI. Lack @@ -298,42 +360,54 @@ mod recvfrom { // with size 2 and two UDP packet with size 1 will be sent. let segment_size: u16 = 2; - let std_sa = SocketAddr::from_str("127.0.0.1:6791").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); - let rsock = socket(AddressFamily::Inet, - SockType::Datagram, - SockFlag::empty(), - None - ).unwrap(); + 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 _)) + setsockopt(&rsock, UdpGsoSegment, &(segment_size as _)) .expect("setsockopt UDP_SEGMENT failed"); - bind(rsock, &sock_addr).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None, - ).expect("send socket failed"); + ) + .expect("send socket failed"); let mut num_packets_received: i32 = 0; - sendrecv(rsock, ssock, move |s, m, flags| { - let iov = [IoVec::from_slice(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; - } - }); + sendrecv( + rsock.as_raw_fd(), + ssock.as_raw_fd(), + move |s, m, flags| { + let iov = [IoSlice::new(m)]; + let cmsg = ControlMessage::UdpGsoSegments(&segment_size); + sendmsg( + s.as_raw_fd(), + &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. @@ -350,13 +424,15 @@ mod recvfrom { // 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(); + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); - setsockopt(rsock, UdpGroSegment, &true) + setsockopt(&rsock, UdpGroSegment, &true) .expect("setsockopt UDP_GRO failed"); } } @@ -369,62 +445,63 @@ mod recvfrom { ))] #[test] pub fn udp_sendmmsg() { - use nix::sys::uio::IoVec; + use std::io::IoSlice; - let std_sa = SocketAddr::from_str("127.0.0.1:6793").unwrap(); - let std_sa2 = SocketAddr::from_str("127.0.0.1:6794").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let inet_addr2 = InetAddr::from_std(&std_sa2); - let sock_addr = SockAddr::new_inet(inet_addr); - let sock_addr2 = SockAddr::new_inet(inet_addr2); + 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, + let rsock = socket( + AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None - ).unwrap(); - bind(rsock, &sock_addr).unwrap(); + None, + ) + .unwrap(); + bind(rsock.as_raw_fd(), &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 iov = [IoVec::from_slice(m)]; - let mut msgs = vec![ - SendMmsgData { - iov: &iov, - cmsgs: &[], - addr: Some(sock_addr), - _lt: Default::default(), + ) + .expect("send socket failed"); + + let from = sendrecv( + rsock.as_raw_fd(), + ssock.as_raw_fd(), + 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 batch_size = 15; - for _ in 0..batch_size { - msgs.push( - SendMmsgData { - iov: &iov, - cmsgs: &[], - addr: Some(sock_addr2), - _lt: Default::default(), - } - ); - } - sendmmsg(s, msgs.iter(), flags) - .map(move |sent_bytes| { - assert!(!sent_bytes.is_empty()); - for sent in &sent_bytes { - assert_eq!(*sent, m.len()); - } - sent_bytes.len() - }) - }, |_, _ | {}); + 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()); + assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); } #[cfg(any( @@ -435,32 +512,40 @@ mod recvfrom { ))] #[test] pub fn udp_recvmmsg() { - use nix::sys::uio::IoVec; - use nix::sys::socket::{MsgFlags, recvmmsg}; + use nix::sys::socket::{recvmmsg, MsgFlags}; + use std::io::IoSliceMut; const NUM_MESSAGES_SENT: usize = 2; - const DATA: [u8; 2] = [1,2]; + const DATA: [u8; 2] = [1, 2]; - let std_sa = SocketAddr::from_str("127.0.0.1:6798").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); + let inet_addr = SocketAddrV4::from_str("127.0.0.1:6798").unwrap(); + let sock_addr = SockaddrIn::from(inet_addr); - let rsock = socket(AddressFamily::Inet, + let rsock = socket( + AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None - ).unwrap(); - bind(rsock, &sock_addr).unwrap(); + None, + ) + .unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None, - ).expect("send socket failed"); + ) + .expect("send socket failed"); let send_thread = thread::spawn(move || { for _ in 0..NUM_MESSAGES_SENT { - sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty()).unwrap(); + sendto( + ssock.as_raw_fd(), + &DATA[..], + &sock_addr, + MsgFlags::empty(), + ) + .unwrap(); } }); @@ -468,22 +553,28 @@ mod recvfrom { // Buffers to receive exactly `NUM_MESSAGES_SENT` messages let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT]; - let iovs: Vec<_> = receive_buffers.iter_mut().map(|buf| { - [IoVec::from_mut_slice(&mut buf[..])] - }).collect(); - - for iov in &iovs { - msgs.push_back(RecvMmsgData { - iov, - cmsg_buffer: None, - }) - }; + msgs.extend( + receive_buffers + .iter_mut() + .map(|buf| [IoSliceMut::new(&mut buf[..])]), + ); - let res = recvmmsg(rsock, &mut msgs, MsgFlags::empty(), None).expect("recvmmsg"); + let mut data = + MultiHeaders::::preallocate(msgs.len(), None); + + let res: Vec> = recvmmsg( + rsock.as_raw_fd(), + &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()); + assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap()); assert_eq!(DATA.len(), bytes); } @@ -502,32 +593,40 @@ mod recvfrom { ))] #[test] pub fn udp_recvmmsg_dontwait_short_read() { - use nix::sys::uio::IoVec; - use nix::sys::socket::{MsgFlags, recvmmsg}; + use nix::sys::socket::{recvmmsg, MsgFlags}; + use std::io::IoSliceMut; const NUM_MESSAGES_SENT: usize = 2; - const DATA: [u8; 4] = [1,2,3,4]; + const DATA: [u8; 4] = [1, 2, 3, 4]; - let std_sa = SocketAddr::from_str("127.0.0.1:6799").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); + let inet_addr = SocketAddrV4::from_str("127.0.0.1:6799").unwrap(); + let sock_addr = SockaddrIn::from(inet_addr); - let rsock = socket(AddressFamily::Inet, + let rsock = socket( + AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None - ).unwrap(); - bind(rsock, &sock_addr).unwrap(); + None, + ) + .unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None, - ).expect("send socket failed"); + ) + .expect("send socket failed"); let send_thread = thread::spawn(move || { for _ in 0..NUM_MESSAGES_SENT { - sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty()).unwrap(); + sendto( + ssock.as_raw_fd(), + &DATA[..], + &sock_addr, + MsgFlags::empty(), + ) + .unwrap(); } }); // Ensure we've sent all the messages before continuing so `recvmmsg` @@ -540,22 +639,30 @@ mod recvfrom { // 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]; - let iovs: Vec<_> = receive_buffers.iter_mut().map(|buf| { - [IoVec::from_mut_slice(&mut buf[..])] - }).collect(); - - for iov in &iovs { - msgs.push_back(RecvMmsgData { - iov, - cmsg_buffer: None, - }) - }; + msgs.extend( + receive_buffers + .iter_mut() + .map(|buf| [IoSliceMut::new(&mut buf[..])]), + ); + + let mut data = MultiHeaders::::preallocate( + NUM_MESSAGES_SENT + 2, + None, + ); - let res = recvmmsg(rsock, &mut msgs, MsgFlags::MSG_DONTWAIT, None).expect("recvmmsg"); + let res: Vec> = recvmmsg( + rsock.as_raw_fd(), + &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()); + assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap()); assert_eq!(DATA.len(), bytes); } @@ -563,19 +670,66 @@ mod recvfrom { 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.as_raw_fd(), &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.as_raw_fd(), &saddr).unwrap(); + let from = sendrecv( + rsock.as_raw_fd(), + ssock.as_raw_fd(), + move |s, m, flags| sendto(s.as_raw_fd(), 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::{MsgFlags, recvmsg}; - use nix::sys::uio::IoVec; + use nix::sys::socket::{recvmsg, MsgFlags}; + use std::io::IoSliceMut; let mut buf = [0u8; 5]; - let iov = [IoVec::from_mut_slice(&mut buf[..])]; - let fd = -1; // Bad file descriptor - let r = recvmsg(fd, &iov, None, MsgFlags::empty()); + let mut iov = [IoSliceMut::new(&mut buf[..])]; + + let fd = -1; // Bad file descriptor + let r = recvmsg::<()>(fd.as_raw_fd(), &mut iov, None, MsgFlags::empty()); + assert_eq!(r.err().unwrap(), Errno::EBADF); } @@ -584,31 +738,53 @@ pub fn test_recvmsg_ebadf() { #[cfg_attr(qemu, ignore)] #[test] pub fn test_scm_rights() { - use nix::sys::uio::IoVec; - use nix::unistd::{pipe, read, write, close}; - use nix::sys::socket::{socketpair, sendmsg, recvmsg, - AddressFamily, SockType, SockFlag, - ControlMessage, ControlMessageOwned, MsgFlags}; - - let (fd1, fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::empty()) - .unwrap(); + 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 = [IoVec::from_slice(b"hello")]; + 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); + assert_eq!( + sendmsg::<()>( + fd1.as_raw_fd(), + &iov, + &[cmsg], + MsgFlags::empty(), + None + ) + .unwrap(), + 5 + ); close(r).unwrap(); - close(fd1).unwrap(); } { let mut buf = [0u8; 5]; - let iov = [IoVec::from_mut_slice(&mut buf[..])]; + + let mut iov = [IoSliceMut::new(&mut buf[..])]; let mut cmsgspace = cmsg_space!([RawFd; 1]); - let msg = recvmsg(fd2, &iov, Some(&mut cmsgspace), MsgFlags::empty()).unwrap(); + let msg = recvmsg::<()>( + fd2.as_raw_fd(), + &mut iov, + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); for cmsg in msg.cmsgs() { if let ControlMessageOwned::ScmRights(fd) = cmsg { @@ -620,31 +796,33 @@ pub fn test_scm_rights() { } } assert_eq!(msg.bytes, 5); - assert!(!msg.flags.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - close(fd2).unwrap(); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); } let received_r = received_r.expect("Did not receive passed fd"); // Ensure that the received file descriptor works - write(w, b"world").unwrap(); + write(w.as_raw_fd(), b"world").unwrap(); let mut buf = [0u8; 5]; - read(received_r, &mut buf).unwrap(); + read(received_r.as_raw_fd(), &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(any(target_os = "linux", target_os = "android"))] #[cfg_attr(qemu, ignore)] #[test] pub fn test_af_alg_cipher() { - use nix::sys::uio::IoVec; - use nix::unistd::read; - use nix::sys::socket::{socket, sendmsg, bind, accept, setsockopt, - AddressFamily, SockType, SockFlag, SockAddr, - ControlMessage, MsgFlags}; 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 @@ -662,41 +840,64 @@ pub fn test_af_alg_cipher() { let payload_len = 256; let payload = vec![2u8; payload_len]; - let sock = socket(AddressFamily::Alg, SockType::SeqPacket, SockFlag::empty(), None) - .expect("socket failed"); + let sock = socket( + AddressFamily::Alg, + SockType::SeqPacket, + SockFlag::empty(), + None, + ) + .expect("socket failed"); - let sockaddr = SockAddr::new_alg(alg_type, alg_name); - bind(sock, &sockaddr).expect("bind failed"); + let sockaddr = AlgAddr::new(alg_type, alg_name); + bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); - if let SockAddr::Alg(alg) = sockaddr { - assert_eq!(alg.alg_name().to_string_lossy(), alg_name); - assert_eq!(alg.alg_type().to_string_lossy(), alg_type); - } else { - panic!("unexpected SockAddr"); - } + 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"); + setsockopt(&sock, AlgSetKey::default(), &key).expect("setsockopt"); + let session_socket = accept(sock.as_raw_fd()).expect("accept failed"); - let msgs = [ControlMessage::AlgSetOp(&libc::ALG_OP_ENCRYPT), ControlMessage::AlgSetIv(iv.as_slice())]; - let iov = IoVec::from_slice(&payload); - sendmsg(session_socket, &[iov], &msgs, MsgFlags::empty(), None).expect("sendmsg encrypt"); + let msgs = [ + ControlMessage::AlgSetOp(&libc::ALG_OP_ENCRYPT), + ControlMessage::AlgSetIv(iv.as_slice()), + ]; + let iov = IoSlice::new(&payload); + sendmsg::<()>( + session_socket.as_raw_fd(), + &[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"); + let num_bytes = + read(session_socket.as_raw_fd(), &mut encrypted).expect("read encrypt"); assert_eq!(num_bytes, payload_len); - let iov = IoVec::from_slice(&encrypted); + 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"); + let msgs = [ + ControlMessage::AlgSetOp(&libc::ALG_OP_DECRYPT), + ControlMessage::AlgSetIv(iv.as_slice()), + ]; + sendmsg::<()>( + session_socket.as_raw_fd(), + &[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"); + let num_bytes = + read(session_socket.as_raw_fd(), &mut decrypted).expect("read decrypt"); assert_eq!(num_bytes, payload_len); assert_eq!(decrypted, payload); @@ -704,18 +905,19 @@ pub fn test_af_alg_cipher() { // 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(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::uio::IoVec; - use nix::unistd::{read, close}; - use nix::sys::socket::{socket, sendmsg, bind, accept, setsockopt, - AddressFamily, SockType, SockFlag, SockAddr, - ControlMessage, MsgFlags}; - use nix::sys::socket::sockopt::{AlgSetKey, AlgSetAeadAuthSize}; + 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::read; + use std::io::IoSlice; skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); // Travis's seccomp profile blocks AF_ALG @@ -734,7 +936,8 @@ pub fn test_af_alg_aead() { 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]; + let mut payload = + vec![2u8; payload_len + (assoc_size as usize) + auth_size]; for i in 0..assoc_size { payload[i as usize] = 10; @@ -746,57 +949,87 @@ pub fn test_af_alg_aead() { payload[len - 1 - i] = 0; } - let sock = socket(AddressFamily::Alg, SockType::SeqPacket, SockFlag::empty(), None) - .expect("socket failed"); + let sock = socket( + AddressFamily::Alg, + SockType::SeqPacket, + SockFlag::empty(), + None, + ) + .expect("socket failed"); - let sockaddr = SockAddr::new_alg(alg_type, alg_name); - bind(sock, &sockaddr).expect("bind failed"); + let sockaddr = AlgAddr::new(alg_type, alg_name); + bind(sock.as_raw_fd(), &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"); + setsockopt(&sock, AlgSetAeadAuthSize, &auth_size) + .expect("setsockopt AlgSetAeadAuthSize"); + setsockopt(&sock, AlgSetKey::default(), &key) + .expect("setsockopt AlgSetKey"); + let session_socket = accept(sock.as_raw_fd()).expect("accept failed"); let msgs = [ ControlMessage::AlgSetOp(&ALG_OP_ENCRYPT), ControlMessage::AlgSetIv(iv.as_slice()), - ControlMessage::AlgSetAeadAssoclen(&assoc_size)]; - let iov = IoVec::from_slice(&payload); - sendmsg(session_socket, &[iov], &msgs, MsgFlags::empty(), None).expect("sendmsg encrypt"); + ControlMessage::AlgSetAeadAssoclen(&assoc_size), + ]; + + let iov = IoSlice::new(&payload); + sendmsg::<()>( + session_socket.as_raw_fd(), + &[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"); + let mut encrypted = + vec![0u8; (assoc_size as usize) + payload_len + auth_size]; + let num_bytes = + read(session_socket.as_raw_fd(), &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 = IoVec::from_slice(&encrypted); + let iov = IoSlice::new(&encrypted); let iv = vec![1u8; iv_len]; - let session_socket = accept(sock).expect("accept failed"); + let session_socket = accept(sock.as_raw_fd()).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"); + sendmsg::<()>( + session_socket.as_raw_fd(), + &[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]; + 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"); + fcntl(session_socket, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) + .expect("fcntl non_blocking"); + let num_bytes = + read(session_socket.as_raw_fd(), &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)]); + 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`. @@ -806,56 +1039,56 @@ pub fn test_af_alg_aead() { // 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"))] +#[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::uio::IoVec; - use nix::sys::socket::{socket, sendmsg, bind, - AddressFamily, SockType, SockFlag, SockAddr, - ControlMessage, MsgFlags}; - - let sock = socket(AddressFamily::Inet, - SockType::Datagram, - SockFlag::empty(), - None) - .expect("socket failed"); + 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 std_sa = SocketAddr::from_str("127.0.0.1:4000").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); + let sock_addr = SockaddrIn::new(127, 0, 0, 1, 4000); - bind(sock, &sock_addr).expect("bind failed"); + bind(sock.as_raw_fd(), &sock_addr).expect("bind failed"); let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; - let iov = [IoVec::from_slice(&slice)]; - - if let InetAddr::V4(sin) = inet_addr { - cfg_if! { - if #[cfg(target_os = "netbsd")] { - let _dontcare = sin; - 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: sin.sin_addr, - }; - } + 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)]; + let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)]; - sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr)) - .expect("sendmsg"); - } else { - panic!("No IPv4 addresses available for testing?"); - } + sendmsg( + sock.as_raw_fd(), + &iov, + &cmsg, + MsgFlags::empty(), + Some(&sock_addr), + ) + .expect("sendmsg"); } // Verify `ControlMessage::Ipv6PacketInfo` for `sendmsg`. @@ -866,49 +1099,107 @@ pub fn test_sendmsg_ipv4packetinfo() { // 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"))] +#[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::uio::IoVec; - use nix::sys::socket::{socket, sendmsg, bind, - AddressFamily, SockType, SockFlag, SockAddr, - ControlMessage, MsgFlags}; - - let sock = socket(AddressFamily::Inet6, - SockType::Datagram, - SockFlag::empty(), - None) - .expect("socket failed"); + 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 = SocketAddr::from_str("[::1]:6000").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); + 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) { + if let Err(Errno::EADDRNOTAVAIL) = bind(sock.as_raw_fd(), &sock_addr) { println!("IPv6 not available, skipping test."); return; } let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; - let iov = [IoVec::from_slice(&slice)]; + let iov = [IoSlice::new(&slice)]; - if let InetAddr::V6(sin) = inet_addr { - let pi = libc::in6_pktinfo { - ipi6_ifindex: 0, /* Unspecified interface */ - ipi6_addr: sin.sin6_addr, - }; + let pi = libc::in6_pktinfo { + ipi6_ifindex: 0, /* Unspecified interface */ + ipi6_addr: sock_addr.as_ref().sin6_addr, + }; - let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)]; + let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)]; - sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr)) - .expect("sendmsg"); - } else { - println!("No IPv6 addresses available for testing: skipping testing Ipv6PacketInfo"); - } + sendmsg::( + sock.as_raw_fd(), + &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.as_raw_fd(), &unspec_sock_addr).expect("bind failed"); + let bound_sock_addr: SockaddrIn = getsockname(sock.as_raw_fd()).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.as_raw_fd(), + &iov, + &cmsg, + MsgFlags::empty(), + Some(&localhost_sock_addr), + ) + .expect("sendmsg"); } /// Tests that passing multiple fds using a single `ControlMessage` works. @@ -917,46 +1208,55 @@ pub fn test_sendmsg_ipv6packetinfo() { #[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::os::unix::io::{RawFd, AsRawFd}; use std::thread; - use nix::sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, - sendmsg, recvmsg}; - use nix::sys::uio::IoVec; let (send, receive) = UnixDatagram::pair().unwrap(); let thread = thread::spawn(move || { let mut buf = [0u8; 8]; - let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!([RawFd; 2]); - let msg = recvmsg( + let msg = recvmsg::<()>( receive.as_raw_fd(), - &iovec, + &mut iovec, Some(&mut space), - MsgFlags::empty() - ).unwrap(); - assert!(!msg.flags.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + 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()); - }, + 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].as_slice(), [1u8, 2, 3, 4, 5, 6, 7, 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 = [IoVec::from_slice(&slice)]; - let fds = [libc::STDIN_FILENO, libc::STDOUT_FILENO]; // pass stdin and stdout + 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(); + sendmsg::<()>(send.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), None) + .unwrap(); thread.join().unwrap(); } @@ -966,32 +1266,49 @@ fn test_scm_rights_single_cmsg_multiple_fds() { // raw `sendmsg`. #[test] pub fn test_sendmsg_empty_cmsgs() { - use nix::sys::uio::IoVec; - use nix::unistd::close; - use nix::sys::socket::{socketpair, sendmsg, recvmsg, - AddressFamily, SockType, SockFlag, MsgFlags}; + use nix::sys::socket::{ + recvmsg, sendmsg, socketpair, AddressFamily, MsgFlags, SockFlag, + SockType, + }; + use std::io::{IoSlice, IoSliceMut}; - let (fd1, fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::empty()) - .unwrap(); + let (fd1, fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); { - let iov = [IoVec::from_slice(b"hello")]; - assert_eq!(sendmsg(fd1, &iov, &[], MsgFlags::empty(), None).unwrap(), 5); - close(fd1).unwrap(); + let iov = [IoSlice::new(b"hello")]; + assert_eq!( + sendmsg::<()>(fd1.as_raw_fd(), &iov, &[], MsgFlags::empty(), None) + .unwrap(), + 5 + ); } { let mut buf = [0u8; 5]; - let iov = [IoVec::from_mut_slice(&mut buf[..])]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + let mut cmsgspace = cmsg_space!([RawFd; 1]); - let msg = recvmsg(fd2, &iov, Some(&mut cmsgspace), MsgFlags::empty()).unwrap(); + let msg = recvmsg::<()>( + fd2.as_raw_fd(), + &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!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); assert_eq!(msg.bytes, 5); - close(fd2).unwrap(); } } @@ -1003,37 +1320,58 @@ pub fn test_sendmsg_empty_cmsgs() { ))] #[test] fn test_scm_credentials() { - use nix::sys::uio::IoVec; - use nix::unistd::{close, getpid, getuid, getgid}; - use nix::sys::socket::{socketpair, sendmsg, recvmsg, - AddressFamily, SockType, SockFlag, - ControlMessage, ControlMessageOwned, MsgFlags, - UnixCredentials}; + 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::{getgid, getpid, getuid}; + use std::io::{IoSlice, IoSliceMut}; - let (send, recv) = socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::empty()) - .unwrap(); + 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(); + setsockopt(&recv, PassCred, &true).unwrap(); { - let iov = [IoVec::from_slice(b"hello")]; + 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(); + assert_eq!( + sendmsg::<()>( + send.as_raw_fd(), + &iov, + &[cmsg], + MsgFlags::empty(), + None + ) + .unwrap(), + 5 + ); } { let mut buf = [0u8; 5]; - let iov = [IoVec::from_mut_slice(&mut buf[..])]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + let mut cmsgspace = cmsg_space!(UnixCredentials); - let msg = recvmsg(recv, &iov, Some(&mut cmsgspace), MsgFlags::empty()).unwrap(); + let msg = recvmsg::<()>( + recv.as_raw_fd(), + &mut iov, + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); let mut received_cred = None; for cmsg in msg.cmsgs() { @@ -1042,7 +1380,7 @@ fn test_scm_credentials() { ControlMessageOwned::ScmCredentials(cred) => cred, #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] ControlMessageOwned::ScmCreds(cred) => cred, - other => panic!("unexpected cmsg {:?}", other), + other => panic!("unexpected cmsg {other:?}"), }; assert!(received_cred.is_none()); assert_eq!(cred.pid(), getpid().as_raw()); @@ -1052,8 +1390,9 @@ fn test_scm_credentials() { } received_cred.expect("no creds received"); assert_eq!(msg.bytes, 5); - assert!(!msg.flags.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - close(recv).unwrap(); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); } } @@ -1084,41 +1423,63 @@ fn test_too_large_cmsgspace() { #[cfg(any(target_os = "android", target_os = "linux"))] fn test_impl_scm_credentials_and_rights(mut space: Vec) { use libc::ucred; - use nix::sys::uio::IoVec; - use nix::unistd::{pipe, write, close, getpid, getuid, getgid}; - use nix::sys::socket::{socketpair, sendmsg, recvmsg, setsockopt, - SockType, SockFlag, - ControlMessage, ControlMessageOwned, MsgFlags}; 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 (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 = [IoVec::from_slice(b"hello")]; + let iov = [IoSlice::new(b"hello")]; let cred = ucred { pid: getpid().as_raw(), uid: getuid().as_raw(), gid: getgid().as_raw(), - }.into(); + } + .into(); let fds = [r]; let cmsgs = [ ControlMessage::ScmCredentials(&cred), ControlMessage::ScmRights(&fds), ]; - assert_eq!(sendmsg(send, &iov, &cmsgs, MsgFlags::empty(), None).unwrap(), 5); + assert_eq!( + sendmsg::<()>( + send.as_raw_fd(), + &iov, + &cmsgs, + MsgFlags::empty(), + None + ) + .unwrap(), + 5 + ); close(r).unwrap(); - close(send).unwrap(); } { let mut buf = [0u8; 5]; - let iov = [IoVec::from_mut_slice(&mut buf[..])]; - let msg = recvmsg(recv, &iov, Some(&mut space), MsgFlags::empty()).unwrap(); + let mut iov = [IoSliceMut::new(&mut buf[..])]; + let msg = recvmsg::<()>( + recv.as_raw_fd(), + &mut iov, + Some(&mut space), + MsgFlags::empty(), + ) + .unwrap(); let mut received_cred = None; assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs"); @@ -1142,15 +1503,16 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { } received_cred.expect("no creds received"); assert_eq!(msg.bytes, 5); - assert!(!msg.flags.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - close(recv).unwrap(); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); } let received_r = received_r.expect("Did not receive passed fd"); // Ensure that the received file descriptor works - write(w, b"world").unwrap(); + write(w.as_raw_fd(), b"world").unwrap(); let mut buf = [0u8; 5]; - read(received_r, &mut buf).unwrap(); + read(received_r.as_raw_fd(), &mut buf).unwrap(); assert_eq!(&buf[..], b"world"); close(received_r).unwrap(); close(w).unwrap(); @@ -1158,54 +1520,119 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { // Test creating and using named unix domain sockets #[test] -pub fn test_unixdomain() { - use nix::sys::socket::{SockType, SockFlag}; - use nix::sys::socket::{bind, socket, connect, listen, accept, SockAddr}; - use nix::unistd::{read, write, close}; +pub fn test_named_unixdomain() { + use nix::sys::socket::{accept, bind, connect, listen, socket, UnixAddr}; + use nix::sys::socket::{SockFlag, SockType}; + use nix::unistd::{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 = SockAddr::new_unix(&sockname).unwrap(); - bind(s1, &sockaddr).expect("bind failed"); - listen(s1, 10).expect("listen failed"); + let s1 = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + let sockaddr = UnixAddr::new(&sockname).unwrap(); + bind(s1.as_raw_fd(), &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 s2 = socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("socket failed"); + connect(s2.as_raw_fd(), &sockaddr).expect("connect failed"); + write(s2.as_raw_fd(), b"hello").expect("write failed"); }); - let s3 = accept(s1).expect("accept failed"); + let s3 = accept(s1.as_raw_fd()).expect("accept failed"); - let mut buf = [0;5]; - read(s3, &mut buf).unwrap(); - close(s3).unwrap(); - close(s1).unwrap(); + let mut buf = [0; 5]; + read(s3.as_raw_fd(), &mut buf).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}; + + let (fd_1, _fd_2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .expect("socketpair failed"); + + let addr_1: UnixAddr = + getsockname(fd_1.as_raw_fd()).expect("getsockname failed"); + assert!(addr_1.is_unnamed()); +} + +// 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}; + + 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.as_raw_fd(), &UnixAddr::new_unnamed()).expect("bind failed"); + + let addr: UnixAddr = + getsockname(fd.as_raw_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); +} + // 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, SockAddr, SockType, SockFlag, SockProtocol}; + use nix::sys::socket::{ + socket, SockFlag, SockProtocol, SockType, SysControlAddr, + }; - let fd = socket(AddressFamily::System, SockType::Datagram, - SockFlag::empty(), SockProtocol::KextControl) - .expect("socket failed"); - let _sockaddr = SockAddr::new_sys_control(fd, "com.apple.net.utun_control", 0).expect("resolving sys_control name failed"); - assert_eq!(SockAddr::new_sys_control(fd, "foo.bar.lol", 0).err(), Some(Errno::ENOENT)); + let fd = socket( + AddressFamily::System, + SockType::Datagram, + SockFlag::empty(), + SockProtocol::KextControl, + ) + .expect("socket failed"); + SysControlAddr::from_name(fd.as_raw_fd(), "com.apple.net.utun_control", 0) + .expect("resolving sys_control name failed"); + assert_eq!( + SysControlAddr::from_name(fd.as_raw_fd(), "foo.bar.lol", 0).err(), + Some(Errno::ENOENT) + ); // requires root privileges - // connect(fd, &sockaddr).expect("connect failed"); + // connect(fd.as_raw_fd(), &sockaddr).expect("connect failed"); } #[cfg(any( @@ -1217,42 +1644,30 @@ pub fn test_syscontrol() { target_os = "netbsd", target_os = "openbsd", ))] -fn loopback_address(family: AddressFamily) -> Option { - use std::io; - use std::io::Write; +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 addrs = match getifaddrs() { + let mut addrs = match getifaddrs() { Ok(iter) => iter, Err(e) => { let stdioerr = io::stderr(); let mut handle = stdioerr.lock(); - writeln!(handle, "getifaddrs: {:?}", e).unwrap(); + writeln!(handle, "getifaddrs: {e:?}").unwrap(); return None; - }, + } }; // return first address matching family - for ifaddr in addrs { - if ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK) { - match ifaddr.address { - Some(SockAddr::Inet(InetAddr::V4(..))) => { - match family { - AddressFamily::Inet => return Some(ifaddr), - _ => continue - } - }, - Some(SockAddr::Inet(InetAddr::V6(..))) => { - match family { - AddressFamily::Inet6 => return Some(ifaddr), - _ => continue - } - }, - _ => continue, - } - } - } - None + addrs.find(|ifaddr| { + ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK) + && ifaddr.address.as_ref().and_then(SockaddrLike::family) + == Some(family) + }) } #[cfg(any( @@ -1263,84 +1678,90 @@ fn loopback_address(family: AddressFamily) -> Option (ifaddr.interface_name, - ifaddr.address.expect("Expect IPv4 address on interface")), + 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 = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv4PacketInfo, &true).expect("setsockopt failed"); + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive.as_raw_fd(), &lo).expect("bind failed"); + let sa: SockaddrIn = + getsockname(receive.as_raw_fd()).expect("getsockname failed"); + setsockopt(&receive, Ipv4PacketInfo, &true).expect("setsockopt failed"); { let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; - let iov = [IoVec::from_slice(&slice)]; + 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"); + ) + .expect("send socket failed"); + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); } { let mut buf = [0u8; 8]; - let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::in_pktinfo); - let msg = recvmsg( - receive, - &iovec, + let msg = recvmsg::<()>( + receive.as_raw_fd(), + &mut iovec, Some(&mut space), MsgFlags::empty(), - ).expect("recvmsg failed"); - assert!( - !msg.flags - .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC) - ); + ) + .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() { + 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, + pktinfo.ipi_ifindex as libc::c_uint, i, "unexpected ifindex (expected {}, got {})", - i, - pktinfo.ipi_ifindex + i, pktinfo.ipi_ifindex ); } assert!(cmsgs.next().is_none(), "unexpected additional control msg"); assert_eq!(msg.bytes, 8); - assert_eq!( - iovec[0].as_slice(), - [1u8, 2, 3, 4, 5, 6, 7, 8] - ); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); } } @@ -1352,27 +1773,32 @@ pub fn test_recv_ipv4pktinfo() { 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)] +#[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::{Ipv4RecvIf, Ipv4RecvDstAddr}; - use nix::sys::socket::{bind, SockFlag, SockType}; - use nix::sys::socket::{getsockname, setsockopt, socket, SockAddr}; + 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 nix::sys::uio::IoVec; + 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")), + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv4 address on interface"), + ), None => return, }; let receive = socket( @@ -1380,39 +1806,45 @@ pub fn test_recvif() { SockType::Datagram, SockFlag::empty(), None, - ).expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv4RecvIf, &true).expect("setsockopt IP_RECVIF failed"); - setsockopt(receive, Ipv4RecvDstAddr, &true).expect("setsockopt IP_RECVDSTADDR failed"); + ) + .expect("receive socket failed"); + bind(receive.as_raw_fd(), &lo).expect("bind failed"); + let sa: SockaddrIn = + getsockname(receive.as_raw_fd()).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 = [IoVec::from_slice(&slice)]; + 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"); + ) + .expect("send socket failed"); + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); } { let mut buf = [0u8; 8]; - let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut iovec = [IoSliceMut::new(&mut buf)]; let mut space = cmsg_space!(libc::sockaddr_dl, libc::in_addr); - let msg = recvmsg( - receive, - &iovec, + let msg = recvmsg::<()>( + receive.as_raw_fd(), + &mut iovec, Some(&mut space), MsgFlags::empty(), - ).expect("recvmsg failed"); - assert!( - !msg.flags - .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC) - ); + ) + .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; @@ -1421,37 +1853,205 @@ pub fn test_recvif() { match cmsg { ControlMessageOwned::Ipv4RecvIf(dl) => { rx_recvif = true; - let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); + let i = if_nametoindex(lo_name.as_bytes()) + .expect("if_nametoindex"); assert_eq!( - dl.sdl_index as libc::c_uint, - i, + dl.sdl_index as libc::c_uint, i, "unexpected ifindex (expected {}, got {})", - i, - dl.sdl_index + i, dl.sdl_index ); - }, + } ControlMessageOwned::Ipv4RecvDstAddr(addr) => { rx_recvdstaddr = true; - if let SockAddr::Inet(InetAddr::V4(a)) = lo { - assert_eq!(a.sin_addr.s_addr, + 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 {})", - a.sin_addr.s_addr, + 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].as_slice(), - [1u8, 2, 3, 4, 5, 6, 7, 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.as_raw_fd(), &lo).expect("bind failed"); + let sa: SockaddrIn = + getsockname(receive.as_raw_fd()).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.as_raw_fd(), &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.as_raw_fd(), + &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.as_raw_fd(), &lo).expect("bind failed"); + let sa: SockaddrIn6 = + getsockname(receive.as_raw_fd()).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.as_raw_fd(), &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.as_raw_fd(), + &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]); } } @@ -1465,27 +2065,32 @@ pub fn test_recvif() { 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)] +#[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}; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6}; use nix::sys::socket::{getsockname, setsockopt, socket}; use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; - use nix::sys::uio::IoVec; + 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 IPv4 address on interface")), + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv6 address on interface"), + ), None => return, }; let receive = socket( @@ -1493,114 +2098,152 @@ pub fn test_recv_ipv6pktinfo() { SockType::Datagram, SockFlag::empty(), None, - ).expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv6RecvPacketInfo, &true).expect("setsockopt failed"); + ) + .expect("receive socket failed"); + bind(receive.as_raw_fd(), &lo).expect("bind failed"); + let sa: SockaddrIn6 = + getsockname(receive.as_raw_fd()).expect("getsockname failed"); + setsockopt(&receive, Ipv6RecvPacketInfo, &true).expect("setsockopt failed"); { let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; - let iov = [IoVec::from_slice(&slice)]; + 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"); + ) + .expect("send socket failed"); + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); } { let mut buf = [0u8; 8]; - let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::in6_pktinfo); - let msg = recvmsg( - receive, - &iovec, + let msg = recvmsg::<()>( + receive.as_raw_fd(), + &mut iovec, Some(&mut space), MsgFlags::empty(), - ).expect("recvmsg failed"); - assert!( - !msg.flags - .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC) - ); + ) + .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, + pktinfo.ipi6_ifindex as libc::c_uint, i, "unexpected ifindex (expected {}, got {})", - i, - pktinfo.ipi6_ifindex + i, pktinfo.ipi6_ifindex ); } assert!(cmsgs.next().is_none(), "unexpected additional control msg"); assert_eq!(msg.bytes, 8); - assert_eq!( - iovec[0].as_slice(), - [1u8, 2, 3, 4, 5, 6, 7, 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::{AddressFamily, socket, bind, connect, listen, - SockAddr, SockType, SockFlag}; - use nix::unistd::{close}; - use std::thread; + use nix::sys::socket::SockaddrLike; + use nix::sys::socket::{AddressFamily, VsockAddr}; + use std::mem; let port: u32 = 3000; - let s1 = socket(AddressFamily::Vsock, SockType::Stream, - SockFlag::empty(), None) - .expect("socket failed"); + let addr_local = VsockAddr::new(libc::VMADDR_CID_LOCAL, port); + assert_eq!(addr_local.cid(), libc::VMADDR_CID_LOCAL); + assert_eq!(addr_local.port(), port); - // VMADDR_CID_HYPERVISOR is reserved, so we expect an EADDRNOTAVAIL error. - let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_HYPERVISOR, port); - assert_eq!(bind(s1, &sockaddr).err(), - Some(Errno::EADDRNOTAVAIL)); + let addr_any = VsockAddr::new(libc::VMADDR_CID_ANY, libc::VMADDR_PORT_ANY); + assert_eq!(addr_any.cid(), libc::VMADDR_CID_ANY); + assert_eq!(addr_any.port(), libc::VMADDR_PORT_ANY); - let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_ANY, port); - assert_eq!(bind(s1, &sockaddr), Ok(())); - listen(s1, 10).expect("listen failed"); + assert_ne!(addr_local, addr_any); + assert_ne!(calculate_hash(&addr_local), calculate_hash(&addr_any)); - let thr = thread::spawn(move || { - let cid: u32 = libc::VMADDR_CID_HOST; + let addr1 = VsockAddr::new(libc::VMADDR_CID_HOST, port); + let addr2 = VsockAddr::new(libc::VMADDR_CID_HOST, port); + assert_eq!(addr1, addr2); + assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); - let s2 = socket(AddressFamily::Vsock, SockType::Stream, - SockFlag::empty(), None) - .expect("socket failed"); + let addr3 = unsafe { + VsockAddr::from_raw( + addr2.as_ref() as *const libc::sockaddr_vm as *const libc::sockaddr, + Some(mem::size_of::().try_into().unwrap()), + ) + } + .unwrap(); + assert_eq!( + addr3.as_ref().svm_family, + AddressFamily::Vsock as libc::sa_family_t + ); + assert_eq!(addr3.as_ref().svm_cid, addr1.cid()); + assert_eq!(addr3.as_ref().svm_port, addr1.port()); +} - let sockaddr = SockAddr::new_vsock(cid, port); +#[cfg(target_os = "macos")] +#[test] +pub fn test_vsock() { + use nix::sys::socket::SockaddrLike; + use nix::sys::socket::{AddressFamily, VsockAddr}; + use std::mem; - // The current implementation does not support loopback devices, so, - // for now, we expect a failure on the connect. - assert_ne!(connect(s2, &sockaddr), Ok(())); + let port: u32 = 3000; - close(s2).unwrap(); - }); + // macOS doesn't have a VMADDR_CID_LOCAL, so test with host again + let addr_host = VsockAddr::new(libc::VMADDR_CID_HOST, port); + assert_eq!(addr_host.cid(), libc::VMADDR_CID_HOST); + assert_eq!(addr_host.port(), port); - close(s1).unwrap(); - thr.join().unwrap(); + let addr_any = VsockAddr::new(libc::VMADDR_CID_ANY, libc::VMADDR_PORT_ANY); + assert_eq!(addr_any.cid(), libc::VMADDR_CID_ANY); + assert_eq!(addr_any.port(), libc::VMADDR_PORT_ANY); + + assert_ne!(addr_host, addr_any); + assert_ne!(calculate_hash(&addr_host), calculate_hash(&addr_any)); + + let addr1 = VsockAddr::new(libc::VMADDR_CID_HOST, port); + let addr2 = VsockAddr::new(libc::VMADDR_CID_HOST, port); + assert_eq!(addr1, addr2); + assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); + + let addr3 = unsafe { + VsockAddr::from_raw( + addr2.as_ref() as *const libc::sockaddr_vm as *const libc::sockaddr, + Some(mem::size_of::().try_into().unwrap()), + ) + } + .unwrap(); + assert_eq!( + addr3.as_ref().svm_family, + AddressFamily::Vsock as libc::sa_family_t + ); + let cid = addr3.as_ref().svm_cid; + let port = addr3.as_ref().svm_port; + assert_eq!(cid, addr1.cid()); + assert_eq!(port, addr1.port()); } // 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"))] +#[cfg(target_os = "linux")] #[test] fn test_recvmsg_timestampns() { use nix::sys::socket::*; - use nix::sys::uio::IoVec; use nix::sys::time::*; + use std::io::{IoSlice, IoSliceMut}; use std::time::*; // Set up @@ -1609,49 +2252,57 @@ fn test_recvmsg_timestampns() { AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None).unwrap(); - setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); - let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); - bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); - let address = getsockname(in_socket).unwrap(); + None, + ) + .unwrap(); + setsockopt(&in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + let localhost = SockaddrIn::new(127, 0, 0, 1, 0); + bind(in_socket.as_raw_fd(), &localhost).unwrap(); + let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); // Get initial time let time0 = SystemTime::now(); // Send the message - let iov = [IoVec::from_slice(message)]; + let iov = [IoSlice::new(message)]; let flags = MsgFlags::empty(); - let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + let l = sendmsg(in_socket.as_raw_fd(), &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 iov = [IoVec::from_mut_slice(&mut buffer)]; - let r = recvmsg(in_socket, &iov, Some(&mut cmsgspace), flags).unwrap(); + + let mut iov = [IoSliceMut::new(&mut buffer)]; + let r = recvmsg::<()>( + in_socket.as_raw_fd(), + &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") + 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); + 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"))] +#[cfg(target_os = "linux")] #[test] fn test_recvmmsg_timestampns() { use nix::sys::socket::*; - use nix::sys::uio::IoVec; use nix::sys::time::*; + use std::io::{IoSlice, IoSliceMut}; use std::time::*; // Set up @@ -1660,44 +2311,43 @@ fn test_recvmmsg_timestampns() { AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None).unwrap(); - setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); - let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); - bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); - let address = getsockname(in_socket).unwrap(); + None, + ) + .unwrap(); + setsockopt(&in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + bind(in_socket.as_raw_fd(), &localhost).unwrap(); + let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); // Get initial time let time0 = SystemTime::now(); // Send the message - let iov = [IoVec::from_slice(message)]; + let iov = [IoSlice::new(message)]; let flags = MsgFlags::empty(); - let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + let l = sendmsg(in_socket.as_raw_fd(), &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 iov = [IoVec::from_mut_slice(&mut buffer)]; - let mut data = vec![ - RecvMmsgData { - iov, - cmsg_buffer: Some(&mut cmsgspace), - }, - ]; - let r = recvmmsg(in_socket, &mut data, flags, None).unwrap(); + 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.as_raw_fd(), &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") + 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); + 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 @@ -1706,10 +2356,10 @@ fn test_recvmmsg_timestampns() { #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] #[test] fn test_recvmsg_rxq_ovfl() { - use nix::Error; + use nix::sys::socket::sockopt::{RcvBuf, RxqOvfl}; use nix::sys::socket::*; - use nix::sys::uio::IoVec; - use nix::sys::socket::sockopt::{RxqOvfl, RcvBuf}; + use nix::Error; + use std::io::{IoSlice, IoSliceMut}; let message = [0u8; 2048]; let bufsize = message.len() * 2; @@ -1718,35 +2368,46 @@ fn test_recvmsg_rxq_ovfl() { AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None).unwrap(); + None, + ) + .unwrap(); let out_socket = socket( AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), - None).unwrap(); + None, + ) + .unwrap(); - let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); - bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); + let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); + bind(in_socket.as_raw_fd(), &localhost).unwrap(); - let address = getsockname(in_socket).unwrap(); - connect(out_socket, &address).unwrap(); + let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); + connect(out_socket.as_raw_fd(), &address).unwrap(); // Set SO_RXQ_OVFL flag. - setsockopt(in_socket, RxqOvfl, &1).unwrap(); + setsockopt(&in_socket, RxqOvfl, &1).unwrap(); // Set the receiver buffer size to hold only 2 messages. - setsockopt(in_socket, RcvBuf, &bufsize).unwrap(); + setsockopt(&in_socket, RcvBuf, &bufsize).unwrap(); let mut drop_counter = 0; for _ in 0..2 { - let iov = [IoVec::from_slice(&message)]; + 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(); + let l = sendmsg( + out_socket.as_raw_fd(), + &iov, + &[], + flags, + Some(&address), + ) + .unwrap(); assert_eq!(message.len(), l); } @@ -1755,41 +2416,42 @@ fn test_recvmsg_rxq_ovfl() { let mut buffer = vec![0u8; message.len()]; let mut cmsgspace = nix::cmsg_space!(u32); - let iov = [IoVec::from_mut_slice(&mut buffer)]; + let mut iov = [IoSliceMut::new(&mut buffer)]; - match recvmsg( - in_socket, - &iov, + match recvmsg::<()>( + in_socket.as_raw_fd(), + &mut iov, Some(&mut cmsgspace), - MsgFlags::MSG_DONTWAIT) { + MsgFlags::MSG_DONTWAIT, + ) { Ok(r) => { drop_counter = match r.cmsgs().next() { - Some(ControlMessageOwned::RxqOvfl(drop_counter)) => drop_counter, + Some(ControlMessageOwned::RxqOvfl(drop_counter)) => { + drop_counter + } Some(_) => panic!("Unexpected control message"), None => 0, }; - }, - Err(Error::EAGAIN) => { break; }, - _ => { panic!("unknown recvmsg() error"); }, + } + 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", -))] +#[cfg(any(target_os = "linux", target_os = "android",))] mod linux_errqueue { + use super::FromStr; use nix::sys::socket::*; - use super::{FromStr, SocketAddr}; + use std::os::unix::io::AsRawFd; // Send a UDP datagram to a bogus destination address and observe an ICMP error (v4). // @@ -1817,18 +2479,23 @@ mod linux_errqueue { // 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 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!(Ipv4Addr(origin.sin_addr), Ipv4Addr::new(127, 0, 0, 1)); + 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); + panic!("Unexpected control message {cmsg:?}"); } }, ) @@ -1860,13 +2527,18 @@ mod linux_errqueue { // 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 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!( - Ipv6Addr(origin.sin6_addr), - Ipv6Addr::from_std(&"::1".parse().unwrap()), + 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 { @@ -1874,49 +2546,57 @@ mod linux_errqueue { } *ext_err } else { - panic!("Unexpected control message {:?}", cmsg); + 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, + 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 nix::sys::uio::IoVec; + use std::io::IoSliceMut; const MESSAGE_CONTENTS: &str = "ABCDEF"; - - let sock_addr = { - let std_sa = SocketAddr::from_str(sa).unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - SockAddr::new_inet(inet_addr) - }; - 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()) { + 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.as_raw_fd(), + MESSAGE_CONTENTS.as_bytes(), + &sock_addr, + MsgFlags::empty(), + ) { assert_eq!(e, Errno::EADDRNOTAVAIL); - println!("{:?} not available, skipping test.", af); + println!("{af:?} not available, skipping test."); return; } let mut buf = [0u8; 8]; - let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut iovec = [IoSliceMut::new(&mut buf)]; let mut cspace = cmsg_space!(libc::sock_extended_err, SA); - let msg = recvmsg(sock, &iovec, Some(&mut cspace), MsgFlags::MSG_ERRQUEUE).unwrap(); + let msg = recvmsg( + sock.as_raw_fd(), + &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()); - assert_eq!(&buf[..msg.bytes], MESSAGE_CONTENTS.as_bytes()); // 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 @@ -1937,5 +2617,71 @@ mod linux_errqueue { 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.as_raw_fd(), &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.as_raw_fd(), + &iov1, + &[cmsg], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + let mut rbuf = [0u8; 2048]; + let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; + recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, None, MsgFlags::empty()) + .unwrap(); +} diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index 01920fd40a..0e34917325 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -1,49 +1,76 @@ -use rand::{thread_rng, Rng}; -use nix::sys::socket::{socket, sockopt, getsockopt, setsockopt, AddressFamily, SockType, SockFlag, SockProtocol}; #[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}; +use std::os::unix::io::AsRawFd; // NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not. -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", -))] +#[cfg(any(target_os = "dragonfly", target_os = "freebsd",))] #[test] pub fn test_local_peercred_seqpacket() { use nix::{ + sys::socket::socketpair, unistd::{Gid, Uid}, - sys::socket::socketpair }; - let (fd1, _fd2) = socketpair(AddressFamily::Unix, SockType::SeqPacket, None, - SockFlag::empty()).unwrap(); - let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap(); + 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" + 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}, - sys::socket::socketpair }; - let (fd1, _fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None, - SockFlag::empty()).unwrap(); - let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap(); + 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(any(target_os = "ios", target_os = "macos"))] +#[test] +pub fn test_local_peer_pid() { + use nix::sys::socket::socketpair; + + let (fd1, _fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let pid = getsockopt(&fd1, sockopt::LocalPeerPid).unwrap(); + assert_eq!(pid, std::process::id() as _); +} + #[cfg(target_os = "linux")] #[test] fn is_so_mark_functional() { @@ -51,41 +78,56 @@ fn is_so_mark_functional() { 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(); + 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 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(); + 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(); + setsockopt(&fd, sockopt::RcvBuf, &bufsize).unwrap(); + let actual = getsockopt(&fd, sockopt::RcvBuf).unwrap(); assert!(actual >= bufsize); } #[test] fn test_so_tcp_maxseg() { - use std::net::SocketAddr; + use nix::sys::socket::{accept, bind, connect, listen, SockaddrIn}; + use nix::unistd::write; + use std::net::SocketAddrV4; use std::str::FromStr; - use nix::sys::socket::{accept, bind, connect, listen, InetAddr, SockAddr}; - use nix::unistd::{close, write}; - - let std_sa = SocketAddr::from_str("127.0.0.1:4001").unwrap(); - let inet_addr = InetAddr::from_std(&std_sa); - let sock_addr = SockAddr::new_inet(inet_addr); - - 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(); + + 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.as_raw_fd(), &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 @@ -93,19 +135,24 @@ fn test_so_tcp_maxseg() { if #[cfg(any(target_os = "android", target_os = "linux"))] { let segsize: u32 = 873; assert!(initial < segsize); - setsockopt(rsock, sockopt::TcpMaxSeg, &segsize).unwrap(); + 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(); + let ssock = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + connect(ssock.as_raw_fd(), &sock_addr).unwrap(); + let rsess = accept(rsock.as_raw_fd()).unwrap(); write(rsess, b"hello").unwrap(); - let actual = getsockopt(ssock, sockopt::TcpMaxSeg).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! { @@ -117,12 +164,39 @@ fn test_so_tcp_maxseg() { 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; + use std::os::unix::io::{FromRawFd, OwnedFd}; + + require_capability!("test_so_type", CAP_NET_RAW); + let raw_fd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_PACKET, 0) }; + assert!(raw_fd >= 0, "Error opening socket: {}", nix::Error::last()); + let sockfd = unsafe { OwnedFd::from_raw_fd(raw_fd) }; + + assert_eq!(Err(Errno::EINVAL), getsockopt(&sockfd, sockopt::SockType)); } // The CI doesn't supported getsockopt and setsockopt on emulated processors. -// It's beleived that a QEMU issue, the tests run ok on a fully emulated system. +// 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] @@ -133,17 +207,25 @@ fn test_so_tcp_maxseg() { fn test_tcp_congestion() { use std::ffi::OsString; - let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), None).unwrap(); + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); - let val = getsockopt(fd, sockopt::TcpCongestion).unwrap(); - setsockopt(fd, sockopt::TcpCongestion, &val).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(); + setsockopt( + &fd, + sockopt::TcpCongestion, + &OsString::from("tcp_congestion_does_not_exist"), + ) + .unwrap_err(); - assert_eq!( - getsockopt(fd, sockopt::TcpCongestion).unwrap(), - val - ); + assert_eq!(getsockopt(&fd, sockopt::TcpCongestion).unwrap(), val); } #[test] @@ -151,49 +233,216 @@ fn test_tcp_congestion() { fn test_bindtodevice() { skip_if_not_root!("test_bindtodevice"); - let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), None).unwrap(); + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); - let val = getsockopt(fd, sockopt::BindToDevice).unwrap(); - setsockopt(fd, sockopt::BindToDevice, &val).unwrap(); + let val = getsockopt(&fd, sockopt::BindToDevice).unwrap(); + setsockopt(&fd, sockopt::BindToDevice, &val).unwrap(); - assert_eq!( - getsockopt(fd, sockopt::BindToDevice).unwrap(), - val - ); + 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", - target_os = "nacl"))] { - 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); + 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.as_raw_fd(), &SockaddrIn::from(std_sa)).unwrap(); + connect(usock.as_raw_fd(), &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) + 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) + 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 0000000000..426b4b6588 --- /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 index 73e6586f62..2897366eff 100644 --- a/test/sys/test_sysinfo.rs +++ b/test/sys/test_sysinfo.rs @@ -9,10 +9,12 @@ fn sysinfo_works() { assert!(l5 >= 0.0); assert!(l15 >= 0.0); - info.uptime(); // just test Duration construction + 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()); + 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 index 4a8615437e..83919378a7 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -1,17 +1,17 @@ -use std::os::unix::prelude::*; +use std::os::unix::io::{AsFd, AsRawFd}; use tempfile::tempfile; -use nix::fcntl; use nix::errno::Errno; +use nix::fcntl; use nix::pty::openpty; -use nix::sys::termios::{self, LocalFlags, OutputFlags, tcgetattr}; -use nix::unistd::{read, write, close}; +use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags}; +use nix::unistd::{read, write}; -/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s -fn write_all(f: RawFd, buf: &[u8]) { +/// Helper function analogous to `std::io::Write::write_all`, but for `Fd`s +fn write_all(f: Fd, buf: &[u8]) { let mut len = 0; while len < buf.len() { - len += write(f, &buf[len..]).unwrap(); + len += write(f.as_fd().as_raw_fd(), &buf[len..]).unwrap(); } } @@ -22,24 +22,14 @@ fn test_tcgetattr_pty() { let _m = crate::PTSNAME_MTX.lock(); let pty = openpty(None, None).expect("openpty failed"); - assert!(termios::tcgetattr(pty.slave).is_ok()); - close(pty.master).expect("closing the master failed"); - close(pty.slave).expect("closing the slave failed"); + termios::tcgetattr(&pty.slave).unwrap(); } // 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)); + assert_eq!(termios::tcgetattr(&file).err(), Some(Errno::ENOTTY)); } // Test modifying output flags @@ -51,36 +41,31 @@ fn test_output_flags() { // 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 + tcgetattr(&pty.slave).expect("tcgetattr failed") }; // Make sure postprocessing '\r' isn't specified by default or this test is useless. - assert!(!termios.output_flags.contains(OutputFlags::OPOST | OutputFlags::OCRNL)); + 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); + 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()); + 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); + 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()); } @@ -93,12 +78,7 @@ fn test_local_flags() { // 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 + tcgetattr(&pty.slave).unwrap() }; // Make sure echo is specified by default or this test is useless. @@ -109,22 +89,19 @@ fn test_local_flags() { // 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(); + let flags = fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_GETFL).unwrap(); + let new_flags = + fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK; + fcntl::fcntl(pty.master.as_raw_fd(), fcntl::F_SETFL(new_flags)).unwrap(); // Write into the master let string = "foofoofoo\r"; - write_all(pty.master, string.as_bytes()); + 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(); + let read = read(pty.master.as_raw_fd(), &mut buf).unwrap_err(); assert_eq!(read, Errno::EAGAIN); } diff --git a/test/sys/test_timerfd.rs b/test/sys/test_timerfd.rs index 24fb2ac002..08e292106c 100644 --- a/test/sys/test_timerfd.rs +++ b/test/sys/test_timerfd.rs @@ -1,10 +1,13 @@ use nix::sys::time::{TimeSpec, TimeValLike}; -use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags}; +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 timer = + TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); let before = Instant::now(); @@ -23,12 +26,16 @@ pub fn test_timerfd_oneshot() { #[test] pub fn test_timerfd_interval() { - let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); + let timer = + TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); let before = Instant::now(); timer .set( - Expiration::IntervalDelayed(TimeSpec::seconds(1), TimeSpec::seconds(2)), + Expiration::IntervalDelayed( + TimeSpec::seconds(1), + TimeSpec::seconds(2), + ), TimerSetTimeFlags::empty(), ) .unwrap(); @@ -46,7 +53,8 @@ pub fn test_timerfd_interval() { #[test] pub fn test_timerfd_unset() { - let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); + let timer = + TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); timer .set( @@ -57,5 +65,5 @@ pub fn test_timerfd_unset() { timer.unset().unwrap(); - assert!(timer.get().unwrap() == None); + assert!(timer.get().unwrap().is_none()); } diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs index c63b58103c..fc09465f19 100644 --- a/test/sys/test_uio.rs +++ b/test/sys/test_uio.rs @@ -1,14 +1,18 @@ use nix::sys::uio::*; use nix::unistd::*; -use rand::{thread_rng, Rng}; use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; +use std::fs::OpenOptions; +use std::io::IoSlice; +use std::os::unix::io::{FromRawFd, OwnedFd}; use std::{cmp, iter}; -use std::fs::{OpenOptions}; -use std::os::unix::io::AsRawFd; #[cfg(not(target_os = "redox"))] -use tempfile::tempfile; +use std::io::IoSliceMut; + use tempfile::tempdir; +#[cfg(not(target_os = "redox"))] +use tempfile::tempfile; #[test] fn test_writev() { @@ -27,41 +31,41 @@ fn test_writev() { 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(IoVec::from_slice(b)); + 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(); - assert!(pipe_res.is_ok()); - let (reader, writer) = pipe_res.ok().unwrap(); + let (reader, writer) = pipe().expect("Couldn't create pipe"); // FileDesc will close its filedesc (reader). let mut read_buf: Vec = iter::repeat(0u8).take(128 * 16).collect(); + + // Temporary workaround to cope with the existing RawFd pipe(2), should be + // removed when pipe(2) becomes I/O-safe. + let writer = unsafe { OwnedFd::from_raw_fd(writer) }; + // Blocking io, should write all data. - let write_res = writev(writer, &iovecs); - // Successful write - assert!(write_res.is_ok()); - let written = write_res.ok().unwrap(); + 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[..]); - // Successful read - assert!(read_res.is_ok()); - let read = read_res.ok().unwrap() as usize; + 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); - let close_res = close(writer); - assert!(close_res.is_ok()); - let close_res = close(reader); - assert!(close_res.is_ok()); + close(reader).expect("closed reader"); } #[test] #[cfg(not(target_os = "redox"))] fn test_readv() { - let s:String = thread_rng() + let s: String = thread_rng() .sample_iter(&Alphanumeric) .map(char::from) .take(128) @@ -71,40 +75,40 @@ fn test_readv() { 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 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(IoVec::from_mut_slice(&mut v[..])); + iovecs.push(IoSliceMut::new(&mut v[..])); } - let pipe_res = pipe(); - assert!(pipe_res.is_ok()); - let (reader, writer) = pipe_res.ok().unwrap(); + let (reader, writer) = pipe().expect("couldn't create pipe"); // Blocking io, should write all data. - let write_res = write(writer, &to_write); - // Successful write - assert!(write_res.is_ok()); - let read_res = readv(reader, &mut iovecs[..]); - assert!(read_res.is_ok()); - let read = read_res.ok().unwrap(); + write(writer, &to_write).expect("write failed"); + + // Temporary workaround to cope with the existing RawFd pipe(2), should be + // removed when pipe(2) becomes I/O-safe. + let reader = unsafe { OwnedFd::from_raw_fd(reader) }; + + 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.as_slice().iter().cloned()); + 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); - let close_res = close(reader); - assert!(close_res.is_ok()); - let close_res = close(writer); - assert!(close_res.is_ok()); + close(writer).expect("couldn't close writer"); } #[test] @@ -113,12 +117,12 @@ 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 buf = [1u8; 8]; + assert_eq!(Ok(8), pwrite(&file, &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]); + let mut expected = vec![0u8; 8]; + expected.extend(vec![1; 8]); assert_eq!(file_content, expected); } @@ -129,39 +133,49 @@ fn test_pread() { 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 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 mut buf = [0u8; 16]; + assert_eq!(Ok(16), pread(&file, &mut buf, 16)); let expected: Vec<_> = (16..32).collect(); assert_eq!(&buf[..], &expected[..]); } #[test] -#[cfg(not(target_os = "redox"))] +#[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 expected: Vec = [vec![0; 100], to_write.clone()].concat(); let iovecs = [ - IoVec::from_slice(&to_write[0..17]), - IoVec::from_slice(&to_write[17..64]), - IoVec::from_slice(&to_write[64..128]), + 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(); + let mut file = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + + let written = pwritev(&file, &iovecs, 100).ok().unwrap(); assert_eq!(written, to_write.len()); // Read the data back and make sure it matches @@ -171,7 +185,7 @@ fn test_pwritev() { } #[test] -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_preadv() { use std::io::Write; @@ -182,21 +196,24 @@ fn test_preadv() { let path = tempdir.path().join("preadv_test_file"); - let mut file = OpenOptions::new().read(true).write(true).create(true) - .truncate(true).open(path).unwrap(); + 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], - ]; + let mut buffers: Vec> = vec![vec![0; 24], vec![0; 1], vec![0; 75]]; { // Borrow the buffers into IoVecs and preadv into them - let iovecs: Vec<_> = buffers.iter_mut().map( - |buf| IoVec::from_mut_slice(&mut buf[..])).collect(); - assert_eq!(Ok(100), preadv(file.as_raw_fd(), &iovecs, 100)); + let mut iovecs: Vec<_> = buffers + .iter_mut() + .map(|buf| IoSliceMut::new(&mut buf[..])) + .collect(); + assert_eq!(Ok(100), preadv(&file, &mut iovecs, 100)); } let all = buffers.concat(); @@ -204,14 +221,15 @@ fn test_preadv() { } #[test] -#[cfg(target_os = "linux")] +#[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 nix::unistd::ForkResult::*; + use crate::*; use nix::sys::signal::*; use nix::sys::wait::*; - use crate::*; + use nix::unistd::ForkResult::*; require_capability!("test_process_vm_readv", CAP_SYS_PTRACE); let _m = crate::FORK_MTX.lock(); @@ -221,7 +239,7 @@ fn test_process_vm_readv() { let mut vector = vec![1u8, 2, 3, 4, 5]; let (r, w) = pipe().unwrap(); - match unsafe{fork()}.expect("Error: Fork Failed") { + match unsafe { fork() }.expect("Error: Fork Failed") { Parent { child } => { close(w).unwrap(); // wait for child @@ -232,16 +250,18 @@ fn test_process_vm_readv() { let remote_iov = RemoteIoVec { base: ptr, len: 5 }; let mut buf = vec![0u8; 5]; - let ret = process_vm_readv(child, - &[IoVec::from_mut_slice(&mut buf)], - &[remote_iov]); + 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 { @@ -249,7 +269,9 @@ fn test_process_vm_readv() { } let _ = write(w, b"\0"); let _ = close(w); - loop { let _ = pause(); } - }, + loop { + pause(); + } + } } } diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index afe4f42b29..d472f1ec19 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -1,25 +1,55 @@ +use libc::_exit; use nix::errno::Errno; -use nix::unistd::*; -use nix::unistd::ForkResult::*; use nix::sys::signal::*; use nix::sys::wait::*; -use libc::_exit; +use nix::unistd::ForkResult::*; +use nix::unistd::*; #[test] -#[cfg(not(target_os = "redox"))] +#[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))); - }, + 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)), + ); + } } } @@ -28,19 +58,53 @@ 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))); - }, + 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, 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)); } @@ -48,7 +112,7 @@ fn test_waitstatus_from_raw() { fn test_waitstatus_pid() { let _m = crate::FORK_MTX.lock(); - match unsafe{fork()}.unwrap() { + match unsafe { fork() }.unwrap() { Child => unsafe { _exit(0) }, Parent { child } => { let status = waitpid(child, None).unwrap(); @@ -57,17 +121,36 @@ fn test_waitstatus_pid() { } } +#[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 nix::sys::ptrace::{self, Options, Event}; + use crate::*; + use libc::_exit; + use nix::sys::ptrace::{self, Event, Options}; use nix::sys::signal::*; use nix::sys::wait::*; - use nix::unistd::*; use nix::unistd::ForkResult::*; - use libc::_exit; - use crate::*; + use nix::unistd::*; fn ptrace_child() -> ! { ptrace::traceme().unwrap(); @@ -77,31 +160,98 @@ mod ptrace { unsafe { _exit(0) } } - fn ptrace_parent(child: Pid) { + fn ptrace_wait_parent(child: Pid) { // Wait for the raised SIGTRAP - assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP))); + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, SIGTRAP)) + ); // We want to test a syscall stop and a PTRACE_EVENT stop - assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok()); + 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() - assert!(ptrace::syscall(child, None).is_ok()); + 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 - assert!(ptrace::cont(child, None).is_ok()); - assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, Event::PTRACE_EVENT_EXIT as i32))); + 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 - assert!(ptrace::cont(child, None).is_ok()); + 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") { + 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_parent(child), + Parent { child } => ptrace_waitid_parent(child), } } } diff --git a/test/test.rs b/test/test.rs index aade937ab9..7e73bb3056 100644 --- a/test/test.rs +++ b/test/test.rs @@ -1,91 +1,109 @@ #[macro_use] extern crate cfg_if; -#[cfg_attr(not(target_os = "redox"), macro_use)] +#[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"))] +#[cfg(any(target_os = "android", target_os = "linux"))] mod test_kmod; -#[cfg(target_os = "freebsd")] -mod test_nmount; -#[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "fushsia", - target_os = "linux", - target_os = "netbsd"))] +#[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; -mod test_resource; +#[cfg(target_os = "freebsd")] +mod test_nmount; mod test_poll; -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] mod test_pty; -#[cfg(any(target_os = "android", - target_os = "linux"))] +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 = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] +#[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 std::os::unix::io::RawFd; -use std::path::PathBuf; -use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use nix::unistd::{chdir, getcwd, read}; +use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; +use std::os::unix::io::{AsFd, AsRawFd}; +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]) { +/// Helper function analogous to `std::io::Read::read_exact`, but for `Fd`s +fn read_exact(f: Fd, 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(); + len += read(f.as_fd().as_raw_fd(), 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(()); -} +/// Any test that creates child processes must grab this mutex, regardless +/// of what it does with those children. +pub static FORK_MTX: std::sync::Mutex<()> = std::sync::Mutex::new(()); +/// 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 CWD_LOCK: RwLock<()> = RwLock::new(()); +/// Any test that changes the process's supplementary groups must grab this +/// mutex +pub static GROUPS_MTX: Mutex<()> = Mutex::new(()); +/// Any tests that loads or unloads kernel modules must grab this mutex +pub static KMOD_MTX: Mutex<()> = Mutex::new(()); +/// Any test that calls ptsname(3) must grab this mutex. +pub static PTSNAME_MTX: Mutex<()> = Mutex::new(()); +/// Any test that alters signal handling must grab this mutex. +pub static SIGNAL_MTX: Mutex<()> = Mutex::new(()); /// RAII object that restores a test's original directory on drop struct DirRestore<'a> { d: PathBuf, - _g: RwLockWriteGuard<'a, ()> + _g: RwLockWriteGuard<'a, ()>, } impl<'a> DirRestore<'a> { fn new() -> Self { let guard = crate::CWD_LOCK.write(); - DirRestore{ + DirRestore { _g: guard, d: getcwd().unwrap(), } diff --git a/test/test_dir.rs b/test/test_dir.rs index 2940b6eafb..2af4aa5c0a 100644 --- a/test/test_dir.rs +++ b/test/test_dir.rs @@ -4,7 +4,6 @@ use nix::sys::stat::Mode; use std::fs::File; use tempfile::tempdir; - #[cfg(test)] fn flags() -> OFlag { #[cfg(target_os = "illumos")] @@ -17,11 +16,11 @@ fn flags() -> OFlag { } #[test] -#[allow(clippy::unnecessary_sort_by)] // False positive +#[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(); + 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())); @@ -43,13 +42,23 @@ fn read() { 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(); + 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 index db2acfbf52..5fef04ba9b 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -1,7 +1,7 @@ #[cfg(not(target_os = "redox"))] use nix::errno::*; #[cfg(not(target_os = "redox"))] -use nix::fcntl::{open, OFlag, readlink}; +use nix::fcntl::{open, readlink, OFlag}; #[cfg(not(target_os = "redox"))] use nix::fcntl::{openat, readlinkat, renameat}; #[cfg(all( @@ -14,34 +14,40 @@ use nix::fcntl::{openat, readlinkat, renameat}; target_arch = "s390x" ) ))] -use nix::fcntl::{RenameFlags, renameat2}; +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 tempfile::{self, NamedTempFile}; -#[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::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 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()); @@ -55,14 +61,18 @@ fn test_openat() { #[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_dirfd = + open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); let old_path = old_dir.path().join("old"); - File::create(&old_path).unwrap(); + 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_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); + 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()); @@ -81,11 +91,13 @@ fn test_renameat() { ))] 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_dirfd = + open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); let old_path = old_dir.path().join("old"); - File::create(&old_path).unwrap(); + 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_dirfd = + open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); renameat2( Some(old_dirfd), "old", @@ -123,14 +135,16 @@ fn test_renameat2_behaves_like_renameat_with_no_flags() { ))] fn test_renameat2_exchange() { let old_dir = tempfile::tempdir().unwrap(); - let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).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_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(); @@ -169,13 +183,15 @@ fn test_renameat2_exchange() { ))] fn test_renameat2_noreplace() { let old_dir = tempfile::tempdir().unwrap(); - let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).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(); + 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_dirfd = + open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); let new_path = new_dir.path().join("new"); - File::create(&new_path).unwrap(); + File::create(new_path).unwrap(); assert_eq!( renameat2( Some(old_dirfd), @@ -193,7 +209,6 @@ fn test_renameat2_noreplace() { assert!(old_dir.path().join("old").exists()); } - #[test] #[cfg(not(target_os = "redox"))] fn test_readlink() { @@ -201,70 +216,78 @@ fn test_readlink() { 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(); + 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); + assert_eq!( + readlinkat(dirfd, "b").unwrap().to_str().unwrap(), + expected_dir + ); +} +/// 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). +#[cfg(any( + target_os = "linux", + // Not available until FreeBSD 13.0 + all(target_os = "freebsd", fbsd14), + target_os = "android" +))] +#[test] +// QEMU does not support copy_file_range. Skip under qemu +#[cfg_attr(qemu, ignore)] +fn test_copy_file_range() { + use nix::fcntl::copy_file_range; + use std::os::unix::io::AsFd; + + const CONTENTS: &[u8] = b"foobarbaz"; + + let mut tmp1 = tempfile::tempfile().unwrap(); + let mut tmp2 = tempfile::tempfile().unwrap(); + + tmp1.write_all(CONTENTS).unwrap(); + tmp1.flush().unwrap(); + + let mut from_offset: i64 = 3; + copy_file_range( + tmp1.as_fd(), + Some(&mut from_offset), + tmp2.as_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); } #[cfg(any(target_os = "linux", target_os = "android"))] mod linux_android { + use libc::loff_t; use std::io::prelude::*; - use std::io::SeekFrom; + use std::io::IoSlice; use std::os::unix::prelude::*; - use libc::loff_t; use nix::fcntl::*; - use nix::sys::uio::IoVec; use nix::unistd::{close, pipe, read, write}; use tempfile::tempfile; - #[cfg(any(target_os = "linux"))] + #[cfg(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.seek(SeekFrom::Start(0)).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"; @@ -273,8 +296,15 @@ mod linux_android { 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(); + let res = splice( + tmp.as_raw_fd(), + Some(&mut offset), + wr, + None, + 2, + SpliceFFlags::empty(), + ) + .unwrap(); assert_eq!(2, res); @@ -319,10 +349,7 @@ mod linux_android { let buf1 = b"abcdef"; let buf2 = b"defghi"; - let iovecs = vec![ - IoVec::from_slice(&buf1[0..3]), - IoVec::from_slice(&buf2[0..3]) - ]; + let iovecs = [IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])]; let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap(); @@ -337,7 +364,7 @@ mod linux_android { close(wr).unwrap(); } - #[cfg(any(target_os = "linux"))] + #[cfg(target_os = "linux")] #[test] fn test_fallocate() { let tmp = NamedTempFile::new().unwrap(); @@ -357,6 +384,7 @@ mod linux_android { #[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; @@ -364,7 +392,7 @@ mod linux_android { let tmp = NamedTempFile::new().unwrap(); let fd = tmp.as_raw_fd(); - let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); + let statfs = nix::sys::statfs::fstatfs(tmp.as_file()).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 @@ -374,7 +402,7 @@ mod linux_android { let inode = fstat(fd).expect("fstat failed").st_ino as usize; let mut flock: libc::flock = unsafe { - mem::zeroed() // required for Linux/mips + 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; @@ -394,6 +422,7 @@ mod linux_android { #[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; @@ -401,7 +430,7 @@ mod linux_android { let tmp = NamedTempFile::new().unwrap(); let fd = tmp.as_raw_fd(); - let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap(); + let statfs = nix::sys::statfs::fstatfs(tmp.as_file()).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 @@ -411,7 +440,7 @@ mod linux_android { let inode = fstat(fd).expect("fstat failed").st_ino as usize; let mut flock: libc::flock = unsafe { - mem::zeroed() // required for Linux/mips + 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; @@ -431,10 +460,7 @@ mod linux_android { #[cfg(all(target_os = "linux", not(target_env = "musl")))] fn lock_info(inode: usize) -> Option<(String, String)> { - use std::{ - fs::File, - io::BufReader - }; + use std::{fs::File, io::BufReader}; let file = File::open("/proc/locks").expect("open /proc/locks failed"); let buf = BufReader::new(file); @@ -454,51 +480,63 @@ mod linux_android { } } -#[cfg(any(target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - any(target_os = "wasi", target_env = "wasi"), - target_env = "uclibc", - target_os = "freebsd"))] +#[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 tempfile::NamedTempFile; - use std::os::unix::io::{RawFd, AsRawFd}; 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(); - let res = posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED); - - assert!(res.is_ok()); + 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); + 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 = "emscripten", - target_os = "fuchsia", - any(target_os = "wasi", target_env = "wasi"), - target_os = "freebsd"))] +#[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 tempfile::NamedTempFile; - use std::{io::Read, os::unix::io::{RawFd, AsRawFd}}; 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() { @@ -530,11 +568,7 @@ mod test_posix_fallocate { 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, - ), + errno => panic!("unexpected errno {errno}",), } } } diff --git a/test/test_kmod/mod.rs b/test/test_kmod/mod.rs index 8eef5384a3..6f9aaa897f 100644 --- a/test/test_kmod/mod.rs +++ b/test/test_kmod/mod.rs @@ -1,22 +1,25 @@ +use crate::*; use std::fs::copy; use std::path::PathBuf; use std::process::Command; use tempfile::{tempdir, TempDir}; -use crate::*; fn compile_kernel_module() -> (PathBuf, String, TempDir) { let _m = crate::FORK_MTX.lock(); - let tmp_dir = tempdir().expect("unable to create temporary build directory"); + 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"); + 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"); + tmp_dir.path().join("Makefile"), + ) + .expect("unable to copy Makefile to temporary build directory"); let status = Command::new("make") .current_dir(tmp_dir.path()) @@ -51,12 +54,16 @@ fn test_finit_and_delete_module() { delete_module( &CString::new(kmod_name).unwrap(), DeleteModuleFlags::empty(), - ).expect("unable to unload kernel module"); + ) + .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); + require_capability!( + "test_finit_and_delete_module_with_params", + CAP_SYS_MODULE + ); let _m0 = crate::KMOD_MTX.lock(); let _m1 = crate::CWD_LOCK.read(); @@ -67,12 +74,14 @@ fn test_finit_and_delete_module_with_params() { &f, &CString::new("who=Rust number=2018").unwrap(), ModuleInitFlags::empty(), - ).expect("unable to load kernel module"); + ) + .expect("unable to load kernel module"); delete_module( &CString::new(kmod_name).unwrap(), DeleteModuleFlags::empty(), - ).expect("unable to unload kernel module"); + ) + .expect("unable to unload kernel module"); } #[test] @@ -87,17 +96,22 @@ fn test_init_and_delete_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"); + 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"); + ) + .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); + require_capability!( + "test_init_and_delete_module_with_params", + CAP_SYS_MODULE + ); let _m0 = crate::KMOD_MTX.lock(); let _m1 = crate::CWD_LOCK.read(); @@ -113,7 +127,8 @@ fn test_init_and_delete_module_with_params() { delete_module( &CString::new(kmod_name).unwrap(), DeleteModuleFlags::empty(), - ).expect("unable to unload kernel module"); + ) + .expect("unable to unload kernel module"); } #[test] @@ -125,14 +140,18 @@ fn test_finit_module_invalid() { 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()); + 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); + require_capability!( + "test_finit_module_twice_and_delete_module", + CAP_SYS_MODULE + ); let _m0 = crate::KMOD_MTX.lock(); let _m1 = crate::CWD_LOCK.read(); @@ -142,14 +161,16 @@ fn test_finit_module_twice_and_delete_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()); + 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"); + ) + .expect("unable to unload kernel module"); } #[test] @@ -158,7 +179,10 @@ fn test_delete_module_not_loaded() { let _m0 = crate::KMOD_MTX.lock(); let _m1 = crate::CWD_LOCK.read(); - let result = delete_module(&CString::new("hello").unwrap(), DeleteModuleFlags::empty()); + 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 index 44287f975f..5cf00408e8 100644 --- a/test/test_mount.rs +++ b/test/test_mount.rs @@ -1,6 +1,6 @@ mod common; -// Impelmentation note: to allow unprivileged users to run it, this test makes +// 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. @@ -27,16 +27,18 @@ exit 23"; const EXPECTED_STATUS: i32 = 23; const NONE: Option<&'static [u8]> = None; - #[allow(clippy::bind_instead_of_map)] // False positive + #[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)); + 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"); @@ -46,8 +48,10 @@ exit 23"; .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 { + .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 @@ -56,60 +60,74 @@ exit 23"; // 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.") + writeln!( + handle, + "Buggy Linux kernel detected. Skipping test." + ) .unwrap(); process::exit(0); - } else { - panic!("open failed: {}", e); - } - ) + } else { + panic!("open failed: {e}"); + } + }) .and_then(|mut f| f.write(SCRIPT_CONTENTS)) - .unwrap_or_else(|e| panic!("write failed: {}", e)); + .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)); + .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)); + 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)); + 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 as i32, - File::create(tempdir.path().join("test")).unwrap_err().raw_os_error().unwrap()); - - umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e)); + 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)); + 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"); @@ -119,24 +137,32 @@ exit 23"; .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)); + .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); + 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 as i32, - Command::new(&test_path).status().unwrap_err().raw_os_error().unwrap()); - - umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e)); + 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() { @@ -146,12 +172,14 @@ exit 23"; { 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)); + 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) @@ -159,9 +187,10 @@ exit 23"; .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)); + .unwrap_or_else(|e| panic!("write failed: {e}")); - umount(mount_point.path()).unwrap_or_else(|e| panic!("umount 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 @@ -170,7 +199,7 @@ exit 23"; 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)); + .unwrap_or_else(|e| panic!("read failed: {e}")); assert_eq!(buf, SCRIPT_CONTENTS); } @@ -182,8 +211,7 @@ exit 23"; let stderr = io::stderr(); let mut handle = stderr.lock(); writeln!(handle, - "unshare failed: {}. Are unprivileged user namespaces available?", - e).unwrap(); + "unshare failed: {e}. Are unprivileged user namespaces available?").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. @@ -194,12 +222,11 @@ exit 23"; 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)); + .and_then(|mut f| f.write(format!("1000 {uid} 1\n").as_bytes())) + .unwrap_or_else(|e| panic!("could not write uid map: {e}")); } } - // Test runner /// Mimic normal test output (hackishly). @@ -220,16 +247,20 @@ macro_rules! run_tests { #[cfg(target_os = "linux")] fn main() { - use test_mount::{setup_namespaces, test_mount_tmpfs_without_flags_allows_rwx, - test_mount_rdonly_disallows_write, test_mount_noexec_disallows_exec, - test_mount_bind}; + 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); + 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"))] diff --git a/test/test_mq.rs b/test/test_mq.rs index 430df5ddcc..1fd8929c17 100644 --- a/test/test_mq.rs +++ b/test/test_mq.rs @@ -1,16 +1,39 @@ -use std::ffi::CString; +use cfg_if::cfg_if; use std::str; use nix::errno::Errno; -use nix::mqueue::{mq_open, mq_close, mq_send, mq_receive, mq_attr_member_t}; -use nix::mqueue::{MqAttr, MQ_OFlag}; +use nix::mqueue::{ + mq_attr_member_t, mq_close, mq_open, mq_receive, mq_send, mq_timedreceive, +}; +use nix::mqueue::{MQ_OFlag, MqAttr}; use nix::sys::stat::Mode; +use nix::sys::time::{TimeSpec, TimeValLike}; +use nix::time::{clock_gettime, ClockId}; + +// 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 attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = "/a_nix_test_queue"; let oflag0 = MQ_OFlag::O_CREAT | MQ_OFlag::O_WRONLY; let mode = Mode::S_IWUSR | Mode::S_IRUSR | Mode::S_IRGRP | Mode::S_IROTH; @@ -21,13 +44,13 @@ fn test_mq_send_and_receive() { }; let mqd0 = r0.unwrap(); let msg_to_send = "msg_1"; - mq_send(mqd0, msg_to_send.as_bytes(), 1).unwrap(); + 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(); + let len = mq_receive(&mqd1, &mut buf, &mut prio).unwrap(); assert_eq!(prio, 1); mq_close(mqd1).unwrap(); @@ -35,14 +58,43 @@ fn test_mq_send_and_receive() { assert_eq!(msg_to_send, str::from_utf8(&buf[0..len]).unwrap()); } +#[test] +fn test_mq_timedreceive() { + const MSG_SIZE: mq_attr_member_t = 32; + let attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = "/a_nix_test_queue"; + + 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 abstime = + clock_gettime(ClockId::CLOCK_REALTIME).unwrap() + TimeSpec::seconds(1); + let len = mq_timedreceive(&mqd1, &mut buf, &mut prio, &abstime).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] -#[cfg(not(any(target_os = "netbsd")))] 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 initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = "/attr_test_get_attr"; 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)); @@ -52,24 +104,22 @@ fn test_mq_getattr() { }; let mqd = r.unwrap(); - let read_attr = mq_getattr(mqd).unwrap(); - assert_eq!(read_attr, initial_attr); + 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(not(any(target_os = "netbsd")))] -#[cfg_attr(all( - qemu, - any(target_arch = "mips", target_arch = "mips64") - ), ignore +#[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 initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = "/attr_test_get_attr"; 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)); @@ -79,38 +129,47 @@ fn test_mq_setattr() { }; 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_eq!(old_attr, initial_attr); + 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); - let new_attr_get = mq_getattr(mqd).unwrap(); - // The following tests make sense. No changes here because according to the Linux man page only + // No changes here because according to the Linux man page only // O_NONBLOCK can be set (see tests below) - 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(); + #[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 - assert_ne!(new_attr_get, initial_attr); - assert_eq!(new_attr_get, new_attr_non_blocking); + #[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(not(any(target_os = "netbsd")))] -#[cfg_attr(all( - qemu, - any(target_arch = "mips", target_arch = "mips64") - ), ignore +#[cfg_attr( + all(qemu, any(target_arch = "mips", target_arch = "mips64")), + ignore )] fn test_mq_set_nonblocking() { - use nix::mqueue::{mq_getattr, mq_set_nonblock, mq_remove_nonblock}; + 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 initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name = "/attr_test_get_attr"; 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)); @@ -119,23 +178,24 @@ fn test_mq_set_nonblocking() { return; }; let mqd = r.unwrap(); - mq_set_nonblock(mqd).unwrap(); - let new_attr = mq_getattr(mqd); - assert_eq!(new_attr.unwrap().flags(), MQ_OFlag::O_NONBLOCK.bits() as mq_attr_member_t); - mq_remove_nonblock(mqd).unwrap(); - let new_attr = mq_getattr(mqd); - assert_eq!(new_attr.unwrap().flags(), 0); + 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] -#[cfg(not(any(target_os = "netbsd")))] 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(); - let mq_name_not_opened = &CString::new(b"/mq_unlink_test".as_ref()).unwrap(); + let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); + let mq_name_opened = "/mq_unlink_test"; + #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] + let mq_name_not_opened = "/mq_unlink_test"; 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)); @@ -146,12 +206,18 @@ fn test_mq_unlink() { let mqd = r.unwrap(); let res_unlink = mq_unlink(mq_name_opened); - assert_eq!(res_unlink, Ok(()) ); + assert_eq!(res_unlink, Ok(())); - let res_unlink_not_opened = mq_unlink(mq_name_not_opened); - assert_eq!(res_unlink_not_opened, Err(Errno::ENOENT) ); + // 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) ); + assert_eq!(res_unlink_after_close, Err(Errno::ENOENT)); } diff --git a/test/test_net.rs b/test/test_net.rs index 40ecd6bb75..c44655a4c9 100644 --- a/test/test_net.rs +++ b/test/test_net.rs @@ -3,10 +3,17 @@ 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")))] +#[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() { - assert!(if_nametoindex(LOOPBACK).is_ok()); + if_nametoindex(LOOPBACK).expect("assertion failed"); } diff --git a/test/test_nix_path.rs b/test/test_nix_path.rs index e69de29bb2..8b13789179 100644 --- a/test/test_nix_path.rs +++ b/test/test_nix_path.rs @@ -0,0 +1 @@ + diff --git a/test/test_nmount.rs b/test/test_nmount.rs index 4c74ecf627..dec806a55f 100644 --- a/test/test_nmount.rs +++ b/test/test_nmount.rs @@ -1,13 +1,9 @@ use crate::*; use nix::{ errno::Errno, - mount::{MntFlags, Nmount, unmount} -}; -use std::{ - ffi::CString, - fs::File, - path::Path + mount::{unmount, MntFlags, Nmount}, }; +use std::{ffi::CString, fs::File, path::Path}; use tempfile::tempdir; #[test] @@ -24,14 +20,15 @@ fn ok() { .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(); - + .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); } @@ -44,8 +41,9 @@ fn bad_fstype() { 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(); - + .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 index e4b369f3f2..045ccd3df1 100644 --- a/test/test_poll.rs +++ b/test/test_poll.rs @@ -1,8 +1,9 @@ use nix::{ errno::Errno, - poll::{PollFlags, poll, PollFd}, - unistd::{write, pipe} + poll::{poll, PollFd, PollFlags}, + unistd::{close, pipe, write}, }; +use std::os::unix::io::{BorrowedFd, FromRawFd, OwnedFd}; macro_rules! loop_while_eintr { ($poll_expr: expr) => { @@ -10,16 +11,17 @@ macro_rules! loop_while_eintr { match $poll_expr { Ok(nfds) => break nfds, Err(Errno::EINTR) => (), - Err(e) => panic!("{}", e) + Err(e) => panic!("{}", e), } } - } + }; } #[test] fn test_poll() { let (r, w) = pipe().unwrap(); - let mut fds = [PollFd::new(r, PollFlags::POLLIN)]; + let r = unsafe { OwnedFd::from_raw_fd(r) }; + let mut fds = [PollFd::new(&r, PollFlags::POLLIN)]; // Poll an idle pipe. Should timeout let nfds = loop_while_eintr!(poll(&mut fds, 100)); @@ -32,15 +34,18 @@ fn test_poll() { let nfds = poll(&mut fds, 100).unwrap(); assert_eq!(nfds, 1); assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); + close(w).unwrap(); } // 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"))] +#[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux" +))] #[test] fn test_ppoll() { use nix::poll::ppoll; @@ -49,33 +54,28 @@ fn test_ppoll() { let timeout = TimeSpec::milliseconds(1); let (r, w) = pipe().unwrap(); - let mut fds = [PollFd::new(r, PollFlags::POLLIN)]; + let r = unsafe { OwnedFd::from_raw_fd(r) }; + 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), sigset)); + 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), SigSet::empty()).unwrap(); + 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); + close(w).unwrap(); } #[test] fn test_pollfd_events() { - let mut pfd = PollFd::new(-1, PollFlags::POLLIN); + let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; + let mut pfd = PollFd::new(&fd_zero, 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 index 71932f2d6e..4cc6620c3c 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -1,29 +1,14 @@ use std::fs::File; use std::io::{Read, Write}; -use std::path::Path; use std::os::unix::prelude::*; -use tempfile::tempfile; +use std::path::Path; use libc::{_exit, STDOUT_FILENO}; -use nix::fcntl::{OFlag, open}; +use nix::fcntl::{open, OFlag}; use nix::pty::*; use nix::sys::stat; use nix::sys::termios::*; -use nix::unistd::{write, close, pause}; - -/// 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(); -} +use nix::unistd::{pause, write}; /// Test equivalence of `ptsname` and `ptsname_r` #[test] @@ -36,7 +21,7 @@ fn test_ptsname_equivalence() { assert!(master_fd.as_raw_fd() > 0); // Get the name of the slave - let slave_name = unsafe { ptsname(&master_fd) }.unwrap() ; + let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); let slave_name_r = ptsname_r(&master_fd).unwrap(); assert_eq!(slave_name, slave_name_r); } @@ -50,7 +35,6 @@ fn test_ptsname_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 = unsafe { ptsname(&master_fd) }.unwrap(); @@ -58,7 +42,7 @@ fn test_ptsname_copy() { assert_eq!(slave_name1, slave_name2); // Also make sure that the string was actually copied and they point to different parts of // memory. - assert!(slave_name1.as_ptr() != slave_name2.as_ptr()); + assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); } /// Test data copying of `ptsname_r` @@ -67,13 +51,12 @@ fn test_ptsname_copy() { 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!(slave_name1.as_ptr() != slave_name2.as_ptr()); + assert_ne!(slave_name1.as_ptr(), slave_name2.as_ptr()); } /// Test that `ptsname` returns different names for different devices @@ -84,16 +67,14 @@ fn test_ptsname_unique() { // 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!(slave_name1 != slave_name2); + assert_ne!(slave_name1, slave_name2); } /// Common setup for testing PTTY pairs @@ -111,7 +92,9 @@ fn open_ptty_pair() -> (PtyMaster, File) { 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(); + let slave_fd = + open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()) + .unwrap(); #[cfg(target_os = "illumos")] // TODO: rewrite using ioctl! @@ -145,43 +128,51 @@ fn open_ptty_pair() -> (PtyMaster, File) { /// /// 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. +/// master/slave PTTY pair. #[test] fn test_open_ptty_pair() { - let (master, slave) = open_ptty_pair(); - assert!(master.as_raw_fd() > 0); - assert!(slave.as_raw_fd() > 0); + let (_, _) = open_ptty_pair(); } /// Put the terminal in raw mode. -fn make_raw(fd: RawFd) { - let mut termios = tcgetattr(fd).unwrap(); +fn make_raw(fd: Fd) { + let mut termios = tcgetattr(&fd).unwrap(); cfmakeraw(&mut termios); - tcsetattr(fd, SetArg::TCSANOW, &termios).unwrap(); + 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()); + make_raw(&slave); 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()); + make_raw(&slave); 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] @@ -190,33 +181,28 @@ fn test_openpty() { 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); + write(pty.master.as_raw_fd(), 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); + 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); + write(pty.slave.as_raw_fd(), 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] @@ -227,51 +213,41 @@ fn test_openpty_with_termios() { // 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 + tcgetattr(&pty.slave).unwrap() }; // 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); + write(pty.master.as_raw_fd(), 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); + 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); + write(pty.slave.as_raw_fd(), 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::unistd::ForkResult::*; 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 @@ -279,23 +255,22 @@ fn test_forkpty() { let string = "naninani\n"; let echoed_string = "naninani\r\n"; - let pty = unsafe { - forkpty(None, None).unwrap() - }; + 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); } - }, + 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); + 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 deleted file mode 100644 index a68f81ee1e..0000000000 --- a/test/test_ptymaster_drop.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[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 index 5969750091..2ab581ba29 100644 --- a/test/test_resource.rs +++ b/test/test_resource.rs @@ -1,4 +1,9 @@ -#[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "illumos")))] +#[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 @@ -10,11 +15,17 @@ use nix::sys::resource::{getrlimit, setrlimit, Resource}; /// 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")))] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "illumos", + target_os = "haiku" +)))] pub fn test_resource_limits_nofile() { - let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); + let (mut soft_limit, hard_limit) = + getrlimit(Resource::RLIMIT_NOFILE).unwrap(); - let soft_limit = Some(soft_limit.map_or(1024, |v| v - 1)); + soft_limit -= 1; assert_ne!(soft_limit, hard_limit); setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); diff --git a/test/test_sched.rs b/test/test_sched.rs index 922196a3db..c52616b8bb 100644 --- a/test/test_sched.rs +++ b/test/test_sched.rs @@ -1,4 +1,4 @@ -use nix::sched::{sched_getaffinity, sched_setaffinity, CpuSet}; +use nix::sched::{sched_getaffinity, sched_getcpu, sched_setaffinity, CpuSet}; use nix::unistd::Pid; #[test] @@ -24,9 +24,16 @@ fn test_sched_affinity() { 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) + 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 index b6559d329b..b85e030fd3 100644 --- a/test/test_sendfile.rs +++ b/test/test_sendfile.rs @@ -1,5 +1,6 @@ use std::io::prelude::*; -use std::os::unix::prelude::*; +#[cfg(any(target_os = "android", target_os = "linux"))] +use std::os::unix::io::{FromRawFd, OwnedFd}; use libc::off_t; use nix::sys::sendfile::*; @@ -8,7 +9,7 @@ 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 = "freebsd", target_os = "ios", target_os = "macos"))] { + } 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; } @@ -23,7 +24,12 @@ fn test_sendfile_linux() { let (rd, wr) = pipe().unwrap(); let mut offset: off_t = 5; - let res = sendfile(wr, tmp.as_raw_fd(), Some(&mut offset), 2).unwrap(); + // The construct of this `OwnedFd` is a temporary workaround, when `pipe(2)` + // becomes I/O-safe: + // pub fn pipe() -> std::result::Result<(OwnedFd, OwnedFd), Error> + // then it is no longer needed. + let wr = unsafe { OwnedFd::from_raw_fd(wr) }; + let res = sendfile(&wr, &tmp, Some(&mut offset), 2).unwrap(); assert_eq!(2, res); @@ -33,7 +39,6 @@ fn test_sendfile_linux() { assert_eq!(7, offset); close(rd).unwrap(); - close(wr).unwrap(); } #[cfg(target_os = "linux")] @@ -45,7 +50,12 @@ fn test_sendfile64_linux() { 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(); + // The construct of this `OwnedFd` is a temporary workaround, when `pipe(2)` + // becomes I/O-safe: + // pub fn pipe() -> std::result::Result<(OwnedFd, OwnedFd), Error> + // then it is no longer needed. + let wr = unsafe { OwnedFd::from_raw_fd(wr) }; + let res = sendfile64(&wr, &tmp, Some(&mut offset), 2).unwrap(); assert_eq!(2, res); @@ -55,33 +65,35 @@ fn test_sendfile64_linux() { 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 header_strings = + ["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"]; + let trailer_strings = ["\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(); + 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(), + &tmp, + &wr, body_offset as off_t, None, Some(headers.as_slice()), @@ -93,8 +105,58 @@ fn test_sendfile_freebsd() { wr.shutdown(Shutdown::Both).unwrap(); // Prepare the expected result - let expected_string = - header_strings.concat() + &body[body_offset..] + &trailer_strings.concat(); + 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 = + ["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"]; + let body = "Xabcdef123456"; + let body_offset = 1; + let trailer_strings = ["\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, + &wr, + 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()); @@ -109,7 +171,8 @@ fn test_sendfile_freebsd() { #[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 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"]; @@ -119,16 +182,18 @@ fn test_sendfile_darwin() { 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(); + 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(), + &tmp, + &wr, body_offset as off_t, None, Some(headers.as_slice()), @@ -138,8 +203,9 @@ fn test_sendfile_darwin() { wr.shutdown(Shutdown::Both).unwrap(); // Prepare the expected result - let expected_string = - header_strings.concat() + &body[body_offset..] + &trailer_strings.concat(); + 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()); diff --git a/test/test_stat.rs b/test/test_stat.rs index 33cf748da3..55f15c0771 100644 --- a/test/test_stat.rs +++ b/test/test_stat.rs @@ -1,42 +1,51 @@ -#[cfg(not(target_os = "redox"))] +#[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, PermissionsExt}; +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::time::{Duration, UNIX_EPOCH}; -#[cfg(not(target_os = "redox"))] use std::path::Path; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::time::{Duration, UNIX_EPOCH}; -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -use libc::{S_IFMT, S_IFLNK}; 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::fcntl; #[cfg(not(target_os = "redox"))] use nix::errno::Errno; #[cfg(not(target_os = "redox"))] -use nix::sys::stat::{self, futimens, utimes}; -use nix::sys::stat::{fchmod, stat}; -#[cfg(not(target_os = "redox"))] -use nix::sys::stat::{fchmodat, utimensat, mkdirat}; -#[cfg(any(target_os = "linux", - target_os = "haiku", - target_os = "ios", - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd"))] +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, UtimensatFlags}; +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(target_os = "redox"))] +#[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; @@ -47,32 +56,35 @@ 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 + 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 +#[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 + 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 + 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 @@ -101,13 +113,11 @@ 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 dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()); - let result = stat::fstatat(dirfd.unwrap(), - &filename, - fcntl::AtFlags::empty()); + let result = + stat::fstatat(dirfd.unwrap(), &filename, fcntl::AtFlags::empty()); assert_stat_results(result); } @@ -167,12 +177,15 @@ fn test_fchmodat() { let fullpath = tempdir.path().join(filename); File::create(&fullpath).unwrap(); - let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).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(); + 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()); @@ -191,34 +204,42 @@ fn test_fchmodat() { /// /// 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(target_os = "redox"))] -fn assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata) { +#[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()); + attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap() + ); assert_eq!( Duration::new(exp_mtime_sec, 0), - attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap()); + attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap() + ); } #[test] -#[cfg(not(target_os = "redox"))] +#[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(); + 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 = "haiku", - target_os = "ios", - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd"))] +#[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"); @@ -227,31 +248,39 @@ fn test_lutimes() { symlink(&target, &fullpath).unwrap(); let exp_target_metadata = fs::symlink_metadata(&target).unwrap(); - lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)).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"); + 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(target_os = "redox"))] +#[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(); + 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(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_utimensat() { let _dr = crate::DirRestore::new(); let tempdir = tempfile::tempdir().unwrap(); @@ -259,16 +288,30 @@ fn test_utimensat() { let fullpath = tempdir.path().join(filename); drop(File::create(&fullpath).unwrap()); - let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).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(); + 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(); + utimensat( + None, + filename, + &TimeSpec::seconds(500), + &TimeSpec::seconds(800), + UtimensatFlags::FollowSymlink, + ) + .unwrap(); assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap()); } @@ -277,20 +320,27 @@ fn test_utimensat() { 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(); - assert!((mkdirat(dirfd, filename, Mode::S_IRWXU)).is_ok()); + 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(target_os = "redox"))] +#[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 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(); - assert!((mkdirat(dirfd, filename, Mode::S_IRWXU)).is_ok()); - let permissions = fs::metadata(tempdir.path().join(filename)).unwrap().permissions(); + 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) } @@ -299,19 +349,27 @@ fn test_mkdirat_success_mode() { #[cfg(not(target_os = "redox"))] fn test_mkdirat_fail() { let tempdir = tempfile::tempdir().unwrap(); - let not_dir_filename= "example_not_dir"; + 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 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 = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "redox")))] +#[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}; @@ -320,16 +378,20 @@ fn test_mknod() { 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!(mode & libc::S_IFREG == libc::S_IFREG); - assert!(mode & libc::S_IRWXU == libc::S_IRWXU); + assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); + assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); } #[test] -#[cfg(not(any(target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "redox")))] +#[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; @@ -337,7 +399,8 @@ fn test_mknodat() { let file_name = "test_file"; let tempdir = tempfile::tempdir().unwrap(); - let target_dir = Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); + let target_dir = + Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); mknodat( target_dir.as_raw_fd(), file_name, @@ -353,6 +416,6 @@ fn test_mknodat() { ) .unwrap() .st_mode as mode_t; - assert!(mode & libc::S_IFREG == libc::S_IFREG); - assert!(mode & libc::S_IRWXU == libc::S_IRWXU); + 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 index dc307e57b3..5f76e61a2d 100644 --- a/test/test_time.rs +++ b/test/test_time.rs @@ -11,12 +11,12 @@ use nix::time::{clock_gettime, ClockId}; #[cfg(not(target_os = "redox"))] #[test] pub fn test_clock_getres() { - assert!(nix::time::clock_getres(ClockId::CLOCK_REALTIME).is_ok()); + nix::time::clock_getres(ClockId::CLOCK_REALTIME).expect("assertion failed"); } #[test] pub fn test_clock_gettime() { - assert!(clock_gettime(ClockId::CLOCK_REALTIME).is_ok()); + clock_gettime(ClockId::CLOCK_REALTIME).expect("assertion failed"); } #[cfg(any( @@ -29,18 +29,18 @@ pub fn test_clock_gettime() { #[test] pub fn test_clock_getcpuclockid() { let clock_id = clock_getcpuclockid(nix::unistd::Pid::this()).unwrap(); - assert!(clock_gettime(clock_id).is_ok()); + clock_gettime(clock_id).unwrap(); } #[cfg(not(target_os = "redox"))] #[test] pub fn test_clock_id_res() { - assert!(ClockId::CLOCK_REALTIME.res().is_ok()); + ClockId::CLOCK_REALTIME.res().unwrap(); } #[test] pub fn test_clock_id_now() { - assert!(ClockId::CLOCK_REALTIME.now().is_ok()); + ClockId::CLOCK_REALTIME.now().unwrap(); } #[cfg(any( @@ -52,7 +52,8 @@ pub fn test_clock_id_now() { ))] #[test] pub fn test_clock_id_pid_cpu_clock_id() { - assert!(ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) + ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) .map(ClockId::now) - .is_ok()); + .unwrap() + .unwrap(); } diff --git a/test/test_timer.rs b/test/test_timer.rs new file mode 100644 index 0000000000..ffd146867b --- /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 index 61062ad229..10284e4127 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -1,15 +1,24 @@ -#[cfg(not(target_os = "redox"))] -use nix::fcntl::{self, open, readlink}; +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; -use nix::unistd::*; -use nix::unistd::ForkResult::*; #[cfg(not(target_os = "redox"))] -use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction}; -use nix::sys::wait::*; +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}; -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] -use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname}; -use nix::errno::Errno; +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; @@ -18,10 +27,13 @@ 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")))] +#[cfg(not(any( + target_os = "fuchsia", + target_os = "redox", + target_os = "haiku" +)))] use std::path::Path; use tempfile::{tempdir, tempfile}; -use libc::{_exit, mode_t, off_t}; use crate::*; @@ -31,7 +43,7 @@ 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") { + match unsafe { fork() }.expect("Error: Fork Failed") { Child => unsafe { _exit(0) }, Parent { child } => { // assert that child was created and pid > 0 @@ -40,16 +52,17 @@ fn test_fork_and_waitpid() { 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), + Ok(WaitStatus::Exited(pid_t, _)) => assert_eq!(pid_t, child), // panic, must never happen - s @ Ok(_) => panic!("Child exited {:?}, should never happen", s), + s @ Ok(_) => { + panic!("Child exited {s:?}, should never happen") + } // panic, waitpid should never fail - Err(s) => panic!("Error: waitpid returned Err({:?}", s) + Err(s) => panic!("Error: waitpid returned Err({s:?}"), } - - }, + } } } @@ -59,14 +72,14 @@ fn test_wait() { let _m = crate::FORK_MTX.lock(); // Safe: Child only calls `_exit`, which is signal-safe - match unsafe{fork()}.expect("Error: Fork Failed") { + 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))); - }, + } } } @@ -80,15 +93,15 @@ fn test_mkstemp() { Ok((fd, path)) => { close(fd).unwrap(); unlink(path.as_path()).unwrap(); - }, - Err(e) => panic!("mkstemp failed: {}", e) + } + Err(e) => panic!("mkstemp failed: {e}"), } } #[test] fn test_mkstemp_directory() { // mkstemp should fail if a directory is given - assert!(mkstemp(&env::temp_dir()).is_err()); + mkstemp(&env::temp_dir()).expect_err("assertion failed"); } #[test] @@ -101,20 +114,24 @@ fn test_mkfifo() { let stats = stat::stat(&mkfifo_fifo).unwrap(); let typ = stat::SFlag::from_bits_truncate(stats.st_mode as mode_t); - assert!(typ == SFlag::S_IFIFO); + assert_eq!(typ, SFlag::S_IFIFO); } #[test] #[cfg(not(target_os = "redox"))] fn test_mkfifo_directory() { // mkfifo should fail if a directory is given - assert!(mkfifo(&env::temp_dir(), Mode::S_IRUSR).is_err()); + 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 = "macos", + target_os = "ios", + target_os = "android", + target_os = "redox", + target_os = "haiku" +)))] fn test_mkfifoat_none() { let _m = crate::CWD_LOCK.read(); @@ -130,8 +147,12 @@ fn test_mkfifoat_none() { #[test] #[cfg(not(any( - target_os = "macos", target_os = "ios", - target_os = "android", target_os = "redox")))] + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "redox", + target_os = "haiku" +)))] fn test_mkfifoat() { use nix::fcntl; @@ -141,26 +162,36 @@ fn test_mkfifoat() { mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap(); - let stats = stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).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 = "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 - assert!(mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_err()); + 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 = "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(); @@ -168,7 +199,8 @@ fn test_mkfifoat_directory() { let mkfifoat_dir = "mkfifoat_dir"; stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap(); - assert!(mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_err()); + mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR) + .expect_err("assertion failed"); } #[test] @@ -201,7 +233,13 @@ mod linux_android { #[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")))] +#[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"); @@ -224,11 +262,14 @@ fn test_setgroups() { #[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 = "illumos")))] +#[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. @@ -259,7 +300,7 @@ fn test_initgroups() { } #[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] -macro_rules! execve_test_factory( +macro_rules! execve_test_factory ( ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => ( #[cfg(test)] @@ -359,36 +400,32 @@ macro_rules! execve_test_factory( ) ); -cfg_if!{ +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 = "freebsd", + } 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 = "dragonfly", - target_os = "illumos", + } 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 DragonFly, ios, macos, NetBSD, OpenBSD. - // - // Note for NetBSD and OpenBSD: although rust-lang/libc includes it - // (under unix/bsd/netbsdlike/) fexecve is not currently implemented on - // NetBSD nor on OpenBSD. + // 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!{ +cfg_if! { if #[cfg(target_os = "android")] { use nix::fcntl::AtFlags; execve_test_factory!(test_execveat_empty, execveat, @@ -421,10 +458,10 @@ fn test_fchdir() { let tmpdir_path = tmpdir.path().canonicalize().unwrap(); let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd(); - assert!(fchdir(tmpdir_fd).is_ok()); + fchdir(tmpdir_fd).expect("assertion failed"); assert_eq!(getcwd().unwrap(), tmpdir_path); - assert!(close(tmpdir_fd).is_ok()); + close(tmpdir_fd).expect("assertion failed"); } #[test] @@ -434,7 +471,7 @@ fn test_getcwd() { let tmpdir = tempdir().unwrap(); let tmpdir_path = tmpdir.path().canonicalize().unwrap(); - assert!(chdir(&tmpdir_path).is_ok()); + chdir(&tmpdir_path).expect("assertion failed"); assert_eq!(getcwd().unwrap(), tmpdir_path); // make path 500 chars longer so that buffer doubling in getcwd @@ -445,9 +482,10 @@ fn test_getcwd() { for _ in 0..5 { let newdir = "a".repeat(100); inner_tmp_dir.push(newdir); - assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok()); + mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU) + .expect("assertion failed"); } - assert!(chdir(inner_tmp_dir.as_path()).is_ok()); + chdir(inner_tmp_dir.as_path()).expect("assertion failed"); assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path()); } @@ -502,7 +540,8 @@ fn test_fchownat() { let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); - fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink).unwrap(); + fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink) + .unwrap(); chdir(tempdir.path()).unwrap(); fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap(); @@ -516,16 +555,13 @@ 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(); + lseek(tmp.as_raw_fd(), offset, Whence::SeekSet).unwrap(); let mut buf = [0u8; 7]; - crate::read_exact(tmpfd, &mut buf); + crate::read_exact(&tmp, &mut buf); assert_eq!(b"f123456", &buf); - - close(tmpfd).unwrap(); } #[cfg(any(target_os = "linux", target_os = "android"))] @@ -534,18 +570,15 @@ 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(); + lseek64(tmp.as_raw_fd(), 5, Whence::SeekSet).unwrap(); let mut buf = [0u8; 7]; - crate::read_exact(tmpfd, &mut buf); + crate::read_exact(&tmp, &mut buf); assert_eq!(b"f123456", &buf); - - close(tmpfd).unwrap(); } -cfg_if!{ +cfg_if! { if #[cfg(any(target_os = "android", target_os = "linux"))] { macro_rules! require_acct{ () => { @@ -559,7 +592,7 @@ cfg_if!{ skip_if_jailed!("test_acct"); } } - } else if #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] { + } else if #[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "haiku")))] { macro_rules! require_acct{ () => { skip_if_not_root!("test_acct"); @@ -569,11 +602,15 @@ cfg_if!{ } #[test] -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] fn test_acct() { - use tempfile::NamedTempFile; use std::process::Command; use std::{thread, time}; + use tempfile::NamedTempFile; let _m = crate::FORK_MTX.lock(); require_acct!(); @@ -584,9 +621,11 @@ fn test_acct() { acct::enable(path).unwrap(); loop { - Command::new("echo").arg("Hello world"); + Command::new("echo").arg("Hello world").output().unwrap(); let len = fs::metadata(path).unwrap().len(); - if len > 0 { break; } + if len > 0 { + break; + } thread::sleep(time::Duration::from_millis(10)); } acct::disable().unwrap(); @@ -597,21 +636,36 @@ 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); + 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); + 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); + assert!( + open_max + .expect("sysconf failed") + .expect("OPEN_MAX is unlimited") + > 0 + ); } #[cfg(target_os = "freebsd")] @@ -624,23 +678,34 @@ fn test_sysconf_unsupported() { assert!(open_max.expect("sysconf failed").is_none()) } - -#[cfg(any(target_os = "android", target_os = "linux"))] +#[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!(resuids.real.as_raw() != libc::uid_t::max_value()); - assert!(resuids.effective.as_raw() != libc::uid_t::max_value()); - assert!(resuids.saved.as_raw() != libc::uid_t::max_value()); + 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 = "linux"))] +#[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!(resgids.real.as_raw() != libc::gid_t::max_value()); - assert!(resgids.effective.as_raw() != libc::gid_t::max_value()); - assert!(resgids.saved.as_raw() != libc::gid_t::max_value()); + 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 @@ -648,25 +713,31 @@ fn test_getresgid() { #[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); + 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); + 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"))] +#[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}; @@ -701,15 +772,12 @@ 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() - }; + let mut file = File::create(&path).unwrap(); + const CONTENTS: &[u8] = b"12345678"; + file.write_all(CONTENTS).unwrap(); - ftruncate(tmpfd, 2).unwrap(); - close(tmpfd).unwrap(); + ftruncate(&file, 2).unwrap(); + drop(file); let metadata = fs::metadata(&path).unwrap(); assert_eq!(2, metadata.len()); @@ -721,8 +789,8 @@ static mut ALARM_CALLED: bool = false; // Used in `test_alarm`. #[cfg(not(target_os = "redox"))] -pub extern fn alarm_signal_handler(raw_signal: libc::c_int) { - assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {}", raw_signal); +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 }; } @@ -730,15 +798,16 @@ pub extern fn alarm_signal_handler(raw_signal: libc::c_int) { #[cfg(not(target_os = "redox"))] fn test_alarm() { use std::{ - time::{Duration, Instant,}, - thread + 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 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") @@ -755,7 +824,7 @@ fn test_alarm() { let starttime = Instant::now(); loop { thread::sleep(Duration::from_millis(100)); - if unsafe { ALARM_CALLED} { + if unsafe { ALARM_CALLED } { break; } if starttime.elapsed() > Duration::from_secs(3) { @@ -782,7 +851,7 @@ fn test_canceling_alarm() { } #[test] -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_symlinkat() { let _m = crate::CWD_LOCK.read(); @@ -810,7 +879,7 @@ fn test_symlinkat() { } #[test] -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_file() { let tempdir = tempdir().unwrap(); let oldfilename = "foo.txt"; @@ -820,18 +889,27 @@ fn test_linkat_file() { let newfilepath = tempdir.path().join(newfilename); // Create file - File::create(&oldfilepath).unwrap(); + File::create(oldfilepath).unwrap(); // Get file descriptor for base directory - let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + 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(); + linkat( + Some(dirfd), + oldfilename, + Some(dirfd), + newfilename, + LinkatFlags::SymlinkFollow, + ) + .unwrap(); assert!(newfilepath.exists()); } #[test] -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_olddirfd_none() { let _dr = crate::DirRestore::new(); @@ -844,19 +922,31 @@ fn test_linkat_olddirfd_none() { let newfilepath = tempdir_newfile.path().join(newfilename); // Create file - File::create(&oldfilepath).unwrap(); + 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(); + 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(); + linkat( + None, + oldfilename, + Some(dirfd), + newfilename, + LinkatFlags::SymlinkFollow, + ) + .unwrap(); assert!(newfilepath.exists()); } #[test] -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_newdirfd_none() { let _dr = crate::DirRestore::new(); @@ -869,19 +959,36 @@ fn test_linkat_newdirfd_none() { let newfilepath = tempdir_newfile.path().join(newfilename); // Create file - File::create(&oldfilepath).unwrap(); + 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(); + 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(); + 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")))] +#[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(); @@ -902,23 +1009,29 @@ fn test_linkat_no_follow_symlink() { symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); // Get file descriptor for base directory - let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + 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(); + 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(), + readlink(&newfilepath).unwrap().to_str().unwrap(), oldfilepath.to_str().unwrap() ); } #[test] -#[cfg(not(target_os = "redox"))] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_follow_symlink() { let _m = crate::CWD_LOCK.read(); @@ -939,15 +1052,26 @@ fn test_linkat_follow_symlink() { symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); // Get file descriptor for base directory - let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + 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(); + 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), + assert_eq!( + (stat::SFlag::from_bits_truncate(newfilestat.st_mode as mode_t) + & SFlag::S_IFMT), SFlag::S_IFREG ); @@ -963,15 +1087,18 @@ fn test_unlinkat_dir_noremovedir() { let dirpath = tempdir.path().join(dirname); // Create dir - DirBuilder::new().recursive(true).create(&dirpath).unwrap(); + 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(); + 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(); + 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"))] @@ -984,12 +1111,14 @@ fn test_unlinkat_dir_removedir() { 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(); + 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"))] @@ -1002,34 +1131,47 @@ fn test_unlinkat_file() { File::create(&filepath).unwrap(); // Get file descriptor for base directory - let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + 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); + 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 path = tempdir.path().join("does_exist.txt"); let _file = File::create(path.clone()).unwrap(); - assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok()); + 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 - let nobody = User::from_name("nobody").unwrap().unwrap(); + #[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(); } @@ -1048,8 +1190,7 @@ fn test_setfsuid() { // create a temporary file with permissions '-rw-r-----' let file = tempfile::NamedTempFile::new_in("/var/tmp").unwrap(); let temp_path = file.into_temp_path(); - dbg!(&temp_path); - let temp_path_2 = (&temp_path).to_path_buf(); + let temp_path_2 = temp_path.to_path_buf(); let mut permissions = fs::metadata(&temp_path).unwrap().permissions(); permissions.set_mode(0o640); @@ -1059,8 +1200,8 @@ fn test_setfsuid() { let fuid = setfsuid(nobody.uid); // trying to open the temporary file should fail with EACCES let res = fs::File::open(&temp_path); - assert!(res.is_err()); - assert_eq!(res.err().unwrap().kind(), io::ErrorKind::PermissionDenied); + 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)); @@ -1074,7 +1215,11 @@ fn test_setfsuid() { } #[test] -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +#[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); @@ -1085,11 +1230,8 @@ fn test_ttyname() { 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"); + 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"); @@ -1105,7 +1247,11 @@ fn test_ttyname_not_pty() { } #[test] -#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + target_os = "haiku" +)))] fn test_ttyname_invalid_fd() { assert_eq!(ttyname(-1), Err(Errno::EBADF)); } @@ -1146,5 +1292,102 @@ fn test_getpeereid() { ))] fn test_getpeereid_invalid_fd() { // getpeereid is not POSIX, so error codes are inconsistent between different Unices. - assert!(getpeereid(-1).is_err()); + 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"); }