From 683c1defaa20703a0df5d5392f001411a43d3b7b Mon Sep 17 00:00:00 2001 From: ljy9810 Date: Tue, 14 Oct 2025 20:15:12 +0800 Subject: [PATCH] =?UTF-8?q?nix=E5=8D=87=E7=BA=A7=E5=88=B00.30.1=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ljy9810 --- .cirrus.yml | 267 +- .github/PULL_REQUEST_TEMPLATE.md | 7 + .github/actions/build/action.yml | 72 + .../actions/check_new_changelog/action.yml | 46 + .github/actions/test/action.yml | 32 + .github/workflows/check_new_changelog.yml | 18 + .github/workflows/ci.yml | 441 +++ BUILD.gn | 10 +- CHANGELOG.md | 552 +++- CONTRIBUTING.md | 63 +- CONVENTIONS.md | 76 +- Cargo.toml | 53 +- Cross.toml | 3 + README.OpenSource | 7 +- README.md | 115 +- RELEASE_PROCEDURE.md | 61 +- bors.toml | 54 - build.rs | 35 + changelog/.keep | 5 + examples/getifaddrs.rs | 55 + justfile | 8 + release.toml | 4 - src/dir.rs | 131 +- src/env.rs | 7 +- src/errno.rs | 1760 ++++++++---- src/fcntl.rs | 1273 +++++++-- src/features.rs | 31 +- src/ifaddrs.rs | 40 +- src/kmod.rs | 12 +- src/lib.rs | 131 +- src/macros.rs | 13 +- src/mount/apple.rs | 111 + src/mount/{bsd.rs => bsd_without_apple.rs} | 56 +- src/mount/linux.rs | 71 +- src/mount/mod.rs | 32 +- src/mqueue.rs | 131 +- src/net/if_.rs | 351 +-- src/poll.rs | 130 +- src/poll_timeout.rs | 224 ++ src/pty.rs | 156 +- src/sched.rs | 44 +- src/spawn.rs | 431 +++ src/sys/aio.rs | 260 +- src/sys/epoll.rs | 135 +- src/sys/event.rs | 390 ++- src/sys/eventfd.rs | 115 +- src/sys/fanotify.rs | 446 +++ src/sys/inotify.rs | 57 +- src/sys/ioctl/bsd.rs | 4 +- src/sys/ioctl/linux.rs | 95 +- src/sys/ioctl/mod.rs | 114 +- src/sys/memfd.rs | 90 +- src/sys/mman.rs | 384 +-- src/sys/mod.rs | 101 +- src/sys/personality.rs | 6 +- src/sys/prctl.rs | 228 ++ src/sys/ptrace/bsd.rs | 42 +- src/sys/ptrace/linux.rs | 369 ++- src/sys/ptrace/mod.rs | 20 +- src/sys/quota.rs | 9 +- src/sys/reboot.rs | 173 +- src/sys/resource.rs | 123 +- src/sys/select.rs | 266 +- src/sys/sendfile.rs | 178 +- src/sys/signal.rs | 806 +++--- src/sys/signalfd.rs | 109 +- src/sys/socket/addr.rs | 1660 +++-------- src/sys/socket/mod.rs | 1445 +++++----- src/sys/socket/sockopt.rs | 1125 ++++++-- src/sys/stat.rs | 154 +- src/sys/statfs.rs | 416 +-- src/sys/statvfs.rs | 55 +- src/sys/sysinfo.rs | 2 +- src/sys/termios.rs | 626 ++--- src/sys/time.rs | 211 +- src/sys/timer.rs | 2 +- src/sys/timerfd.rs | 68 +- src/sys/uio.rs | 143 +- src/sys/utsname.rs | 23 +- src/sys/wait.rs | 71 +- src/syslog.rs | 293 ++ src/time.rs | 241 +- src/unistd.rs | 2455 +++++++++++------ test/common/mod.rs | 14 +- test/mount/mod.rs | 6 + test/mount/test_mount.rs | 189 ++ test/mount/test_mount_apple.rs | 8 + test/{ => mount}/test_nmount.rs | 0 test/sys/mod.rs | 70 +- test/sys/test_aio.rs | 247 +- test/sys/test_aio_drop.rs | 8 +- test/sys/test_epoll.rs | 2 + test/sys/test_event.rs | 41 + test/sys/test_fanotify.rs | 220 ++ test/sys/test_ioctl.rs | 33 +- test/sys/test_memfd.rs | 20 + test/sys/test_mman.rs | 132 +- test/sys/test_prctl.rs | 172 ++ test/sys/test_pthread.rs | 14 +- test/sys/test_ptrace.rs | 157 +- test/{ => sys}/test_resource.rs | 34 +- test/sys/test_select.rs | 250 +- test/sys/test_signal.rs | 333 ++- test/sys/test_signalfd.rs | 65 +- test/sys/test_socket.rs | 1368 ++++++--- test/sys/test_sockopt.rs | 1065 ++++++- test/sys/test_stat.rs | 493 +++- test/sys/test_statfs.rs | 98 + test/sys/test_statvfs.rs | 13 + test/sys/test_termios.rs | 71 +- test/sys/test_time.rs | 91 + test/{ => sys}/test_timer.rs | 0 test/sys/test_uio.rs | 61 +- test/sys/test_utsname.rs | 17 + test/sys/test_wait.rs | 16 +- test/test.rs | 101 +- test/test_dir.rs | 11 +- test/test_errno.rs | 16 + test/test_fcntl.rs | 535 +++- test/test_mount.rs | 271 -- test/test_mq.rs | 71 +- test/test_net.rs | 21 +- test/test_poll.rs | 33 +- test/test_pty.rs | 121 +- test/test_ptymaster_drop.rs | 20 - test/test_sendfile.rs | 107 +- test/test_spawn.rs | 193 ++ test/test_stat.rs | 421 --- test/test_syslog.rs | 38 + test/test_time.rs | 49 +- test/test_unistd.rs | 459 +-- towncrier.toml | 27 + 132 files changed, 18532 insertions(+), 9899 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/actions/build/action.yml create mode 100644 .github/actions/check_new_changelog/action.yml create mode 100644 .github/actions/test/action.yml create mode 100644 .github/workflows/check_new_changelog.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 bors.toml create mode 100644 build.rs create mode 100644 changelog/.keep create mode 100644 examples/getifaddrs.rs create mode 100644 justfile delete mode 100644 release.toml create mode 100644 src/mount/apple.rs rename src/mount/{bsd.rs => bsd_without_apple.rs} (87%) create mode 100644 src/poll_timeout.rs create mode 100644 src/spawn.rs create mode 100644 src/sys/fanotify.rs create mode 100644 src/sys/prctl.rs create mode 100644 src/syslog.rs create mode 100644 test/mount/mod.rs create mode 100644 test/mount/test_mount.rs create mode 100644 test/mount/test_mount_apple.rs rename test/{ => mount}/test_nmount.rs (100%) create mode 100644 test/sys/test_event.rs create mode 100644 test/sys/test_fanotify.rs create mode 100644 test/sys/test_memfd.rs create mode 100644 test/sys/test_prctl.rs rename test/{ => sys}/test_resource.rs (55%) create mode 100644 test/sys/test_statfs.rs create mode 100644 test/sys/test_statvfs.rs create mode 100644 test/sys/test_time.rs rename test/{ => sys}/test_timer.rs (100%) create mode 100644 test/sys/test_utsname.rs create mode 100644 test/test_errno.rs delete mode 100644 test/test_mount.rs delete mode 100644 test/test_ptymaster_drop.rs create mode 100644 test/test_spawn.rs delete mode 100644 test/test_stat.rs create mode 100644 test/test_syslog.rs create mode 100644 towncrier.toml diff --git a/.cirrus.yml b/.cirrus.yml index 5fd77fe2..4fb70d7b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -9,7 +9,7 @@ env: RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings TOOL: cargo - MSRV: 1.56.1 + MSRV: 1.69.0 ZFLAGS: # Tests that don't require executing the build binaries @@ -18,9 +18,9 @@ build: &BUILD - . $HOME/.cargo/env || true - $TOOL -Vv - rustc -Vv - - $TOOL $BUILD $ZFLAGS --target $TARGET --all-targets - - $TOOL doc $ZFLAGS --no-deps --target $TARGET - - $TOOL clippy $ZFLAGS --target $TARGET --all-targets -- $CLIPPYFLAGS + - $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 @@ -40,12 +40,10 @@ task: env: TARGET: x86_64-unknown-freebsd matrix: - - name: FreeBSD 12 amd64 & i686 - freebsd_instance: - image: freebsd-12-3-release-amd64 - name: FreeBSD 14 amd64 & i686 freebsd_instance: - image_family: freebsd-14-0-snap + image: freebsd-14-1-release-amd64-ufs + cpu: 1 # Enable tests that would fail on FreeBSD 12 RUSTFLAGS: --cfg fbsd14 -D warnings RUSTDOCFLAGS: --cfg fbsd14 @@ -56,262 +54,13 @@ task: - . $HOME/.cargo/env - rustup target add i686-unknown-freebsd - rustup component add clippy - - cp Cargo.lock.msrv Cargo.lock << : *TEST i386_test_script: - . $HOME/.cargo/env - - cargo build --target i686-unknown-freebsd - - cargo doc --no-deps --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 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 macOS aarch64 in a full VM -task: - name: macOS aarch64 - env: - 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 $MSRV - - . $HOME/.cargo/env - - rustup component add clippy - - cp Cargo.lock.msrv Cargo.lock - << : *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: - 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 - matrix: - - name: Linux arm gnueabi - env: - TARGET: arm-unknown-linux-gnueabi - - name: Linux armv7 gnueabihf - env: - TARGET: armv7-unknown-linux-gnueabihf - - name: Linux i686 - env: - TARGET: i686-unknown-linux-gnu - - name: Linux i686 musl - env: - TARGET: i686-unknown-linux-musl - - name: Linux MIPS - env: - TARGET: mips-unknown-linux-gnu - - name: Linux MIPS64 - env: - TARGET: mips64-unknown-linux-gnuabi64 - - name: Linux MIPS64 el - env: - TARGET: mips64el-unknown-linux-gnuabi64 - - name: Linux mipsel - env: - TARGET: mipsel-unknown-linux-gnu - - name: Linux powerpc64le - env: - TARGET: powerpc64le-unknown-linux-gnu - compute_engine_instance: - image_project: cirrus-images - image: family/docker-builder - platform: linux - cpu: 1 # Since QEMU will only use 1 thread - memory: 4G - 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 $MSRV - - . $HOME/.cargo/env - - cargo install cross --version 0.2.1 --locked # cross 0.2.2 bumped the MSRV to 1.58.1 - - cp Cargo.lock.msrv Cargo.lock - << : *TEST - before_cache_script: rm -rf $CARGO_HOME/registry/index - -# Tasks for Linux native builds -task: - matrix: - - name: Linux aarch64 - arm_container: - image: rust:1.56 - env: - TARGET: aarch64-unknown-linux-gnu - - name: Linux x86_64 - container: - image: rust:1.56 - env: - TARGET: x86_64-unknown-linux-gnu - - name: Linux x86_64 musl - container: - image: rust:1.56 - 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 - -task: - name: Rust Stable - container: - image: rust:latest - env: - TARGET: x86_64-unknown-linux-gnu - setup_script: - - rustup component add clippy - << : *TEST - before_cache_script: rm -rf $CARGO_HOME/registry/index - -# Tasks for cross-compiling, but no testing -task: - container: - image: rust:1.56 - 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. - - name: Android aarch64 - env: - TARGET: aarch64-linux-android - - name: Android arm - env: - TARGET: arm-linux-androideabi - - name: Android armv7 - env: - TARGET: armv7-linux-androideabi - - name: Android i686 - env: - TARGET: i686-linux-android - - name: Android x86_64 - env: - TARGET: x86_64-linux-android - - name: Linux arm-musleabi - env: - TARGET: arm-unknown-linux-musleabi - - name: Fuchsia x86_64 - env: - TARGET: x86_64-fuchsia - - name: Illumos - env: - TARGET: x86_64-unknown-illumos - # 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 - - name: iOS x86_64 - env: - # cargo hack tries to invoke the iphonesimulator SDK for iOS - NOHACK: 1 - TARGET: x86_64-apple-ios - # 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 - env: - TARGET: powerpc64-unknown-linux-gnu - - name: Linux s390x - env: - TARGET: s390x-unknown-linux-gnu - - name: Linux x32 - env: - TARGET: x86_64-unknown-linux-gnux32 - - name: macOS x86_64 - env: - TARGET: x86_64-apple-darwin - - name: NetBSD x86_64 - env: - TARGET: x86_64-unknown-netbsd - setup_script: - - rustup target add $TARGET - - rustup component add clippy - - cp Cargo.lock.msrv Cargo.lock - << : *BUILD - before_cache_script: rm -rf $CARGO_HOME/registry/index - -task: - container: - # Redox's MSRV policy is unclear. Until they define it, use nightly. - image: rustlang/rust:nightly - env: - BUILD: check - name: Redox x86_64 - env: - HOST: x86_64-unknown-linux-gnu - TARGET: x86_64-unknown-redox - CLIPPYFLAGS: -D warnings - setup_script: - - rustup target add $TARGET - - rustup component add clippy - << : *BUILD - before_cache_script: rm -rf $CARGO_HOME/registry/index - -## Rust Tier 3 targets can't use Rustup -task: - container: - image: rustlang/rust:nightly - env: - BUILD: check - HOST: x86_64-unknown-linux-gnu - ZFLAGS: -Zbuild-std - CLIPPYFLAGS: -D warnings - matrix: - - name: DragonFly BSD x86_64 - env: - TARGET: x86_64-unknown-dragonfly - - name: OpenBSD x86_64 - env: - TARGET: x86_64-unknown-openbsd - - name: Linux armv7 uclibceabihf - env: - TARGET: armv7-unknown-linux-uclibceabihf - - name: Haiku x86_64 - env: - TARGET: x86_64-unknown-haiku - setup_script: - - rustup component add rust-src - << : *BUILD - before_cache_script: rm -rf $CARGO_HOME/registry/index - -# Test that we can build with the lowest version of all dependencies. -# "cargo test" doesn't work because some of our dev-dependencies, like -# rand, can't build with their own minimal dependencies. -task: - name: Minver - env: - HOST: x86_64-unknown-linux-gnu - container: - image: rustlang/rust:nightly - setup_script: - - cargo update -Zminimal-versions - 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 - setup_script: rustup component add rustfmt - test_script: cargo fmt --all -- --check **/*.rs diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..ba373546 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +## What does this PR do + +## Checklist: + +- [ ] I have read `CONTRIBUTING.md` +- [ ] I have written necessary tests and rustdoc comments +- [ ] A change log has been added if this PR modifies nix's API diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 00000000..9cf2fbff --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,72 @@ +name: 'Build' +description: 'Build nix' +inputs: + # This is required + TARGET: + required: true + + BUILD: + required: false + default: build + + CLIPPYFLAGS: + required: false + default: -D warnings -A unknown-lints + + RUSTFLAGS: + required: false + default: -D warnings -A unknown-lints + + RUSTDOCFLAGS: + required: false + default: -D warnings + + TOOL: + description: 'Tool used to involve the BUILD command, can be cargo or cross' + required: false + default: cargo + + ZFLAGS: + required: false + default: + + NOHACK: + description: "whether to run cargo hack" + required: false + default: false + +runs: + using: "composite" + steps: + - name: set up Rust env + shell: bash + run: | + echo "RUSTFLAGS=${{ inputs.RUSTFLAGS }}" >> $GITHUB_ENV + echo "RUSTDOCFLAGS=${{ inputs.RUSTDOCFLAGS }}" >> $GITHUB_ENV + + - name: debug info + shell: bash + run: | + ${{ inputs.TOOL }} -Vv + rustc -Vv + + - name: build + shell: bash + run: ${{ inputs.TOOL }} ${{ inputs.BUILD }} ${{ inputs.ZFLAGS }} --target ${{ inputs.TARGET }} --all-targets --all-features + + - name: doc + shell: bash + run: ${{ inputs.TOOL }} doc ${{ inputs.ZFLAGS }} --no-deps --target ${{ inputs.TARGET }} --all-features + + - name: clippy + shell: bash + run: ${{ inputs.TOOL}} clippy ${{ inputs.ZFLAGS }} --target ${{ inputs.TARGET }} --all-targets --all-features -- ${{ inputs.CLIPPYFLAGS }} + + - name: Set up cargo-hack + if: inputs.NOHACK == 'false' + uses: taiki-e/install-action@cargo-hack + + - name: run cargo hack + shell: bash + if: inputs.NOHACK == 'false' + run: ${{ inputs.TOOL }} hack ${{ inputs.ZFLAGS }} check --target ${{ inputs.TARGET }} --each-feature diff --git a/.github/actions/check_new_changelog/action.yml b/.github/actions/check_new_changelog/action.yml new file mode 100644 index 00000000..c366654a --- /dev/null +++ b/.github/actions/check_new_changelog/action.yml @@ -0,0 +1,46 @@ +name: 'Check new CHANGELOG' +description: 'Check new CHANGELOG kind and PR number' + +runs: + using: "composite" + steps: + - name: Get newly added CHANGELOGs + id: new-changelogs + uses: tj-actions/changed-files@v44 + with: + # Only checek the files under the `changelog` directory + files: changelog/** + + - name: Check them + shell: bash + if: steps.new-changelogs.outputs.added_files_count != 0 + env: + NEW_CHANGELOGS: ${{ steps.new-changelogs.outputs.added_files }} + PR_NUMBER: ${{ github.event.number }} + run: | + # `cl` will be something like "changelog/1.added.md" + for cl in ${NEW_CHANGELOGS}; do + # Trim the directory name + prefix="changelog/"; trimmed_cl=${cl/#$prefix}; cl="${trimmed_cl}"; + + # parse it + IFS='.' read id kind file_extension <<< "${cl}" + + # Check the kind field + if [ "$kind" != "added" ] && [ "$kind" != "changed" ] && [ "$kind" != "fixed" ] && [ "$kind" != "removed" ]; then + echo "Invalid CHANGELOG kind [${kind}] from [${cl}], available options are [added, changed, fixed, removed]"; + exit 1; + fi + + # Check the file extension + if [ "$file_extension" != "md" ]; then + echo "Invalid file extension [${file_extension}] from [${cl}], it should be [md]"; + exit 1; + fi + + # Check PR number + if [ "$id" != "$PR_NUMBER" ]; then + echo "Mismatched PR number [${id}] from [${cl}], it should be ${PR_NUMBER}"; + exit 1; + fi + done diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 00000000..31a6fd7a --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,32 @@ +name: 'Test' +description: 'Test nix' +inputs: + # This is required + TARGET: + required: true + + SUDO: + description: 'Set it to an empty string to run the tests as the current user, leave it with the default value to test with "sudo"' + required: false + default: sudo --preserve-env=HOME + + TOOL: + description: 'Tool used to involve the test command, can be cargo or cross' + required: false + default: cargo + + RUSTFLAGS: + required: false + default: -D warnings -A unknown-lints + +runs: + using: "composite" + steps: + - name: set up Rust env + shell: bash + run: | + echo "RUSTFLAGS=${{ inputs.RUSTFLAGS }}" >> $GITHUB_ENV + + - name: test + shell: bash + run: ${{ inputs.SUDO }} $(which ${{ inputs.TOOL }}) test --target ${{ inputs.TARGET }} diff --git a/.github/workflows/check_new_changelog.yml b/.github/workflows/check_new_changelog.yml new file mode 100644 index 00000000..911a9f4e --- /dev/null +++ b/.github/workflows/check_new_changelog.yml @@ -0,0 +1,18 @@ +name: Check new CHANGELOGs + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + check_new_changelog: + runs-on: ubuntu-24.04 + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: check new CHANGELOG + uses: ./.github/actions/check_new_changelog diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..8b91e790 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,441 @@ +name: CI + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - master + merge_group: + types: [checks_requested] + +permissions: + contents: read + +env: + MSRV: 1.69.0 + # Rust's Loongarch support merged in 1.71.0 + MSRV_LOONGARCH: 1.71.0 + # Minimal Rust version to support all 3 official OpenHarmony targets as tier2 + MSRV_OHOS: 1.78.0 + RUSTFLAGS: -Dwarnings + +jobs: + macos: + runs-on: macos-13 + env: + TARGET: x86_64-apple-darwin + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: '${{ env.MSRV }}' + components: clippy + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ env.TARGET }}' + + - name: test + uses: ./.github/actions/test + with: + TARGET: '${{ env.TARGET }}' + + - name: before_cache_script + run: sudo rm -rf $CARGO_HOME/registry/index + + macos-aarch64: + runs-on: macos-latest + env: + TARGET: aarch64-apple-darwin + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: '${{ env.MSRV }}' + components: clippy + + - name: build + uses: ./.github/actions/build + with: + TARGET: "${{ env.TARGET }}" + + - name: test + uses: ./.github/actions/test + with: + TARGET: "${{ env.TARGET }}" + + - name: before_cache_script + run: sudo rm -rf $CARGO_HOME/registry/index + + # Use cross for QEMU-based testing + # cross needs to execute Docker, GitHub Action already has it installed + cross: + runs-on: ubuntu-24.04 + needs: [rustfmt, minver, macos, x86_64_linux_native_builds, rust_stable] + strategy: + fail-fast: false + matrix: + target: [ + arm-unknown-linux-gnueabi, + armv7-unknown-linux-gnueabihf, + i686-unknown-linux-gnu, + i686-unknown-linux-musl, + + # Disable MIPS CIs, see https://github.com/nix-rust/nix/issues/2593 + # for detailed info. + # + # mips-unknown-linux-gnu, + # mips64-unknown-linux-gnuabi64, + # mips64el-unknown-linux-gnuabi64, + # mipsel-unknown-linux-gnu, + + powerpc64le-unknown-linux-gnu, + loongarch64-unknown-linux-gnu, + ] + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@master + with: + # Use a newer version rustc if the target is Loongarch, remove this workaround after MSRV is newer than 1.71.0 + toolchain: "${{ matrix.target == 'loongarch64-unknown-linux-gnu' && env.MSRV_LOONGARCH || env.MSRV }}" + components: clippy + + # cross relies on docker or podman, GitHub Acton already has it installed. + - name: Set up cross + uses: taiki-e/install-action@v2 + with: + tool: cross@0.2.5 + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ matrix.target }}' + TOOL: cross + RUSTFLAGS: --cfg qemu -D warnings + + - name: test + uses: ./.github/actions/test + with: + TARGET: '${{ matrix.target }}' + SUDO: "" + TOOL: cross + RUSTFLAGS: --cfg qemu -D warnings + + - name: before_cache_script + run: rm -rf $CARGO_HOME/registry/index + + + + # Tasks for x86_64 Linux native builds + x86_64_linux_native_builds: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: [ + x86_64-unknown-linux-gnu, + x86_64-unknown-linux-musl, + ] + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: '${{ env.MSRV }}' + components: clippy + + - name: install targets + run: rustup target add ${{ matrix.target }} + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ matrix.TARGET }}' + + - name: test + uses: ./.github/actions/test + with: + TARGET: '${{ matrix.TARGET }}' + + - name: before_cache_script + run: sudo rm -rf $CARGO_HOME/registry/index; + + # Tasks for aarch64 Linux native builds + aarch64_linux_native_builds: + runs-on: ubuntu-24.04-arm + strategy: + fail-fast: false + matrix: + target: [ + aarch64-unknown-linux-gnu, + aarch64-unknown-linux-musl, + ] + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: '${{ env.MSRV }}' + components: clippy + + - name: install targets + run: rustup target add ${{ matrix.target }} + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ matrix.TARGET }}' + + - name: test + uses: ./.github/actions/test + with: + TARGET: '${{ matrix.TARGET }}' + + - name: before_cache_script + run: sudo rm -rf $CARGO_HOME/registry/index; + + rust_stable: + runs-on: ubuntu-latest + env: + TARGET: x86_64-unknown-linux-gnu + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ env.TARGET }}' + + - name: test + uses: ./.github/actions/test + with: + TARGET: '${{ env.TARGET }}' + + - name: before_cache_script + run: sudo rm -rf $CARGO_HOME/registry/index + + + + # Tasks for cross-compiling, but no testing + cross_compiling: + runs-on: ubuntu-latest + needs: [rustfmt, minver, macos, x86_64_linux_native_builds, rust_stable] + env: + BUILD: check + strategy: + fail-fast: false + matrix: + include: + # Cross claims to support Android, but when it tries to run Nix's tests it + # reports undefined symbol references. + - target: aarch64-linux-android + - target: arm-linux-androideabi + - target: armv7-linux-androideabi + - target: i686-linux-android + - target: x86_64-linux-android + - target: arm-unknown-linux-musleabi + - target: x86_64-unknown-fuchsia + - target: x86_64-unknown-illumos + # Cross claims to support running tests on iOS, but it actually doesn't. + # https://github.com/rust-embedded/cross/issues/535 + - target: aarch64-apple-ios + # cargo hack tries to invoke the iphonesimulator SDK for iOS + NOHACK: true + # Cross claims to support Linux powerpc64, but it really doesn't. + # https://github.com/rust-embedded/cross/issues/441 + - target: powerpc64-unknown-linux-gnu + - target: s390x-unknown-linux-gnu + - target: x86_64-unknown-linux-gnux32 + - target: x86_64-unknown-netbsd + - target: aarch64-unknown-linux-ohos + - target: armv7-unknown-linux-ohos + - target: x86_64-unknown-linux-ohos + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@master + with: + # Use a newer version rustc if it is OpenHarmony, remove this workaround after MSRV is newer than 1.78.0 + toolchain: "${{ contains(matrix.target, 'ohos') && env.MSRV_OHOS || env.MSRV }}" + components: clippy + + - name: install targets + run: rustup target add ${{ matrix.target }} + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ matrix.target }}' + BUILD: '${{ env.BUILD }}' + NOHACK: '${{ matrix.NOHACK }}' + + - name: before_cache_script + run: rm -rf $CARGO_HOME/registry/index + + + redox: + runs-on: ubuntu-latest + needs: [rustfmt, minver, macos, x86_64_linux_native_builds, rust_stable] + env: + TARGET: x86_64-unknown-redox + CLIPPYFLAGS: -D warnings + BUILD: check + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + # Redox's MSRV policy is unclear. Until they define it, use nightly. + uses: dtolnay/rust-toolchain@nightly + with: + components: clippy + + - name: install targets + run: rustup target add ${{ env.TARGET }} + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ env.TARGET }}' + BUILD: '${{ env.BUILD }}' + CLIPPYFLAGS: '${{ env.CLIPPYFLAGS }}' + + - name: before_cache_script + run: rm -rf $CARGO_HOME/registry/index + + + + # Rust Tier 3 targets can't use Rustup + tier3: + runs-on: ubuntu-latest + env: + BUILD: check + ZFLAGS: -Zbuild-std + CLIPPYFLAGS: -D warnings + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-dragonfly + - target: x86_64-unknown-openbsd + - target: x86_64-unknown-haiku + - target: armv7-unknown-linux-uclibceabihf + # Disable Hurd due to + # 1. https://github.com/rust-lang/libc/issues/4421 + # 2. https://github.com/nix-rust/nix/pull/2635#issuecomment-2842062528 + # + # We can bring it back when 1 gets fixed and it is applied to the std lib + # - target: i686-unknown-hurd-gnu + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@nightly + with: + components: clippy + + - name: install src + run: rustup component add rust-src + + - name: build + uses: ./.github/actions/build + with: + TARGET: '${{ matrix.target }}' + BUILD: '${{ env.BUILD }}' + ZFLAGS: '${{ env.ZFLAGS }}' + CLIPPYFLAGS: '${{ env.CLIPPYFLAGS }}' + + - name: before_cache_script + run: rm -rf $CARGO_HOME/registry/index + + solaris: + name: solaris (x86_64-pc-solaris) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: build and test + uses: vmactions/solaris-vm@v1 + with: + release: "11.4-gcc" + usesh: true + mem: 4096 + copyback: false + prepare: | + source <(curl -s https://raw.githubusercontent.com/psumbera/solaris-rust/refs/heads/main/sh.rust-web-install) + echo "~~~~ rustc --version ~~~~" + rustc --version + echo "~~~~ Solaris-version ~~~~" + uname -a + run: | + export PATH=$HOME/.rust_solaris/bin:$PATH + cargo build --target x86_64-pc-solaris --all-targets --all-features && sudo cargo test + + # Test that we can build with the lowest version of all dependencies. + # "cargo test" doesn't work because some of our dev-dependencies, like + # rand, can't build with their own minimal dependencies. + minver: + runs-on: ubuntu-latest + env: + TARGET: x86_64-unknown-linux-gnu + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup Rust + uses: dtolnay/rust-toolchain@nightly + + - name: setup + run: cargo update -Zdirect-minimal-versions + + - name: check + run: cargo check + + - name: before_cache_script + run: rm -rf $CARGO_HOME/registry/index + + # Tasks that checks if the code is formatted right using `cargo fmt` tool + rustfmt: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Check format + run: cargo fmt --all -- --check **/*.rs + + - name: before_cache_script + run: rm -rf $CARGO_HOME/registry/index + diff --git a/BUILD.gn b/BUILD.gn index d248833b..6371f2ed 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -19,8 +19,8 @@ ohos_cargo_crate("lib") { crate_root = "src/lib.rs" sources = ["src/lib.rs"] - edition = "2018" - cargo_pkg_version = "0.26.4" + edition = "2021" + cargo_pkg_version = "0.30.1" cargo_pkg_authors = "The nix-rust Project Developers" cargo_pkg_name = "nix" cargo_pkg_description = "Rust friendly bindings to *nix APIs" @@ -30,14 +30,15 @@ ohos_cargo_crate("lib") { "//third_party/rust/crates/libc:lib", "//third_party/rust/crates/memoffset:lib", "//third_party/rust/crates/pin-utils:lib", - "//third_party/rust/crates/static-assertions-rs:lib", ] features = [ "acct", + "aio", "default", "dir", "env", "event", + "fanotify", "feature", "fs", "hostname", @@ -47,9 +48,9 @@ ohos_cargo_crate("lib") { "memoffset", "mman", "mount", + "mqueue", "net", "personality", - "pin-utils", "poll", "process", "pthread", @@ -60,6 +61,7 @@ ohos_cargo_crate("lib") { "sched", "signal", "socket", + "syslog", "term", "time", "ucontext", diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc4c0c0..4e320bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,549 @@ -# Change Log - All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## [0.26.4] - 2023-08-28 +# Change Log + +## [0.30.1] - 2025-05-04 ### Fixed -- Fixed an unintended API change in release 0.26.3, due to the upgrade of the - bitflags dependency. - ([#2117](https://github.com/nix-rust/nix/pull/2117)) +- doc.rs build + ([#2634](https://github.com/nix-rust/nix/pull/2634)) + + +## [0.30.0] - 2025-04-29 + + +### Added + +- Add socket option `IPV6_PKTINFO` for BSDs/Linux/Android, also + `IPV6_RECVPKTINFO` for DragonFlyBSD + ([#2113](https://github.com/nix-rust/nix/pull/2113)) +- Add `fcntl`'s `F_PREALLOCATE` constant for Apple targets. + ([#2393](https://github.com/nix-rust/nix/pull/2393)) +- Improve support for extracting the TTL / Hop Limit from incoming packets + and support for DSCP (ToS / Traffic Class). + ([#2425](https://github.com/nix-rust/nix/pull/2425)) +- Add socket option IP_TOS (nix::sys::socket::sockopt::IpTos) IPV6_TCLASS + (nix::sys::socket::sockopt::Ipv6TClass) on Android/FreeBSD + ([#2464](https://github.com/nix-rust/nix/pull/2464)) +- Add `SeekData` and `SeekHole` to `Whence` for hurd and apple targets + ([#2473](https://github.com/nix-rust/nix/pull/2473)) +- Add `From` trait implementation between `SocketAddr` and `Sockaddr`, + `Sockaddr6` ([#2474](https://github.com/nix-rust/nix/pull/2474)) +- Added wrappers for `posix_spawn` API + ([#2475](https://github.com/nix-rust/nix/pull/2475)) +- Add the support for Emscripten. + ([#2477](https://github.com/nix-rust/nix/pull/2477)) +- Add fcntl constant `F_RDADVISE` for Apple target + ([#2480](https://github.com/nix-rust/nix/pull/2480)) +- Add fcntl constant `F_RDAHEAD` for Apple target + ([#2482](https://github.com/nix-rust/nix/pull/2482)) +- Add `F_LOG2PHYS` and `F_LOG2PHYS_EXT` for Apple target + ([#2483](https://github.com/nix-rust/nix/pull/2483)) +- `MAP_SHARED_VALIDATE` was added for all linux targets. & `MAP_SYNC` was added + for linux with the exclusion of mips architecures, and uclibc + ([#2499](https://github.com/nix-rust/nix/pull/2499)) +- Add `getregs()`/`getregset()`/`setregset()` for Linux/musl/aarch64 + ([#2502](https://github.com/nix-rust/nix/pull/2502)) +- Add FcntlArgs `F_TRANSFEREXTENTS` constant for Apple targets + ([#2504](https://github.com/nix-rust/nix/pull/2504)) +- Add `MapFlags::MAP_STACK` in `sys::man` for netbsd + ([#2526](https://github.com/nix-rust/nix/pull/2526)) +- Add support for `libc::LOCAL_PEERTOKEN` in `getsockopt`. + ([#2529](https://github.com/nix-rust/nix/pull/2529)) +- Add support for `syslog`, `openlog`, `closelog` on all `unix`. + ([#2537](https://github.com/nix-rust/nix/pull/2537)) +- Add the `TCP_FUNCTION_BLK` sockopt, on FreeBSD. + ([#2539](https://github.com/nix-rust/nix/pull/2539)) +- Implements `Into` for `PtyMaster/Fanotify/Inotify/SignalFd/TimerFd` + ([#2548](https://github.com/nix-rust/nix/pull/2548)) +- Add `MremapFlags::MREMAP_DONTUNMAP` to `sys::mman::mremap` for linux target. + ([#2555](https://github.com/nix-rust/nix/pull/2555)) +- Added `sockopt_impl!` to the public API. It's now possible for users to + define + their own sockopts without needing to make a PR to Nix. + ([#2556](https://github.com/nix-rust/nix/pull/2556)) +- Add the `TCP_FUNCTION_ALIAS` sockopt, on FreeBSD. + ([#2558](https://github.com/nix-rust/nix/pull/2558)) +- Add `sys::mman::MmapAdvise` `MADV_PAGEOUT`, `MADV_COLD`, `MADV_WIPEONFORK`, + `MADV_KEEPONFORK` for Linux and Android targets + ([#2559](https://github.com/nix-rust/nix/pull/2559)) +- Add socket protocol `Sctp`, as well as `MSG_NOTIFICATION` for non-Android + Linux targets. ([#2562](https://github.com/nix-rust/nix/pull/2562)) +- Added `from_owned_fd` constructor to `EventFd` + ([#2563](https://github.com/nix-rust/nix/pull/2563)) +- Add `sys::mman::MmapAdvise` `MADV_POPULATE_READ`, `MADV_POPULATE_WRITE` for + Linux and Android targets + ([#2565](https://github.com/nix-rust/nix/pull/2565)) +- Added `from_owned_fd` constructor to + `PtyMaster/Fanotify/Inotify/SignalFd/TimerFd` + ([#2566](https://github.com/nix-rust/nix/pull/2566)) +- Added `FcntlArg::F_READAHEAD` for FreeBSD target + ([#2569](https://github.com/nix-rust/nix/pull/2569)) +- Added `sockopt::LingerSec` for Apple targets + ([#2572](https://github.com/nix-rust/nix/pull/2572)) +- Added `sockopt::EsclBind` for solarish targets + ([#2573](https://github.com/nix-rust/nix/pull/2573)) +- Exposed the ```std::os::fd::AsRawFd``` trait method for + ```nix::sys::fanotify::Fanotify``` struct + ([#2575](https://github.com/nix-rust/nix/pull/2575)) +- Add support for syslog's `setlogmask` on all `unix`. + ([#2579](https://github.com/nix-rust/nix/pull/2579)) +- Added Fuchsia support for `ioctl`. + ([#2580](https://github.com/nix-rust/nix/pull/2580)) +- Add ```sys::socket::SockProtocol::EthIp```, + ```sys::socket::SockProtocol::EthIpv6```, + ```sys::socket::SockProtocol::EthLoop``` + ([#2581](https://github.com/nix-rust/nix/pull/2581)) +- Add OpenHarmony target into CI and Update documents. + ([#2599](https://github.com/nix-rust/nix/pull/2599)) +- Added the TcpMaxSeg `setsockopt` option for apple targets + ([#2603](https://github.com/nix-rust/nix/pull/2603)) +- Add `FilAttach` and `FilDetach` to socket::sockopt for Illumos + ([#2611](https://github.com/nix-rust/nix/pull/2611)) +- Add `PeerPidfd` (`SO_PEERPIDFD`) to `socket::sockopt` for Linux + ([#2620](https://github.com/nix-rust/nix/pull/2620)) +- Added `socket::sockopt::AttachReusePortCbpf` for Linux + ([#2621](https://github.com/nix-rust/nix/pull/2621)) +- Add `ptrace::syscall_info` for linux/glibc + ([#2627](https://github.com/nix-rust/nix/pull/2627)) + +### Changed + +- Module sys/signal now adopts I/O safety + ([#1936](https://github.com/nix-rust/nix/pull/1936)) +- Change the type of the `name` argument of `memfd_create()` from `&CStr` to + `(name: &P)` ([#2431](https://github.com/nix-rust/nix/pull/2431)) +- Public interfaces in `fcntl.rs` and `dir.rs` now use I/O-safe types. + ([#2434](https://github.com/nix-rust/nix/pull/2434)) +- Module `sys/stat` now adopts I/O safety. + ([#2439](https://github.com/nix-rust/nix/pull/2439)) +- Module unistd now adopts I/O safety. + ([#2440](https://github.com/nix-rust/nix/pull/2440)) +- Module sys/fanotify now adopts I/O safety + ([#2443](https://github.com/nix-rust/nix/pull/2443)) +- Socket option `IpTos` has been renamed to `Ipv4Tos`, the old symbol is + deprecated since 0.30.0 ([#2465](https://github.com/nix-rust/nix/pull/2465)) +- Rename Flags `EventFlag` to `EvFlags`, and `MemFdCreateFlag` to `MFdFlags` + ([#2476](https://github.com/nix-rust/nix/pull/2476)) +- Made `nix::sys::socket::UnknownCmsg` public and more readable + ([#2520](https://github.com/nix-rust/nix/pull/2520)) +- recvmsg: take slice for cmsg_buffer instead of Vec + ([#2524](https://github.com/nix-rust/nix/pull/2524)) +- linkat: allow distinct types for path arguments + ([#2582](https://github.com/nix-rust/nix/pull/2582)) + +### Fixed + +- Disable unsupported signals on sparc-linux + ([#2454](https://github.com/nix-rust/nix/pull/2454)) +- Fix cmsg_len() return type on OpenHarmony + ([#2456](https://github.com/nix-rust/nix/pull/2456)) +- The `ns` argument of `sys::prctl::set_timerslack()` should be of type + `c_ulong` ([#2505](https://github.com/nix-rust/nix/pull/2505)) +- Properly exclude NUL characters from `OSString`s returned by `getsockopt`. + ([#2557](https://github.com/nix-rust/nix/pull/2557)) +- Fixes the build on OpenHarmony + ([#2587](https://github.com/nix-rust/nix/pull/2587)) + +### Removed + +- Type `SigevNotify` is no longer `PartialEq`, `Eq` and `Hash` due to the use + of `BorrowedFd` ([#1936](https://github.com/nix-rust/nix/pull/1936)) +- `EventFd::defuse()` is removed because it does nothing, `EventFd::arm()` is + also removed for symmetry reasons. + ([#2452](https://github.com/nix-rust/nix/pull/2452)) +- Removed the `Copy` trait from `PollFd` + ([#2631](https://github.com/nix-rust/nix/pull/2631)) + + +## [0.29.0] - 2024-05-24 + + +### Added + +- Add `getregset()/setregset()` for Linux/glibc/x86/x86_64/aarch64/riscv64 and + `getregs()/setregs()` for Linux/glibc/aarch64/riscv64 + ([#2044](https://github.com/nix-rust/nix/pull/2044)) +- Add socket option Ipv6Ttl for apple targets. + ([#2287](https://github.com/nix-rust/nix/pull/2287)) +- Add socket option UtunIfname. + ([#2325](https://github.com/nix-rust/nix/pull/2325)) +- make SigAction repr(transparent) & can be converted to the libc raw type + ([#2326](https://github.com/nix-rust/nix/pull/2326)) +- Add `From` trait implementation for conversions between `sockaddr_in` and + `SockaddrIn`, `sockaddr_in6` and `SockaddrIn6` + ([#2328](https://github.com/nix-rust/nix/pull/2328)) +- Add socket option ReusePortLb for FreeBSD. + ([#2332](https://github.com/nix-rust/nix/pull/2332)) +- Added support for openat2 on linux. + ([#2339](https://github.com/nix-rust/nix/pull/2339)) +- Add if_indextoname function. + ([#2340](https://github.com/nix-rust/nix/pull/2340)) +- Add `mount` and `unmount` API for apple targets. + ([#2347](https://github.com/nix-rust/nix/pull/2347)) +- Added `_PC_MIN_HOLE_SIZE` for `pathconf` and `fpathconf`. + ([#2349](https://github.com/nix-rust/nix/pull/2349)) +- Added `impl AsFd for pty::PtyMaster` + ([#2355](https://github.com/nix-rust/nix/pull/2355)) +- Add `open` flag `O_SEARCH` to AIX, Empscripten, FreeBSD, Fuchsia, solarish, + WASI ([#2374](https://github.com/nix-rust/nix/pull/2374)) +- Add prctl function `prctl_set_vma_anon_name` for Linux/Android. + ([#2378](https://github.com/nix-rust/nix/pull/2378)) +- Add `sync(2)` for `apple_targets/solarish/haiku/aix/hurd`, `syncfs(2)` for + `hurd` and `fdatasync(2)` for `aix/hurd` + ([#2379](https://github.com/nix-rust/nix/pull/2379)) +- Add fdatasync support for Apple targets. + ([#2380](https://github.com/nix-rust/nix/pull/2380)) +- Add `fcntl::OFlag::O_PATH` for FreeBSD and Fuchsia + ([#2382](https://github.com/nix-rust/nix/pull/2382)) +- Added `PathconfVar::MIN_HOLE_SIZE` for apple_targets. + ([#2388](https://github.com/nix-rust/nix/pull/2388)) +- Add `open` flag `O_SEARCH` to apple_targets + ([#2391](https://github.com/nix-rust/nix/pull/2391)) +- `O_DSYNC` may now be used with `aio_fsync` and `fcntl` on FreeBSD. + ([#2404](https://github.com/nix-rust/nix/pull/2404)) +- Added `Flock::relock` for upgrading and downgrading locks. + ([#2407](https://github.com/nix-rust/nix/pull/2407)) + +### Changed + +- Change the `ForkptyResult` type to the following repr so that the + uninitialized + `master` field won't be accessed in the child process: + + ```rs + pub enum ForkptyResult { + Parent { + child: Pid, + master: OwnedFd, + }, + Child, + } + ``` ([#2315](https://github.com/nix-rust/nix/pull/2315)) +- Updated `cfg_aliases` dependency from version 0.1 to 0.2 + ([#2322](https://github.com/nix-rust/nix/pull/2322)) +- Change the signature of `ptrace::write` and `ptrace::write_user` to make them + safe ([#2324](https://github.com/nix-rust/nix/pull/2324)) +- Allow use of `SignalFd` through shared reference + + Like with many other file descriptors, concurrent use of signalfds is safe. + Changing the signal mask of and reading signals from a signalfd can now be + done + with the `SignalFd` API even if other references to it exist. + ([#2367](https://github.com/nix-rust/nix/pull/2367)) +- Changed tee, splice and vmsplice RawFd arguments to AsFd. + ([#2387](https://github.com/nix-rust/nix/pull/2387)) +- Added I/O safety to the sys/aio module. Most functions that previously + accepted a `AsRawFd` argument now accept an `AsFd` instead. + ([#2401](https://github.com/nix-rust/nix/pull/2401)) +- `RecvMsg::cmsgs()` now returns a `Result`, and checks that cmsgs were not + truncated. ([#2413](https://github.com/nix-rust/nix/pull/2413)) + +### Fixed + +- No longer panics when the `fanotify` queue overflows. + ([#2399](https://github.com/nix-rust/nix/pull/2399)) +- Fixed ControlMessageOwned::UdpGroSegments wrapped type from u16 to i32 to + reflect the used kernel's one. + ([#2406](https://github.com/nix-rust/nix/pull/2406)) + + +## [0.28.0] - 2024-02-24 + + +### Added + +- Added `mkdtemp` wrapper ([#1297](https://github.com/nix-rust/nix/pull/1297)) +- Add associated constants `UTIME_OMIT` `UTIME_NOW` for `TimeSpec` + ([#1879](https://github.com/nix-rust/nix/pull/1879)) +- Added `EventFd` type. ([#1945](https://github.com/nix-rust/nix/pull/1945)) +- - Added `impl From for SigSet`. + - Added `impl std::ops::BitOr for SigSet`. + - Added `impl std::ops::BitOr for Signal`. + - Added `impl std::ops::BitOr for SigSet` + + ([#1959](https://github.com/nix-rust/nix/pull/1959)) +- Added `TlsGetRecordType` control message type and corresponding enum for + linux ([#2065](https://github.com/nix-rust/nix/pull/2065)) +- Added `Ipv6HopLimit` to `::nix::sys::socket::ControlMessage` for Linux, + MacOS, FreeBSD, DragonflyBSD, Android, iOS and Haiku. + ([#2074](https://github.com/nix-rust/nix/pull/2074)) +- Added `Icmp` and `IcmpV6` to `SockProtocol` + ([#2103](https://github.com/nix-rust/nix/pull/2103)) +- Added rfork support for FreeBSD in `unistd` + ([#2121](https://github.com/nix-rust/nix/pull/2121)) +- Added `MapFlags::map_hugetlb_with_size_log2` method for Linux targets + ([#2125](https://github.com/nix-rust/nix/pull/2125)) +- Added `mmap_anonymous` function + ([#2127](https://github.com/nix-rust/nix/pull/2127)) +- Added `mips32r6` and `mips64r6` support for signal, ioctl and ptrace + ([#2138](https://github.com/nix-rust/nix/pull/2138)) +- Added `F_GETPATH` FcntlFlags entry on Apple/NetBSD/DragonflyBSD for + `::nix::fcntl`. ([#2142](https://github.com/nix-rust/nix/pull/2142)) +- Added `F_KINFO` FcntlFlags entry on FreeBSD for `::nix::fcntl`. + ([#2152](https://github.com/nix-rust/nix/pull/2152)) +- Added `F_GETPATH_NOFIRMLINK` and `F_BARRIERFSYNC` FcntlFlags entry + on Apple for `::nix::fcntl`. + ([#2155](https://github.com/nix-rust/nix/pull/2155)) +- Added newtype `Flock` to automatically unlock a held flock upon drop. + Added `Flockable` trait to represent valid types for `Flock`. + ([#2170](https://github.com/nix-rust/nix/pull/2170)) +- Added `SetSockOpt` impls to enable Linux Kernel TLS on a TCP socket and to + import TLS parameters. ([#2175](https://github.com/nix-rust/nix/pull/2175)) +- - Added the `::nix::sys::socket::SocketTimestamp` enum for configuring the + `TsClock` (a.k.a `SO_TS_CLOCK`) sockopt + - Added FreeBSD's `ScmRealtime` and `ScmMonotonic` as new options in + `::nix::sys::socket::ControlMessageOwned` + + ([#2187](https://github.com/nix-rust/nix/pull/2187)) +- Added new fanotify API: wrappers for `fanotify_init` and `fanotify_mark` + ([#2194](https://github.com/nix-rust/nix/pull/2194)) +- Added `SpecialCharacterindices` support for haiku. + ([#2195](https://github.com/nix-rust/nix/pull/2195)) +- Added `sys::sendfile` support for solaris/illumos. + ([#2198](https://github.com/nix-rust/nix/pull/2198)) +- impl Display for InterfaceFlags + ([#2206](https://github.com/nix-rust/nix/pull/2206)) +- Added `sendfilev` in sys::sendfile for solarish + ([#2207](https://github.com/nix-rust/nix/pull/2207)) +- Added `fctrl::SealFlag::F_SEAL_FUTURE_WRITE` + ([#2213](https://github.com/nix-rust/nix/pull/2213)) +- Added `Ipv6MulticastHops` as socket option to set and read. + ([#2234](https://github.com/nix-rust/nix/pull/2234)) +- Enable `ControlMessageOwned::Ipv4RecvIf` and + `ControlMessageOwned::Ipv4RecvDstAddr` for DragonFlyBSD + ([#2240](https://github.com/nix-rust/nix/pull/2240)) +- `ClockId::set_time()` and `time::clock_settime()` are now enabled on macOS + ([#2241](https://github.com/nix-rust/nix/pull/2241)) +- Added `IpBindAddressNoPort` sockopt to support `IP_BIND_ADDRESS_NO_PORT` + available on linux. ([#2244](https://github.com/nix-rust/nix/pull/2244)) +- Enable `MapFlags::map_hugetlb_with_size_log2` method for Android/Fuchsia + ([#2245](https://github.com/nix-rust/nix/pull/2245)) +- Added `TcpFastOpenConnect` sockopt to support `TCP_FASTOPEN_CONNECT` + available on linux. ([#2247](https://github.com/nix-rust/nix/pull/2247)) +- Add `reboot(2)` for OpenBSD/NetBSD + ([#2251](https://github.com/nix-rust/nix/pull/2251)) +- Added new `MemFdCreateFlag` constants to `sys::memfd` on Linux and Android + related to hugetlbfs support. + ([#2252](https://github.com/nix-rust/nix/pull/2252)) +- Expose the inner fd of `Kqueue` through: + + * impl AsFd for Kqueue + * impl From\ for OwnedFd + + ([#2258](https://github.com/nix-rust/nix/pull/2258)) +- Added `sys::eventfd` support on FreeBSD + ([#2259](https://github.com/nix-rust/nix/pull/2259)) +- Added `MmapFlags::MAP_FIXED` constant in `sys::mman` for netbsd and openbsd + ([#2260](https://github.com/nix-rust/nix/pull/2260)) +- Added the `SO_LISTENQLIMIT` sockopt. + ([#2263](https://github.com/nix-rust/nix/pull/2263)) +- Enable the `AT_EMPTY_PATH` flag for the `fchownat()` function + ([#2267](https://github.com/nix-rust/nix/pull/2267)) +- Add `AtFlags::AT_EMPTY_PATH` for FreeBSD and Hurd + ([#2270](https://github.com/nix-rust/nix/pull/2270)) +- Enable `OFlag::O_DIRECTORY for Solarish + ([#2275](https://github.com/nix-rust/nix/pull/2275)) +- Added the `Backlog` wrapper type for the `listen` call. + ([#2276](https://github.com/nix-rust/nix/pull/2276)) +- Add `clock_nanosleep()` ([#2277](https://github.com/nix-rust/nix/pull/2277)) +- Enabled `O_DIRECT` in `fcntl::OFlags` for solarish + ([#2278](https://github.com/nix-rust/nix/pull/2278)) +- Added a new API sigsuspend. + ([#2279](https://github.com/nix-rust/nix/pull/2279)) +- - Added `errno::Errno::set` function + - Added `errno::Errno::set_raw` function + - Added `errno::Errno::last_raw` function + - Added `errno::Errno::from_raw` function + + ([#2283](https://github.com/nix-rust/nix/pull/2283)) +- Enable the `AT_EMPTY_PATH` flag for the `linkat()` function + ([#2284](https://github.com/nix-rust/nix/pull/2284)) +- Enable unistd::{sync, syncfs} for Android + ([#2296](https://github.com/nix-rust/nix/pull/2296)) + +### Changed + +- `poll` now takes `PollTimeout` replacing `libc::c_int`. + ([#1876](https://github.com/nix-rust/nix/pull/1876)) +- Deprecated `sys::eventfd::eventfd`. + ([#1945](https://github.com/nix-rust/nix/pull/1945)) +- `mmap`, `mmap_anonymous`, `munmap`, `mremap`, `madvise`, `msync`, `mprotect`, + `munlock` and `mlock` updated to use `NonNull`. + ([#2000](https://github.com/nix-rust/nix/pull/2000)) +- `mmap` function now accepts `F` instead of `Option` + ([#2127](https://github.com/nix-rust/nix/pull/2127)) +- `PollFd::new` now takes a `BorrowedFd` argument, with relaxed lifetime + requirements relative to the previous version. + ([#2134](https://github.com/nix-rust/nix/pull/2134)) +- `FdSet::{insert, remove, contains}` now take `BorrowedFd` arguments, and have + relaxed lifetime requirements relative to 0.27.1. + ([#2136](https://github.com/nix-rust/nix/pull/2136)) +- The following APIs now take an implementation of `AsFd` rather than a + `RawFd`: + + - `unistd::tcgetpgrp` + - `unistd::tcsetpgrp` + - `unistd::fpathconf` + - `unistd::ttyname` + - `unistd::getpeereid` ([#2137](https://github.com/nix-rust/nix/pull/2137)) +- Changed `openat()` and `Dir::openat()`, now take optional `dirfd`s + ([#2139](https://github.com/nix-rust/nix/pull/2139)) +- The MSRV is now 1.69 ([#2144](https://github.com/nix-rust/nix/pull/2144)) +- Changed function `SockaddrIn::ip()` to return `net::Ipv4Addr` and refactored + `SocketAddrV6::ip()` to be `const` + ([#2151](https://github.com/nix-rust/nix/pull/2151)) +- The following APIs now take optional `dirfd`s: + + - `readlinkat()` + - `fstatat()` + - `mknodat()` + - `mkdirat()` + - `execveat()` + + ([#2157](https://github.com/nix-rust/nix/pull/2157)) +- `Epoll::wait` now takes `EpollTimeout` replacing `isize`. + ([#2202](https://github.com/nix-rust/nix/pull/2202)) +- - Deprecated `errno::errno()` function (use `Errno::last_raw()`) + - Deprecated `errno::from_i32()` function (use `Errno::from_raw()`) + - Deprecated `errno::Errno::from_i32()` function (use `Errno::from_raw()`) + + ([#2283](https://github.com/nix-rust/nix/pull/2283)) + +### Fixed + +- Fix `SigSet` incorrect implementation of `Eq`, `PartialEq` and `Hash` + ([#1946](https://github.com/nix-rust/nix/pull/1946)) +- Fixed `::sys::socket::sockopt::IpMulticastTtl` by fixing the value of optlen + passed to `libc::setsockopt` and added tests. + ([#2072](https://github.com/nix-rust/nix/pull/2072)) +- Fixed the function signature of `recvmmsg`, potentially causing UB + ([#2119](https://github.com/nix-rust/nix/pull/2119)) +- Fix `SignalFd::set_mask`. In 0.27.0 it would actually close the file + descriptor. ([#2141](https://github.com/nix-rust/nix/pull/2141)) +- Fixed UnixAddr::new for haiku, it did not record the `sun_len` value as + needed. + Fixed `sys::socket::addr::from_raw_parts` and + `sys::socket::Sockaddrlike::len` build for solaris. + ([#2242](https://github.com/nix-rust/nix/pull/2242)) +- Fixed solaris build globally. + ([#2248](https://github.com/nix-rust/nix/pull/2248)) +- Changed the `dup3` wrapper to perform a real call to `dup3` instead of + emulating it via `dup2` and `fcntl` to get rid of race condition + ([#2268](https://github.com/nix-rust/nix/pull/2268)) +- Fixed `::unistd::Group::members` using read_unaligned to avoid crash on + misaligned pointers ([#2311](https://github.com/nix-rust/nix/pull/2311)) + +### Removed + +- The `FchownatFlags` type has been deprecated, please use `AtFlags` instead. + ([#2267](https://github.com/nix-rust/nix/pull/2267)) +- Removed the `dup3` wrapper on macOS, which was emulated via `dup2` and + `fcntl` and could cause a race condition. The `dup3` system call is not + supported on macOS. ([#2268](https://github.com/nix-rust/nix/pull/2268)) +- The `LinkatFlags` type has been deprecated, please use `AtFlags` instead. + ([#2284](https://github.com/nix-rust/nix/pull/2284)) + + +## [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 +- 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)) @@ -27,8 +556,11 @@ This project adheres to [Semantic Versioning](https://semver.org/). ([#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. + +- 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 @@ -99,7 +631,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ([#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. @@ -232,7 +764,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1563](https://github.com/nix-rust/nix/pull/1563)) - Added `process_vm_readv` and `process_vm_writev` on Android. (#[1557](https://github.com/nix-rust/nix/pull/1557)) -- Added `nix::uncontext` module on s390x. +- 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`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00221157..9dc6100c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,11 +61,32 @@ pull' model described there. Please make pull requests against the `master` branch. If you change the API by way of adding, removing or changing something or if -you fix a bug, please add an appropriate note to the [change log][cl]. We -follow the conventions of [Keep A CHANGELOG][kacl]. +you fix a bug, please add an appropriate note, every note should be a new markdown +file under the [changelog directory][cl] stating the change made by your pull request, +the filename should be in the following format: -[cl]: https://github.com/nix-rust/nix/blob/master/CHANGELOG.md -[kacl]: https://github.com/olivierlacan/keep-a-changelog/tree/18adb5f5be7a898d046f6a4acb93e39dcf40c4ad +``` +..md +``` + +These are 4 `TYPE`s available: + +1. `added` +2. `changed` +3. `fixed` +4. `removed` + +Let's say you have added a new API to nix, then a change log like this should +be added (assume it is PR #0) + +```md +# file: 0.added.md +Added a new API xxx +``` + +And having multiple change logs for one PR is allowed. + +[cl]: https://github.com/nix-rust/nix/tree/master/changelog [pr-docs]: https://help.github.com/articles/using-pull-requests/ ## Testing @@ -75,36 +96,26 @@ requests to include tests where they make sense. For example, when fixing a bug, add a test that would have failed without the fix. After you've made your change, make sure the tests pass in your development -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]. +environment. We also have continuous integration set up on [Cirrus-CI][cirrus-ci] +and GitHub Action, which might find some issues on other platforms. The CI will +run once you open a pull request. [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! +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, 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. - -[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/CONVENTIONS.md b/CONVENTIONS.md index 2461085e..036762e4 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -17,8 +17,15 @@ We follow the conventions laid out in [Keep A CHANGELOG][kacl]. ## libc constants, functions and structs -We do not define integer constants ourselves, but use or reexport them from the -[libc crate][libc]. +We do not define ffi functions or their associated constants and types ourselves, +but use or reexport them from the [libc crate][libc], if your PR uses something +that does not exist in the libc crate, you should add it to libc first. Once +your libc PR gets merged, you can adjust our `libc` dependency to include that +libc change. Use a git dependency if necessary. + +```toml +libc = { git = "https://github.com/rust-lang/libc", rev = "the commit includes your libc PR", ... } +``` We use the functions exported from [libc][libc] instead of writing our own `extern` declarations. @@ -37,6 +44,16 @@ impl SigSet { When creating newtypes, we use Rust's `CamelCase` type naming convention. +## cfg gates + +When creating operating-system-specific functionality, we gate it by +`#[cfg(target_os = ...)]`. If **MORE THAN ONE operating system** is affected, we +prefer to use the cfg aliases defined in build.rs, like `#[cfg(bsd)]`. + +Please **DO NOT** use cfg aliases for **ONLY ONE** system as [they are bad][mismatched_target_os]. + +[mismatched_target_os]: https://rust-lang.github.io/rust-clippy/master/index.html#/mismatched_target_os + ## Bitflags Many C functions have flags parameters that are combined from constants using @@ -57,9 +74,9 @@ libc_bitflags!{ PROT_READ; PROT_WRITE; PROT_EXEC; - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(linux_android)] PROT_GROWSDOWN; - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(linux_android)] PROT_GROWSUP; } } @@ -84,3 +101,54 @@ the variable. [enum]: https://doc.rust-lang.org/reference.html#enumerations [libc]: https://crates.io/crates/libc/ [std_MaybeUninit]: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html + +## Pointer type casting + +We prefer [`cast()`], [`cast_mut()`] and [`cast_const()`] to cast pointer types +over the `as` keyword because it is much more difficult to accidentally change +type or mutability that way. + +[`cast()`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast +[`cast_mut()`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast_mut +[`cast_const()`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast_const + +## Remove/deprecate an interface + +In Nix, if we want to remove something, we don't do it immediately, instead, we +deprecate it for at least one release before removing it. + +To deprecate an interface, put the following attribute on the top of it: + +``` +#[deprecated(since = "", note = "")] +``` + +`` is the version where this interface will be deprecated, in most +cases, it will be the version of the next release. And a user-friendly note +should be added. Normally, there should be a new interface that will replace +the old one, so a note should be something like: "`` should be +used instead". + +## Where to put a test + +If you want to add a test for a feature that is in `xxx.rs`, then the test should +be put in the corresponding `test_xxx.rs` file unless you cannot do this, e.g., +the test involves private stuff and thus cannot be added outside of Nix, then +it is allowed to leave the test in `xxx.rs`. + +## Syscall/libc function error handling + +Most syscall and libc functions return an [`ErrnoSentinel`][trait] value on error, +we has a nice utility function [`Errno::result()`][util] to convert it to the +Rusty `Result` type, e.g., here is how `dup(2)` uses it: + +```rs +pub fn dup(oldfd: RawFd) -> Result { + let res = unsafe { libc::dup(oldfd) }; + + Errno::result(res) +} +``` + +[trait]: https://docs.rs/nix/latest/nix/errno/trait.ErrnoSentinel.html +[util]: https://docs.rs/nix/latest/nix/errno/enum.Errno.html#method.result diff --git a/Cargo.toml b/Cargo.toml index ad917de0..9970f121 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "nix" description = "Rust friendly bindings to *nix APIs" -edition = "2018" -version = "0.26.4" -rust-version = "1.56" +edition = "2021" +version = "0.30.1" +rust-version = "1.69" authors = ["The nix-rust Project Developers"] repository = "https://github.com/nix-rust/nix" license = "MIT" categories = ["os::unix-apis"] -include = ["src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] +include = ["build.rs", "src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] [package.metadata.docs.rs] +all-features = true rustdoc-args = ["--cfg", "docsrs"] targets = [ "x86_64-unknown-linux-gnu", @@ -21,34 +22,27 @@ targets = [ "x86_64-unknown-openbsd", "x86_64-unknown-netbsd", "x86_64-unknown-dragonfly", - "x86_64-fuchsia", + "x86_64-unknown-fuchsia", "x86_64-unknown-redox", "x86_64-unknown-illumos" ] [dependencies] -libc = { version = "0.2.137", features = [ "extra_traits" ] } -bitflags = "1.1" +libc = { version = "0.2.171", features = ["extra_traits"] } +bitflags = "2.3.3" cfg-if = "1.0" pin-utils = { version = "0.1.0", optional = true } - -[target.'cfg(not(target_os = "redox"))'.dependencies] -memoffset = { version = "0.7", optional = true } +memoffset = { version = "0.9", optional = true } [features] -default = [ - "acct", "aio", "dir", "env", "event", "feature", "fs", - "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", - "net", "personality", "poll", "process", "pthread", "ptrace", "quota", - "reboot", "resource", "sched", "signal", "socket", "term", "time", - "ucontext", "uio", "user", "zerocopy", -] +default = [] acct = [] aio = ["pin-utils"] dir = ["fs"] env = [] -event = [] +event = ["poll"] +fanotify = [] feature = [] fs = [] hostname = [] @@ -70,6 +64,7 @@ resource = [] sched = ["process"] signal = ["process"] socket = ["memoffset"] +syslog = [] term = [] time = [] ucontext = ["signal"] @@ -79,11 +74,15 @@ zerocopy = ["fs", "uio"] [dev-dependencies] assert-impl = "0.1" -lazy_static = "1.4" parking_lot = "0.12" -rand = "0.8" -tempfile = "3.3" +rand = "0.9" +tempfile = "3.7.1" semver = "1.0.7" +nix = { path = ".", features = ["acct", "aio", "dir", "env", "event", "fanotify", + "feature", "fs", "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", + "net", "personality", "poll", "pthread", "ptrace", "quota", "process", "reboot", + "resource", "sched", "signal", "socket", "syslog", "term", "time", "ucontext", "uio", + "user", "zerocopy"] } [target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies] caps = "0.5.3" @@ -91,6 +90,9 @@ caps = "0.5.3" [target.'cfg(target_os = "freebsd")'.dev-dependencies] sysctl = "0.4" +[build-dependencies] +cfg_aliases = "0.2.1" + [[test]] name = "test" path = "test/test.rs" @@ -104,10 +106,5 @@ name = "test-clearenv" path = "test/test_clearenv.rs" [[test]] -name = "test-mount" -path = "test/test_mount.rs" -harness = false - -[[test]] -name = "test-ptymaster-drop" -path = "test/test_ptymaster_drop.rs" +name = "test-prctl" +path = "test/sys/test_prctl.rs" diff --git a/Cross.toml b/Cross.toml index acd94f30..0fc503c6 100644 --- a/Cross.toml +++ b/Cross.toml @@ -3,3 +3,6 @@ passthrough = [ "RUSTFLAGS", "RUST_TEST_THREADS" ] + +[target.loongarch64-unknown-linux-gnu] +image = "ghcr.io/cross-rs/loongarch64-unknown-linux-gnu:edge" diff --git a/README.OpenSource b/README.OpenSource index dc035edc..a7913e9f 100644 --- a/README.OpenSource +++ b/README.OpenSource @@ -3,14 +3,15 @@ "Name": "nix", "License": "MIT", "License File": "LICENSE", - "Version Number": "0.26.4", + "Version Number": "0.30.1", "Owner": "fangting12@huawei.com", "Upstream URL": "https://github.com/nix-rust/nix", "Description": "A Rust library that provides support for interacting with Unix-like operating systems.", "Dependencies": [ - "Static Assertions", + "libc", + "bitflags" "memoffset", - "autocfg", + "cfg-if", "pin-utils" ] } diff --git a/README.md b/README.md index 2c42b905..7d1d7c53 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ [![Cirrus Build Status](https://api.cirrus-ci.com/github/nix-rust/nix.svg)](https://cirrus-ci.com/github/nix-rust/nix) [![crates.io](https://img.shields.io/crates/v/nix.svg)](https://crates.io/crates/nix) - -[Documentation (Releases)](https://docs.rs/nix/) +[![docs.rs](https://img.shields.io/badge/docs.rs-nix-blue?style=flat-square&logo=docs.rs)](https://docs.rs/nix) +![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) +[![msrv](https://img.shields.io/badge/msrv-1.69-blue?style=flat-square&logo=rust)](https://www.rust-lang.org) Nix seeks to provide friendly bindings to various *nix platform APIs (Linux, Darwin, ...). The goal is to not provide a 100% unified interface, but to unify @@ -30,7 +31,7 @@ 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,55 +42,75 @@ 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-apple-darwin - * aarch64-unknown-linux-gnu - * arm-unknown-linux-gnueabi - * armv7-unknown-linux-gnueabihf - * i686-unknown-freebsd - * i686-unknown-linux-gnu - * i686-unknown-linux-musl - * mips-unknown-linux-gnu - * mips64-unknown-linux-gnuabi64 - * mips64el-unknown-linux-gnuabi64 - * mipsel-unknown-linux-gnu - * powerpc64le-unknown-linux-gnu - * x86_64-unknown-freebsd - * x86_64-unknown-linux-gnu - * x86_64-unknown-linux-musl - -Tier 2: - * aarch64-apple-ios - * aarch64-linux-android - * arm-linux-androideabi - * arm-unknown-linux-musleabi - * armv7-linux-androideabi - * i686-linux-android - * powerpc-unknown-linux-gnu - * s390x-unknown-linux-gnu - * x86_64-apple-ios - * x86_64-linux-android - * x86_64-apple-darwin - * x86_64-unknown-illumos - * x86_64-unknown-netbsd - -Tier 3: - * armv7-unknown-linux-uclibceabihf - * x86_64-fuchsia - * x86_64-unknown-dragonfly - * x86_64-unknown-haiku - * x86_64-unknown-linux-gnux32 - * x86_64-unknown-openbsd - * x86_64-unknown-redox + + + + + + + + + + + +
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
  • +
  • aarch64-unknown-linux-ohos
  • +
  • arm-linux-androideabi
  • +
  • arm-unknown-linux-musleabi
  • +
  • armv7-linux-androideabi
  • +
  • armv7-unknown-linux-ohos
  • +
  • i686-linux-android
  • +
  • loongarch64-unknown-linux-gnu
  • +
  • s390x-unknown-linux-gnu
  • +
  • x86_64-linux-android
  • +
  • x86_64-unknown-illumos
  • +
  • x86_64-unknown-linux-ohos
  • +
  • x86_64-unknown-netbsd
  • +
+
  • armv7-unknown-linux-uclibceabihf
  • +
  • powerpc64-unknown-linux-gnu
  • +
  • x86_64-unknown-fuchsia
  • +
  • x86_64-unknown-dragonfly
  • +
  • x86_64-unknown-haiku
  • +
  • x86_64-unknown-linux-gnux32
  • +
  • x86_64-unknown-openbsd
  • +
  • x86_64-unknown-redox
  • +
  • i686-unknown-hurd-gnu
  • +
    ## Minimum Supported Rust Version (MSRV) -nix is supported on Rust 1.56.1 and higher. Its MSRV will not be +nix is supported on Rust 1.69 and higher. Its MSRV will not be changed in the future without bumping the major or minor version. ## Contributing @@ -97,7 +118,7 @@ changed in the future without bumping the major or minor version. Contributions are very welcome. Please See [CONTRIBUTING](CONTRIBUTING.md) for additional details. -Feel free to join us in [the nix-rust/nix](https://gitter.im/nix-rust/nix) channel on Gitter to +Feel free to join us in [the nix-rust/nix](https://discord.com/invite/rkBeJUsmyd) channel on Discord to discuss `nix` development. ## License diff --git a/RELEASE_PROCEDURE.md b/RELEASE_PROCEDURE.md index 9c68f78b..c49c34bf 100644 --- a/RELEASE_PROCEDURE.md +++ b/RELEASE_PROCEDURE.md @@ -4,16 +4,63 @@ library. # Before Release Nix uses [cargo release](https://github.com/crate-ci/cargo-release) to automate -the release process. Based on changes since the last release, pick a new -version number following semver conventions. For nix, a change that drops +the release process. Based on changes since the last release, pick a new +version number following semver conventions. For Nix, a change that drops support for some Rust versions counts as a breaking change, and requires a major bump. The release is prepared as follows: -- Ask for a new libc version if, necessary. It usually is. Then update the - dependency in Cargo.toml accordingly. +> NOTE: the following procedure should be done directly against the master +> branch of the repo. + +- Clone the `nix-rust/nix` repository with your preferred way, and `cd` to it: + + ```sh + $ git clone https://github.com/nix-rust/nix.git + $ cd nix + ``` + +- If we are using `libc` from git, replace it with a usable release from crates.io. + + ```diff + [dependencies] + -libc = { git = "https://github.com/rust-lang/libc", rev = "", features = ["extra_traits"] } + +libc = { version = "", features = ["extra_traits"] } + ``` + +- Update the version number in `Cargo.toml` +- Generate `CHANGELOG.md` for this release by + + ```sh + $ towncrier build --version= --yes + Loading template... + Finding news fragments... + Rendering news fragments... + Writing to newsfile... + Staging newsfile... + Removing the following files: + nix/changelog/xxxx.xxxx.md + nix/changelog/xxxx.xxxx.md + ... + nix/changelog/xxxx.xxxx.md + Removing news fragments... + Done! + ``` + +- Push the changes made by the above steps to the master branch + +- Ensure you have a crates.io token + 1. With the `publish-update` scope + 2. Can be used for crate `nix` + 3. It is set via `cargo login` + + If not, create a new token [here](https://crates.io/settings/tokens), and set + it. + - 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. + `cargo release ` +- Create the release with `cargo release -x `, this step will publish + the version to crates.io and push the new version tag to GitHub. + +- Congratulations on a new Nix release! diff --git a/bors.toml b/bors.toml deleted file mode 100644 index b6f81c33..00000000 --- a/bors.toml +++ /dev/null @@ -1,54 +0,0 @@ -status = [ - "Android aarch64", - "Android arm", - "Android armv7", - "Android i686", - "Android x86_64", - "DragonFly BSD x86_64", - "FreeBSD 12 amd64 & i686", - "FreeBSD 14 amd64 & i686", - "Fuchsia x86_64", - "Linux MIPS", - "Linux MIPS64 el", - "Linux MIPS64", - "Linux aarch64", - "Linux arm gnueabi", - "Linux arm-musleabi", - "Linux armv7 gnueabihf", - "Linux armv7 uclibceabihf", - "Linux i686 musl", - "Linux i686", - "Linux mipsel", - "Linux powerpc", - "Linux powerpc64", - "Linux powerpc64le", - "Linux s390x", - "Linux x32", - "Linux x86_64 musl", - "Linux x86_64", - "macOS aarch64", - "macOS x86_64", - "Minver", - "NetBSD x86_64", - "OpenBSD x86_64", - "Redox x86_64", - "Rust Stable", - "Rust Formatter", - "iOS aarch64", - "iOS x86_64", - "Illumos", - "Haiku x86_64", -] - -# 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/build.rs b/build.rs new file mode 100644 index 00000000..eb279438 --- /dev/null +++ b/build.rs @@ -0,0 +1,35 @@ +use cfg_aliases::cfg_aliases; + +fn main() { + cfg_aliases! { + android: { target_os = "android" }, + dragonfly: { target_os = "dragonfly" }, + ios: { target_os = "ios" }, + freebsd: { target_os = "freebsd" }, + illumos: { target_os = "illumos" }, + linux: { target_os = "linux" }, + macos: { target_os = "macos" }, + netbsd: { target_os = "netbsd" }, + openbsd: { target_os = "openbsd" }, + solaris: { target_os = "solaris" }, + watchos: { target_os = "watchos" }, + tvos: { target_os = "tvos" }, + visionos: { target_os = "visionos" }, + + + // cfg aliases we would like to use + apple_targets: { any(ios, macos, watchos, tvos, visionos) }, + bsd: { any(freebsd, dragonfly, netbsd, openbsd, apple_targets) }, + bsd_without_apple: { any(freebsd, dragonfly, netbsd, openbsd) }, + linux_android: { any(android, linux) }, + freebsdlike: { any(dragonfly, freebsd) }, + netbsdlike: { any(netbsd, openbsd) }, + solarish: { any(illumos, solaris) }, + } + + // Below are custom cfg values set during some CI steps. + println!("cargo:rustc-check-cfg=cfg(fbsd14)"); + println!("cargo:rustc-check-cfg=cfg(qemu)"); + // Cygwin target, added in 1.86 + println!("cargo:rustc-check-cfg=cfg(target_os, values(\"cygwin\"))"); +} diff --git a/changelog/.keep b/changelog/.keep new file mode 100644 index 00000000..f36597d9 --- /dev/null +++ b/changelog/.keep @@ -0,0 +1,5 @@ +Do not remove this file. This is used to keep the `changelog` dir around after +generating new changelog file. + +Without this, `towncrier` would remove the changelog files as well as the +directory if it is empty. diff --git a/examples/getifaddrs.rs b/examples/getifaddrs.rs new file mode 100644 index 00000000..20ae766d --- /dev/null +++ b/examples/getifaddrs.rs @@ -0,0 +1,55 @@ +//! Print all interfaces and interface addresses on the system, in a format +//! similar to ifconfig(8). +#![cfg(feature = "net")] +#[cfg(any(bsd, linux_android, target_os = "illumos"))] +fn main() { + use nix::ifaddrs::getifaddrs; + use nix::sys::socket::{SockaddrLike, SockaddrStorage}; + + let addrs = getifaddrs().unwrap(); + let mut ifname = None; + for addr in addrs { + if ifname.as_ref() != Some(&addr.interface_name) { + if ifname.is_some() { + println!(); + } + ifname = Some(addr.interface_name.clone()); + println!( + "{}: flags={:x}<{}>", + addr.interface_name, + addr.flags.bits(), + addr.flags + ); + } + if let Some(dl) = addr.address.as_ref().unwrap().as_link_addr() { + if dl.addr().is_none() { + continue; + } + } + let family = addr + .address + .as_ref() + .and_then(SockaddrStorage::family) + .map(|af| format!("{af:?}")) + .unwrap_or("".to_owned()); + match ( + &addr.address, + &addr.netmask, + &addr.broadcast, + &addr.destination, + ) { + (Some(a), Some(nm), Some(b), None) => { + println!("\t{family} {a} netmask {nm} broadcast {b}") + } + (Some(a), Some(nm), None, None) => { + println!("\t{family} {a} netmask {nm}") + } + (Some(a), None, None, None) => println!("\t{family} {a}"), + (Some(a), None, None, Some(d)) => println!("\t{family} {a} -> {d}"), + x => todo!("{x:?}"), + } + } +} + +#[cfg(not(any(bsd, linux_android, target_os = "illumos")))] +fn main() {} diff --git a/justfile b/justfile new file mode 100644 index 00000000..9bc65e4d --- /dev/null +++ b/justfile @@ -0,0 +1,8 @@ +# If no sub-command is given, simply list all the available options +_default: + just --list + +# Build the doc +doc *args='': + RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc --all-features --no-deps {{args}} + diff --git a/release.toml b/release.toml deleted file mode 100644 index 23488fbf..00000000 --- a/release.toml +++ /dev/null @@ -1,4 +0,0 @@ -pre-release-replacements = [ - { file="CHANGELOG.md", search="Unreleased", replace="{{version}}" }, - { file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}" } -] diff --git a/src/dir.rs b/src/dir.rs index ad10ce16..b514212e 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -3,7 +3,7 @@ use crate::errno::Errno; use crate::fcntl::{self, OFlag}; use crate::sys; -use crate::{Error, NixPath, Result}; +use crate::{NixPath, Result}; use cfg_if::cfg_if; use std::ffi; use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; @@ -17,17 +17,38 @@ use libc::{dirent, readdir_r}; /// An open directory. /// -/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: -/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing -/// if the path represents a file or directory). -/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. -/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` -/// after the `Dir` is dropped. +/// This is a lower-level interface than [`std::fs::ReadDir`]. Notable differences: +/// * can be opened from a file descriptor (as returned by [`openat`][openat], +/// perhaps before knowing if the path represents a file or directory). +/// * implements [`AsFd`][AsFd], so it can be passed to [`fstat`][fstat], +/// [`openat`][openat], etc. The file descriptor continues to be owned by the +/// `Dir`, so callers must not keep a `RawFd` after the `Dir` is dropped. /// * can be iterated through multiple times without closing and reopening the file /// descriptor. Each iteration rewinds when finished. /// * returns entries for `.` (current directory) and `..` (parent directory). -/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc +/// * returns entries' names as a [`CStr`][cstr] (no allocation or conversion beyond whatever libc /// does). +/// +/// [AsFd]: std::os::fd::AsFd +/// [fstat]: crate::sys::stat::fstat +/// [openat]: crate::fcntl::openat +/// [cstr]: std::ffi::CStr +/// +/// # Examples +/// +/// Traverse the current directory, and print entries' names: +/// +/// ``` +/// use nix::dir::Dir; +/// use nix::fcntl::OFlag; +/// use nix::sys::stat::Mode; +/// +/// let mut cwd = Dir::open(".", OFlag::O_RDONLY | OFlag::O_CLOEXEC, Mode::empty()).unwrap(); +/// for res_entry in cwd.iter() { +/// let entry = res_entry.unwrap(); +/// println!("File name: {}", entry.file_name().to_string_lossy()); +/// } +/// ``` #[derive(Debug, Eq, Hash, PartialEq)] pub struct Dir(ptr::NonNull); @@ -43,8 +64,8 @@ impl Dir { } /// Opens the given path as with `fcntl::openat`. - pub fn openat( - dirfd: RawFd, + pub fn openat( + dirfd: Fd, path: &P, oflag: OFlag, mode: sys::stat::Mode, @@ -54,21 +75,46 @@ impl Dir { } /// Converts from a descriptor-based object, closing the descriptor on success or failure. + /// + /// # Safety + /// + /// It is only safe if `fd` is an owned file descriptor. #[inline] - pub fn from(fd: F) -> Result { - Dir::from_fd(fd.into_raw_fd()) + #[deprecated( + since = "0.30.0", + note = "Deprecate this since it is not I/O-safe, use from_fd instead." + )] + pub unsafe fn from(fd: F) -> Result { + use std::os::fd::FromRawFd; + use std::os::fd::OwnedFd; + + // SAFETY: + // + // This is indeed unsafe is `fd` it not an owned fd. + let owned_fd = unsafe { OwnedFd::from_raw_fd(fd.into_raw_fd()) }; + Dir::from_fd(owned_fd) } - /// Converts from a file descriptor, closing it on success or failure. + /// Converts from a file descriptor, closing it on failure. + /// + /// # Examples + /// + /// `ENOTDIR` would be returned if `fd` does not refer to a directory: + /// + /// ```should_panic + /// use std::os::fd::OwnedFd; + /// use nix::dir::Dir; + /// + /// let temp_file = tempfile::tempfile().unwrap(); + /// let temp_file_fd: OwnedFd = temp_file.into(); + /// let never = Dir::from_fd(temp_file_fd).unwrap(); + /// ``` #[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 - }, - )?; + pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result { + // take the ownership as the constructed `Dir` is now the owner + let raw_fd = fd.into_raw_fd(); + let d = ptr::NonNull::new(unsafe { libc::fdopendir(raw_fd) }) + .ok_or(Errno::last())?; Ok(Dir(d)) } @@ -86,6 +132,18 @@ impl Dir { // `Dir` is safe to pass from one thread to another, as it's not reference-counted. unsafe impl Send for Dir {} +impl std::os::fd::AsFd for Dir { + fn as_fd(&self) -> std::os::fd::BorrowedFd { + let raw_fd = self.as_raw_fd(); + + // SAFETY: + // + // `raw_fd` should be open and valid for the lifetime of the returned + // `BorrowedFd` as it is extracted from `&self`. + unsafe { std::os::fd::BorrowedFd::borrow_raw(raw_fd) } + } +} + impl AsRawFd for Dir { fn as_raw_fd(&self) -> RawFd { unsafe { libc::dirfd(self.0.as_ptr()) } @@ -132,7 +190,7 @@ fn next(dir: &mut Dir) -> Option> { #[derive(Debug, Eq, Hash, PartialEq)] pub struct Iter<'d>(&'d mut Dir); -impl<'d> Iterator for Iter<'d> { +impl Iterator for Iter<'_> { type Item = Result; fn next(&mut self) -> Option { @@ -140,7 +198,7 @@ impl<'d> Iterator for Iter<'d> { } } -impl<'d> Drop for Iter<'d> { +impl Drop for Iter<'_> { fn drop(&mut self) { unsafe { libc::rewinddir((self.0).0.as_ptr()) } } @@ -224,16 +282,15 @@ impl Entry { #[allow(clippy::unnecessary_cast)] pub fn ino(&self) -> u64 { cfg_if! { - if #[cfg(any(target_os = "android", + if #[cfg(any(target_os = "aix", 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"))] { + target_os = "hurd", + target_os = "cygwin", + solarish, + linux_android, + apple_targets))] { self.0.d_ino as u64 } else { u64::from(self.0.d_fileno) @@ -243,7 +300,7 @@ impl Entry { /// 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. @@ -252,11 +309,7 @@ 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", - target_os = "haiku" - )))] + #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))] match self.0.d_type { libc::DT_FIFO => Some(Type::Fifo), libc::DT_CHR => Some(Type::CharacterDevice), @@ -269,11 +322,7 @@ impl Entry { } // illumos, Solaris, and Haiku systems do not have the d_type member at all: - #[cfg(any( - target_os = "illumos", - target_os = "solaris", - target_os = "haiku" - ))] + #[cfg(any(solarish, target_os = "aix", target_os = "haiku"))] None } } diff --git a/src/env.rs b/src/env.rs index 95177a1d..510bbb09 100644 --- a/src/env.rs +++ b/src/env.rs @@ -40,13 +40,12 @@ impl std::error::Error for ClearEnvError {} /// thread safety must still be upheld. pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> { cfg_if! { - if #[cfg(any(target_os = "fuchsia", + if #[cfg(any(linux_android, + target_os = "fuchsia", target_os = "wasi", target_env = "uclibc", - target_os = "linux", - target_os = "android", target_os = "emscripten"))] { - let ret = libc::clearenv(); + let ret = unsafe { libc::clearenv() }; } else { use std::env; for (name, _) in env::vars_os() { diff --git a/src/errno.rs b/src/errno.rs index d8ad28de..497e33db 100644 --- a/src/errno.rs +++ b/src/errno.rs @@ -1,70 +1,128 @@ +//! Safe wrappers around errno functions +//! +//! # Example +//! ``` +//! use nix::errno::Errno; +//! +//! Errno::EIO.set(); +//! assert_eq!(Errno::last(), Errno::EIO); +//! +//! Errno::clear(); +//! assert_eq!(Errno::last(), Errno::from_raw(0)); +//! ``` + use crate::Result; use cfg_if::cfg_if; use libc::{c_int, c_void}; -use std::convert::TryFrom; use std::{error, fmt, io}; pub use self::consts::*; cfg_if! { if #[cfg(any(target_os = "freebsd", - target_os = "ios", - target_os = "macos"))] { + apple_targets,))] { unsafe fn errno_location() -> *mut c_int { - libc::__error() + unsafe { libc::__error() } } - } else if #[cfg(any(target_os = "android", - target_os = "netbsd", - target_os = "openbsd"))] { + } else if #[cfg(any(target_os = "android", netbsdlike, target_os = "cygwin"))] { unsafe fn errno_location() -> *mut c_int { - libc::__errno() + unsafe { libc::__errno() } } } else if #[cfg(any(target_os = "linux", target_os = "redox", target_os = "dragonfly", - target_os = "fuchsia"))] { + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten"))] { unsafe fn errno_location() -> *mut c_int { - libc::__errno_location() + unsafe { libc::__errno_location() } } - } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { + } else if #[cfg(solarish)] { unsafe fn errno_location() -> *mut c_int { - libc::___errno() + unsafe { libc::___errno() } } } else if #[cfg(any(target_os = "haiku",))] { unsafe fn errno_location() -> *mut c_int { - libc::_errnop() + unsafe { libc::_errnop() } + } + } else if #[cfg(any(target_os = "aix"))] { + unsafe fn errno_location() -> *mut c_int { + unsafe { libc::_Errno() } } - } -} - -/// Sets the platform-specific errno to no-error -fn clear() { - // Safe because errno is a thread-local variable - unsafe { - *errno_location() = 0; } } /// Returns the platform-specific value of errno +#[deprecated(since = "0.28.0", note = "please use `Errno::last_raw()` instead")] pub fn errno() -> i32 { - unsafe { *errno_location() } + Errno::last_raw() } impl Errno { + /// Returns the current value of errno pub fn last() -> Self { - last() + Self::from_raw(Self::last_raw()) + } + + /// Returns the current raw i32 value of errno + pub fn last_raw() -> i32 { + unsafe { *errno_location() } + } + + /// Sets the value of errno. + /// + /// # Example + /// ``` + /// use nix::errno::Errno; + /// + /// Errno::EIO.set(); + /// + /// assert_eq!(Errno::last(), Errno::EIO); + /// ``` + pub fn set(self) { + Self::set_raw(self as i32) + } + + /// Sets the raw i32 value of errno. + pub fn set_raw(errno: i32) { + // Safe because errno is a thread-local variable + unsafe { + *errno_location() = errno; + } + } + + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] + pub const fn from_i32(err: i32) -> Errno { + Self::from_raw(err) + } + + pub const fn from_raw(err: i32) -> Errno { + #[allow(deprecated)] + from_i32(err) } pub fn desc(self) -> &'static str { desc(self) } - pub const fn from_i32(err: i32) -> Errno { - from_i32(err) - } - + /// Sets the platform-specific errno to no-error + /// + /// ``` + /// use nix::errno::Errno; + /// + /// Errno::EIO.set(); + /// + /// Errno::clear(); + /// + /// let err = Errno::last(); + /// assert_ne!(err, Errno::EIO); + /// assert_eq!(err, Errno::from_raw(0)); + /// ``` pub fn clear() { - clear() + Self::set_raw(0) } /// Returns `Ok(value)` if it does not contain the sentinel value. This @@ -133,14 +191,10 @@ 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_raw).ok_or(ioerror) } } -fn last() -> Errno { - Errno::from_i32(errno()) -} - fn desc(errno: Errno) -> &'static str { use self::Errno::*; match errno { @@ -221,550 +275,586 @@ fn desc(errno: Errno) -> &'static str { EHOSTUNREACH => "No route to host", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ECHRNG => "Channel number out of range", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EL2NSYNC => "Level 2 not synchronized", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EL3HLT => "Level 3 halted", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EL3RST => "Level 3 reset", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ELNRNG => "Link number out of range", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EUNATCH => "Protocol driver not attached", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ENOCSI => "No CSI structure available", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EL2HLT => "Level 2 halted", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBADE => "Invalid exchange", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBADR => "Invalid request descriptor", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EXFULL => "Exchange full", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ENOANO => "No anode", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBADRQC => "Invalid request code", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBADSLT => "Invalid slot", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBFONT => "Bad font file format", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] ENOSTR => "Device not a stream", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] ENODATA => "No data available", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] ETIME => "Timer expired", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] ENOSR => "Out of streams resources", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ENONET => "Machine is not on the network", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ENOPKG => "Package not installed", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] EREMOTE => "Object is remote", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", ))] ENOLINK => "Link has been severed", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EADV => "Advertise error", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ESRMNT => "Srmount error", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ECOMM => "Communication error on send", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EPROTO => "Protocol error", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", ))] EMULTIHOP => "Multihop attempted", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EDOTDOT => "RFS specific error", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBADMSG => "Not a data message", - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(solarish)] EBADMSG => "Trying to read unreadable message", #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, + target_os = "aix", target_os = "fuchsia", - target_os = "haiku" + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] 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" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ENOTUNIQ => "Name not unique on network", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EBADFD => "File descriptor in bad state", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EREMCHG => "Remote address changed", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] 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" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ELIBBAD => "Accessing a corrupted shared library", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ELIBSCN => ".lib section in a.out corrupted", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] 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" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] ELIBEXEC => "Cannot exec a shared library directly", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", + linux_android, + solarish, + target_os = "aix", target_os = "fuchsia", - target_os = "openbsd" + target_os = "openbsd", + target_os = "emscripten", + target_os = "cygwin", ))] EILSEQ => "Illegal byte sequence", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", ))] ERESTART => "Interrupted system call should be restarted", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ESTRPIPE => "Streams pipe error", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", - target_os = "fuchsia" + linux_android, + solarish, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EUSERS => "Too many users", #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "fuchsia", target_os = "netbsd", - target_os = "redox" + target_os = "redox", + target_os = "emscripten", + target_os = "cygwin", ))] EOPNOTSUPP => "Operation not supported on transport endpoint", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] ESTALE => "Stale file handle", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] EUCLEAN => "Structure needs cleaning", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] ENOTNAM => "Not a XENIX named type file", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] ENAVAIL => "No XENIX semaphores available", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] EISNAM => "Is a named type file", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] EREMOTEIO => "Remote I/O error", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] EDQUOT => "Quota exceeded", #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "fuchsia", target_os = "openbsd", - target_os = "dragonfly" + target_os = "dragonfly", + target_os = "emscripten", + target_os = "cygwin", ))] ENOMEDIUM => "No medium found", #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "fuchsia", - target_os = "openbsd" + target_os = "openbsd", + target_os = "emscripten", ))] EMEDIUMTYPE => "Wrong medium type", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "illumos", - target_os = "solaris", + linux_android, + solarish, target_os = "fuchsia", - target_os = "haiku" + target_os = "haiku", + target_os = "emscripten", + target_os = "cygwin", ))] ECANCELED => "Operation canceled", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] ENOKEY => "Required key not available", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] EKEYEXPIRED => "Key has expired", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] EKEYREVOKED => "Key has been revoked", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "fuchsia", + target_os = "emscripten", ))] EKEYREJECTED => "Key was rejected by service", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "aix", + target_os = "fuchsia", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", ))] EOWNERDEAD => "Owner died", - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(solarish)] EOWNERDEAD => "Process died with lock", #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "fuchsia" + linux_android, + target_os = "aix", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "cygwin", ))] ENOTRECOVERABLE => "State not recoverable", - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(solarish)] ENOTRECOVERABLE => "Lock is not recoverable", #[cfg(any( all(target_os = "linux", not(target_arch = "mips")), - target_os = "fuchsia" + target_os = "fuchsia", + target_os = "emscripten", ))] ERFKILL => "Operation not possible due to RF-kill", #[cfg(any( all(target_os = "linux", not(target_arch = "mips")), - target_os = "fuchsia" + target_os = "fuchsia", + target_os = "emscripten", ))] EHWPOISON => "Memory page has hardware error", - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] EDOOFUS => "Programming error", #[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "redox" + freebsdlike, + target_os = "hurd", + target_os = "redox", + target_os = "cygwin" ))] EMULTIHOP => "Multihop attempted", #[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "redox" + freebsdlike, + target_os = "hurd", + target_os = "redox", + target_os = "cygwin" ))] ENOLINK => "Link has been severed", @@ -774,328 +864,195 @@ fn desc(errno: Errno) -> &'static str { #[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" - ))] + #[cfg(any(bsd, target_os = "hurd"))] 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" - ))] + #[cfg(any(bsd, target_os = "redox", solarish))] EOVERFLOW => "Value too large to be stored in data type", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", + freebsdlike, + apple_targets, target_os = "netbsd", target_os = "redox", - target_os = "haiku" + target_os = "haiku", + target_os = "hurd" ))] 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" - ))] + #[cfg(any(bsd, 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", + bsd, target_os = "redox", - target_os = "haiku" + target_os = "haiku", + target_os = "hurd" ))] 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" + bsd, + target_os = "haiku", + target_os = "hurd", + target_os = "redox" ))] EPROTO => "Protocol error", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd" + freebsdlike, + apple_targets, + target_os = "openbsd", + target_os = "hurd" ))] ENOTRECOVERABLE => "State not recoverable", - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd" - ))] + #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))] EOWNERDEAD => "Previous owner died", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd", - target_os = "illumos", - target_os = "solaris", - target_os = "haiku" + bsd, + target_os = "aix", + solarish, + target_os = "haiku", + target_os = "hurd", + target_os = "cygwin" ))] ENOTSUP => "Operation not supported", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd" + bsd, + target_os = "aix", + target_os = "hurd", + target_os = "cygwin" ))] EPROCLIM => "Too many processes", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd", + bsd, + target_os = "aix", + target_os = "hurd", 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", + bsd, + solarish, target_os = "redox", - target_os = "illumos", - target_os = "solaris", - target_os = "haiku" + target_os = "aix", + target_os = "haiku", + target_os = "hurd" ))] EDQUOT => "Disc quota exceeded", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd", + bsd, + solarish, target_os = "redox", - target_os = "illumos", - target_os = "solaris", + target_os = "aix", target_os = "haiku" ))] ESTALE => "Stale NFS file handle", - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd", - target_os = "redox" - ))] + #[cfg(any(bsd, target_os = "aix", 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" - ))] + #[cfg(any(bsd, target_os = "hurd"))] 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" - ))] + #[cfg(any(bsd, target_os = "hurd"))] ERPCMISMATCH => "RPC version wrong", - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd" - ))] + #[cfg(any(bsd, target_os = "hurd"))] 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" - ))] + #[cfg(any(bsd, target_os = "hurd"))] EPROGMISMATCH => "Program version wrong", - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd" - ))] + #[cfg(any(bsd, target_os = "hurd"))] 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" - ))] + #[cfg(any(bsd, target_os = "hurd", target_os = "cygwin"))] 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" - ))] + #[cfg(any(bsd, target_os = "hurd"))] EAUTH => "Authentication error", #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "openbsd", - target_os = "netbsd", + bsd, + target_os = "aix", + target_os = "hurd", target_os = "redox" ))] ECANCELED => "Operation canceled", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] EPWROFF => "Device power is off", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] EDEVERR => "Device error, e.g. paper out", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] EBADEXEC => "Bad executable", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] EBADARCH => "Bad CPU type in executable", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] ESHLIBVERS => "Shared library version mismatch", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] EBADMACHO => "Malformed Macho file", - #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "netbsd", - target_os = "haiku" - ))] + #[cfg(any(apple_targets, target_os = "netbsd", target_os = "haiku"))] EMULTIHOP => "Reserved", #[cfg(any( - target_os = "macos", - target_os = "ios", + apple_targets, + 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" - ))] + #[cfg(any(apple_targets, target_os = "netbsd", target_os = "haiku"))] ENOLINK => "Reserved", #[cfg(any( - target_os = "macos", - target_os = "ios", + apple_targets, + target_os = "aix", target_os = "netbsd", target_os = "redox" ))] ENOSR => "No STREAM resources", #[cfg(any( - target_os = "macos", - target_os = "ios", + apple_targets, + target_os = "aix", target_os = "netbsd", target_os = "redox" ))] ENOSTR => "Not a STREAM", #[cfg(any( - target_os = "macos", - target_os = "ios", + apple_targets, + target_os = "aix", target_os = "netbsd", target_os = "redox" ))] ETIME => "STREAM ioctl timeout", - #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "illumos", - target_os = "solaris" - ))] + #[cfg(any(apple_targets, solarish, target_os = "aix"))] EOPNOTSUPP => "Operation not supported on socket", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] ENOPOLICY => "No such policy registered", - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] EQFULL => "Interface output queue is full", - #[cfg(target_os = "openbsd")] + #[cfg(any(target_os = "openbsd", target_os = "hurd"))] EOPNOTSUPP => "Operation not supported", #[cfg(target_os = "openbsd")] @@ -1104,18 +1061,33 @@ fn desc(errno: Errno) -> &'static str { #[cfg(target_os = "dragonfly")] EASYNC => "Async", - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(any(solarish, target_os = "cygwin"))] EDEADLOCK => "Resource deadlock would occur", - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(solarish)] ELOCKUNMAPPED => "Locked lock was unmapped", - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[cfg(solarish)] ENOTACTIVE => "Facility is not active", + + #[cfg(target_os = "hurd")] + EBACKGROUND => "Inappropriate operation for background process", + + #[cfg(target_os = "hurd")] + EDIED => "Translator died", + + #[cfg(target_os = "hurd")] + EGREGIOUS => "You really blew it this time", + + #[cfg(target_os = "hurd")] + EIEIO => "Computer bought the farm", + + #[cfg(target_os = "hurd")] + EGRATUITOUS => "Gratuitous error", } } -#[cfg(any(target_os = "linux", target_os = "android", target_os = "fuchsia"))] +#[cfg(any(linux_android, target_os = "fuchsia", target_os = "emscripten"))] mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] @@ -1263,6 +1235,10 @@ mod consts { pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1405,7 +1381,7 @@ mod consts { } } -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(apple_targets)] mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] @@ -1526,6 +1502,10 @@ mod consts { pub const EDEADLOCK: Errno = Errno::EDEADLK; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1753,6 +1733,10 @@ mod consts { pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -1970,6 +1954,10 @@ mod consts { pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -2182,6 +2170,10 @@ mod consts { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -2396,6 +2388,10 @@ mod consts { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -2599,6 +2595,10 @@ mod consts { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -2693,7 +2693,7 @@ mod consts { } } -#[cfg(any(target_os = "illumos", target_os = "solaris"))] +#[cfg(solarish)] mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] @@ -2828,6 +2828,10 @@ mod consts { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -3048,6 +3052,10 @@ mod consts { pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; } + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; @@ -3131,3 +3139,721 @@ mod consts { } } } + +#[cfg(target_os = "aix")] +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, + 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, + 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, + 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, + EREMOTE = libc::EREMOTE, + ECANCELED = libc::ECANCELED, + ENODATA = libc::ENODATA, + ENOSR = libc::ENOSR, + ENOSTR = libc::ENOSTR, + ETIME = libc::ETIME, + EOPNOTSUPP = libc::EOPNOTSUPP, + } + + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] + pub const fn from_i32(e: i32) -> Errno { + use self::Errno::*; + + match e { + libc::EPERM => EPERM, + libc::ENOENT => ENOENT, + libc::ESRCH => ESRCH, + libc::EINTR => EINTR, + libc::EIO => EIO, + libc::ENXIO => ENXIO, + libc::E2BIG => E2BIG, + libc::ENOEXEC => ENOEXEC, + libc::EBADF => EBADF, + libc::ECHILD => ECHILD, + libc::EAGAIN => EAGAIN, + libc::ENOMEM => ENOMEM, + libc::EACCES => EACCES, + libc::EFAULT => EFAULT, + libc::ENOTBLK => ENOTBLK, + libc::EBUSY => EBUSY, + libc::EEXIST => EEXIST, + libc::EXDEV => EXDEV, + libc::ENODEV => ENODEV, + libc::ENOTDIR => ENOTDIR, + libc::EISDIR => EISDIR, + libc::EINVAL => EINVAL, + libc::ENFILE => ENFILE, + libc::EMFILE => EMFILE, + libc::ENOTTY => ENOTTY, + libc::ETXTBSY => ETXTBSY, + libc::EFBIG => EFBIG, + libc::ENOSPC => ENOSPC, + libc::ESPIPE => ESPIPE, + libc::EROFS => EROFS, + libc::EMLINK => EMLINK, + libc::EPIPE => EPIPE, + libc::EDOM => EDOM, + libc::ERANGE => ERANGE, + libc::EDEADLK => EDEADLK, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::ENOLCK => ENOLCK, + libc::ENOSYS => ENOSYS, + libc::ENOTEMPTY => ENOTEMPTY, + libc::ELOOP => ELOOP, + libc::ENOMSG => ENOMSG, + libc::EIDRM => EIDRM, + libc::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::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::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, + } + } +} + +#[cfg(target_os = "hurd")] +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, + EMFILE = libc::EMFILE, + ENFILE = libc::ENFILE, + 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, + 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, + EDESTADDRREQ = libc::EDESTADDRREQ, + 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, + EFTYPE = libc::EFTYPE, + EAUTH = libc::EAUTH, + ENEEDAUTH = libc::ENEEDAUTH, + ENOSYS = libc::ENOSYS, + ELIBEXEC = libc::ELIBEXEC, + ENOTSUP = libc::ENOTSUP, + EILSEQ = libc::EILSEQ, + EBACKGROUND = libc::EBACKGROUND, + EDIED = libc::EDIED, + EGREGIOUS = libc::EGREGIOUS, + EIEIO = libc::EIEIO, + EGRATUITOUS = libc::EGRATUITOUS, + EBADMSG = libc::EBADMSG, + EIDRM = libc::EIDRM, + EMULTIHOP = libc::EMULTIHOP, + ENODATA = libc::ENODATA, + ENOLINK = libc::ENOLINK, + ENOMSG = libc::ENOMSG, + ENOSR = libc::ENOSR, + ENOSTR = libc::ENOSTR, + EOVERFLOW = libc::EOVERFLOW, + EPROTO = libc::EPROTO, + ETIME = libc::ETIME, + ECANCELED = libc::ECANCELED, + EOWNERDEAD = libc::EOWNERDEAD, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + } + + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + } + + #[deprecated( + since = "0.28.0", + note = "please use `Errno::from_raw()` instead" + )] + 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::EMFILE => EMFILE, + libc::ENFILE => ENFILE, + 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::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::EDESTADDRREQ => EDESTADDRREQ, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::ETIMEDOUT => ETIMEDOUT, + libc::ECONNREFUSED => ECONNREFUSED, + libc::ELOOP => ELOOP, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::ENOTEMPTY => ENOTEMPTY, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::EREMOTE => EREMOTE, + libc::EBADRPC => EBADRPC, + libc::ERPCMISMATCH => ERPCMISMATCH, + libc::EPROGUNAVAIL => EPROGUNAVAIL, + libc::EPROGMISMATCH => EPROGMISMATCH, + libc::EPROCUNAVAIL => EPROCUNAVAIL, + libc::ENOLCK => ENOLCK, + libc::EFTYPE => EFTYPE, + libc::EAUTH => EAUTH, + libc::ENEEDAUTH => ENEEDAUTH, + libc::ENOSYS => ENOSYS, + libc::ELIBEXEC => ELIBEXEC, + libc::ENOTSUP => ENOTSUP, + libc::EILSEQ => EILSEQ, + libc::EBACKGROUND => EBACKGROUND, + libc::EDIED => EDIED, + libc::EGREGIOUS => EGREGIOUS, + libc::EIEIO => EIEIO, + libc::EGRATUITOUS => EGRATUITOUS, + libc::EBADMSG => EBADMSG, + libc::EIDRM => EIDRM, + libc::EMULTIHOP => EMULTIHOP, + libc::ENODATA => ENODATA, + libc::ENOLINK => ENOLINK, + libc::ENOMSG => ENOMSG, + libc::ENOSR => ENOSR, + libc::ENOSTR => ENOSTR, + libc::EOVERFLOW => EOVERFLOW, + libc::EPROTO => EPROTO, + libc::ETIME => ETIME, + libc::ECANCELED => ECANCELED, + libc::EOWNERDEAD => EOWNERDEAD, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + _ => UnknownErrno, + } + } +} + +#[cfg(target_os = "cygwin")] +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, + EBADE = libc::EBADE, + EBADR = libc::EBADR, + EXFULL = libc::EXFULL, + ENOANO = libc::ENOANO, + EBADRQC = libc::EBADRQC, + EBADSLT = libc::EBADSLT, + EDEADLOCK = libc::EDEADLOCK, + 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, + EFTYPE = libc::EFTYPE, + ENOTUNIQ = libc::ENOTUNIQ, + EBADFD = libc::EBADFD, + EREMCHG = libc::EREMCHG, + ELIBACC = libc::ELIBACC, + ELIBBAD = libc::ELIBBAD, + ELIBSCN = libc::ELIBSCN, + ELIBMAX = libc::ELIBMAX, + ELIBEXEC = libc::ELIBEXEC, + ENOSYS = libc::ENOSYS, + ENOTEMPTY = libc::ENOTEMPTY, + ENAMETOOLONG = libc::ENAMETOOLONG, + ELOOP = libc::ELOOP, + EOPNOTSUPP = libc::EOPNOTSUPP, + EPFNOSUPPORT = libc::EPFNOSUPPORT, + ECONNRESET = libc::ECONNRESET, + ENOBUFS = libc::ENOBUFS, + EAFNOSUPPORT = libc::EAFNOSUPPORT, + EPROTOTYPE = libc::EPROTOTYPE, + ENOTSOCK = libc::ENOTSOCK, + ENOPROTOOPT = libc::ENOPROTOOPT, + ESHUTDOWN = libc::ESHUTDOWN, + ECONNREFUSED = libc::ECONNREFUSED, + EADDRINUSE = libc::EADDRINUSE, + ECONNABORTED = libc::ECONNABORTED, + ENETUNREACH = libc::ENETUNREACH, + ENETDOWN = libc::ENETDOWN, + ETIMEDOUT = libc::ETIMEDOUT, + EHOSTDOWN = libc::EHOSTDOWN, + EHOSTUNREACH = libc::EHOSTUNREACH, + EINPROGRESS = libc::EINPROGRESS, + EALREADY = libc::EALREADY, + EDESTADDRREQ = libc::EDESTADDRREQ, + EMSGSIZE = libc::EMSGSIZE, + EPROTONOSUPPORT = libc::EPROTONOSUPPORT, + ESOCKTNOSUPPORT = libc::ESOCKTNOSUPPORT, + EADDRNOTAVAIL = libc::EADDRNOTAVAIL, + ENETRESET = libc::ENETRESET, + EISCONN = libc::EISCONN, + ENOTCONN = libc::ENOTCONN, + ETOOMANYREFS = libc::ETOOMANYREFS, + EPROCLIM = libc::EPROCLIM, + EUSERS = libc::EUSERS, + EDQUOT = libc::EDQUOT, + ESTALE = libc::ESTALE, + ENOTSUP = libc::ENOTSUP, + ENOMEDIUM = libc::ENOMEDIUM, + EILSEQ = libc::EILSEQ, + EOVERFLOW = libc::EOVERFLOW, + ECANCELED = libc::ECANCELED, + ENOTRECOVERABLE = libc::ENOTRECOVERABLE, + EOWNERDEAD = libc::EOWNERDEAD, + ESTRPIPE = libc::ESTRPIPE, + } + + impl Errno { + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + pub const EDEADLOCK: Errno = Errno::EDEADLK; + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + } + + pub(crate) 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::EBADE => EBADE, + libc::EBADR => EBADR, + libc::EXFULL => EXFULL, + libc::ENOANO => ENOANO, + libc::EBADRQC => EBADRQC, + libc::EBADSLT => EBADSLT, + libc::EDEADLOCK => EDEADLOCK, + libc::EBFONT => EBFONT, + libc::ENOSTR => ENOSTR, + libc::ENODATA => ENODATA, + libc::ETIME => ETIME, + libc::ENOSR => ENOSR, + libc::ENONET => ENONET, + libc::ENOPKG => ENOPKG, + libc::EREMOTE => EREMOTE, + libc::ENOLINK => ENOLINK, + libc::EADV => EADV, + libc::ESRMNT => ESRMNT, + libc::ECOMM => ECOMM, + libc::EPROTO => EPROTO, + libc::EMULTIHOP => EMULTIHOP, + libc::EDOTDOT => EDOTDOT, + libc::EBADMSG => EBADMSG, + libc::EFTYPE => EFTYPE, + 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::ENOSYS => ENOSYS, + libc::ENOTEMPTY => ENOTEMPTY, + libc::ENAMETOOLONG => ENAMETOOLONG, + libc::ELOOP => ELOOP, + libc::EOPNOTSUPP => EOPNOTSUPP, + libc::EPFNOSUPPORT => EPFNOSUPPORT, + libc::ECONNRESET => ECONNRESET, + libc::ENOBUFS => ENOBUFS, + libc::EAFNOSUPPORT => EAFNOSUPPORT, + libc::EPROTOTYPE => EPROTOTYPE, + libc::ENOTSOCK => ENOTSOCK, + libc::ENOPROTOOPT => ENOPROTOOPT, + libc::ESHUTDOWN => ESHUTDOWN, + libc::ECONNREFUSED => ECONNREFUSED, + libc::EADDRINUSE => EADDRINUSE, + libc::ECONNABORTED => ECONNABORTED, + libc::ENETUNREACH => ENETUNREACH, + libc::ENETDOWN => ENETDOWN, + libc::ETIMEDOUT => ETIMEDOUT, + libc::EHOSTDOWN => EHOSTDOWN, + libc::EHOSTUNREACH => EHOSTUNREACH, + libc::EINPROGRESS => EINPROGRESS, + libc::EALREADY => EALREADY, + libc::EDESTADDRREQ => EDESTADDRREQ, + libc::EMSGSIZE => EMSGSIZE, + libc::EPROTONOSUPPORT => EPROTONOSUPPORT, + libc::ESOCKTNOSUPPORT => ESOCKTNOSUPPORT, + libc::EADDRNOTAVAIL => EADDRNOTAVAIL, + libc::ENETRESET => ENETRESET, + libc::EISCONN => EISCONN, + libc::ENOTCONN => ENOTCONN, + libc::ETOOMANYREFS => ETOOMANYREFS, + libc::EPROCLIM => EPROCLIM, + libc::EUSERS => EUSERS, + libc::EDQUOT => EDQUOT, + libc::ESTALE => ESTALE, + libc::ENOTSUP => ENOTSUP, + libc::ENOMEDIUM => ENOMEDIUM, + libc::EILSEQ => EILSEQ, + libc::EOVERFLOW => EOVERFLOW, + libc::ECANCELED => ECANCELED, + libc::ENOTRECOVERABLE => ENOTRECOVERABLE, + libc::EOWNERDEAD => EOWNERDEAD, + libc::ESTRPIPE => ESTRPIPE, + _ => UnknownErrno, + } + } +} diff --git a/src/fcntl.rs b/src/fcntl.rs index e227e7c0..13ee1919 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -1,19 +1,37 @@ +//! File control options use crate::errno::Errno; -use libc::{self, c_char, c_int, c_uint, size_t, ssize_t}; +#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] +use core::slice; +use libc::{c_int, c_uint, size_t, ssize_t}; +#[cfg(any( + target_os = "netbsd", + apple_targets, + target_os = "dragonfly", + all(target_os = "freebsd", target_arch = "x86_64"), +))] +use std::ffi::CStr; use std::ffi::OsString; -#[cfg(not(target_os = "redox"))] -use std::os::raw; +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +use std::ops::{Deref, DerefMut}; use std::os::unix::ffi::OsStringExt; +#[cfg(not(target_os = "redox"))] +use std::os::unix::io::OwnedFd; use std::os::unix::io::RawFd; +#[cfg(any( + target_os = "netbsd", + apple_targets, + target_os = "dragonfly", + all(target_os = "freebsd", target_arch = "x86_64"), +))] +use std::path::PathBuf; +#[cfg(any(linux_android, target_os = "freebsd"))] +use std::ptr; #[cfg(feature = "fs")] use crate::{sys::stat::Mode, NixPath, Result}; -#[cfg(any(target_os = "android", target_os = "linux"))] -use std::ptr; // For splice and copy_file_range #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", target_os = "wasi", @@ -23,39 +41,96 @@ use std::ptr; // For splice and copy_file_range #[cfg(feature = "fs")] pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice}; +/// A file descriptor referring to the working directory of the current process +/// **that should be ONLY passed to the `dirfd` argument of those `xxat()` functions**. +/// +/// # Examples +/// +/// Use it in [`openat()`]: +/// +/// ```no_run +/// use nix::fcntl::AT_FDCWD; +/// use nix::fcntl::openat; +/// use nix::fcntl::OFlag; +/// use nix::sys::stat::Mode; +/// +/// let fd = openat(AT_FDCWD, "foo", OFlag::O_RDONLY | OFlag::O_CLOEXEC, Mode::empty()).unwrap(); +/// ``` +/// +/// # WARNING +/// +/// Do NOT pass this symbol to non-`xxat()` functions, it won't work: +/// +/// ```should_panic +/// use nix::errno::Errno; +/// use nix::fcntl::AT_FDCWD; +/// use nix::sys::stat::fstat; +/// +/// let never = fstat(AT_FDCWD).unwrap(); +/// ``` +// +// SAFETY: +// 1. `AT_FDCWD` is usable for the whole process life, so it is `'static`. +// 2. It is not a valid file descriptor, but OS will handle it for us when passed +// to `xxat(2)` calls. +#[cfg(not(target_os = "redox"))] // Redox does not have this +pub const AT_FDCWD: std::os::fd::BorrowedFd<'static> = + unsafe { std::os::fd::BorrowedFd::borrow_raw(libc::AT_FDCWD) }; + #[cfg(not(target_os = "redox"))] -#[cfg(any(feature = "fs", feature = "process"))] +#[cfg(any(feature = "fs", feature = "process", feature = "user"))] libc_bitflags! { + /// Flags that control how the various *at syscalls behave. #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "process"))))] pub struct AtFlags: c_int { + #[allow(missing_docs)] + #[doc(hidden)] + // Should not be used by the public API, but only internally. AT_REMOVEDIR; + /// Used with [`linkat`](crate::unistd::linkat`) to create a link to a symbolic link's + /// target, instead of to the symbolic link itself. AT_SYMLINK_FOLLOW; + /// Used with functions like [`fstatat`](crate::sys::stat::fstatat`) to operate on a link + /// itself, instead of the symbolic link's target. AT_SYMLINK_NOFOLLOW; - #[cfg(any(target_os = "android", target_os = "linux"))] + /// Don't automount the terminal ("basename") component of pathname if it is a directory + /// that is an automount point. + #[cfg(linux_android)] AT_NO_AUTOMOUNT; - #[cfg(any(target_os = "android", target_os = "linux"))] + /// If the provided path is an empty string, operate on the provided directory file + /// descriptor instead. + #[cfg(any(linux_android, target_os = "freebsd", target_os = "hurd"))] AT_EMPTY_PATH; - #[cfg(any(target_os = "illumos", target_os = "solaris"))] + /// Used with [`faccessat`](crate::unistd::faccessat), the checks for accessibility are + /// performed using the effective user and group IDs instead of the real user and group ID + #[cfg(not(target_os = "android"))] AT_EACCESS; } } -#[cfg(any(feature = "fs", feature = "term"))] +#[cfg(any( + feature = "fs", + feature = "term", + all(feature = "fanotify", target_os = "linux") +))] libc_bitflags!( /// Configuration options for opened files. - #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term", all(feature = "fanotify", target_os = "linux")))))] pub struct OFlag: c_int { /// Mask for the access mode of the file. O_ACCMODE; /// Use alternate I/O semantics. #[cfg(target_os = "netbsd")] - #[cfg_attr(docsrs, doc(cfg(all())))] O_ALT_IO; /// Open the file in append-only mode. O_APPEND; /// Generate a signal when input or output becomes possible. - #[cfg(not(any(target_os = "illumos", target_os = "solaris", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any( + solarish, + target_os = "aix", + target_os = "haiku", + target_os = "cygwin" + )))] O_ASYNC; /// Closes the file descriptor once an `execve` call is made. /// @@ -64,68 +139,42 @@ libc_bitflags!( /// Create the file if it does not exist. O_CREAT; /// Try to minimize cache effects of the I/O for this file. - #[cfg(any(target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + linux_android, + target_os = "illumos", + target_os = "netbsd" + ))] O_DIRECT; /// If the specified path isn't a directory, fail. - #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] - #[cfg_attr(docsrs, doc(cfg(all())))] O_DIRECTORY; /// Implicitly follow each `write()` with an `fdatasync()`. - #[cfg(any(target_os = "android", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, apple_targets, target_os = "freebsd", netbsdlike))] O_DSYNC; /// Error out if a file was not created. O_EXCL; /// Open for execute only. #[cfg(target_os = "freebsd")] - #[cfg_attr(docsrs, doc(cfg(all())))] O_EXEC; /// Open with an exclusive file lock. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "redox"))] O_EXLOCK; /// Same as `O_SYNC`. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - all(target_os = "linux", not(target_env = "musl")), - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", + #[cfg(any(bsd, + all(target_os = "linux", not(target_env = "musl"), not(target_env = "ohos")), 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] O_NOATIME; /// Don't attach the device as the process' controlling terminal. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] O_NOCTTY; /// Same as `O_NONBLOCK`. - #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_os = "redox", target_os = "haiku", target_os = "cygwin")))] O_NDELAY; /// `open()` will fail if the given path is a symbolic link. O_NOFOLLOW; @@ -133,13 +182,11 @@ 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())))] + #[cfg(any(linux_android, target_os = "redox", target_os = "freebsd", target_os = "fuchsia"))] O_PATH; /// Only allow reading. /// @@ -150,36 +197,34 @@ libc_bitflags!( /// This should not be combined with `O_WRONLY` or `O_RDONLY`. O_RDWR; /// Similar to `O_DSYNC` but applies to `read`s instead. - #[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(target_os = "linux", netbsdlike))] O_RSYNC; - /// Skip search permission checks. - #[cfg(target_os = "netbsd")] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Open directory for search only. Skip search permission checks on + /// later `openat()` calls using the obtained file descriptor. + #[cfg(any( + apple_targets, + solarish, + target_os = "netbsd", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "aix", + target_os = "wasi" + ))] O_SEARCH; /// Open with a shared file lock. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "redox"))] 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())))] + #[cfg(linux_android)] 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. /// @@ -191,44 +236,206 @@ libc_bitflags!( feature! { #![feature = "fs"] +/// open or create a file for reading, writing or executing +/// +/// # See Also +/// [`open`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html) // The conversion is not identical on all operating systems. #[allow(clippy::useless_conversion)] -pub fn open(path: &P, oflag: OFlag, mode: Mode) -> Result { - let fd = path.with_nix_path(|cstr| { - unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) } - })?; - - Errno::result(fd) -} - -// The conversion is not identical on all operating systems. -#[allow(clippy::useless_conversion)] -#[cfg(not(target_os = "redox"))] -pub fn openat( - dirfd: RawFd, +pub fn open( path: &P, oflag: OFlag, mode: Mode, -) -> Result { - let fd = path.with_nix_path(|cstr| { - unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) } +) -> Result { + use std::os::fd::FromRawFd; + + let fd = path.with_nix_path(|cstr| unsafe { + libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) })?; - Errno::result(fd) + Errno::result(fd)?; + + // SAFETY: + // + // `open(2)` should return a valid owned fd on success + Ok( unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) } ) } +/// open or create a file for reading, writing or executing +/// +/// The `openat` function is equivalent to the [`open`] function except in the case where the path +/// specifies a relative path. In that case, the file to be opened is determined relative to the +/// directory associated with the file descriptor `dirfd`. +/// +/// # See Also +/// [`openat`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html) +// The conversion is not identical on all operating systems. +#[allow(clippy::useless_conversion)] #[cfg(not(target_os = "redox"))] -pub fn renameat( - old_dirfd: Option, +pub fn openat( + dirfd: Fd, + path: &P, + oflag: OFlag, + mode: Mode, +) -> Result { + use std::os::fd::AsRawFd; + use std::os::fd::FromRawFd; + + let fd = path.with_nix_path(|cstr| unsafe { + libc::openat(dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) + })?; + Errno::result(fd)?; + + // SAFETY: + // + // `openat(2)` should return a valid owned fd on success + Ok( unsafe { OwnedFd::from_raw_fd(fd) } ) +} + +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + libc_bitflags! { + /// Path resolution flags. + /// + /// See [path resolution(7)](https://man7.org/linux/man-pages/man7/path_resolution.7.html) + /// for details of the resolution process. + pub struct ResolveFlag: libc::c_ulonglong { + /// Do not permit the path resolution to succeed if any component of + /// the resolution is not a descendant of the directory indicated by + /// dirfd. This causes absolute symbolic links (and absolute values of + /// pathname) to be rejected. + RESOLVE_BENEATH; + + /// Treat the directory referred to by dirfd as the root directory + /// while resolving pathname. + RESOLVE_IN_ROOT; + + /// Disallow all magic-link resolution during path resolution. Magic + /// links are symbolic link-like objects that are most notably found + /// in proc(5); examples include `/proc/[pid]/exe` and `/proc/[pid]/fd/*`. + /// + /// See symlink(7) for more details. + RESOLVE_NO_MAGICLINKS; + + /// Disallow resolution of symbolic links during path resolution. This + /// option implies RESOLVE_NO_MAGICLINKS. + RESOLVE_NO_SYMLINKS; + + /// Disallow traversal of mount points during path resolution (including + /// all bind mounts). + RESOLVE_NO_XDEV; + } + } + + /// Specifies how [`openat2()`] should open a pathname. + /// + /// # Reference + /// + /// * [Linux](https://man7.org/linux/man-pages/man2/open_how.2type.html) + #[repr(transparent)] + #[derive(Clone, Copy, Debug)] + pub struct OpenHow(libc::open_how); + + impl OpenHow { + /// Create a new zero-filled `open_how`. + pub fn new() -> Self { + // safety: according to the man page, open_how MUST be zero-initialized + // on init so that unknown fields are also zeroed. + Self(unsafe { + std::mem::MaybeUninit::zeroed().assume_init() + }) + } + + /// Set the open flags used to open a file, completely overwriting any + /// existing flags. + pub fn flags(mut self, flags: OFlag) -> Self { + let flags = flags.bits() as libc::c_ulonglong; + self.0.flags = flags; + self + } + + /// Set the file mode new files will be created with, overwriting any + /// existing flags. + pub fn mode(mut self, mode: Mode) -> Self { + let mode = mode.bits() as libc::c_ulonglong; + self.0.mode = mode; + self + } + + /// Set resolve flags, completely overwriting any existing flags. + /// + /// See [ResolveFlag] for more detail. + pub fn resolve(mut self, resolve: ResolveFlag) -> Self { + let resolve = resolve.bits(); + self.0.resolve = resolve; + self + } + } + + // safety: default isn't derivable because libc::open_how must be zeroed + impl Default for OpenHow { + fn default() -> Self { + Self::new() + } + } + + /// Open or create a file for reading, writing or executing. + /// + /// `openat2` is an extension of the [`openat`] function that allows the caller + /// to control how path resolution happens. + /// + /// # See also + /// + /// [openat2](https://man7.org/linux/man-pages/man2/openat2.2.html) + pub fn openat2( + dirfd: Fd, + path: &P, + mut how: OpenHow, + ) -> Result { + use std::os::fd::AsRawFd; + use std::os::fd::FromRawFd; + + let fd = path.with_nix_path(|cstr| unsafe { + libc::syscall( + libc::SYS_openat2, + dirfd.as_fd().as_raw_fd(), + cstr.as_ptr(), + &mut how as *mut OpenHow, + std::mem::size_of::(), + ) + })? as RawFd; + Errno::result(fd)?; + + // SAFETY: + // + // `openat2(2)` should return a valid owned fd on success + Ok( unsafe { OwnedFd::from_raw_fd(fd) } ) + } + } +} + +/// Change the name of a file. +/// +/// The `renameat` function is equivalent to `rename` except in the case where either `old_path` +/// or `new_path` specifies a relative path. In such cases, the file to be renamed (or the its new +/// name, respectively) is located relative to `old_dirfd` or `new_dirfd`, respectively +/// +/// # See Also +/// [`renameat`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html) +#[cfg(not(target_os = "redox"))] +pub fn renameat( + old_dirfd: Fd1, old_path: &P1, - new_dirfd: Option, + new_dirfd: Fd2, new_path: &P2, ) -> Result<()> { + use std::os::fd::AsRawFd; + let res = old_path.with_nix_path(|old_cstr| { new_path.with_nix_path(|new_cstr| unsafe { libc::renameat( - at_rawfd(old_dirfd), + old_dirfd.as_fd().as_raw_fd(), old_cstr.as_ptr(), - at_rawfd(new_dirfd), + new_dirfd.as_fd().as_raw_fd(), new_cstr.as_ptr(), ) }) @@ -237,36 +444,49 @@ pub fn renameat( } } -#[cfg(all(target_os = "linux", target_env = "gnu",))] +#[cfg(all(target_os = "linux", target_env = "gnu"))] #[cfg(feature = "fs")] libc_bitflags! { + /// Flags for use with [`renameat2`]. #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] pub struct RenameFlags: u32 { + /// Atomically exchange `old_path` and `new_path`. RENAME_EXCHANGE; + /// Don't overwrite `new_path` of the rename. Return an error if `new_path` already + /// exists. RENAME_NOREPLACE; + /// creates a "whiteout" object at the source of the rename at the same time as performing + /// the rename. + /// + /// This operation makes sense only for overlay/union filesystem implementations. RENAME_WHITEOUT; } } feature! { #![feature = "fs"] -#[cfg(all( - target_os = "linux", - target_env = "gnu", -))] -pub fn renameat2( - old_dirfd: Option, +/// Like [`renameat`], but with an additional `flags` argument. +/// +/// A `renameat2` call with an empty flags argument is equivalent to `renameat`. +/// +/// # See Also +/// * [`rename`](https://man7.org/linux/man-pages/man2/rename.2.html) +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub fn renameat2( + old_dirfd: Fd1, old_path: &P1, - new_dirfd: Option, + new_dirfd: Fd2, new_path: &P2, flags: RenameFlags, ) -> Result<()> { + use std::os::fd::AsRawFd; + let res = old_path.with_nix_path(|old_cstr| { new_path.with_nix_path(|new_cstr| unsafe { libc::renameat2( - at_rawfd(old_dirfd), + old_dirfd.as_fd().as_raw_fd(), old_cstr.as_ptr(), - at_rawfd(new_dirfd), + new_dirfd.as_fd().as_raw_fd(), new_cstr.as_ptr(), flags.bits(), ) @@ -281,7 +501,16 @@ fn wrap_readlink_result(mut v: Vec, len: ssize_t) -> Result { Ok(OsString::from_vec(v.to_vec())) } -fn readlink_maybe_at( +/// Read the symlink specified by `path` and `dirfd` and put the contents in `v`. +/// Return the number of bytes placed in `v`. +/// +/// This function can call `readlink(2)` or `readlinkat(2)` depending on if `dirfd` +/// is some, if it is, then `readlinkat(2)` is called, otherwise, call `readlink(2)`. +/// +/// # Safety +/// +/// This function is not I/O-safe considering it employs the `RawFd` type. +unsafe fn readlink_maybe_at( dirfd: Option, path: &P, v: &mut Vec, @@ -289,96 +518,181 @@ fn readlink_maybe_at( path.with_nix_path(|cstr| unsafe { match dirfd { #[cfg(target_os = "redox")] - Some(_) => unreachable!(), + Some(_) => unreachable!("redox does not have readlinkat(2)"), #[cfg(not(target_os = "redox"))] Some(dirfd) => libc::readlinkat( dirfd, cstr.as_ptr(), - v.as_mut_ptr() as *mut c_char, + v.as_mut_ptr().cast(), v.capacity() as size_t, ), None => libc::readlink( cstr.as_ptr(), - v.as_mut_ptr() as *mut c_char, + v.as_mut_ptr().cast(), v.capacity() as size_t, ), } }) } -fn inner_readlink(dirfd: Option, path: &P) -> Result { - let mut v = Vec::with_capacity(libc::PATH_MAX as usize); - // simple case: result is strictly less than `PATH_MAX` - let res = readlink_maybe_at(dirfd, path, &mut v)?; - let len = Errno::result(res)?; - debug_assert!(len >= 0); - if (len as usize) < v.capacity() { - return wrap_readlink_result(v, res); - } - // Uh oh, the result is too long... - // Let's try to ask lstat how many bytes to allocate. - let reported_size = match dirfd { - #[cfg(target_os = "redox")] - Some(_) => unreachable!(), - #[cfg(any(target_os = "android", target_os = "linux"))] - Some(dirfd) => { - let flags = if path.is_empty() { AtFlags::AT_EMPTY_PATH } else { AtFlags::empty() }; - super::sys::stat::fstatat(dirfd, path, flags | AtFlags::AT_SYMLINK_NOFOLLOW) - }, - #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "redox")))] - Some(dirfd) => super::sys::stat::fstatat(dirfd, path, AtFlags::AT_SYMLINK_NOFOLLOW), - None => super::sys::stat::lstat(path) - } - .map(|x| x.st_size) - .unwrap_or(0); - let mut try_size = if reported_size > 0 { - // Note: even if `lstat`'s apparently valid answer turns out to be - // wrong, we will still read the full symlink no matter what. - reported_size as usize + 1 - } else { - // If lstat doesn't cooperate, or reports an error, be a little less - // precise. - (libc::PATH_MAX as usize).max(128) << 1 - }; - loop { - v.reserve_exact(try_size); - let res = readlink_maybe_at(dirfd, path, &mut v)?; +/// The actual implementation of [`readlink(2)`] or [`readlinkat(2)`]. +/// +/// This function can call `readlink(2)` or `readlinkat(2)` depending on if `dirfd` +/// is some, if it is, then `readlinkat(2)` is called, otherwise, call `readlink(2)`. +/// +/// # Safety +/// +/// This function is marked unsafe because it uses `RawFd`. +unsafe fn inner_readlink( + dirfd: Option, + path: &P, +) -> Result { + #[cfg(not(target_os = "hurd"))] + const PATH_MAX: usize = libc::PATH_MAX as usize; + #[cfg(target_os = "hurd")] + const PATH_MAX: usize = 1024; // Hurd does not define a hard limit, so try a guess first + let mut v = Vec::with_capacity(PATH_MAX); + + { + // simple case: result is strictly less than `PATH_MAX` + + // SAFETY: + // + // If this call of `readlink_maybe_at()` is safe or not depends on the + // usage of `unsafe fn inner_readlink()`. + let res = unsafe { 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), + 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 mut try_size = { + let reported_size = match dirfd { + #[cfg(target_os = "redox")] + Some(_) => unreachable!("redox does not have readlinkat(2)"), + #[cfg(any(linux_android, target_os = "freebsd", target_os = "hurd"))] + Some(dirfd) => { + // SAFETY: + // + // If this call of `borrow_raw()` is safe or not depends on the + // usage of `unsafe fn inner_readlink()`. + let dirfd = unsafe { + std::os::fd::BorrowedFd::borrow_raw(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( + linux_android, + target_os = "redox", + target_os = "freebsd", + target_os = "hurd" + )))] + Some(dirfd) => { + // SAFETY: + // + // If this call of `borrow_raw()` is safe or not depends on the + // usage of `unsafe fn inner_readlink()`. + let dirfd = unsafe { + std::os::fd::BorrowedFd::borrow_raw(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); + + 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. + PATH_MAX.max(128) << 1 + } + }; + + loop { + { + v.reserve_exact(try_size); + // SAFETY: + // + // If this call of `readlink_maybe_at()` is safe or not depends on the + // usage of `unsafe fn inner_readlink()`. + let res = unsafe { 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), } } } +/// Read value of a symbolic link +/// +/// # See Also +/// * [`readlink`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html) pub fn readlink(path: &P) -> Result { - inner_readlink(None, path) + // argument `dirfd` should be `None` since we call it from `readlink()` + // + // Do NOT call it with `Some(AT_CWD)` as in that way, we are emulating + // `readlink(2)` with `readlinkat(2)`, which will make us lose `readlink(2)` + // on Redox. + // + // SAFETY: + // + // It is definitely safe because the argument involving `RawFd` is `None` + unsafe { inner_readlink(None, path) } } +/// Read value of a symbolic link. +/// +/// Equivalent to [`readlink` ] except for the case where `path` specifies a +/// relative path, `path` will be interpreted relative to the path specified +/// by `dirfd`. (Use [`AT_FDCWD`] to make it relative to the working directory). +/// +/// # See Also +/// * [`readlink`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html) #[cfg(not(target_os = "redox"))] -pub fn readlinkat(dirfd: RawFd, path: &P) -> Result { - inner_readlink(Some(dirfd), path) -} +pub fn readlinkat( + dirfd: Fd, + path: &P, +) -> Result { + use std::os::fd::AsRawFd; -/// Computes the raw fd consumed by a function of the form `*at`. -#[cfg(not(target_os = "redox"))] -pub(crate) fn at_rawfd(fd: Option) -> raw::c_int { - match fd { - None => libc::AT_FDCWD, - Some(fd) => fd, - } + // argument `dirfd` should be `Some` since we call it from `readlinkat()` + // + // SAFETY: + // + // The passed `RawFd` should be valid since it is borrowed from `Fd: AsFd`. + unsafe { inner_readlink(Some(dirfd.as_fd().as_raw_fd()), path) } } } -#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "fs")] libc_bitflags!( /// Additional flags for file sealing, which allows for limiting operations on a file. @@ -392,6 +706,10 @@ libc_bitflags!( F_SEAL_GROW; /// The file contents cannot be modified. F_SEAL_WRITE; + /// The file contents cannot be modified, except via shared writable mappings that were + /// created prior to the seal being set. Since Linux 5.1. + #[cfg(linux_android)] + F_SEAL_FUTURE_WRITE; } ); @@ -408,57 +726,149 @@ libc_bitflags!( feature! { #![feature = "fs"] +/// Commands for use with [`fcntl`]. #[cfg(not(target_os = "redox"))] #[derive(Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum FcntlArg<'a> { + /// Duplicate the provided file descriptor F_DUPFD(RawFd), + /// Duplicate the provided file descriptor and set the `FD_CLOEXEC` flag on it. F_DUPFD_CLOEXEC(RawFd), + /// Get the close-on-exec flag associated with the file descriptor F_GETFD, + /// Set the close-on-exec flag associated with the file descriptor F_SETFD(FdFlag), // FD_FLAGS + /// Get descriptor status flags F_GETFL, + /// Set descriptor status flags F_SETFL(OFlag), // O_NONBLOCK + /// Set or clear a file segment lock F_SETLK(&'a libc::flock), + /// Like [`F_SETLK`](FcntlArg::F_SETLK) except that if a shared or exclusive lock is blocked by + /// other locks, the process waits until the request can be satisfied. F_SETLKW(&'a libc::flock), + /// Get the first lock that blocks the lock description F_GETLK(&'a mut libc::flock), - #[cfg(any(target_os = "linux", target_os = "android"))] + /// Acquire or release an open file description lock + #[cfg(linux_android)] F_OFD_SETLK(&'a libc::flock), - #[cfg(any(target_os = "linux", target_os = "android"))] + /// Like [`F_OFD_SETLK`](FcntlArg::F_OFD_SETLK) except that if a conflicting lock is held on + /// the file, then wait for that lock to be released. + #[cfg(linux_android)] F_OFD_SETLKW(&'a libc::flock), - #[cfg(any(target_os = "linux", target_os = "android"))] + /// Determine whether it would be possible to create the given lock. If not, return details + /// about one existing lock that would prevent it. + #[cfg(linux_android)] F_OFD_GETLK(&'a mut libc::flock), - #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + /// Add seals to the file + #[cfg(any( + linux_android, + target_os = "freebsd" + ))] F_ADD_SEALS(SealFlag), - #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + /// Get seals associated with the file + #[cfg(any( + linux_android, + target_os = "freebsd" + ))] F_GET_SEALS, - #[cfg(any(target_os = "macos", target_os = "ios"))] + /// Asks the drive to flush all buffered data to permanent storage. + #[cfg(apple_targets)] F_FULLFSYNC, - #[cfg(any(target_os = "linux", target_os = "android"))] + /// fsync + issue barrier to drive + #[cfg(apple_targets)] + F_BARRIERFSYNC, + /// Return the capacity of a pipe + #[cfg(linux_android)] F_GETPIPE_SZ, - #[cfg(any(target_os = "linux", target_os = "android"))] + /// Change the capacity of a pipe + #[cfg(linux_android)] F_SETPIPE_SZ(c_int), + /// Look up the path of an open file descriptor, if possible. + #[cfg(any( + target_os = "netbsd", + target_os = "dragonfly", + apple_targets, + ))] + F_GETPATH(&'a mut PathBuf), + /// Look up the path of an open file descriptor, if possible. + #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] + F_KINFO(&'a mut PathBuf), + /// Return the full path without firmlinks of the fd. + #[cfg(apple_targets)] + F_GETPATH_NOFIRMLINK(&'a mut PathBuf), + /// Issue an advisory read async with no copy to user + #[cfg(apple_targets)] + F_RDADVISE(libc::radvisory), + /// Turn read ahead off/on + #[cfg(apple_targets)] + F_RDAHEAD(bool), + /// Pre-allocate storage with different policies on fd. + /// Note that we want a mutable reference for the OUT + /// fstore_t field fst_bytesalloc. + #[cfg(apple_targets)] + F_PREALLOCATE(&'a mut libc::fstore_t), + #[cfg(apple_targets)] + /// Get disk device information. In practice, + /// only the file offset data is set. + F_LOG2PHYS(&'a mut libc::off_t), + #[cfg(apple_targets)] + /// Get disk device information. In practice, + /// only the file offset data is set. + /// The difference with F_LOG2PHYS is the struct passed + /// is used as both IN/OUT as both its l2p_devoffset and + /// l2p_contigbytes can be used for more specific queries. + F_LOG2PHYS_EXT(&'a mut libc::log2phys), + /// Transfer any extra space in the file past the logical EOF + /// (as previously allocated via F_PREALLOCATE) to another file. + /// The other file is specified via a file descriptor as the lone extra argument. + /// Both descriptors must reference regular files in the same volume. + #[cfg(apple_targets)] + F_TRANSFEREXTENTS(RawFd), + /// Set or clear the read ahead (pre-fetch) amount for sequential access or + /// disable it with 0 or to system default for any value < 0. + /// It manages how the kernel caches file data. + #[cfg(target_os = "freebsd")] + F_READAHEAD(c_int), // TODO: Rest of flags } +/// Commands for use with [`fcntl`]. #[cfg(target_os = "redox")] #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum FcntlArg { + /// Duplicate the provided file descriptor F_DUPFD(RawFd), + /// Duplicate the provided file descriptor and set the `FD_CLOEXEC` flag on it. F_DUPFD_CLOEXEC(RawFd), + /// Get the close-on-exec flag associated with the file descriptor F_GETFD, + /// Set the close-on-exec flag associated with the file descriptor F_SETFD(FdFlag), // FD_FLAGS + /// Get descriptor status flags F_GETFL, + /// Set descriptor status flags F_SETFL(OFlag), // O_NONBLOCK } pub use self::FcntlArg::*; +/// Perform various operations on open file descriptors. +/// +/// # See Also +/// * [`fcntl`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html) // TODO: Figure out how to handle value fcntl returns -pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result { +pub fn fcntl(fd: Fd, arg: FcntlArg) -> Result { + use std::os::fd::AsRawFd; + + let fd = fd.as_fd().as_raw_fd(); 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), @@ -469,41 +879,128 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result { F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock), #[cfg(not(target_os = "redox"))] F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock), - #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] - F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()), - #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + #[cfg(any( + linux_android, + target_os = "freebsd" + ))] + F_ADD_SEALS(flag) => { + libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()) + } + #[cfg(any( + linux_android, + target_os = "freebsd" + ))] F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS), - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC), - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(apple_targets)] + F_BARRIERFSYNC => libc::fcntl(fd, libc::F_BARRIERFSYNC), + #[cfg(linux_android)] F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ), - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(linux_android)] F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size), + #[cfg(any( + target_os = "dragonfly", + target_os = "netbsd", + apple_targets, + ))] + F_GETPATH(path) => { + let mut buffer = vec![0; libc::PATH_MAX as usize]; + let res = libc::fcntl(fd, libc::F_GETPATH, buffer.as_mut_ptr()); + let ok_res = Errno::result(res)?; + let optr = CStr::from_bytes_until_nul(&buffer).unwrap(); + *path = PathBuf::from(OsString::from(optr.to_str().unwrap())); + return Ok(ok_res) + }, + #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] + F_KINFO(path) => { + let mut info: libc::kinfo_file = std::mem::zeroed(); + info.kf_structsize = std::mem::size_of::() as i32; + let res = libc::fcntl(fd, libc::F_KINFO, &mut info); + let ok_res = Errno::result(res)?; + let p = info.kf_path; + let u8_slice = slice::from_raw_parts(p.as_ptr().cast(), p.len()); + let optr = CStr::from_bytes_until_nul(u8_slice).unwrap(); + *path = PathBuf::from(OsString::from(optr.to_str().unwrap())); + return Ok(ok_res) + }, + #[cfg(apple_targets)] + F_GETPATH_NOFIRMLINK(path) => { + let mut buffer = vec![0; libc::PATH_MAX as usize]; + let res = libc::fcntl(fd, libc::F_GETPATH_NOFIRMLINK, buffer.as_mut_ptr()); + let ok_res = Errno::result(res)?; + let optr = CStr::from_bytes_until_nul(&buffer).unwrap(); + *path = PathBuf::from(OsString::from(optr.to_str().unwrap())); + return Ok(ok_res) + }, + #[cfg(apple_targets)] + F_RDADVISE(rad) => { + libc::fcntl(fd, libc::F_RDADVISE, &rad) + }, + #[cfg(apple_targets)] + F_LOG2PHYS(offset) => { + let mut info: libc::log2phys = std::mem::zeroed(); + let res = libc::fcntl(fd, libc::F_LOG2PHYS, &mut info); + let ok_res = Errno::result(res)?; + *offset = info.l2p_devoffset; + return Ok(ok_res) + } + #[cfg(apple_targets)] + F_LOG2PHYS_EXT(info) => { + libc::fcntl(fd, libc::F_LOG2PHYS_EXT, info) + } + #[cfg(apple_targets)] + F_RDAHEAD(on) => { + let val = if on { 1 } else { 0 }; + libc::fcntl(fd, libc::F_RDAHEAD, val) + }, + #[cfg(apple_targets)] + F_PREALLOCATE(st) => { + libc::fcntl(fd, libc::F_PREALLOCATE, st) + }, + #[cfg(apple_targets)] + F_TRANSFEREXTENTS(rawfd) => { + libc::fcntl(fd, libc::F_TRANSFEREXTENTS, rawfd) + }, + #[cfg(target_os = "freebsd")] + F_READAHEAD(val) => { + libc::fcntl(fd, libc::F_READAHEAD, val) + }, } }; Errno::result(res) } -// TODO: convert to libc_enum +/// Operations for use with [`Flock::lock`]. +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub enum FlockArg { + /// shared file lock LockShared, + /// exclusive file lock LockExclusive, + /// Unlock file Unlock, + /// Shared lock. Do not block when locking. LockSharedNonblock, + /// Exclusive lock. Do not block when locking. LockExclusiveNonblock, + #[allow(missing_docs)] + #[deprecated(since = "0.28.0", note = "Use FlockArg::Unlock instead")] UnlockNonblock, } -#[cfg(not(target_os = "redox"))] +#[allow(missing_docs)] +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +#[deprecated(since = "0.28.0", note = "`fcntl::Flock` should be used instead.")] pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> { use self::FlockArg::*; @@ -512,17 +1009,163 @@ 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) + } + #[allow(deprecated)] UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB), } }; Errno::result(res).map(drop) } + +/// Represents valid types for flock. +/// +/// # Safety +/// Types implementing this must not be `Clone`. +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +pub unsafe trait Flockable: std::os::fd::AsRawFd {} + +/// Represents an owned flock, which unlocks on drop. +/// +/// See [flock(2)](https://linux.die.net/man/2/flock) for details on locking semantics. +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +#[derive(Debug)] +pub struct Flock(T); + +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +impl Drop for Flock { + fn drop(&mut self) { + let res = Errno::result(unsafe { libc::flock(self.0.as_raw_fd(), libc::LOCK_UN) }); + if res.is_err() && !std::thread::panicking() { + panic!("Failed to remove flock: {}", res.unwrap_err()); + } + } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +impl Deref for Flock { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +impl DerefMut for Flock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +impl Flock { + /// Obtain a/an flock. + /// + /// # Example + /// ``` + /// # use std::io::Write; + /// # use std::fs::File; + /// # use nix::fcntl::{Flock, FlockArg}; + /// # fn do_stuff(file: File) { + /// let mut file = match Flock::lock(file, FlockArg::LockExclusive) { + /// Ok(l) => l, + /// Err(_) => return, + /// }; + /// + /// // Do stuff + /// let data = "Foo bar"; + /// _ = file.write(data.as_bytes()); + /// _ = file.sync_data(); + /// # } + pub fn lock(t: T, args: FlockArg) -> std::result::Result { + let flags = match args { + FlockArg::LockShared => libc::LOCK_SH, + FlockArg::LockExclusive => libc::LOCK_EX, + FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB, + FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB, + #[allow(deprecated)] + FlockArg::Unlock | FlockArg::UnlockNonblock => return Err((t, Errno::EINVAL)), + }; + match Errno::result(unsafe { libc::flock(t.as_raw_fd(), flags) }) { + Ok(_) => Ok(Self(t)), + Err(errno) => Err((t, errno)), + } + } + + /// Remove the lock and return the object wrapped within. + /// + /// # Example + /// ``` + /// # use std::fs::File; + /// # use nix::fcntl::{Flock, FlockArg}; + /// fn do_stuff(file: File) -> nix::Result<()> { + /// let mut lock = match Flock::lock(file, FlockArg::LockExclusive) { + /// Ok(l) => l, + /// Err((_,e)) => return Err(e), + /// }; + /// + /// // Do critical section + /// + /// // Unlock + /// let file = match lock.unlock() { + /// Ok(f) => f, + /// Err((_, e)) => return Err(e), + /// }; + /// + /// // Do anything else + /// + /// Ok(()) + /// } + pub fn unlock(self) -> std::result::Result { + let inner = unsafe { match Errno::result(libc::flock(self.0.as_raw_fd(), libc::LOCK_UN)) { + Ok(_) => std::ptr::read(&self.0), + Err(errno) => return Err((self, errno)), + }}; + + std::mem::forget(self); + Ok(inner) + } + + /// Relock the file. This can upgrade or downgrade the lock type. + /// + /// # Example + /// ``` + /// # use std::fs::File; + /// # use nix::fcntl::{Flock, FlockArg}; + /// # use tempfile::tempfile; + /// let f: std::fs::File = tempfile().unwrap(); + /// let locked_file = Flock::lock(f, FlockArg::LockExclusive).unwrap(); + /// // Do stuff, then downgrade the lock + /// locked_file.relock(FlockArg::LockShared).unwrap(); + /// ``` + pub fn relock(&self, arg: FlockArg) -> Result<()> { + let flags = match arg { + FlockArg::LockShared => libc::LOCK_SH, + FlockArg::LockExclusive => libc::LOCK_EX, + FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB, + FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB, + #[allow(deprecated)] + FlockArg::Unlock | FlockArg::UnlockNonblock => return Err(Errno::EINVAL), + }; + Errno::result(unsafe { libc::flock(self.as_raw_fd(), flags) }).map(drop) + } +} + +// Safety: `File` is not [std::clone::Clone]. +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +unsafe impl Flockable for std::fs::File {} + +// Safety: `OwnedFd` is not [std::clone::Clone]. +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +unsafe impl Flockable for OwnedFd {} +} + +#[cfg(linux_android)] #[cfg(feature = "zerocopy")] libc_bitflags! { /// Additional flags to `splice` and friends. @@ -552,56 +1195,85 @@ feature! { /// /// 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(linux_android, target_os = "freebsd"))] +pub fn copy_file_range( + fd_in: Fd1, + off_in: Option<&mut i64>, + fd_out: Fd2, + off_out: Option<&mut i64>, len: usize, ) -> Result { + use std::os::fd::AsRawFd; + 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.as_fd().as_raw_fd(), + off_in, + fd_out.as_fd().as_raw_fd(), + off_out, + len, + 0, + ) + }; + } + } Errno::result(ret).map(|r| r as usize) } -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn splice( - fd_in: RawFd, +/// Splice data to/from a pipe +/// +/// # See Also +/// *[`splice`](https://man7.org/linux/man-pages/man2/splice.2.html) +#[cfg(linux_android)] +pub fn splice( + fd_in: Fd1, off_in: Option<&mut libc::loff_t>, - fd_out: RawFd, + fd_out: Fd2, off_out: Option<&mut libc::loff_t>, len: usize, flags: SpliceFFlags, ) -> Result { + use std::os::fd::AsRawFd; + let off_in = off_in .map(|offset| offset as *mut libc::loff_t) .unwrap_or(ptr::null_mut()); @@ -609,27 +1281,45 @@ 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.as_fd().as_raw_fd(), off_in, fd_out.as_fd().as_raw_fd(), off_out, len, flags.bits()) + }; Errno::result(ret).map(|r| r as usize) } -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn tee(fd_in: RawFd, fd_out: RawFd, len: usize, flags: SpliceFFlags) -> Result { - let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) }; +/// Duplicate pipe content +/// +/// # See Also +/// *[`tee`](https://man7.org/linux/man-pages/man2/tee.2.html) +#[cfg(linux_android)] +pub fn tee( + fd_in: Fd1, + fd_out: Fd2, + len: usize, + flags: SpliceFFlags, +) -> Result { + use std::os::fd::AsRawFd; + + let ret = unsafe { libc::tee(fd_in.as_fd().as_raw_fd(), fd_out.as_fd().as_raw_fd(), len, flags.bits()) }; Errno::result(ret).map(|r| r as usize) } -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn vmsplice( - fd: RawFd, +/// Splice user pages to/from a pipe +/// +/// # See Also +/// *[`vmsplice`](https://man7.org/linux/man-pages/man2/vmsplice.2.html) +#[cfg(linux_android)] +pub fn vmsplice( + fd: F, iov: &[std::io::IoSlice<'_>], - flags: SpliceFFlags - ) -> Result -{ + flags: SpliceFFlags, +) -> Result { + use std::os::fd::AsRawFd; + let ret = unsafe { libc::vmsplice( - fd, - iov.as_ptr() as *const libc::iovec, + fd.as_fd().as_raw_fd(), + iov.as_ptr().cast(), iov.len(), flags.bits(), ) @@ -666,7 +1356,7 @@ libc_bitflags!( FALLOC_FL_INSERT_RANGE; /// Shared file data extants are made private to the file. /// - /// Gaurantees that a subsequent write will not fail due to lack of space. + /// Guarantees that a subsequent write will not fail due to lack of space. FALLOC_FL_UNSHARE_RANGE; } ); @@ -680,13 +1370,15 @@ feature! { /// file referred to by fd. #[cfg(target_os = "linux")] #[cfg(feature = "fs")] -pub fn fallocate( - fd: RawFd, +pub fn fallocate( + fd: Fd, mode: FallocateFlags, offset: libc::off_t, len: libc::off_t, ) -> Result<()> { - let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) }; + use std::os::fd::AsRawFd; + + let res = unsafe { libc::fallocate(fd.as_fd().as_raw_fd(), mode.bits(), offset, len) }; Errno::result(res).map(drop) } @@ -698,16 +1390,22 @@ pub struct SpacectlRange(pub libc::off_t, pub libc::off_t); #[cfg(any(target_os = "freebsd"))] impl SpacectlRange { + /// Is the range empty? + /// + /// After a successful call to [`fspacectl`], A value of `true` for `SpacectlRange::is_empty` + /// indicates that the operation is complete. #[inline] pub fn is_empty(&self) -> bool { self.1 == 0 } + /// Remaining length of the range #[inline] pub fn len(&self) -> libc::off_t { self.1 } + /// Next file offset to operate on #[inline] pub fn offset(&self) -> libc::off_t { self.0 @@ -747,22 +1445,30 @@ impl SpacectlRange { /// f.write_all(INITIAL).unwrap(); /// let mut range = SpacectlRange(3, 6); /// while (!range.is_empty()) { -/// range = fspacectl(f.as_raw_fd(), range).unwrap(); +/// range = fspacectl(&f, 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, +#[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined +pub fn fspacectl(fd: Fd, range: SpacectlRange) -> Result { + use std::os::fd::AsRawFd; + + let mut rqsr = libc::spacectl_range { + r_offset: range.0, + r_len: range.1, + }; + let res = unsafe { + libc::fspacectl( + fd.as_fd().as_raw_fd(), libc::SPACECTL_DEALLOC, // Only one command is supported ATM &rqsr, - 0, // No flags are currently supported - &mut rqsr - )}; + 0, // No flags are currently supported + &mut rqsr, + ) + }; Errno::result(res).map(|_| SpacectlRange(rqsr.r_offset, rqsr.r_len)) } @@ -791,32 +1497,41 @@ pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result { /// const INITIAL: &[u8] = b"0123456789abcdef"; /// let mut f = tempfile().unwrap(); /// f.write_all(INITIAL).unwrap(); -/// fspacectl_all(f.as_raw_fd(), 3, 6).unwrap(); +/// fspacectl_all(&f, 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}; +#[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined +pub fn fspacectl_all( + fd: Fd, + offset: libc::off_t, + len: libc::off_t, +) -> Result<()> { + use std::os::fd::AsRawFd; + + let mut rqsr = libc::spacectl_range { + r_offset: offset, + r_len: len, + }; while rqsr.r_len > 0 { - let res = unsafe { libc::fspacectl( - fd, + let res = unsafe { + libc::fspacectl( + fd.as_fd().as_raw_fd(), libc::SPACECTL_DEALLOC, // Only one command is supported ATM &rqsr, - 0, // No flags are currently supported - &mut rqsr - )}; + 0, // No flags are currently supported + &mut rqsr, + ) + }; Errno::result(res)?; } Ok(()) } #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", target_os = "wasi", @@ -825,58 +1540,80 @@ pub fn fspacectl_all(fd: RawFd, offset: libc::off_t, len: libc::off_t) ))] mod posix_fadvise { use crate::errno::Errno; - use std::os::unix::io::RawFd; use crate::Result; #[cfg(feature = "fs")] libc_enum! { + /// The specific advice provided to [`posix_fadvise`]. #[repr(i32)] #[non_exhaustive] #[cfg_attr(docsrs, doc(cfg(feature = "fs")))] pub enum PosixFadviseAdvice { + /// Revert to the default data access behavior. POSIX_FADV_NORMAL, + /// The file data will be accessed sequentially. POSIX_FADV_SEQUENTIAL, + /// A hint that file data will be accessed randomly, and prefetching is likely not + /// advantageous. POSIX_FADV_RANDOM, + /// The specified data will only be accessed once and then not reused. POSIX_FADV_NOREUSE, + /// The specified data will be accessed in the near future. POSIX_FADV_WILLNEED, + /// The specified data will not be accessed in the near future. POSIX_FADV_DONTNEED, } } feature! { #![feature = "fs"] - pub fn posix_fadvise( - fd: RawFd, + /// Allows a process to describe to the system its data access behavior for an open file + /// descriptor. + /// + /// # See Also + /// * [`posix_fadvise`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fadvise.html) + pub fn posix_fadvise( + fd: Fd, offset: libc::off_t, len: libc::off_t, advice: PosixFadviseAdvice, ) -> Result<()> { - let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) }; + use std::os::fd::AsRawFd; + + let res = unsafe { libc::posix_fadvise(fd.as_fd().as_raw_fd(), offset, len, advice as libc::c_int) }; if res == 0 { Ok(()) } else { - Err(Errno::from_i32(res)) + Err(Errno::from_raw(res)) } } } } +/// Pre-allocate storage for a range in a file +/// +/// # See Also +/// * [`posix_fallocate`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_fallocate.html) #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "dragonfly", + linux_android, + freebsdlike, target_os = "emscripten", target_os = "fuchsia", target_os = "wasi", - target_os = "freebsd" ))] -pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Result<()> { - let res = unsafe { libc::posix_fallocate(fd, offset, len) }; +pub fn posix_fallocate( + fd: Fd, + offset: libc::off_t, + len: libc::off_t, +) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::posix_fallocate(fd.as_fd().as_raw_fd(), offset, len) }; match Errno::result(res) { Err(err) => Err(err), Ok(0) => Ok(()), - Ok(errno) => Err(Errno::from_i32(errno)), + Ok(errno) => Err(Errno::from_raw(errno)), } } } diff --git a/src/features.rs b/src/features.rs index 39d17604..b472f803 100644 --- a/src/features.rs +++ b/src/features.rs @@ -1,11 +1,12 @@ //! Feature tests for OS functionality pub use self::os::*; -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(linux_android, target_os = "emscripten"))] 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 @@ -72,15 +73,15 @@ mod os { } fn kernel_version() -> Result { - static mut KERNEL_VERS: usize = 0; + static KERNEL_VERS: AtomicUsize = AtomicUsize::new(0); + let mut kernel_vers = KERNEL_VERS.load(Ordering::Relaxed); - unsafe { - if KERNEL_VERS == 0 { - KERNEL_VERS = parse_kernel_version()?; - } - - Ok(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 @@ -91,18 +92,18 @@ mod os { } #[test] - pub fn test_parsing_kernel_version() { + fn test_parsing_kernel_version() { assert!(kernel_version().unwrap() > 0); } } #[cfg(any( - target_os = "dragonfly", // Since ??? - target_os = "freebsd", // Since 10.0 + freebsdlike, // FreeBSD since 10.0 DragonFlyBSD since ??? + netbsdlike, // NetBSD since 6.0 OpenBSD since 5.7 + target_os = "hurd", // Since glibc 2.28 target_os = "illumos", // Since ??? - target_os = "netbsd", // Since 6.0 - target_os = "openbsd", // Since 5.7 target_os = "redox", // Since 1-july-2020 + target_os = "cygwin", ))] mod os { /// Check if the OS supports atomic close-on-exec for sockets @@ -112,8 +113,8 @@ mod os { } #[cfg(any( - target_os = "macos", - target_os = "ios", + target_os = "aix", + apple_targets, target_os = "fuchsia", target_os = "haiku", target_os = "solaris" diff --git a/src/ifaddrs.rs b/src/ifaddrs.rs index 70b50b01..991a073e 100644 --- a/src/ifaddrs.rs +++ b/src/ifaddrs.rs @@ -4,7 +4,7 @@ //! of interfaces and their associated addresses. use cfg_if::cfg_if; -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(apple_targets)] use std::convert::TryFrom; use std::ffi; use std::iter::Iterator; @@ -33,7 +33,7 @@ pub struct InterfaceAddress { } cfg_if! { - if #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux"))] { + if #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] { fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { info.ifa_ifu } @@ -53,7 +53,7 @@ cfg_if! { /// 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"))] +#[cfg(apple_targets)] unsafe fn workaround_xnu_bug(info: &libc::ifaddrs) -> Option { let src_sock = info.ifa_netmask; if src_sock.is_null() { @@ -62,22 +62,24 @@ unsafe fn workaround_xnu_bug(info: &libc::ifaddrs) -> Option { 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(), - ); + let dst_sock = unsafe { + // memcpy only sa_len bytes, assume the rest is zero + std::ptr::copy_nonoverlapping( + src_sock as *const u8, + dst_sock.as_mut_ptr().cast(), + (*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(); + // Initialize ss_len to sizeof(libc::sockaddr_storage). + (*dst_sock.as_mut_ptr()).ss_len = + u8::try_from(mem::size_of::()).unwrap(); + 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) + unsafe { SockaddrStorage::from_raw(dst_sock_ptr, None) } } impl InterfaceAddress { @@ -85,14 +87,16 @@ impl InterfaceAddress { fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress { let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) }; let address = unsafe { SockaddrStorage::from_raw(info.ifa_addr, None) }; - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] let netmask = unsafe { workaround_xnu_bug(info) }; - #[cfg(not(any(target_os = "ios", target_os = "macos")))] + #[cfg(not(apple_targets))] 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), + interface_name: ifname.to_string_lossy().into_owned(), + flags: InterfaceFlags::from_bits_truncate( + info.ifa_flags as IflagsType, + ), address, netmask, broadcast: None, diff --git a/src/kmod.rs b/src/kmod.rs index 1fa6c170..5cf2ed2c 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,15 +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, +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(), ) @@ -102,7 +102,11 @@ libc_bitflags!( /// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html) /// for a detailed description how these flags work. pub struct DeleteModuleFlags: libc::c_int { + /// `delete_module` will return immediately, with an error, if the module has a nonzero + /// reference count. O_NONBLOCK; + /// `delete_module` will unload the module immediately, regardless of whether it has a + /// nonzero reference count. O_TRUNC; } ); diff --git a/src/lib.rs b/src/lib.rs index c0cfbd01..ec1d2600 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,11 +12,12 @@ //! * `dir` - Stuff relating to directory iteration //! * `env` - Manipulate environment variables //! * `event` - Event-driven APIs, like `kqueue` and `epoll` +//! * `fanotify` - Linux's `fanotify` filesystem events monitoring API //! * `feature` - Query characteristics of the OS at runtime //! * `fs` - File system functionality //! * `hostname` - Get and set the system's hostname //! * `inotify` - Linux's `inotify` file system notification API -//! * `ioctl` - The `ioctl` syscall, and wrappers for my specific instances +//! * `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 @@ -33,6 +34,7 @@ //! * `sched` - Manipulate process's scheduling //! * `socket` - Sockets, whether for networking or local use //! * `signal` - Send and receive signals to processes +//! * `syslog` - System logging //! * `term` - Terminal control APIs //! * `time` - Query the operating system's clocks //! * `ucontext` - User thread context @@ -41,20 +43,65 @@ //! * `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))] +// A clear document is a good document no matter if it has a summary in its +// first paragraph or not. +#![allow(clippy::too_long_first_doc_paragraph)] #![recursion_limit = "500"] #![deny(unused)] +#![deny(unexpected_cfgs)] #![allow(unused_macros)] -#![cfg_attr(not(feature = "default"), allow(unused_imports))] +#![cfg_attr( + not(all( + feature = "acct", + feature = "aio", + feature = "dir", + feature = "env", + feature = "event", + feature = "fanotify", + 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 = "syslog", + 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)] -#![allow(clippy::bad_bit_mask)] +#![deny(unsafe_op_in_unsafe_fn)] +// I found the change suggested by this rules could hurt code readability. I cannot +// remeber every type's default value, in such cases, it forces me to open +// the std doc to insepct the Default value, which is unnecessary with +// `.unwrap_or(value)`. +#![allow(clippy::unwrap_or_default)] // Re-exported external crates pub use libc; @@ -81,30 +128,22 @@ feature! { #[deny(missing_docs)] pub mod features; } -#[allow(missing_docs)] pub mod fcntl; feature! { #![feature = "net"] - #[cfg(any(target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd"))] + #[cfg(any(linux_android, + bsd, + solarish))] #[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"))] +#[cfg(linux_android)] feature! { #![feature = "kmod"] - #[allow(missing_docs)] pub mod kmod; } feature! { @@ -112,9 +151,8 @@ feature! { pub mod mount; } #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", + freebsdlike, + all(target_os = "linux", not(target_env = "ohos")), target_os = "netbsd" ))] feature! { @@ -138,23 +176,46 @@ feature! { pub mod sys; feature! { #![feature = "time"] - #[allow(missing_docs)] pub mod time; } // This can be implemented for other platforms as soon as libc // provides bindings for them. #[cfg(all( target_os = "linux", - any(target_arch = "s390x", target_arch = "x86", target_arch = "x86_64") + 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; +#[cfg(any(feature = "poll", feature = "event"))] +mod poll_timeout; + +#[cfg(any( + target_os = "freebsd", + target_os = "haiku", + target_os = "linux", + target_os = "netbsd", + apple_targets +))] +feature! { + #![feature = "process"] + pub mod spawn; +} + +feature! { + #![feature = "syslog"] + pub mod syslog; +} + use std::ffi::{CStr, CString, OsStr}; use std::mem::MaybeUninit; use std::os::unix::ffi::OsStrExt; @@ -175,7 +236,7 @@ pub type Result = result::Result; /// * `Eq` /// * Small size /// * Represents all of the system's errnos, instead of just the most common -/// ones. +/// ones. pub type Error = Errno; /// Common trait used to represent file system paths by many Nix functions. @@ -259,7 +320,7 @@ impl NixPath for [u8] { 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. + // longer than ~300 bytes. See 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: @@ -271,7 +332,7 @@ impl NixPath for [u8] { } let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit(); - let buf_ptr = buf.as_mut_ptr() as *mut u8; + let buf_ptr = buf.as_mut_ptr().cast(); unsafe { ptr::copy_nonoverlapping(self.as_ptr(), buf_ptr, self.len()); @@ -332,3 +393,21 @@ impl NixPath for PathBuf { self.as_os_str().with_nix_path(f) } } + +/// Like `NixPath::with_nix_path()`, but allow the `path` argument to be optional. +/// +/// A NULL pointer will be provided if `path.is_none()`. +#[cfg(any( + all(apple_targets, feature = "mount"), + all(linux_android, any(feature = "mount", feature = "fanotify")) +))] +pub(crate) fn with_opt_nix_path(path: Option<&P>, f: F) -> Result +where + P: ?Sized + NixPath, + F: FnOnce(*const libc::c_char) -> T, +{ + match path { + Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), + None => Ok(f(ptr::null())), + } +} diff --git a/src/macros.rs b/src/macros.rs index 99e0de88..3010a1a0 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -27,9 +27,9 @@ macro_rules! feature { /// /// PROT_WRITE enables write protect /// PROT_WRITE; /// PROT_EXEC; -/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// #[cfg(linux_android)] /// PROT_GROWSDOWN; -/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// #[cfg(linux_android)] /// PROT_GROWSUP; /// } /// } @@ -63,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 { $( @@ -87,15 +89,14 @@ macro_rules! libc_bitflags { /// PROT_READ, /// PROT_WRITE, /// PROT_EXEC, -/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// #[cfg(linux_android)] /// PROT_GROWSDOWN, -/// #[cfg(any(target_os = "linux", target_os = "android"))] +/// #[cfg(linux_android)] /// PROT_GROWSUP, /// } /// } /// ``` // Some targets don't use all rules. -#[allow(unknown_lints)] #[allow(unused_macro_rules)] macro_rules! libc_enum { // Exit rule. @@ -133,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/apple.rs b/src/mount/apple.rs new file mode 100644 index 00000000..ce0ab1e9 --- /dev/null +++ b/src/mount/apple.rs @@ -0,0 +1,111 @@ +use crate::{Errno, NixPath, Result}; +use libc::c_int; + +libc_bitflags!( + /// Used with [`mount()`] and [`unmount()`]. + pub struct MntFlags: c_int { + /// Do not interpret special files on the filesystem. + MNT_NODEV; + /// Enable data protection on the filesystem if the filesystem is configured for it. + MNT_CPROTECT; + /// file system is quarantined + MNT_QUARANTINE; + /// filesystem is stored locally + MNT_LOCAL; + /// quotas are enabled on filesystem + MNT_QUOTA; + /// identifies the root filesystem + MNT_ROOTFS; + /// file system is not appropriate path to user data + MNT_DONTBROWSE; + /// VFS will ignore ownership information on filesystem objects + MNT_IGNORE_OWNERSHIP; + /// filesystem was mounted by automounter + MNT_AUTOMOUNTED; + /// filesystem is journaled + MNT_JOURNALED; + /// Don't allow user extended attributes + MNT_NOUSERXATTR; + /// filesystem should defer writes + MNT_DEFWRITE; + /// don't block unmount if not responding + MNT_NOBLOCK; + /// file system is exported + MNT_EXPORTED; + /// file system written asynchronously + MNT_ASYNC; + /// Force a read-write mount even if the file system appears to be + /// unclean. + MNT_FORCE; + /// MAC support for objects. + MNT_MULTILABEL; + /// Do not update access times. + MNT_NOATIME; + /// Disallow program execution. + MNT_NOEXEC; + /// Do not honor setuid or setgid bits on files when executing them. + MNT_NOSUID; + /// Mount read-only. + MNT_RDONLY; + /// Causes the vfs subsystem to update its data structures pertaining to + /// the specified already mounted file system. + MNT_RELOAD; + /// Create a snapshot of the file system. + MNT_SNAPSHOT; + /// All I/O to the file system should be done synchronously. + MNT_SYNCHRONOUS; + /// Union with underlying fs. + MNT_UNION; + /// Indicates that the mount command is being applied to an already + /// mounted file system. + MNT_UPDATE; + } +); + +/// Mount a file system. +/// +/// # Arguments +/// - `source` - Specifies the file system. e.g. `/dev/sd0`. +/// - `target` - Specifies the destination. e.g. `/mnt`. +/// - `flags` - Optional flags controlling the mount. +/// - `data` - Optional file system specific data. +/// +/// # see also +/// [`mount`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mount.2.html) +pub fn mount< + P1: ?Sized + NixPath, + P2: ?Sized + NixPath, + P3: ?Sized + NixPath, +>( + source: &P1, + target: &P2, + flags: MntFlags, + data: Option<&P3>, +) -> Result<()> { + let res = source.with_nix_path(|s| { + target.with_nix_path(|t| { + crate::with_opt_nix_path(data, |d| unsafe { + libc::mount( + s.as_ptr(), + t.as_ptr(), + flags.bits(), + d.cast_mut().cast(), + ) + }) + }) + })???; + + Errno::result(res).map(drop) +} + +/// Umount the file system mounted at `target`. +pub fn unmount

    (target: &P, flags: MntFlags) -> Result<()> +where + P: ?Sized + NixPath, +{ + let res = target.with_nix_path(|cstr| unsafe { + libc::unmount(cstr.as_ptr(), flags.bits()) + })?; + + Errno::result(res).map(drop) +} diff --git a/src/mount/bsd.rs b/src/mount/bsd_without_apple.rs similarity index 87% rename from src/mount/bsd.rs rename to src/mount/bsd_without_apple.rs index 771be7f4..b387470c 100644 --- a/src/mount/bsd.rs +++ b/src/mount/bsd_without_apple.rs @@ -17,36 +17,29 @@ libc_bitflags!( 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())))] + #[cfg(target_os = "freebsd")] MNT_MULTILABEL; /// Disable read clustering. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] MNT_NOCLUSTERR; /// Disable write clustering. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] 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; @@ -55,8 +48,7 @@ libc_bitflags!( /// Do not honor setuid or setgid bits on files when executing them. MNT_NOSUID; /// Do not follow symlinks. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] MNT_NOSYMFOLLOW; /// Mount read-only. MNT_RDONLY; @@ -66,39 +58,28 @@ libc_bitflags!( /// Create a snapshot of the file system. /// /// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs) - #[cfg(any(target_os = "macos", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(target_os = "freebsd")] MNT_SNAPSHOT; /// Using soft updates. - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(freebsdlike, netbsdlike))] 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())))] + #[cfg(freebsdlike)] MNT_SUIDDIR; /// All I/O to the file system should be done synchronously. MNT_SYNCHRONOUS; /// Union with underlying fs. #[cfg(any( - target_os = "macos", target_os = "freebsd", target_os = "netbsd" ))] - #[cfg_attr(docsrs, doc(cfg(all())))] MNT_UNION; /// Indicates that the mount command is being applied to an already /// mounted file system. MNT_UPDATE; /// Check vnode use counts. #[cfg(target_os = "freebsd")] - #[cfg_attr(docsrs, doc(cfg(all())))] MNT_NONBUSY; } ); @@ -198,7 +179,6 @@ 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> { // n.b. notgull: In reality, this is a list that contains @@ -210,12 +190,11 @@ pub struct Nmount<'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_base: val.as_ptr().cast_mut().cast(), iov_len: val.len(), }); self.is_owned.push(is_owned); @@ -386,23 +365,20 @@ impl<'a> Nmount<'a> { // 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_base: errmsg.as_mut_ptr().cast(), iov_len: errmsg.len(), }); let niov = self.iov.len() as c_uint; let iovp = self.iov.as_mut_ptr(); - let res = unsafe { libc::nmount(iovp, niov, flags.bits) }; + let res = unsafe { libc::nmount(iovp, niov, flags.bits()) }; match Errno::result(res) { Ok(_) => Ok(()), Err(error) => { - let errmsg = match errmsg.iter().position(|&x| x == 0) { - None => None, - Some(0) => None, - Some(n) => { - let sl = &errmsg[0..n + 1]; - Some(CStr::from_bytes_with_nul(sl).unwrap()) - } + let errmsg = if errmsg[0] == 0 { + None + } else { + CStr::from_bytes_until_nul(&errmsg[..]).ok() }; Err(NmountError::new(error, errmsg)) } @@ -411,7 +387,7 @@ impl<'a> Nmount<'a> { } #[cfg(target_os = "freebsd")] -impl<'a> Drop for Nmount<'a> { +impl Drop for Nmount<'_> { fn drop(&mut self) { for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) { if *is_owned { @@ -446,7 +422,7 @@ where P: ?Sized + NixPath, { let res = mountpoint.with_nix_path(|cstr| unsafe { - libc::unmount(cstr.as_ptr(), flags.bits) + 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 cf6a60b0..3c271507 100644 --- a/src/mount/linux.rs +++ b/src/mount/linux.rs @@ -1,9 +1,9 @@ -#![allow(missing_docs)] 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,36 +27,80 @@ 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; } ); +/// 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, @@ -69,26 +113,15 @@ pub fn mount< flags: MsFlags, data: Option<&P4>, ) -> Result<()> { - fn with_opt_nix_path(p: Option<&P>, f: F) -> Result - where - P: ?Sized + NixPath, - F: FnOnce(*const libc::c_char) -> T, - { - match p { - Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), - None => Ok(f(std::ptr::null())), - } - } - - let res = with_opt_nix_path(source, |s| { + let res = crate::with_opt_nix_path(source, |s| { target.with_nix_path(|t| { - with_opt_nix_path(fstype, |ty| { - with_opt_nix_path(data, |d| unsafe { + crate::with_opt_nix_path(fstype, |ty| { + crate::with_opt_nix_path(data, |d| unsafe { libc::mount( s, t.as_ptr(), ty, - flags.bits, + flags.bits(), d as *const libc::c_void, ) }) @@ -99,6 +132,7 @@ pub fn mount< Errno::result(res).map(drop) } +/// Unmount the file system mounted at `target`. pub fn umount(target: &P) -> Result<()> { let res = target.with_nix_path(|cstr| unsafe { libc::umount(cstr.as_ptr()) })?; @@ -106,9 +140,12 @@ pub fn umount(target: &P) -> Result<()> { 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) + 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 e98b49c3..41e7b3ec 100644 --- a/src/mount/mod.rs +++ b/src/mount/mod.rs @@ -1,26 +1,18 @@ //! Mount file systems -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(linux_android)] mod linux; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] pub use self::linux::*; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" -))] -#[cfg_attr(docsrs, doc(cfg(all())))] -mod bsd; +#[cfg(bsd_without_apple)] +mod bsd_without_apple; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" -))] -pub use self::bsd::*; +#[cfg(bsd_without_apple)] +pub use self::bsd_without_apple::*; + +#[cfg(apple_targets)] +mod apple; + +#[cfg(apple_targets)] +pub use self::apple::*; diff --git a/src/mqueue.rs b/src/mqueue.rs index 33599bf9..eea45be7 100644 --- a/src/mqueue.rs +++ b/src/mqueue.rs @@ -9,16 +9,16 @@ //! use nix::sys::stat::Mode; //! //! const MSG_SIZE: mq_attr_member_t = 32; -//! let mq_name= CString::new("/a_nix_test_queue").unwrap(); +//! 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 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 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(); @@ -31,12 +31,20 @@ //! [Further reading and details on the C API](https://man7.org/linux/man-pages/man7/mq_overview.7.html) use crate::errno::Errno; +use crate::NixPath; use crate::Result; use crate::sys::stat::Mode; -use libc::{self, c_char, mqd_t, size_t}; -use std::ffi::CStr; +use libc::{self, 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! { /// Used with [`mq_open`]. @@ -80,11 +88,9 @@ pub struct MqdT(mqd_t); // 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 { @@ -139,33 +145,41 @@ impl MqAttr { /// 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: &CStr, +pub fn mq_open

    ( + name: &P, oflag: MQ_OFlag, mode: Mode, attr: Option<&MqAttr>, -) -> Result { - let res = match attr { +) -> Result +where + P: ?Sized + NixPath, +{ + let res = name.with_nix_path(|cstr| match attr { Some(mq_attr) => unsafe { libc::mq_open( - name.as_ptr(), + 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()) }, - }; + 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: &CStr) -> 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) } @@ -189,7 +203,7 @@ pub fn mq_receive( let res = unsafe { libc::mq_receive( mqdes.0, - message.as_mut_ptr() as *mut c_char, + message.as_mut_ptr().cast(), len, msg_prio as *mut u32, ) @@ -197,17 +211,38 @@ pub fn mq_receive( 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().cast(), + 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: &MqdT, message: &[u8], msq_prio: u32) -> Result<()> { let res = unsafe { - libc::mq_send( - mqdes.0, - message.as_ptr() as *const c_char, - message.len(), - msq_prio, - ) + libc::mq_send(mqdes.0, message.as_ptr().cast(), message.len(), msq_prio) }; Errno::result(res).map(drop) } @@ -225,9 +260,11 @@ pub fn mq_getattr(mqd: &MqdT) -> Result { }) } -/// Set the attributes of the message queue. Only `O_NONBLOCK` can be set, everything else will be ignored -/// Returns the old attributes -/// It is recommend to use the `mq_set_nonblock()` and `mq_remove_nonblock()` convenience functions as they are easier to use +/// Set the attributes of the message queue. Only `O_NONBLOCK` can be set, +/// everything else will be ignored. Returns the old attributes. +/// +/// It is recommend to use the `mq_set_nonblock()` and `mq_remove_nonblock()` +/// convenience functions as they are easier to use. /// /// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_setattr.html) pub fn mq_setattr(mqd: &MqdT, newattr: &MqAttr) -> Result { @@ -274,3 +311,43 @@ pub fn mq_remove_nonblock(mqd: &MqdT) -> Result { ); 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 b2423bc6..cae6e3f8 100644 --- a/src/net/if_.rs +++ b/src/net/if_.rs @@ -3,10 +3,18 @@ //! Uses Linux and/or POSIX functions to resolve interface names like "eth0" //! or "socan1" into device numbers. -use crate::{Error, NixPath, Result}; -use libc::c_uint; +use std::{ffi::{CStr, CString}, fmt}; +use crate::{errno::Errno, Error, NixPath, Result}; +use libc::{c_uint, IF_NAMESIZE}; -/// Resolve an interface into a interface number. +#[cfg(not(solarish))] +/// type alias for InterfaceFlags +pub type IflagsType = libc::c_int; +#[cfg(solarish)] +/// type alias for InterfaceFlags +pub type IflagsType = libc::c_longlong; + +/// Resolve an interface into an interface number. pub fn if_nametoindex(name: &P) -> Result { let if_index = name .with_nix_path(|name| unsafe { libc::if_nametoindex(name.as_ptr()) })?; @@ -18,324 +26,254 @@ pub fn if_nametoindex(name: &P) -> Result { } } +/// Resolve an interface number into an interface. +pub fn if_indextoname(index: c_uint) -> Result { + // We need to allocate this anyway, so doing it directly is faster. + let mut buf = vec![0u8; IF_NAMESIZE]; + + let return_buf = unsafe { + libc::if_indextoname(index, buf.as_mut_ptr().cast()) + }; + + Errno::result(return_buf.cast())?; + Ok(CStr::from_bytes_until_nul(buf.as_slice()).unwrap().to_owned()) +} + libc_bitflags!( /// Standard interface flags, used by `getifaddrs` - pub struct InterfaceFlags: libc::c_int { + pub struct InterfaceFlags: IflagsType { + /// Interface is running. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_UP; + IFF_UP as IflagsType; /// Valid broadcast address set. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_BROADCAST; + IFF_BROADCAST as IflagsType; /// Internal debugging flag. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - #[cfg(not(target_os = "haiku"))] - IFF_DEBUG; + #[cfg(not(any(target_os = "haiku", target_os = "cygwin")))] + IFF_DEBUG as IflagsType; /// Interface is a loopback interface. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_LOOPBACK; + IFF_LOOPBACK as IflagsType; /// Interface is a point-to-point link. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_POINTOPOINT; + IFF_POINTOPOINT as IflagsType; /// Avoid use of trailers. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - #[cfg(any(target_os = "android", + #[cfg(any( + linux_android, + solarish, + apple_targets, target_os = "fuchsia", - target_os = "ios", - target_os = "linux", - target_os = "macos", target_os = "netbsd", - target_os = "illumos", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_NOTRAILERS; + target_os = "cygwin"))] + IFF_NOTRAILERS as IflagsType; /// Interface manages own routes. #[cfg(any(target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_SMART; + IFF_SMART as IflagsType; /// Resources allocated. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - #[cfg(any(target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", + #[cfg(any( + linux_android, + bsd, + solarish, target_os = "fuchsia", - target_os = "illumos", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_RUNNING; + target_os = "cygwin"))] + IFF_RUNNING as IflagsType; /// No arp protocol, L2 destination address not set. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_NOARP; + IFF_NOARP as IflagsType; /// Interface is in promiscuous mode. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_PROMISC; + IFF_PROMISC as IflagsType; /// Receive all multicast packets. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_ALLMULTI; + #[cfg(not(target_os = "cygwin"))] + IFF_ALLMULTI as IflagsType; /// 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())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] IFF_MASTER; /// transmission in progress, tx hardware queue is full - #[cfg(any(target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "ios"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(target_os = "freebsd", apple_targets, netbsdlike))] IFF_OACTIVE; /// Protocol code on board. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_INTELLIGENT; + #[cfg(solarish)] + IFF_INTELLIGENT as IflagsType; /// 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())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] IFF_SLAVE; /// Can't hear own transmissions. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] IFF_SIMPLEX; /// Supports multicast. (see /// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html)) - IFF_MULTICAST; + IFF_MULTICAST as IflagsType; /// Per link layer defined bit. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "ios"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] IFF_LINK0; /// Multicast using broadcast. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_MULTI_BCAST; + #[cfg(solarish)] + IFF_MULTI_BCAST as IflagsType; /// 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())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] IFF_PORTSEL; /// Per link layer defined bit. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "ios"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] IFF_LINK1; /// Non-unique address. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_UNNUMBERED; + #[cfg(solarish)] + IFF_UNNUMBERED as IflagsType; /// 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())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] IFF_AUTOMEDIA; /// Per link layer defined bit. - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "ios"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] 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())))] + #[cfg(any(freebsdlike, apple_targets))] IFF_ALTPHYS; /// DHCP controls interface. - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_DHCPRUNNING; + #[cfg(solarish)] + IFF_DHCPRUNNING as IflagsType; /// 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())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] IFF_DYNAMIC; /// Do not advertise. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_PRIVATE; + #[cfg(solarish)] + IFF_PRIVATE as IflagsType; /// Driver signals L1 up. Volatile. - #[cfg(any(target_os = "fuchsia", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(target_os = "fuchsia", target_os = "linux", target_os = "cygwin"))] 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; + #[cfg(solarish)] + IFF_NOXMIT as IflagsType; /// Driver signals dormant. Volatile. - #[cfg(any(target_os = "fuchsia", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(target_os = "fuchsia", target_os = "linux", target_os = "cygwin"))] IFF_DORMANT; /// User-requested promisc mode. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] IFF_PPROMISC; /// Just on-link subnet. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_NOLOCAL; + #[cfg(solarish)] + IFF_NOLOCAL as IflagsType; /// 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())))] + #[cfg(freebsdlike)] IFF_MONITOR; /// Address is deprecated. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_DEPRECATED; + #[cfg(solarish)] + IFF_DEPRECATED as IflagsType; /// Static ARP. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] IFF_STATICARP; /// Address from stateless addrconf. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_ADDRCONF; + #[cfg(solarish)] + IFF_ADDRCONF as IflagsType; /// 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; + #[cfg(solarish)] + IFF_ROUTER as IflagsType; /// 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; + #[cfg(solarish)] + IFF_NONUD as IflagsType; /// 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; + #[cfg(solarish)] + IFF_ANYCAST as IflagsType; /// Don't exchange routing info. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_NORTEXCH; + #[cfg(solarish)] + IFF_NORTEXCH as IflagsType; /// 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; + #[cfg(any(linux_android, target_os = "fuchsia"))] + IFF_NO_PI as IflagsType; /// 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; + #[cfg(any(linux_android, target_os = "fuchsia"))] + IFF_TUN as IflagsType; /// 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; + #[cfg(any(linux_android, target_os = "fuchsia"))] + IFF_TAP as IflagsType; /// IPv4 interface. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_IPV4; + #[cfg(solarish)] + IFF_IPV4 as IflagsType; /// IPv6 interface. - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_IPV6; + #[cfg(solarish)] + IFF_IPV6 as IflagsType; /// in.mpathd test address - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_NOFAILOVER; + #[cfg(solarish)] + IFF_NOFAILOVER as IflagsType; /// Interface has failed - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_FAILED; + #[cfg(solarish)] + IFF_FAILED as IflagsType; /// Interface is a hot-spare - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_STANDBY; + #[cfg(solarish)] + IFF_STANDBY as IflagsType; /// Functioning but not used - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_INACTIVE; + #[cfg(solarish)] + IFF_INACTIVE as IflagsType; /// 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; + #[cfg(solarish)] + IFF_OFFLINE as IflagsType; + /// Has CoS marking supported + #[cfg(solarish)] + IFF_COS_ENABLED as IflagsType; + /// Prefer as source addr + #[cfg(solarish)] + IFF_PREFERRED as IflagsType; /// 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; + #[cfg(solarish)] + IFF_TEMPORARY as IflagsType; + /// MTU set + #[cfg(solarish)] + IFF_FIXEDMTU as IflagsType; + /// Cannot send/receive packets + #[cfg(solarish)] + IFF_VIRTUAL as IflagsType; /// Local address in use - #[cfg(target_os = "solaris")] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_DUPLICATE; + #[cfg(solarish)] + IFF_DUPLICATE as IflagsType; /// IPMP IP interface - #[cfg(target_os = "solaris")] - #[cfg_attr(docsrs, doc(cfg(all())))] - IFF_IPMP; + #[cfg(solarish)] + IFF_IPMP as IflagsType; } ); +impl fmt::Display for InterfaceFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + bitflags::parser::to_writer(self, f) + } +} + + #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", + bsd, target_os = "fuchsia", - target_os = "ios", target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", + solarish, ))] -#[cfg_attr(docsrs, doc(cfg(all())))] mod if_nameindex { use super::*; @@ -372,6 +310,7 @@ mod if_nameindex { } /// A list of the network interfaces available on this system. Obtained from [`if_nameindex()`]. + #[repr(transparent)] pub struct Interfaces { ptr: NonNull, } @@ -387,7 +326,7 @@ mod if_nameindex { /// null-terminated, so calling this calculates the length. If random access isn't needed, /// [`Interfaces::iter()`] should be used instead. pub fn to_slice(&self) -> &[Interface] { - let ifs = self.ptr.as_ptr() as *const Interface; + let ifs = self.ptr.as_ptr().cast(); let len = self.iter().count(); unsafe { std::slice::from_raw_parts(ifs, len) } } @@ -457,13 +396,9 @@ mod if_nameindex { } } #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", + bsd, target_os = "fuchsia", - target_os = "ios", target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", + solarish, ))] pub use if_nameindex::*; diff --git a/src/poll.rs b/src/poll.rs index 6f227fee..416a6945 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -1,7 +1,8 @@ //! Wait for events to trigger on specific file descriptors -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd}; use crate::errno::Errno; +pub use crate::poll_timeout::{PollTimeout, PollTimeoutTryFromError}; use crate::Result; /// This is a wrapper around `libc::pollfd`. @@ -10,30 +11,54 @@ use crate::Result; /// [`ppoll`](fn.ppoll.html) functions to specify the events of interest /// for a specific file descriptor. /// -/// After a call to `poll` or `ppoll`, the events that occurred can be -/// retrieved by calling [`revents()`](#method.revents) on the `PollFd`. +/// After a call to `poll` or `ppoll`, the events that occurred can be retrieved by calling +/// [`revents()`](#method.revents) on the `PollFd` object from the array passed to `poll`. #[repr(transparent)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct PollFd { +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +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 { + /// + /// # Examples + /// ```no_run + /// # use std::os::unix::io::{AsFd, AsRawFd, FromRawFd}; + /// # use nix::{ + /// # poll::{PollTimeout, PollFd, PollFlags, poll}, + /// # unistd::{pipe, read} + /// # }; + /// let (r, w) = pipe().unwrap(); + /// let pfd = PollFd::new(r.as_fd(), PollFlags::POLLIN); + /// ``` + /// These are placed in an array and passed to [`poll`] or [`ppoll`](fn.ppoll.html). + // Unlike I/O functions, constructors like this must take `BorrowedFd` + // instead of AsFd or &AsFd. Otherwise, an `OwnedFd` argument would be + // dropped at the end of the method, leaving the structure referencing a + // closed file descriptor. For example: + // + // ```rust + // let (r, _) = pipe().unwrap(); + // let pollfd = PollFd::new(r, flag); // Drops the OwnedFd + // // Do something with `pollfd`, which uses the CLOSED fd. + // ``` + pub fn new(fd: BorrowedFd<'fd>, events: PollFlags) -> PollFd<'fd> { PollFd { pollfd: libc::pollfd { - fd, + fd: fd.as_raw_fd(), events: events.bits(), revents: PollFlags::empty().bits(), }, + _fd: std::marker::PhantomData, } } /// 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 { + pub fn revents(&self) -> Option { PollFlags::from_bits(self.pollfd.revents) } @@ -43,7 +68,7 @@ impl PollFd { /// Equivalent to `x.revents()? != PollFlags::empty()`. /// /// This is marginally more efficient than [`PollFd::all`]. - pub fn any(self) -> Option { + pub fn any(&self) -> Option { Some(self.revents()? != PollFlags::empty()) } @@ -53,12 +78,12 @@ impl PollFd { /// Equivalent to `x.revents()? & x.events() == x.events()`. /// /// This is marginally less efficient than [`PollFd::any`]. - pub fn all(self) -> Option { + pub fn all(&self) -> Option { Some(self.revents()? & self.events() == self.events()) } /// The events of interest for this `PollFd`. - pub fn events(self) -> PollFlags { + pub fn events(&self) -> PollFlags { PollFlags::from_bits(self.pollfd.events).unwrap() } @@ -68,9 +93,29 @@ impl PollFd { } } -impl AsRawFd for PollFd { - fn as_raw_fd(&self) -> RawFd { - self.pollfd.fd +impl AsFd for PollFd<'_> { + 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) } } } @@ -97,19 +142,15 @@ 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); @@ -148,16 +189,47 @@ libc_bitflags! { /// /// Note that the timeout interval will be rounded up to the system clock /// granularity, and kernel scheduling delays mean that the blocking -/// interval may overrun by a small amount. Specifying a negative value -/// in timeout means an infinite timeout. Specifying a timeout of zero -/// causes `poll()` to return immediately, even if no file descriptors are -/// ready. -pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result { +/// interval may overrun by a small amount. Specifying a [`PollTimeout::NONE`] +/// in timeout means an infinite timeout. Specifying a timeout of +/// [`PollTimeout::ZERO`] causes `poll()` to return immediately, even if no file +/// descriptors are ready. +/// +/// The return value contains the number of `fds` which have selected events ([`PollFd::revents`]). +/// +/// # Examples +/// ```no_run +/// # use std::os::unix::io::{AsFd, AsRawFd, FromRawFd}; +/// # use nix::{ +/// # poll::{PollTimeout, PollFd, PollFlags, poll}, +/// # unistd::{pipe, read} +/// # }; +/// let (r0, w0) = pipe().unwrap(); +/// let (r1, w1) = pipe().unwrap(); +/// +/// let mut pollfds = [ +/// PollFd::new(r0.as_fd(), PollFlags::POLLIN), +/// PollFd::new(r1.as_fd(), PollFlags::POLLIN), +/// ]; +/// +/// let nready = poll(&mut pollfds, PollTimeout::NONE).unwrap(); +/// assert!(nready >= 1); // Since there is no timeout +/// +/// let mut buf = [0u8; 80]; +/// if pollfds[0].any().unwrap_or_default() { +/// read(&r0, &mut buf[..]); +/// } else if pollfds[1].any().unwrap_or_default() { +/// read(&r1, &mut buf[..]); +/// } +/// ``` +pub fn poll>( + fds: &mut [PollFd], + timeout: T, +) -> Result { let res = unsafe { libc::poll( - fds.as_mut_ptr() as *mut libc::pollfd, + fds.as_mut_ptr().cast(), fds.len() as libc::nfds_t, - timeout, + i32::from(timeout.into()), ) }; @@ -170,14 +242,14 @@ feature! { /// descriptor becomes ready or until a signal is caught. /// ([`poll(2)`](https://man7.org/linux/man-pages/man2/poll.2.html)) /// -/// `ppoll` behaves like `poll`, but let you specify what signals may interrupt it +/// `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"))] +#[cfg(any(linux_android, freebsdlike))] pub fn ppoll( fds: &mut [PollFd], timeout: Option, @@ -187,7 +259,7 @@ pub fn ppoll( 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, + libc::ppoll(fds.as_mut_ptr().cast(), fds.len() as libc::nfds_t, timeout, sigmask) diff --git a/src/poll_timeout.rs b/src/poll_timeout.rs new file mode 100644 index 00000000..f7d9015f --- /dev/null +++ b/src/poll_timeout.rs @@ -0,0 +1,224 @@ +use std::time::Duration; + +/// PollTimeout argument for polling. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +pub struct PollTimeout(i32); + +impl PollTimeout { + /// Blocks indefinitely. + /// + /// > Specifying a negative value in timeout means an infinite timeout. + pub const NONE: Self = Self(-1); + /// Returns immediately. + /// + /// > Specifying a timeout of zero causes poll() to return immediately, even if no file + /// > descriptors are ready. + pub const ZERO: Self = Self(0); + /// Blocks for at most [`i32::MAX`] milliseconds. + pub const MAX: Self = Self(i32::MAX); + /// Returns if `self` equals [`PollTimeout::NONE`]. + pub fn is_none(&self) -> bool { + // > Specifying a negative value in timeout means an infinite timeout. + *self <= Self::NONE + } + /// Returns if `self` does not equal [`PollTimeout::NONE`]. + pub fn is_some(&self) -> bool { + !self.is_none() + } + /// Returns the timeout in milliseconds if there is some, otherwise returns `None`. + pub fn as_millis(&self) -> Option { + self.is_some().then_some(u32::try_from(self.0).unwrap()) + } + /// Returns the timeout as a `Duration` if there is some, otherwise returns `None`. + pub fn duration(&self) -> Option { + self.as_millis() + .map(|x| Duration::from_millis(u64::from(x))) + } +} + +/// Error type for integer conversions into `PollTimeout`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PollTimeoutTryFromError { + /// Passing a value less than -1 is invalid on some systems, see + /// . + TooNegative, + /// Passing a value greater than `i32::MAX` is invalid. + TooPositive, +} + +impl std::fmt::Display for PollTimeoutTryFromError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::TooNegative => write!(f, "Passed a negative timeout less than -1."), + Self::TooPositive => write!(f, "Passed a positive timeout greater than `i32::MAX` milliseconds.") + } + } +} + +impl std::error::Error for PollTimeoutTryFromError {} + +impl> From> for PollTimeout { + fn from(x: Option) -> Self { + x.map_or(Self::NONE, |x| x.into()) + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: Duration) -> std::result::Result { + Ok(Self( + i32::try_from(x.as_millis()) + .map_err(|_| PollTimeoutTryFromError::TooPositive)?, + )) + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: u128) -> std::result::Result { + Ok(Self( + i32::try_from(x) + .map_err(|_| PollTimeoutTryFromError::TooPositive)?, + )) + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: u64) -> std::result::Result { + Ok(Self( + i32::try_from(x) + .map_err(|_| PollTimeoutTryFromError::TooPositive)?, + )) + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: u32) -> std::result::Result { + Ok(Self( + i32::try_from(x) + .map_err(|_| PollTimeoutTryFromError::TooPositive)?, + )) + } +} +impl From for PollTimeout { + fn from(x: u16) -> Self { + Self(i32::from(x)) + } +} +impl From for PollTimeout { + fn from(x: u8) -> Self { + Self(i32::from(x)) + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: i128) -> std::result::Result { + match x { + ..=-2 => Err(PollTimeoutTryFromError::TooNegative), + -1.. => Ok(Self( + i32::try_from(x) + .map_err(|_| PollTimeoutTryFromError::TooPositive)?, + )), + } + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: i64) -> std::result::Result { + match x { + ..=-2 => Err(PollTimeoutTryFromError::TooNegative), + -1.. => Ok(Self( + i32::try_from(x) + .map_err(|_| PollTimeoutTryFromError::TooPositive)?, + )), + } + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: i32) -> std::result::Result { + match x { + ..=-2 => Err(PollTimeoutTryFromError::TooNegative), + -1.. => Ok(Self(x)), + } + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: i16) -> std::result::Result { + match x { + ..=-2 => Err(PollTimeoutTryFromError::TooNegative), + -1.. => Ok(Self(i32::from(x))), + } + } +} +impl TryFrom for PollTimeout { + type Error = PollTimeoutTryFromError; + fn try_from(x: i8) -> std::result::Result { + match x { + ..=-2 => Err(PollTimeoutTryFromError::TooNegative), + -1.. => Ok(Self(i32::from(x))), + } + } +} +impl TryFrom for Duration { + type Error = (); + fn try_from(x: PollTimeout) -> std::result::Result { + x.duration().ok_or(()) + } +} +impl TryFrom for u128 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} +impl TryFrom for u64 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} +impl TryFrom for u32 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} +impl TryFrom for u16 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} +impl TryFrom for u8 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} +impl From for i128 { + fn from(x: PollTimeout) -> Self { + Self::from(x.0) + } +} +impl From for i64 { + fn from(x: PollTimeout) -> Self { + Self::from(x.0) + } +} +impl From for i32 { + fn from(x: PollTimeout) -> Self { + x.0 + } +} +impl TryFrom for i16 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} +impl TryFrom for i8 { + type Error = >::Error; + fn try_from(x: PollTimeout) -> std::result::Result { + Self::try_from(x.0) + } +} diff --git a/src/pty.rs b/src/pty.rs index 28ae5e92..ab449e4a 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -5,89 +5,98 @@ 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::errno::Errno; +#[cfg(not(target_os = "aix"))] use crate::sys::termios::Termios; -#[cfg(feature = "process")] -use crate::unistd::{ForkResult, Pid}; +#[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)] -pub struct ForkptyResult { - /// The master port in a virtual pty pair - pub master: RawFd, - /// Metadata about forked process - pub fork_result: ForkResult, +/// A successful result of [`forkpty()`]. +#[derive(Debug)] +pub enum ForkptyResult { + /// This is the parent process of the underlying fork. + Parent { + /// The PID of the fork's child process + child: Pid, + /// A file descriptor referring to master side of the pseudoterminal of + /// the child process. + master: OwnedFd, + }, + /// This is the child process of the underlying fork. + Child, } } /// 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 PtyMaster { + /// Constructs a `PytMaster` wrapping an existing `OwnedFd`. + /// + /// # Safety + /// + /// `OwnedFd` is a valid `PtyMaster`. + pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { + Self(fd) + } +} impl AsRawFd for PtyMaster { fn as_raw_fd(&self) -> RawFd { - self.0 + self.0.as_raw_fd() + } +} + +impl AsFd for PtyMaster { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl From for OwnedFd { + fn from(value: PtyMaster) -> Self { + value.0 } } impl IntoRawFd for PtyMaster { fn into_raw_fd(self) -> RawFd { let fd = self.0; - mem::forget(self); - fd - } -} - -impl Drop for PtyMaster { - fn drop(&mut self) { - // On drop, we ignore errors like EINTR and EIO because there's no clear - // way to handle them, we can't return anything, and (on FreeBSD at - // least) the file descriptor is deallocated in these cases. However, - // we must panic on EBADF, because it is always an error to close an - // invalid file descriptor. That frequently indicates a double-close - // condition, which can cause confusing errors for future I/O - // operations. - let e = unistd::close(self.0); - if e == Err(Errno::EBADF) { - panic!("Closing an invalid file descriptor!"); - }; + fd.into_raw_fd() } } 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, buf).map_err(io::Error::from) } } impl io::Write for PtyMaster { fn write(&mut self, buf: &[u8]) -> io::Result { - unistd::write(self.0, buf).map_err(io::Error::from) + unistd::write(&self.0, buf).map_err(io::Error::from) } fn flush(&mut self) -> io::Result<()> { Ok(()) @@ -96,13 +105,13 @@ impl io::Write for PtyMaster { 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, buf).map_err(io::Error::from) } } impl io::Write for &PtyMaster { fn write(&mut self, buf: &[u8]) -> io::Result { - unistd::write(self.0, buf).map_err(io::Error::from) + unistd::write(&self.0, buf).map_err(io::Error::from) } fn flush(&mut self) -> io::Result<()> { Ok(()) @@ -164,7 +173,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result { return Err(Errno::last()); } - Ok(PtyMaster(fd)) + Ok(PtyMaster(unsafe { OwnedFd::from_raw_fd(fd) })) } /// Get the name of the slave pseudoterminal (see @@ -185,12 +194,12 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result { /// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`. #[inline] pub unsafe fn ptsname(fd: &PtyMaster) -> Result { - let name_ptr = libc::ptsname(fd.as_raw_fd()); + let name_ptr = unsafe { libc::ptsname(fd.as_raw_fd()) }; if name_ptr.is_null() { return Err(Errno::last()); } - let name = CStr::from_ptr(name_ptr); + let name = unsafe { CStr::from_ptr(name_ptr) }; Ok(name.to_string_lossy().into_owned()) } @@ -203,8 +212,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())))] +#[cfg(linux_android)] #[inline] pub fn ptsname_r(fd: &PtyMaster) -> Result { let mut name_buf = Vec::::with_capacity(64); @@ -244,6 +252,7 @@ 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] +#[cfg(not(target_os = "aix"))] pub fn openpty< 'a, 'b, @@ -308,17 +317,15 @@ pub fn openpty< 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)). +/// Create a new process operating in a pseudoterminal. /// /// If `winsize` is not `None`, the window size of the slave will be set to /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's @@ -327,14 +334,20 @@ feature! { /// # Safety /// /// In a multithreaded program, only [async-signal-safe] functions like `pause` -/// and `_exit` may be called by the child (the parent isn't restricted). Note -/// that memory allocation may **not** be async-signal-safe and thus must be -/// prevented. +/// and `_exit` may be called by the child (the parent isn't restricted) until +/// a call of `execve(2)`. Note that memory allocation may **not** be +/// async-signal-safe and thus must be prevented. /// /// Those functions are only a small subset of your operating system's API, so /// special care must be taken to only invoke code you can control and audit. /// /// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html +/// +/// # Reference +/// +/// * [FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=forkpty) +/// * [Linux](https://man7.org/linux/man-pages/man3/forkpty.3.html) +#[cfg(not(target_os = "aix"))] pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into>>( winsize: T, termios: U, @@ -356,16 +369,25 @@ pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into ForkResult::Child, - res => ForkResult::Parent { child: Pid::from_raw(res) }, - })?; + let success_ret = Errno::result(res)?; + let forkpty_result = match success_ret { + // In the child process + 0 => ForkptyResult::Child, + // In the parent process + child_pid => { + // SAFETY: + // 1. The master buffer is guaranteed to be initialized in the parent process + // 2. OwnedFd::from_raw_fd won't panic as the fd is a valid file descriptor + let master = unsafe { OwnedFd::from_raw_fd( master.assume_init() ) }; + ForkptyResult::Parent { + master, + child: Pid::from_raw(child_pid), + } + } + }; - Ok(ForkptyResult { - master: master.assume_init(), - fork_result, - }) + Ok(forkpty_result) } } diff --git a/src/sched.rs b/src/sched.rs index d5b1233c..617d0049 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -4,11 +4,10 @@ //! [sched.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sched.h.html) use crate::{Errno, Result}; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] pub use self::sched_linux_like::*; -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(linux_android)] mod sched_linux_like { use crate::errno::Errno; use crate::unistd::Pid; @@ -16,7 +15,7 @@ mod sched_linux_like { use libc::{self, c_int, c_void}; use std::mem; use std::option::Option; - use std::os::unix::io::RawFd; + use std::os::unix::io::{AsFd, AsRawFd}; // For some functions taking with a parameter of type CloneFlags, // only a subset of these flags have an effect. @@ -95,7 +94,17 @@ mod sched_linux_like { /// 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( + /// + /// # 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, @@ -106,12 +115,15 @@ mod sched_linux_like { (*cb)() as c_int } + let combined = flags.bits() | signal.unwrap_or(0); 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( + mem::transmute::< + extern "C" fn(*mut Box isize>) -> i32, + extern "C" fn(*mut libc::c_void) -> i32, + >( callback as extern "C" fn(*mut Box isize>) -> i32, ), @@ -136,27 +148,17 @@ mod sched_linux_like { /// 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()) }; + 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" -))] +#[cfg(any(linux_android, freebsdlike))] pub use self::sched_affinity::*; -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux" -))] +#[cfg(any(linux_android, freebsdlike))] mod sched_affinity { use crate::errno::Errno; use crate::unistd::Pid; diff --git a/src/spawn.rs b/src/spawn.rs new file mode 100644 index 00000000..34c8666b --- /dev/null +++ b/src/spawn.rs @@ -0,0 +1,431 @@ +//! Safe wrappers around posix_spawn* functions found in the libc "spawn.h" header. + +use std::{ffi::CStr, mem, os::fd::RawFd}; + +#[cfg(any(feature = "fs", feature = "term"))] +use crate::fcntl::OFlag; +#[cfg(feature = "signal")] +use crate::sys::signal::SigSet; +#[cfg(feature = "fs")] +use crate::sys::stat::Mode; +use crate::{errno::Errno, unistd::Pid, NixPath, Result}; + +/// A spawn attributes object. See [posix_spawnattr_t](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_init.html). +#[repr(transparent)] +#[derive(Debug)] +pub struct PosixSpawnAttr { + attr: libc::posix_spawnattr_t, +} + +impl PosixSpawnAttr { + /// Initialize the spawn attributes object. See + /// [posix_spawnattr_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_init.html). + #[doc(alias("posix_spawnattr_init"))] + pub fn init() -> Result { + let mut attr = mem::MaybeUninit::uninit(); + let res = unsafe { libc::posix_spawnattr_init(attr.as_mut_ptr()) }; + + Errno::result(res)?; + + let attr = unsafe { attr.assume_init() }; + Ok(PosixSpawnAttr { attr }) + } + + /// Reinitialize the spawn attributes object. + /// This is a wrapper around + /// [posix_spawnattr_destroy](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_destroy.html) + /// followed by + /// [posix_spawnattr_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_init.html). + #[doc(alias("posix_spawnattr_destroy"))] + pub fn reinit(mut self) -> Result { + let res = unsafe { + libc::posix_spawnattr_destroy( + &mut self.attr as *mut libc::posix_spawnattr_t, + ) + }; + Errno::result(res)?; + + let res = unsafe { + libc::posix_spawnattr_init( + &mut self.attr as *mut libc::posix_spawnattr_t, + ) + }; + Errno::result(res)?; + + Ok(self) + } + + /// Set spawn flags. See + /// [posix_spawnattr_setflags](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setflags.html). + #[doc(alias("posix_spawnattr_setflags"))] + pub fn set_flags(&mut self, flags: PosixSpawnFlags) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setflags( + &mut self.attr as *mut libc::posix_spawnattr_t, + flags.bits() as libc::c_short, + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn flags. See + /// [posix_spawnattr_getflags](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getflags.html). + #[doc(alias("posix_spawnattr_getflags"))] + pub fn flags(&self) -> Result { + let mut flags: libc::c_short = 0; + let res = unsafe { + libc::posix_spawnattr_getflags( + &self.attr as *const libc::posix_spawnattr_t, + &mut flags, + ) + }; + Errno::result(res)?; + + Ok(PosixSpawnFlags::from_bits_truncate(flags.into())) + } + + /// Set spawn pgroup. See + /// [posix_spawnattr_setpgroup](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setpgroup.html). + #[doc(alias("posix_spawnattr_setpgroup"))] + pub fn set_pgroup(&mut self, pgroup: Pid) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setpgroup( + &mut self.attr as *mut libc::posix_spawnattr_t, + pgroup.as_raw(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn pgroup. See + /// [posix_spawnattr_getpgroup](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getpgroup.html). + #[doc(alias("posix_spawnattr_getpgroup"))] + pub fn pgroup(&self) -> Result { + let mut pid: libc::pid_t = 0; + + let res = unsafe { + libc::posix_spawnattr_getpgroup( + &self.attr as *const libc::posix_spawnattr_t, + &mut pid, + ) + }; + Errno::result(res)?; + + Ok(Pid::from_raw(pid)) + } + + feature! { + #![feature = "signal"] + /// Set spawn sigdefault. See + /// [posix_spawnattr_setsigdefault](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigdefault.html). + #[doc(alias("posix_spawnattr_setsigdefault"))] + pub fn set_sigdefault(&mut self, sigdefault: &SigSet) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setsigdefault( + &mut self.attr as *mut libc::posix_spawnattr_t, + sigdefault.as_ref(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn sigdefault. See + /// [posix_spawnattr_getsigdefault](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getsigdefault.html). + #[doc(alias("posix_spawnattr_getsigdefault"))] + pub fn sigdefault(&self) -> Result { + let mut sigset = mem::MaybeUninit::uninit(); + + let res = unsafe { + libc::posix_spawnattr_getsigdefault( + &self.attr as *const libc::posix_spawnattr_t, + sigset.as_mut_ptr(), + ) + }; + Errno::result(res)?; + + let sigdefault = + unsafe { SigSet::from_sigset_t_unchecked(sigset.assume_init()) }; + Ok(sigdefault) + } + + /// Set spawn sigmask. See + /// [posix_spawnattr_setsigmask](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigmask.html). + #[doc(alias("posix_spawnattr_setsigmask"))] + pub fn set_sigmask(&mut self, sigdefault: &SigSet) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setsigmask( + &mut self.attr as *mut libc::posix_spawnattr_t, + sigdefault.as_ref(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn sigmask. See + /// [posix_spawnattr_getsigmask](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getsigmask.html). + #[doc(alias("posix_spawnattr_getsigmask"))] + pub fn sigmask(&self) -> Result { + let mut sigset = mem::MaybeUninit::uninit(); + + let res = unsafe { + libc::posix_spawnattr_getsigmask( + &self.attr as *const libc::posix_spawnattr_t, + sigset.as_mut_ptr(), + ) + }; + Errno::result(res)?; + + let sigdefault = + unsafe { SigSet::from_sigset_t_unchecked(sigset.assume_init()) }; + Ok(sigdefault) + } + } +} + +impl Drop for PosixSpawnAttr { + fn drop(&mut self) { + unsafe { + libc::posix_spawnattr_destroy( + &mut self.attr as *mut libc::posix_spawnattr_t, + ); + } + } +} + +libc_bitflags!( + /// Process attributes to be changed in the new process image when invoking [`posix_spawn`] + /// or [`posix_spawnp`]. See + /// [posix_spawn](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn.html). + pub struct PosixSpawnFlags: libc::c_int { + /// Reset effective user ID of the child process to parent's real user ID. + POSIX_SPAWN_RESETIDS; + /// Put the child in a process group specified by the spawn-pgroup attribute. See + /// [posix_spawnattr_setpgroup](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setpgroup.html). + POSIX_SPAWN_SETPGROUP; + /// Force set signals to default signal handling in child process. See + /// [posix_spawnattr_setsigdefault](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigdefault.html). + #[cfg(feature = "signal")] + POSIX_SPAWN_SETSIGDEF; + /// Set signal mask of child process. See + /// [posix_spawnattr_setsigmask](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigmask.html). + #[cfg(feature = "signal")] + POSIX_SPAWN_SETSIGMASK; + // TODO: Add support for the following two flags whenever support for + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sched.h.html + // is added to nix. + // POSIX_SPAWN_SETSCHEDPARAM; + // POSIX_SPAWN_SETSCHEDULER; + } +); + +/// A spawn file actions object. See [posix_spawn_file_actions_t](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addclose.html). +#[repr(transparent)] +#[derive(Debug)] +pub struct PosixSpawnFileActions { + fa: libc::posix_spawn_file_actions_t, +} + +impl PosixSpawnFileActions { + /// Initialize the spawn file actions object. See + /// [posix_spawn_file_actions_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_init.html). + #[doc(alias("posix_spawn_file_actions_init"))] + pub fn init() -> Result { + let mut actions = mem::MaybeUninit::uninit(); + let res = unsafe { + libc::posix_spawn_file_actions_init(actions.as_mut_ptr()) + }; + Errno::result(res)?; + Ok(unsafe { + PosixSpawnFileActions { + fa: actions.assume_init(), + } + }) + } + + /// Reinitialize the spawn file actions object. + /// This is a wrapper around + /// [posix_spawn_file_actions_destroy](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_destroy.html). + /// followed by + /// [posix_spawn_file_actions_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_init.html). + #[doc(alias("posix_spawn_file_actions_destroy"))] + pub fn reinit(mut self) -> Result { + let res = unsafe { + libc::posix_spawn_file_actions_destroy( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + ) + }; + Errno::result(res)?; + + let res = unsafe { + libc::posix_spawn_file_actions_init( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + ) + }; + Errno::result(res)?; + + Ok(self) + } + + /// Add a [dup2](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup2.html) action. See + /// [posix_spawn_file_actions_adddup2](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_adddup2.html). + #[doc(alias("posix_spawn_file_actions_adddup2"))] + pub fn add_dup2(&mut self, fd: RawFd, newfd: RawFd) -> Result<()> { + let res = unsafe { + libc::posix_spawn_file_actions_adddup2( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + fd, + newfd, + ) + }; + Errno::result(res)?; + + Ok(()) + } + + feature! { + #![all(feature = "fs", feature = "term")] + /// Add an open action. See + /// [posix_spawn_file_actions_addopen](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addopen.html). + #[doc(alias("posix_spawn_file_actions_addopen"))] + pub fn add_open( + &mut self, + fd: RawFd, + path: &P, + oflag: OFlag, + mode: Mode, + ) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::posix_spawn_file_actions_addopen( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + fd, + cstr.as_ptr(), + oflag.bits(), + mode.bits(), + ) + })?; + Errno::result(res)?; + + Ok(()) + } + } + + /// Add a close action. See + /// [posix_spawn_file_actions_addclose](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addclose.html). + #[doc(alias("posix_spawn_file_actions_addclose"))] + pub fn add_close(&mut self, fd: RawFd) -> Result<()> { + let res = unsafe { + libc::posix_spawn_file_actions_addclose( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + fd, + ) + }; + Errno::result(res)?; + + Ok(()) + } +} + +impl Drop for PosixSpawnFileActions { + fn drop(&mut self) { + unsafe { + libc::posix_spawn_file_actions_destroy( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + ); + } + } +} + +// The POSIX standard requires those `args` and `envp` to be of type `*const *mut [c_char]`, +// but implementations won't modify them, making the `mut` type redundant. Considering this, +// Nix does not expose this mutability, but we have to change the interface when calling the +// underlying libc interfaces , this helper function does the conversion job. +// +// SAFETY: +// It is safe to add the mutability in types as implementations won't mutable them. +unsafe fn to_exec_array>(args: &[S]) -> Vec<*mut libc::c_char> { + let mut v: Vec<*mut libc::c_char> = args + .iter() + .map(|s| s.as_ref().as_ptr().cast_mut()) + .collect(); + v.push(std::ptr::null_mut()); + v +} + +/// Create a new child process from the specified process image. See +/// [posix_spawn](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn.html). +pub fn posix_spawn( + path: &P, + file_actions: &PosixSpawnFileActions, + attr: &PosixSpawnAttr, + args: &[SA], + envp: &[SE], +) -> Result +where + P: NixPath + ?Sized, + SA: AsRef, + SE: AsRef, +{ + let mut pid = 0; + + let ret = unsafe { + let args_p = to_exec_array(args); + let env_p = to_exec_array(envp); + + path.with_nix_path(|c_str| { + libc::posix_spawn( + &mut pid as *mut libc::pid_t, + c_str.as_ptr(), + &file_actions.fa as *const libc::posix_spawn_file_actions_t, + &attr.attr as *const libc::posix_spawnattr_t, + args_p.as_ptr(), + env_p.as_ptr(), + ) + })? + }; + + if ret != 0 { + return Err(Errno::from_raw(ret)); + } + + Ok(Pid::from_raw(pid)) +} + +/// Create a new child process from the specified process image. See +/// [posix_spawnp](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnp.html). +pub fn posix_spawnp, SE: AsRef>( + path: &CStr, + file_actions: &PosixSpawnFileActions, + attr: &PosixSpawnAttr, + args: &[SA], + envp: &[SE], +) -> Result { + let mut pid = 0; + + let ret = unsafe { + let args_p = to_exec_array(args); + let env_p = to_exec_array(envp); + + libc::posix_spawnp( + &mut pid as *mut libc::pid_t, + path.as_ptr(), + &file_actions.fa as *const libc::posix_spawn_file_actions_t, + &attr.attr as *const libc::posix_spawnattr_t, + args_p.as_ptr(), + env_p.as_ptr(), + ) + }; + + if ret != 0 { + return Err(Errno::from_raw(ret)); + } + + Ok(Pid::from_raw(pid)) +} diff --git a/src/sys/aio.rs b/src/sys/aio.rs index e2ce19b7..39cd15cc 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -30,12 +30,12 @@ use std::{ fmt::{self, Debug}, marker::{PhantomData, PhantomPinned}, mem, - os::unix::io::RawFd, + os::unix::io::{AsFd, AsRawFd, BorrowedFd}, pin::Pin, ptr, thread, }; -use libc::{c_void, off_t}; +use libc::off_t; use pin_utils::unsafe_pinned; use crate::{ @@ -53,12 +53,10 @@ libc_enum! { /// do it like `fsync` O_SYNC, /// on supported operating systems only, do it like `fdatasync` - #[cfg(any(target_os = "ios", + #[cfg(any(apple_targets, target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + target_os = "freebsd", + netbsdlike))] O_DSYNC } impl TryFrom @@ -105,7 +103,7 @@ unsafe impl Sync for LibcAiocb {} // provide polymorphism at the wrong level. Instead, the best place for // polymorphism is at the level of `Futures`. #[repr(C)] -struct AioCb { +struct AioCb<'a> { aiocb: LibcAiocb, /// Could this `AioCb` potentially have any in-kernel state? // It would be really nice to perform the in-progress check entirely at @@ -115,9 +113,10 @@ struct AioCb { // that there's no way to write an AioCb constructor that neither boxes // the object itself, nor moves it during return. in_progress: bool, + _fd: PhantomData>, } -impl AioCb { +impl<'a> AioCb<'a> { pin_utils::unsafe_unpinned!(aiocb: LibcAiocb); fn aio_return(mut self: Pin<&mut Self>) -> Result { @@ -142,18 +141,23 @@ impl AioCb { } } - fn common_init(fd: RawFd, prio: i32, sigev_notify: SigevNotify) -> Self { + fn common_init( + fd: BorrowedFd<'a>, + 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_fildes = fd.as_raw_fd(); a.aio_reqprio = prio; a.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); AioCb { aiocb: LibcAiocb(a), in_progress: false, + _fd: PhantomData, } } @@ -161,9 +165,9 @@ impl AioCb { let r = unsafe { libc::aio_error(&self.aiocb().0) }; match r { 0 => Ok(()), - num if num > 0 => Err(Errno::from_i32(num)), + num if num > 0 => Err(Errno::from_raw(num)), -1 => Err(Errno::last()), - num => panic!("unknown aio_error return value {:?}", num), + num => panic!("unknown aio_error return value {num:?}"), } } @@ -189,7 +193,7 @@ impl AioCb { } } -impl Debug for AioCb { +impl Debug for AioCb<'_> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("AioCb") .field("aiocb", &self.aiocb.0) @@ -198,7 +202,7 @@ impl Debug for AioCb { } } -impl Drop for AioCb { +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) { @@ -246,11 +250,11 @@ pub trait Aio { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::Write; - /// # use std::os::unix::io::AsRawFd; + /// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); - /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// &wbuf[..], /// 0, //priority @@ -287,11 +291,11 @@ pub trait Aio { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; - /// # use std::os::unix::io::AsRawFd; + /// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); - /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// WBUF, /// 0, //priority @@ -309,7 +313,7 @@ pub trait Aio { fn error(self: Pin<&mut Self>) -> Result<()>; /// Returns the underlying file descriptor associated with the operation. - fn fd(&self) -> RawFd; + fn fd(&self) -> BorrowedFd; /// Does this operation currently have any in-kernel state? /// @@ -324,10 +328,10 @@ pub trait Aio { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify::SigevNone; /// # use std::{thread, time}; - /// # use std::os::unix::io::AsRawFd; + /// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let f = tempfile().unwrap(); - /// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, + /// let mut aiof = Box::pin(AioFsync::new(f.as_fd(), AioFsyncMode::O_SYNC, /// 0, SigevNone)); /// assert!(!aiof.as_mut().in_progress()); /// aiof.as_mut().submit().expect("aio_fsync failed early"); @@ -367,8 +371,10 @@ macro_rules! aio_methods { self.aiocb().error() } - fn fd(&self) -> RawFd { - self.aiocb.aiocb.0.aio_fildes + fn fd(&self) -> BorrowedFd<'a> { + // safe because self's lifetime is the same as the original file + // descriptor. + unsafe { BorrowedFd::borrow_raw(self.aiocb.aiocb.0.aio_fildes) } } fn in_progress(&self) -> bool { @@ -416,10 +422,10 @@ macro_rules! aio_methods { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify::SigevNone; /// # use std::{thread, time}; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let f = tempfile().unwrap(); -/// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, +/// let mut aiof = Box::pin(AioFsync::new(f.as_fd(), AioFsyncMode::O_SYNC, /// 0, SigevNone)); /// aiof.as_mut().submit().expect("aio_fsync failed early"); /// while (aiof.as_mut().error() == Err(Errno::EINPROGRESS)) { @@ -429,13 +435,13 @@ macro_rules! aio_methods { /// ``` #[derive(Debug)] #[repr(transparent)] -pub struct AioFsync { - aiocb: AioCb, +pub struct AioFsync<'a> { + aiocb: AioCb<'a>, _pin: PhantomPinned, } -impl AioFsync { - unsafe_pinned!(aiocb: AioCb); +impl<'a> AioFsync<'a> { + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the operation's fsync mode: data and metadata or data only? pub fn mode(&self) -> AioFsyncMode { @@ -449,12 +455,11 @@ impl AioFsync { /// * `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. + /// 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, + fd: BorrowedFd<'a>, mode: AioFsyncMode, prio: i32, sigev_notify: SigevNotify, @@ -472,7 +477,7 @@ impl AioFsync { } } -impl Aio for AioFsync { +impl<'a> Aio for AioFsync<'a> { type Output = (); aio_methods!(); @@ -493,7 +498,7 @@ impl Aio for AioFsync { // AioFsync does not need AsMut, since it can't be used with lio_listio -impl AsRef for AioFsync { +impl AsRef for AioFsync<'_> { fn as_ref(&self) -> &libc::aiocb { &self.aiocb.aiocb.0 } @@ -515,7 +520,7 @@ impl AsRef for AioFsync { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::Write; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const INITIAL: &[u8] = b"abcdef123456"; /// const LEN: usize = 4; @@ -525,7 +530,7 @@ impl AsRef for AioFsync { /// { /// let mut aior = Box::pin( /// AioRead::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// &mut rbuf, /// 0, //priority @@ -543,13 +548,13 @@ impl AsRef for AioFsync { #[derive(Debug)] #[repr(transparent)] pub struct AioRead<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [u8]>, _pin: PhantomPinned, } impl<'a> AioRead<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the requested length of the aio operation in bytes /// @@ -567,13 +572,11 @@ impl<'a> AioRead<'a> { /// * `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. + /// * `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, + fd: BorrowedFd<'a>, offs: off_t, buf: &'a mut [u8], prio: i32, @@ -581,7 +584,7 @@ impl<'a> AioRead<'a> { ) -> 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_buf = buf.as_mut_ptr().cast(); aiocb.aiocb.0.aio_lio_opcode = libc::LIO_READ; aiocb.aiocb.0.aio_offset = offs; AioRead { @@ -603,13 +606,13 @@ impl<'a> Aio for AioRead<'a> { aio_methods!(aio_read); } -impl<'a> AsMut for AioRead<'a> { +impl AsMut for AioRead<'_> { fn as_mut(&mut self) -> &mut libc::aiocb { &mut self.aiocb.aiocb.0 } } -impl<'a> AsRef for AioRead<'a> { +impl AsRef for AioRead<'_> { fn as_ref(&self) -> &libc::aiocb { &self.aiocb.aiocb.0 } @@ -632,7 +635,7 @@ impl<'a> AsRef for AioRead<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::{IoSliceMut, Write}; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const INITIAL: &[u8] = b"abcdef123456"; /// let mut rbuf0 = vec![0; 4]; @@ -644,7 +647,7 @@ impl<'a> AsRef for AioRead<'a> { /// { /// let mut aior = Box::pin( /// AioReadv::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// &mut rbufs, /// 0, //priority @@ -664,14 +667,14 @@ impl<'a> AsRef for AioRead<'a> { #[derive(Debug)] #[repr(transparent)] pub struct AioReadv<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [&'a [u8]]>, _pin: PhantomPinned, } #[cfg(target_os = "freebsd")] impl<'a> AioReadv<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the number of buffers the operation will read into. pub fn iovlen(&self) -> usize { @@ -692,7 +695,7 @@ impl<'a> AioReadv<'a> { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, offs: off_t, bufs: &mut [IoSliceMut<'a>], prio: i32, @@ -702,7 +705,7 @@ impl<'a> AioReadv<'a> { // 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_buf = bufs.as_mut_ptr().cast(); aiocb.aiocb.0.aio_lio_opcode = libc::LIO_READV; aiocb.aiocb.0.aio_offset = offs; AioReadv { @@ -726,14 +729,14 @@ impl<'a> Aio for AioReadv<'a> { } #[cfg(target_os = "freebsd")] -impl<'a> AsMut for AioReadv<'a> { +impl AsMut for AioReadv<'_> { fn as_mut(&mut self) -> &mut libc::aiocb { &mut self.aiocb.aiocb.0 } } #[cfg(target_os = "freebsd")] -impl<'a> AsRef for AioReadv<'a> { +impl AsRef for AioReadv<'_> { fn as_ref(&self) -> &libc::aiocb { &self.aiocb.aiocb.0 } @@ -753,13 +756,13 @@ impl<'a> AsRef for AioReadv<'a> { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin( /// AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// WBUF, /// 0, //priority @@ -775,13 +778,13 @@ impl<'a> AsRef for AioReadv<'a> { #[derive(Debug)] #[repr(transparent)] pub struct AioWrite<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [u8]>, _pin: PhantomPinned, } impl<'a> AioWrite<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the requested length of the aio operation in bytes /// @@ -799,13 +802,11 @@ impl<'a> AioWrite<'a> { /// * `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. + /// * `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, + fd: BorrowedFd<'a>, offs: off_t, buf: &'a [u8], prio: i32, @@ -817,7 +818,7 @@ impl<'a> AioWrite<'a> { // 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_buf = buf.as_ptr().cast_mut().cast(); aiocb.aiocb.0.aio_lio_opcode = libc::LIO_WRITE; aiocb.aiocb.0.aio_offset = offs; AioWrite { @@ -839,13 +840,13 @@ impl<'a> Aio for AioWrite<'a> { aio_methods!(aio_write); } -impl<'a> AsMut for AioWrite<'a> { +impl AsMut for AioWrite<'_> { fn as_mut(&mut self) -> &mut libc::aiocb { &mut self.aiocb.aiocb.0 } } -impl<'a> AsRef for AioWrite<'a> { +impl AsRef for AioWrite<'_> { fn as_ref(&self) -> &libc::aiocb { &self.aiocb.aiocb.0 } @@ -867,7 +868,7 @@ impl<'a> AsRef for AioWrite<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::IoSlice; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const wbuf0: &[u8] = b"abcdef"; /// const wbuf1: &[u8] = b"123456"; @@ -876,7 +877,7 @@ impl<'a> AsRef for AioWrite<'a> { /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin( /// AioWritev::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// &wbufs, /// 0, //priority @@ -893,14 +894,14 @@ impl<'a> AsRef for AioWrite<'a> { #[derive(Debug)] #[repr(transparent)] pub struct AioWritev<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [&'a [u8]]>, _pin: PhantomPinned, } #[cfg(target_os = "freebsd")] impl<'a> AioWritev<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the number of buffers the operation will read into. pub fn iovlen(&self) -> usize { @@ -921,7 +922,7 @@ impl<'a> AioWritev<'a> { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, offs: off_t, bufs: &[IoSlice<'a>], prio: i32, @@ -935,7 +936,7 @@ impl<'a> AioWritev<'a> { // 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_buf = bufs.as_ptr().cast_mut().cast(); aiocb.aiocb.0.aio_lio_opcode = libc::LIO_WRITEV; aiocb.aiocb.0.aio_offset = offs; AioWritev { @@ -959,14 +960,14 @@ impl<'a> Aio for AioWritev<'a> { } #[cfg(target_os = "freebsd")] -impl<'a> AsMut for AioWritev<'a> { +impl AsMut for AioWritev<'_> { fn as_mut(&mut self) -> &mut libc::aiocb { &mut self.aiocb.aiocb.0 } } #[cfg(target_os = "freebsd")] -impl<'a> AsRef for AioWritev<'a> { +impl AsRef for AioWritev<'_> { fn as_ref(&self) -> &libc::aiocb { &self.aiocb.aiocb.0 } @@ -986,17 +987,17 @@ impl<'a> AsRef for AioWritev<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::Write; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); -/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// &wbuf[..], /// 0, //priority /// SigevNotify::SigevNone)); /// aiocb.as_mut().submit().unwrap(); -/// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); +/// let cs = aio_cancel_all(f.as_fd()).unwrap(); /// if cs == AioCancelStat::AioNotCanceled { /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); @@ -1009,8 +1010,8 @@ impl<'a> AsRef for AioWritev<'a> { /// # 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()) } { +pub fn aio_cancel_all(fd: F) -> Result { + match unsafe { libc::aio_cancel(fd.as_fd().as_raw_fd(), ptr::null_mut()) } { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), @@ -1031,18 +1032,18 @@ pub fn aio_cancel_all(fd: RawFd) -> Result { /// ``` /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); -/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// let mut aiocb = Box::pin(AioWrite::new(f.as_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()); +/// assert_eq!(aiocb.as_mut().aio_return().unwrap(), WBUF.len()); /// ``` /// # References /// @@ -1051,8 +1052,15 @@ pub fn aio_suspend( list: &[&dyn AsRef], timeout: Option, ) -> Result<()> { - let p = list as *const [&dyn AsRef] - as *const [*const libc::aiocb] as *const *const libc::aiocb; + // 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, @@ -1074,14 +1082,14 @@ pub fn aio_suspend( /// 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 std::os::unix::io::AsFd; /// # 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(), +/// f.as_fd(), /// 2, // offset /// WBUF, /// 0, // priority @@ -1098,7 +1106,7 @@ pub fn aio_suspend( /// technique for reducing overall context-switch overhead, especially when /// combined with kqueue. /// ``` -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use std::thread; /// # use std::time; /// # use nix::errno::Errno; @@ -1108,7 +1116,7 @@ pub fn aio_suspend( /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin(AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, // offset /// WBUF, /// 0, // priority @@ -1132,18 +1140,15 @@ pub fn aio_suspend( /// possibly resubmit some. /// ``` /// # use libc::c_int; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use std::sync::atomic::{AtomicBool, Ordering}; /// # use std::thread; /// # use std::time; -/// # use lazy_static::lazy_static; /// # use nix::errno::Errno; /// # use nix::sys::aio::*; /// # use nix::sys::signal::*; /// # use tempfile::tempfile; -/// lazy_static! { -/// pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); -/// } +/// pub static SIGNALED: AtomicBool = AtomicBool::new(false); /// /// extern fn sigfunc(_: c_int) { /// SIGNALED.store(true, Ordering::Relaxed); @@ -1157,7 +1162,7 @@ pub fn aio_suspend( /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin(AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, // offset /// WBUF, /// 0, // priority @@ -1172,6 +1177,10 @@ pub fn aio_suspend( /// // 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>], @@ -1186,56 +1195,3 @@ pub fn lio_listio( }) .map(drop) } - -#[cfg(test)] -mod t { - use super::*; - - /// aio_suspend relies on casting Rust Aio* struct pointers to libc::aiocb - /// pointers. This test ensures that such casts are valid. - #[test] - fn casting() { - let sev = SigevNotify::SigevNone; - let aiof = AioFsync::new(666, AioFsyncMode::O_SYNC, 0, sev); - assert_eq!( - aiof.as_ref() as *const libc::aiocb, - &aiof as *const AioFsync as *const libc::aiocb - ); - - let mut rbuf = []; - let aior = AioRead::new(666, 0, &mut rbuf, 0, sev); - assert_eq!( - aior.as_ref() as *const libc::aiocb, - &aior as *const AioRead as *const libc::aiocb - ); - - let wbuf = []; - let aiow = AioWrite::new(666, 0, &wbuf, 0, sev); - assert_eq!( - aiow.as_ref() as *const libc::aiocb, - &aiow as *const AioWrite as *const libc::aiocb - ); - } - - #[cfg(target_os = "freebsd")] - #[test] - fn casting_vectored() { - let sev = SigevNotify::SigevNone; - - let mut rbuf = []; - let mut rbufs = [IoSliceMut::new(&mut rbuf)]; - let aiorv = AioReadv::new(666, 0, &mut rbufs[..], 0, sev); - assert_eq!( - aiorv.as_ref() as *const libc::aiocb, - &aiorv as *const AioReadv as *const libc::aiocb - ); - - let wbuf = []; - let wbufs = [IoSlice::new(&wbuf)]; - let aiowv = AioWritev::new(666, 0, &wbufs, 0, sev); - assert_eq!( - aiowv.as_ref() as *const libc::aiocb, - &aiowv as *const AioWritev as *const libc::aiocb - ); - } -} diff --git a/src/sys/epoll.rs b/src/sys/epoll.rs index 58def2e7..6cc5a88f 100644 --- a/src/sys/epoll.rs +++ b/src/sys/epoll.rs @@ -1,9 +1,10 @@ use crate::errno::Errno; +pub use crate::poll_timeout::PollTimeout as EpollTimeout; +pub use crate::poll_timeout::PollTimeoutTryFromError as EpollTimeoutTryFromError; use crate::Result; use libc::{self, c_int}; use std::mem; -use std::os::unix::io::RawFd; -use std::ptr; +use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}; libc_bitflags!( pub struct EpollFlags: c_int { @@ -70,6 +71,126 @@ impl EpollEvent { } } +/// A safe wrapper around [`epoll`](https://man7.org/linux/man-pages/man7/epoll.7.html). +/// ``` +/// # use nix::sys::{epoll::{EpollTimeout, Epoll, EpollEvent, EpollFlags, EpollCreateFlags}, eventfd::{EventFd, EfdFlags}}; +/// # use nix::unistd::write; +/// # use std::os::unix::io::{OwnedFd, FromRawFd, AsFd}; +/// # use std::time::{Instant, Duration}; +/// # fn main() -> nix::Result<()> { +/// const DATA: u64 = 17; +/// const MILLIS: u8 = 100; +/// +/// // Create epoll +/// let epoll = Epoll::new(EpollCreateFlags::empty())?; +/// +/// // Create eventfd & Add event +/// let eventfd = EventFd::new()?; +/// epoll.add(&eventfd, EpollEvent::new(EpollFlags::EPOLLIN,DATA))?; +/// +/// // Arm eventfd & Time wait +/// eventfd.write(1)?; +/// let now = Instant::now(); +/// +/// // Wait on event +/// let mut events = [EpollEvent::empty()]; +/// epoll.wait(&mut events, MILLIS)?; +/// +/// // Assert data correct & timeout didn't occur +/// assert_eq!(events[0].data(), DATA); +/// assert!(now.elapsed().as_millis() < MILLIS.into()); +/// # 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: T, + ) -> Result { + let res = unsafe { + libc::epoll_wait( + self.0.as_raw_fd(), + events.as_mut_ptr().cast(), + events.len() as c_int, + timeout.into().into(), + ) + }; + + 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) }; @@ -77,6 +198,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()) }; @@ -84,6 +206,10 @@ pub fn epoll_create1(flags: EpollCreateFlags) -> Result { Errno::result(res) } +#[deprecated( + since = "0.27.0", + note = "Use corresponding Epoll methods instead" +)] #[inline] pub fn epoll_ctl<'a, T>( epfd: RawFd, @@ -102,13 +228,14 @@ where 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, @@ -118,7 +245,7 @@ pub fn epoll_wait( let res = unsafe { libc::epoll_wait( epfd, - events.as_mut_ptr() as *mut libc::epoll_event, + events.as_mut_ptr().cast(), events.len() as c_int, timeout_ms as c_int, ) diff --git a/src/sys/event.rs b/src/sys/event.rs index 2cbb28d6..ce3d5583 100644 --- a/src/sys/event.rs +++ b/src/sys/event.rs @@ -1,5 +1,7 @@ -/* 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"))] @@ -8,23 +10,88 @@ use libc::{c_int, c_long, intptr_t, time_t, timespec, uintptr_t}; use libc::{c_long, intptr_t, size_t, time_t, timespec, uintptr_t}; use std::convert::TryInto; use std::mem; -use std::os::unix::io::RawFd; +use std::os::fd::{AsFd, BorrowedFd}; +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 AsFd for Kqueue { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl From for OwnedFd { + fn from(value: Kqueue) -> Self { + value.0 + } +} + +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().cast(), + changelist.len() as type_of_nchanges, + eventlist.as_mut_ptr().cast(), + 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(freebsdlike, apple_targets, target_os = "openbsd"))] type type_of_udata = *mut libc::c_void; #[cfg(target_os = "netbsd")] type type_of_udata = intptr_t; @@ -37,22 +104,31 @@ 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"))] + #[cfg(any(freebsdlike, apple_targets))] + /// 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"))] + #[cfg(apple_targets)] + /// 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,157 +136,207 @@ 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"))] + #[cfg(any(freebsdlike, apple_targets))] + /// Notifies only when explicitly requested by the user. EVFILT_USER, - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] + /// 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(freebsdlike, apple_targets, target_os = "openbsd"))] +#[doc(hidden)] pub type type_of_event_flag = u16; #[cfg(target_os = "netbsd")] +#[doc(hidden)] pub type type_of_event_flag = u32; libc_bitflags! { - pub struct EventFlag: type_of_event_flag { + /// 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 EvFlags: 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"))] + #[cfg(bsd)] + #[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"))] + #[cfg(apple_targets)] + #[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"))] + #[cfg(apple_targets)] + #[allow(missing_docs)] EV_OOBAND; - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] + #[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"))] + #[cfg(bsd)] + #[allow(missing_docs)] EV_RECEIPT; } } +#[deprecated(since = "0.30.0", note = "Use `EvFlags instead`")] +/// The deprecated EventFlag type alias +pub type EventFlag = EvFlags; + 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"))] + #[cfg(apple_targets)] + #[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"))] + #[cfg(apple_targets)] + #[allow(missing_docs)] NOTE_EXITSTATUS; + #[allow(missing_docs)] NOTE_EXTEND; - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly"))] + #[cfg(any(apple_targets, freebsdlike))] + #[allow(missing_docs)] NOTE_FFAND; - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly"))] + #[cfg(any(apple_targets, freebsdlike))] + #[allow(missing_docs)] NOTE_FFCOPY; - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly"))] + #[cfg(any(apple_targets, freebsdlike))] + #[allow(missing_docs)] NOTE_FFCTRLMASK; - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly"))] + #[cfg(any(apple_targets, freebsdlike))] + #[allow(missing_docs)] NOTE_FFLAGSMASK; - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly"))] + #[cfg(any(apple_targets, freebsdlike))] + #[allow(missing_docs)] NOTE_FFNOP; - #[cfg(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly"))] + #[cfg(any(apple_targets, freebsdlike))] + #[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"))] + #[cfg(apple_targets)] + #[allow(missing_docs)] NOTE_NONE; - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))] + #[cfg(any( + apple_targets, + 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"))] + #[cfg(any( + apple_targets, + target_os = "freebsd"))] + #[allow(missing_docs)] NOTE_SECONDS; - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] + #[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"))] + #[cfg(any(apple_targets, freebsdlike))] + #[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"))] + #[cfg(any( + apple_targets, + target_os = "freebsd"))] + #[allow(missing_docs)] NOTE_USECONDS; - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] + #[allow(missing_docs)] NOTE_VM_ERROR; - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] + #[allow(missing_docs)] NOTE_VM_PRESSURE; - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] + #[allow(missing_docs)] NOTE_VM_PRESSURE_SUDDEN_TERMINATE; - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] + #[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 @@ -220,10 +346,12 @@ unsafe impl Send for KEvent {} impl KEvent { #[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, + flags: EvFlags, fflags: FilterFlag, data: intptr_t, udata: intptr_t, @@ -242,33 +370,46 @@ impl KEvent { } } + /// 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() } - pub fn flags(&self) -> EventFlag { - EventFlag::from_bits(self.kevent.flags).unwrap() + /// Flags control what the kernel will do when this event is added with + /// [`Kqueue::kevent`]. + pub fn flags(&self) -> EvFlags { + EvFlags::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 } } +#[allow(missing_docs)] +#[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] pub fn kevent( - kq: RawFd, + kq: &Kqueue, changelist: &[KEvent], eventlist: &mut [KEvent], timeout_ms: usize, @@ -279,50 +420,34 @@ pub fn kevent( 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(apple_targets, freebsdlike, target_os = "openbsd"))] type type_of_nchanges = c_int; #[cfg(target_os = "netbsd")] type type_of_nchanges = size_t; +#[allow(missing_docs)] +#[deprecated(since = "0.27.0", note = "Use Kqueue::kevent instead")] pub fn kevent_ts( - kq: RawFd, + kq: &Kqueue, 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) + 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, + flags: EvFlags, fflags: FilterFlag, udata: intptr_t, ) { @@ -333,42 +458,3 @@ pub fn ev_set( ev.kevent.data = 0; ev.kevent.udata = udata as type_of_udata; } - -#[test] -fn test_struct_kevent() { - use std::mem; - - let udata: intptr_t = 12345; - - let actual = KEvent::new( - 0xdead_beef, - EventFilter::EVFILT_READ, - EventFlag::EV_ONESHOT | EventFlag::EV_ADD, - FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, - 0x1337, - udata, - ); - assert_eq!(0xdead_beef, actual.ident()); - let filter = actual.kevent.filter; - assert_eq!(libc::EVFILT_READ, filter); - assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits()); - assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits()); - assert_eq!(0x1337, actual.data()); - assert_eq!(udata as type_of_udata, actual.udata() as type_of_udata); - assert_eq!(mem::size_of::(), mem::size_of::()); -} - -#[test] -fn test_kevent_filter() { - let udata: intptr_t = 12345; - - let actual = KEvent::new( - 0xdead_beef, - EventFilter::EVFILT_READ, - EventFlag::EV_ONESHOT | EventFlag::EV_ADD, - FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, - 0x1337, - udata, - ); - assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); -} diff --git a/src/sys/eventfd.rs b/src/sys/eventfd.rs index cd906720..382a72e7 100644 --- a/src/sys/eventfd.rs +++ b/src/sys/eventfd.rs @@ -1,17 +1,118 @@ use crate::errno::Errno; -use crate::Result; -use std::os::unix::io::RawFd; +use crate::{unistd, Result}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; libc_bitflags! { + /// Eventfd flags. pub struct EfdFlags: libc::c_int { - EFD_CLOEXEC; // Since Linux 2.6.27 - EFD_NONBLOCK; // Since Linux 2.6.27 - EFD_SEMAPHORE; // Since Linux 2.6.30 + /// Set the close-on-exec (`FD_CLOEXEC`) flag on the new event file descriptor. + EFD_CLOEXEC; // Since Linux 2.6.27/FreeBSD 13.0 + /// Set the `O_NONBLOCK` file status flag on the new event file description. + EFD_NONBLOCK; // Since Linux 2.6.27/FreeBSD 13.0 + /// Provide semaphore-like semantics for reads from the new event file + /// descriptor. + EFD_SEMAPHORE; // Since Linux 2.6.30/FreeBSD 13.0 } } -pub fn eventfd(initval: libc::c_uint, flags: EfdFlags) -> Result { +#[deprecated( + since = "0.28.0", + note = "Use EventFd::from_value_and_flags() instead" +)] +#[allow(missing_docs)] +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) }) +} + +/// An eventfd file descriptor. +#[derive(Debug)] +#[repr(transparent)] +pub struct EventFd(OwnedFd); + +impl EventFd { + /// [`EventFd::from_value_and_flags`] with `init_val = 0` and `flags = EfdFlags::empty()`. + pub fn new() -> Result { + Self::from_value_and_flags(0, EfdFlags::empty()) + } + + /// Constructs [`EventFd`] with the given `init_val` and `flags`. + /// + /// Wrapper around [`libc::eventfd`]. + pub fn from_value_and_flags( + init_val: u32, + flags: EfdFlags, + ) -> Result { + let res = unsafe { libc::eventfd(init_val, flags.bits()) }; + Errno::result(res).map(|r| Self(unsafe { OwnedFd::from_raw_fd(r) })) + } + + /// [`EventFd::from_value_and_flags`] with `init_val = 0` and given `flags`. + pub fn from_flags(flags: EfdFlags) -> Result { + Self::from_value_and_flags(0, flags) + } + + /// [`EventFd::from_value_and_flags`] with given `init_val` and `flags = EfdFlags::empty()`. + pub fn from_value(init_val: u32) -> Result { + Self::from_value_and_flags(init_val, EfdFlags::empty()) + } + + /// Constructs an `EventFd` wrapping an existing `OwnedFd`. + /// + /// # Safety + /// + /// `OwnedFd` is a valid eventfd. + pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { + Self(fd) + } + + /// Enqueues `value` triggers, i.e., adds the integer value supplied in `value` + /// to the counter. + /// + /// The next `value` calls to `poll`, `select` or `epoll` will return immediately. + /// + /// [`EventFd::write`] with `value`. + pub fn write(&self, value: u64) -> Result { + unistd::write(&self.0, &value.to_ne_bytes()) + } + + /// Reads the value from the file descriptor. + /// + /// * If [`EFD_SEMAPHORE`](EfdFlags::EFD_SEMAPHORE) was not specified and + /// the eventfd counter has a nonzero value, then this function returns + /// an `u64` containing that value, and the counter's value is reset to + /// zero. + /// + /// * If [`EFD_SEMAPHORE`](EfdFlags::EFD_SEMAPHORE) was specified and the + /// eventfd counter has a nonzero value, then this function returns an + /// `u64` containing the value 1, and the counter's value is decremented + /// by 1. + /// + /// * If the eventfd counter is zero at the time of this call, then the + /// call either blocks until the counter becomes nonzero (at which time, + /// this function proceeds as described above) or fails with the error + /// `EAGAIN` if the file descriptor has been made nonblocking with + /// [`EFD_NONBLOCK`](EfdFlags::EFD_NONBLOCK). + pub fn read(&self) -> Result { + let mut arr = [0; std::mem::size_of::()]; + unistd::read(&self.0, &mut arr)?; + Ok(u64::from_ne_bytes(arr)) + } +} +impl AsFd for EventFd { + fn as_fd(&self) -> BorrowedFd { + self.0.as_fd() + } +} +impl AsRawFd for EventFd { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl From for OwnedFd { + fn from(value: EventFd) -> Self { + value.0 + } } diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs new file mode 100644 index 00000000..fd3089f7 --- /dev/null +++ b/src/sys/fanotify.rs @@ -0,0 +1,446 @@ +//! Monitoring API for filesystem events. +//! +//! Fanotify is a Linux-only API to monitor filesystems events. +//! +//! Additional capabilities compared to the `inotify` API include the ability to +//! monitor all of the objects in a mounted filesystem, the ability to make +//! access permission decisions, and the possibility to read or modify files +//! before access by other applications. +//! +//! For more documentation, please read +//! [fanotify(7)](https://man7.org/linux/man-pages/man7/fanotify.7.html). + +use crate::errno::Errno; +use crate::fcntl::OFlag; +use crate::unistd::{close, read, write}; +use crate::{NixPath, Result}; +use std::marker::PhantomData; +use std::mem::{size_of, MaybeUninit}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; +use std::ptr; + +libc_bitflags! { + /// Mask for defining which events shall be listened with [`Fanotify::mark()`] + /// and for querying notifications. + pub struct MaskFlags: u64 { + /// File was accessed. + FAN_ACCESS; + /// File was modified. + FAN_MODIFY; + /// Metadata has changed. Since Linux 5.1. + FAN_ATTRIB; + /// Writtable file was closed. + FAN_CLOSE_WRITE; + /// Unwrittable file was closed. + FAN_CLOSE_NOWRITE; + /// File was opened. + FAN_OPEN; + /// File was moved from X. Since Linux 5.1. + FAN_MOVED_FROM; + /// File was moved to Y. Since Linux 5.1. + FAN_MOVED_TO; + /// Subfile was created. Since Linux 5.1. + FAN_CREATE; + /// Subfile was deleted. Since Linux 5.1. + FAN_DELETE; + /// Self was deleted. Since Linux 5.1. + FAN_DELETE_SELF; + /// Self was moved. Since Linux 5.1. + FAN_MOVE_SELF; + /// File was opened for execution. Since Linux 5.0. + FAN_OPEN_EXEC; + + /// Event queue overflowed. + FAN_Q_OVERFLOW; + /// Filesystem error. Since Linux 5.16. + FAN_FS_ERROR; + + /// Permission to open file was requested. + FAN_OPEN_PERM; + /// Permission to access file was requested. + FAN_ACCESS_PERM; + /// Permission to open file for execution was requested. Since Linux + /// 5.0. + FAN_OPEN_EXEC_PERM; + + /// Interested in child events. + FAN_EVENT_ON_CHILD; + + /// File was renamed. Since Linux 5.17. + FAN_RENAME; + + /// Event occurred against dir. + FAN_ONDIR; + + /// Combination of `FAN_CLOSE_WRITE` and `FAN_CLOSE_NOWRITE`. + FAN_CLOSE; + /// Combination of `FAN_MOVED_FROM` and `FAN_MOVED_TO`. + FAN_MOVE; + } +} + +libc_bitflags! { + /// Configuration options for [`Fanotify::init()`]. + pub struct InitFlags: libc::c_uint { + /// Close-on-exec flag set on the file descriptor. + FAN_CLOEXEC; + /// Nonblocking flag set on the file descriptor. + FAN_NONBLOCK; + + /// Receipt of events notifications. + FAN_CLASS_NOTIF; + /// Receipt of events for permission decisions, after they contain final + /// data. + FAN_CLASS_CONTENT; + /// Receipt of events for permission decisions, before they contain + /// final data. + FAN_CLASS_PRE_CONTENT; + + /// Remove the limit on the number of events in the event queue. + /// + /// Prior to Linux kernel 5.13, this limit was hardcoded to 16384. After + /// 5.13, one can change it via file `/proc/sys/fs/fanotify/max_queued_events`. + /// + /// See `fanotify(7)` for details about this limit. Use of this flag + /// requires the `CAP_SYS_ADMIN` capability. + FAN_UNLIMITED_QUEUE; + /// Remove the limit on the number of fanotify marks per user. + /// + /// Prior to Linux kernel 5.13, this limit was hardcoded to 8192 (per + /// group, not per user). After 5.13, one can change it via file + /// `/proc/sys/fs/fanotify/max_user_marks`. + /// + /// See `fanotify(7)` for details about this limit. Use of this flag + /// requires the `CAP_SYS_ADMIN` capability. + FAN_UNLIMITED_MARKS; + + /// Make `FanotifyEvent::pid` return pidfd. Since Linux 5.15. + FAN_REPORT_PIDFD; + /// Make `FanotifyEvent::pid` return thread id. Since Linux 4.20. + FAN_REPORT_TID; + } +} + +libc_bitflags! { + /// File status flags for fanotify events file descriptors. + pub struct EventFFlags: libc::c_uint { + /// Read only access. + O_RDONLY as libc::c_uint; + /// Write only access. + O_WRONLY as libc::c_uint; + /// Read and write access. + O_RDWR as libc::c_uint; + /// Support for files exceeded 2 GB. + O_LARGEFILE as libc::c_uint; + /// Close-on-exec flag for the file descriptor. Since Linux 3.18. + O_CLOEXEC as libc::c_uint; + /// Append mode for the file descriptor. + O_APPEND as libc::c_uint; + /// Synchronized I/O data integrity completion. + O_DSYNC as libc::c_uint; + /// No file last access time update. + O_NOATIME as libc::c_uint; + /// Nonblocking mode for the file descriptor. + O_NONBLOCK as libc::c_uint; + /// Synchronized I/O file integrity completion. + O_SYNC as libc::c_uint; + } +} + +impl TryFrom for EventFFlags { + type Error = Errno; + + fn try_from(o_flag: OFlag) -> Result { + EventFFlags::from_bits(o_flag.bits() as u32).ok_or(Errno::EINVAL) + } +} + +impl From for OFlag { + fn from(event_f_flags: EventFFlags) -> Self { + OFlag::from_bits_retain(event_f_flags.bits() as i32) + } +} + +libc_bitflags! { + /// Configuration options for [`Fanotify::mark()`]. + pub struct MarkFlags: libc::c_uint { + /// Add the events to the marks. + FAN_MARK_ADD; + /// Remove the events to the marks. + FAN_MARK_REMOVE; + /// Don't follow symlinks, mark them. + FAN_MARK_DONT_FOLLOW; + /// Raise an error if filesystem to be marked is not a directory. + FAN_MARK_ONLYDIR; + /// Events added to or removed from the marks. + FAN_MARK_IGNORED_MASK; + /// Ignore mask shall survive modify events. + FAN_MARK_IGNORED_SURV_MODIFY; + /// Remove all marks. + FAN_MARK_FLUSH; + /// Do not pin inode object in the inode cache. Since Linux 5.19. + FAN_MARK_EVICTABLE; + /// Events added to or removed from the marks. Since Linux 6.0. + FAN_MARK_IGNORE; + + /// Default flag. + FAN_MARK_INODE; + /// Mark the mount specified by pathname. + FAN_MARK_MOUNT; + /// Mark the filesystem specified by pathname. Since Linux 4.20. + FAN_MARK_FILESYSTEM; + + /// Combination of `FAN_MARK_IGNORE` and `FAN_MARK_IGNORED_SURV_MODIFY`. + FAN_MARK_IGNORE_SURV; + } +} + +/// Compile version number of fanotify API. +pub const FANOTIFY_METADATA_VERSION: u8 = libc::FANOTIFY_METADATA_VERSION; + +/// Abstract over [`libc::fanotify_event_metadata`], which represents an event +/// received via [`Fanotify::read_events`]. +// Is not Clone due to fd field, to avoid use-after-close scenarios. +#[derive(Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +#[allow(missing_copy_implementations)] +pub struct FanotifyEvent(libc::fanotify_event_metadata); + +impl FanotifyEvent { + /// Version number for the structure. It must be compared to + /// `FANOTIFY_METADATA_VERSION` to verify compile version and runtime + /// version does match. It can be done with the + /// `FanotifyEvent::check_version` method. + pub fn version(&self) -> u8 { + self.0.vers + } + + /// Checks that compile fanotify API version is equal to the version of the + /// event. + pub fn check_version(&self) -> bool { + self.version() == FANOTIFY_METADATA_VERSION + } + + /// Mask flags of the events. + pub fn mask(&self) -> MaskFlags { + MaskFlags::from_bits_truncate(self.0.mask) + } + + /// The file descriptor of the event. If the value is `None` when reading + /// from the fanotify group, this event is to notify that a group queue + /// overflow occured. + pub fn fd(&self) -> Option { + if self.0.fd == libc::FAN_NOFD { + None + } else { + // SAFETY: self.0.fd will be opened for the lifetime of `Self`, + // which is longer than the lifetime of the returned BorrowedFd, so + // it is safe. + Some(unsafe { BorrowedFd::borrow_raw(self.0.fd) }) + } + } + + /// PID of the process that caused the event. TID in case flag + /// `FAN_REPORT_TID` was set at group initialization. + pub fn pid(&self) -> i32 { + self.0.pid + } +} + +impl Drop for FanotifyEvent { + fn drop(&mut self) { + if self.0.fd == libc::FAN_NOFD { + return; + } + let e = close(self.0.fd); + if !std::thread::panicking() && e == Err(Errno::EBADF) { + panic!("Closing an invalid file descriptor!"); + }; + } +} + +/// Abstraction over the structure to be sent to allow or deny a given event. +#[derive(Debug)] +#[repr(transparent)] +pub struct FanotifyResponse<'a> { + inner: libc::fanotify_response, + _borrowed_fd: PhantomData>, +} + +impl<'a> FanotifyResponse<'a> { + /// Create a new response. + pub fn new(fd: BorrowedFd<'a>, response: Response) -> Self { + Self { + inner: libc::fanotify_response { + fd: fd.as_raw_fd(), + response: response.bits(), + }, + _borrowed_fd: PhantomData, + } + } +} + +libc_bitflags! { + /// Response to be wrapped in [`FanotifyResponse`] and sent to the [`Fanotify`] + /// group to allow or deny an event. + pub struct Response: u32 { + /// Allow the event. + FAN_ALLOW; + /// Deny the event. + FAN_DENY; + } +} + +/// A fanotify group. This is also a file descriptor that can feed to other +/// interfaces consuming file descriptors. +#[derive(Debug)] +pub struct Fanotify { + fd: OwnedFd, +} + +impl Fanotify { + /// Initialize a new fanotify group. + /// + /// Returns a Result containing a Fanotify instance. + /// + /// For more information, see [fanotify_init(2)](https://man7.org/linux/man-pages/man7/fanotify_init.2.html). + pub fn init( + flags: InitFlags, + event_f_flags: EventFFlags, + ) -> Result { + let res = Errno::result(unsafe { + libc::fanotify_init(flags.bits(), event_f_flags.bits()) + }); + res.map(|fd| Fanotify { + fd: unsafe { OwnedFd::from_raw_fd(fd) }, + }) + } + + /// Add, remove, or modify an fanotify mark on a filesystem object. + /// + /// Returns a Result containing either `()` on success or errno otherwise. + /// + /// For more information, see [fanotify_mark(2)](https://man7.org/linux/man-pages/man7/fanotify_mark.2.html). + pub fn mark( + &self, + flags: MarkFlags, + mask: MaskFlags, + dirfd: Fd, + path: Option<&P>, + ) -> Result<()> { + let res = crate::with_opt_nix_path(path, |p| unsafe { + libc::fanotify_mark( + self.fd.as_raw_fd(), + flags.bits(), + mask.bits(), + dirfd.as_fd().as_raw_fd(), + p, + ) + })?; + + Errno::result(res).map(|_| ()) + } + + /// Read incoming events from the fanotify group. + /// + /// Returns a Result containing either a `Vec` of events on success or errno + /// otherwise. + /// + /// # Errors + /// + /// Possible errors can be those that are explicitly listed in + /// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in + /// addition to the possible errors caused by `read` call. + /// In particular, `EAGAIN` is returned when no event is available on a + /// group that has been initialized with the flag `InitFlags::FAN_NONBLOCK`, + /// thus making this method nonblocking. + pub fn read_events(&self) -> Result> { + let metadata_size = size_of::(); + const BUFSIZ: usize = 4096; + let mut buffer = [0u8; BUFSIZ]; + let mut events = Vec::new(); + let mut offset = 0; + + let nread = read(&self.fd, &mut buffer)?; + + while (nread - offset) >= metadata_size { + let metadata = unsafe { + let mut metadata = + MaybeUninit::::uninit(); + ptr::copy_nonoverlapping( + buffer.as_ptr().add(offset), + metadata.as_mut_ptr().cast(), + (BUFSIZ - offset).min(metadata_size), + ); + metadata.assume_init() + }; + + events.push(FanotifyEvent(metadata)); + offset += metadata.event_len as usize; + } + + Ok(events) + } + + /// Write an event response on the fanotify group. + /// + /// Returns a Result containing either `()` on success or errno otherwise. + /// + /// # Errors + /// + /// Possible errors can be those that are explicitly listed in + /// [fanotify(2)](https://man7.org/linux/man-pages/man7/fanotify.2.html) in + /// addition to the possible errors caused by `write` call. + /// In particular, `EAGAIN` or `EWOULDBLOCK` is returned when no event is + /// available on a group that has been initialized with the flag + /// `InitFlags::FAN_NONBLOCK`, thus making this method nonblocking. + pub fn write_response(&self, response: FanotifyResponse) -> Result<()> { + write(self.fd.as_fd(), unsafe { + std::slice::from_raw_parts( + (&response.inner as *const libc::fanotify_response).cast(), + size_of::(), + ) + })?; + Ok(()) + } +} + +impl FromRawFd for Fanotify { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Fanotify { + fd: unsafe { OwnedFd::from_raw_fd(fd) }, + } + } +} + +impl AsFd for Fanotify { + fn as_fd(&'_ self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl AsRawFd for Fanotify { + fn as_raw_fd(&self) -> RawFd + { + self.fd.as_raw_fd() + } +} + +impl From for OwnedFd { + fn from(value: Fanotify) -> Self { + value.fd + } +} + +impl Fanotify { + /// Constructs a `Fanotify` wrapping an existing `OwnedFd`. + /// + /// # Safety + /// + /// `OwnedFd` is a valid `Fanotify`. + pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { + Self { + fd + } + } +} \ No newline at end of file diff --git a/src/sys/inotify.rs b/src/sys/inotify.rs index 84356ec7..3a00ea0d 100644 --- a/src/sys/inotify.rs +++ b/src/sys/inotify.rs @@ -32,7 +32,7 @@ use libc::{c_char, c_int}; use std::ffi::{CStr, OsStr, OsString}; use std::mem::{size_of, MaybeUninit}; use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::ptr; libc_bitflags! { @@ -101,9 +101,9 @@ libc_bitflags! { /// 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. @@ -143,7 +143,9 @@ impl Inotify { pub fn init(flags: InitFlags) -> Result { 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. @@ -152,12 +154,16 @@ impl Inotify { /// /// 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, + &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()) + libc::inotify_add_watch( + self.fd.as_raw_fd(), + cstr.as_ptr(), + mask.bits(), + ) })?; Errno::result(res).map(|wd| WatchDescriptor { wd }) @@ -169,7 +175,7 @@ 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). - pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> { + pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> { cfg_if! { if #[cfg(target_os = "linux")] { let arg = wd.wd; @@ -177,7 +183,7 @@ impl Inotify { let arg = wd.wd as u32; } } - let res = unsafe { libc::inotify_rm_watch(self.fd, arg) }; + let res = unsafe { libc::inotify_rm_watch(self.fd.as_raw_fd(), arg) }; Errno::result(res).map(drop) } @@ -188,21 +194,21 @@ 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, &mut buffer)?; while (nread - offset) >= header_size { let event = unsafe { let mut event = MaybeUninit::::uninit(); ptr::copy_nonoverlapping( buffer.as_ptr().add(offset), - event.as_mut_ptr() as *mut u8, + event.as_mut_ptr().cast(), (BUFSIZ - offset).min(header_size), ); event.assume_init() @@ -233,16 +239,35 @@ impl Inotify { Ok(events) } -} -impl AsRawFd for Inotify { - fn as_raw_fd(&self) -> RawFd { - self.fd + /// Constructs an `Inotify` wrapping an existing `OwnedFd`. + /// + /// # Safety + /// + /// `OwnedFd` is a valid `Inotify`. + pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { + Self { + fd + } } } impl FromRawFd for Inotify { unsafe fn from_raw_fd(fd: RawFd) -> Self { - Inotify { fd } + Inotify { + fd: unsafe { OwnedFd::from_raw_fd(fd) }, + } } } + +impl AsFd for Inotify { + fn as_fd(&'_ self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +impl From for OwnedFd { + fn from(value: Inotify) -> Self { + value.fd + } +} \ No newline at end of file diff --git a/src/sys/ioctl/bsd.rs b/src/sys/ioctl/bsd.rs index 307994cb..cedc8e63 100644 --- a/src/sys/ioctl/bsd.rs +++ b/src/sys/ioctl/bsd.rs @@ -1,10 +1,10 @@ /// The datatype used for the ioctl number #[doc(hidden)] -#[cfg(not(target_os = "illumos"))] +#[cfg(not(solarish))] pub type ioctl_num_type = ::libc::c_ulong; #[doc(hidden)] -#[cfg(target_os = "illumos")] +#[cfg(solarish)] pub type ioctl_num_type = ::libc::c_int; /// The datatype used for the 3rd argument diff --git a/src/sys/ioctl/linux.rs b/src/sys/ioctl/linux.rs index c71e7d89..314b1c9b 100644 --- a/src/sys/ioctl/linux.rs +++ b/src/sys/ioctl/linux.rs @@ -1,8 +1,20 @@ +use cfg_if::cfg_if; + /// The datatype used for the ioctl number -#[cfg(any(target_os = "android", target_env = "musl", target_env = "ohos"))] +#[cfg(any( + target_os = "android", + target_os = "fuchsia", + target_env = "musl", + target_env = "ohos" +))] #[doc(hidden)] pub type ioctl_num_type = ::libc::c_int; -#[cfg(not(any(target_os = "android", target_env = "musl", target_env = "ohos")))] +#[cfg(not(any( + target_os = "android", + target_os = "fuchsia", + target_env = "musl", + target_env = "ohos" +)))] #[doc(hidden)] pub type ioctl_num_type = ::libc::c_ulong; /// The datatype used for the 3rd argument @@ -14,48 +26,43 @@ pub const NRBITS: ioctl_num_type = 8; #[doc(hidden)] pub const TYPEBITS: ioctl_num_type = 8; -#[cfg(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc", - target_arch = "powerpc64", - target_arch = "sparc64" -))] -mod consts { - #[doc(hidden)] - pub const NONE: u8 = 1; - #[doc(hidden)] - pub const READ: u8 = 2; - #[doc(hidden)] - pub const WRITE: u8 = 4; - #[doc(hidden)] - pub const SIZEBITS: u8 = 13; - #[doc(hidden)] - pub const DIRBITS: u8 = 3; -} - -// "Generic" ioctl protocol -#[cfg(any( - target_arch = "x86", - target_arch = "arm", - target_arch = "s390x", - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "riscv32", - target_arch = "riscv64", - target_arch = "loongarch64" -))] -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 = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + 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::*; diff --git a/src/sys/ioctl/mod.rs b/src/sys/ioctl/mod.rs index 0b3fe3e7..aab48192 100644 --- a/src/sys/ioctl/mod.rs +++ b/src/sys/ioctl/mod.rs @@ -72,7 +72,7 @@ //! # const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h //! # const SPI_IOC_TYPE_MODE: u8 = 1; //! pub unsafe fn spi_read_mode(fd: c_int, data: *mut u8) -> Result { -//! let res = libc::ioctl(fd, request_code_read!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, mem::size_of::()), data); +//! let res = unsafe { libc::ioctl(fd, request_code_read!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, mem::size_of::()), data) }; //! Errno::result(res) //! } //! # fn main() {} @@ -121,11 +121,11 @@ //! //! ``` //! # #[macro_use] extern crate nix; -//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # #[cfg(linux_android)] //! # use nix::libc::TCGETS as TCGETS; -//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # #[cfg(linux_android)] //! # use nix::libc::termios as termios; -//! # #[cfg(any(target_os = "android", target_os = "linux"))] +//! # #[cfg(linux_android)] //! ioctl_read_bad!(tcgets, TCGETS, termios); //! # fn main() {} //! ``` @@ -179,9 +179,13 @@ //! # const SPI_IOC_TYPE_MESSAGE: u8 = 0; //! # pub struct spi_ioc_transfer(u64); //! pub unsafe fn spi_message(fd: c_int, data: &mut [spi_ioc_transfer]) -> Result { -//! let res = libc::ioctl(fd, -//! request_code_write!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, data.len() * mem::size_of::()), -//! data); +//! let res = unsafe { +//! libc::ioctl( +//! fd, +//! request_code_write!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, data.len() * mem::size_of::()), +//! data +//! ) +//! }; //! Errno::result(res) //! } //! # fn main() {} @@ -223,40 +227,18 @@ //! ``` use cfg_if::cfg_if; -#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] +#[cfg(any(linux_android, target_os = "fuchsia", target_os = "redox"))] #[macro_use] mod linux; -#[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "redox" -))] +#[cfg(any(linux_android, target_os = "fuchsia", target_os = "redox"))] pub use self::linux::*; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "haiku", - target_os = "openbsd" -))] +#[cfg(any(bsd, solarish, target_os = "haiku",))] #[macro_use] mod bsd; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "haiku", - target_os = "openbsd" -))] +#[cfg(any(bsd, solarish, target_os = "haiku",))] pub use self::bsd::*; /// Convert raw ioctl return value to a Nix result @@ -305,7 +287,9 @@ macro_rules! ioctl_none { $(#[$attr])* pub unsafe fn $name(fd: $crate::libc::c_int) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, request_code_none!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_none!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type)) + } } ) } @@ -345,7 +329,9 @@ macro_rules! ioctl_none_bad { $(#[$attr])* pub unsafe fn $name(fd: $crate::libc::c_int) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type)) + } } ) } @@ -383,7 +369,9 @@ macro_rules! ioctl_read { pub unsafe fn $name(fd: $crate::libc::c_int, data: *mut $ty) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -408,7 +396,7 @@ macro_rules! ioctl_read { /// /// ``` /// # #[macro_use] extern crate nix; -/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// # #[cfg(linux_android)] /// ioctl_read_bad!(tcgets, libc::TCGETS, libc::termios); /// # fn main() {} /// ``` @@ -419,7 +407,9 @@ macro_rules! ioctl_read_bad { pub unsafe fn $name(fd: $crate::libc::c_int, data: *mut $ty) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -456,7 +446,9 @@ macro_rules! ioctl_write_ptr { pub unsafe fn $name(fd: $crate::libc::c_int, data: *const $ty) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -481,7 +473,7 @@ macro_rules! ioctl_write_ptr { /// /// ``` /// # #[macro_use] extern crate nix; -/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// # #[cfg(linux_android)] /// ioctl_write_ptr_bad!(tcsets, libc::TCSETS, libc::termios); /// # fn main() {} /// ``` @@ -492,13 +484,15 @@ macro_rules! ioctl_write_ptr_bad { pub unsafe fn $name(fd: $crate::libc::c_int, data: *const $ty) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } cfg_if! { - if #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] { + if #[cfg(freebsdlike)] { /// Generates a wrapper function for a ioctl that writes an integer to the kernel. /// /// The arguments to this macro are: @@ -533,7 +527,9 @@ cfg_if! { pub unsafe fn $name(fd: $crate::libc::c_int, data: $crate::sys::ioctl::ioctl_param_type) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write_int!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write_int!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -574,7 +570,9 @@ cfg_if! { pub unsafe fn $name(fd: $crate::libc::c_int, data: $crate::sys::ioctl::ioctl_param_type) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$crate::libc::c_int>()) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$crate::libc::c_int>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -600,7 +598,7 @@ cfg_if! { /// /// ``` /// # #[macro_use] extern crate nix; -/// # #[cfg(any(target_os = "android", target_os = "linux"))] +/// # #[cfg(linux_android)] /// ioctl_write_int_bad!(tcsbrk, libc::TCSBRK); /// # fn main() {} /// ``` @@ -618,7 +616,9 @@ macro_rules! ioctl_write_int_bad { pub unsafe fn $name(fd: $crate::libc::c_int, data: $crate::libc::c_int) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -655,7 +655,11 @@ macro_rules! ioctl_readwrite { pub unsafe fn $name(fd: $crate::libc::c_int, data: *mut $ty) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + let ioty = $ioty; + let nr = $nr; + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!(ioty, nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -683,7 +687,9 @@ macro_rules! ioctl_readwrite_bad { pub unsafe fn $name(fd: $crate::libc::c_int, data: *mut $ty) -> $crate::Result<$crate::libc::c_int> { - convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data)) + } } ) } @@ -712,7 +718,9 @@ 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, ::std::mem::size_of_val(data)) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + 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.as_mut_ptr())) + } } ) } @@ -751,7 +759,9 @@ 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, ::std::mem::size_of_val(data)) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + 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.as_ptr())) + } } ) } @@ -780,7 +790,9 @@ 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, ::std::mem::size_of_val(data)) as $crate::sys::ioctl::ioctl_num_type, data)) + unsafe { + 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.as_mut_ptr())) + } } ) } diff --git a/src/sys/memfd.rs b/src/sys/memfd.rs index 699cbd76..ecf80e1e 100644 --- a/src/sys/memfd.rs +++ b/src/sys/memfd.rs @@ -1,15 +1,14 @@ //! Interfaces for managing memory-backed files. use cfg_if::cfg_if; -use std::os::unix::io::RawFd; +use std::os::unix::io::{FromRawFd, OwnedFd, RawFd}; use crate::errno::Errno; -use crate::Result; -use std::ffi::CStr; +use crate::{NixPath, Result}; libc_bitflags!( /// Options that change the behavior of [`memfd_create`]. - pub struct MemFdCreateFlag: libc::c_uint { + pub struct MFdFlags: libc::c_uint { /// Set the close-on-exec ([`FD_CLOEXEC`]) flag on the new file descriptor. /// /// By default, the new file descriptor is set to remain open across an [`execve`] @@ -29,9 +28,68 @@ libc_bitflags!( /// /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html MFD_ALLOW_SEALING; + /// Anonymous file will be created using huge pages. It should be safe now to + /// combine with [`MFD_ALLOW_SEALING`] too. + /// However, despite its presence, on FreeBSD it is unimplemented for now (ENOSYS). + /// + /// See also the hugetlb filesystem in [`memfd_create(2)`]. + /// + /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html + #[cfg(linux_android)] + MFD_HUGETLB; + /// Shift to get the huge page size. + #[cfg(target_env = "ohos")] + MFD_HUGE_SHIFT; + /// Mask to get the huge page size. + #[cfg(target_env = "ohos")] + MFD_HUGE_MASK; + /// hugetlb size of 64KB. + #[cfg(target_env = "ohos")] + MFD_HUGE_64KB; + /// hugetlb size of 512KB. + #[cfg(target_env = "ohos")] + MFD_HUGE_512KB; + /// Following are to be used with [`MFD_HUGETLB`], indicating the desired hugetlb size. + /// + /// See also the hugetlb filesystem in [`memfd_create(2)`]. + /// + /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html + #[cfg(linux_android)] + MFD_HUGE_1MB; + /// hugetlb size of 2MB. + #[cfg(linux_android)] + MFD_HUGE_2MB; + /// hugetlb size of 8MB. + #[cfg(linux_android)] + MFD_HUGE_8MB; + /// hugetlb size of 16MB. + #[cfg(linux_android)] + MFD_HUGE_16MB; + /// hugetlb size of 32MB. + #[cfg(linux_android)] + MFD_HUGE_32MB; + /// hugetlb size of 256MB. + #[cfg(linux_android)] + MFD_HUGE_256MB; + /// hugetlb size of 512MB. + #[cfg(linux_android)] + MFD_HUGE_512MB; + /// hugetlb size of 1GB. + #[cfg(linux_android)] + MFD_HUGE_1GB; + /// hugetlb size of 2GB. + #[cfg(linux_android)] + MFD_HUGE_2GB; + /// hugetlb size of 16GB. + #[cfg(linux_android)] + MFD_HUGE_16GB; } ); +#[deprecated(since = "0.30.0", note = "Use `MFdFlags instead`")] +/// The deprecated MemFdCreateFlag type alias +pub type MemFdCreateFlag = MFdFlags; + /// Creates an anonymous file that lives in memory, and return a file-descriptor to it. /// /// The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on. @@ -40,26 +98,32 @@ 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 { - let res = unsafe { - cfg_if! { +#[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined +pub fn memfd_create( + name: &P, + flags: MFdFlags, +) -> Result { + let res = name.with_nix_path(|cstr| { + unsafe { + cfg_if! { if #[cfg(all( // Android does not have a memfd_create symbol not(target_os = "android"), any( target_os = "freebsd", - // If the OS is Linux, gnu and musl expose a memfd_create symbol but not uclibc + // If the OS is Linux, gnu/musl/ohos expose a memfd_create symbol but not uclibc target_env = "gnu", target_env = "musl", - target_env = "ohos", + target_env = "ohos" )))] { - libc::memfd_create(name.as_ptr(), flags.bits()) + libc::memfd_create(cstr.as_ptr(), flags.bits()) } else { - libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags.bits()) + libc::syscall(libc::SYS_memfd_create, cstr.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 1d63e864..867d207d 100644 --- a/src/sys/mman.rs +++ b/src/sys/mman.rs @@ -1,14 +1,18 @@ //! Memory management declarations. use crate::errno::Errno; -#[cfg(not(any(target_os = "android", target_env = "ohos")))] +#[cfg(not(target_os = "android"))] use crate::NixPath; use crate::Result; -#[cfg(not(any(target_os = "android", target_env = "ohos")))] +#[cfg(not(target_os = "android"))] #[cfg(feature = "fs")] use crate::{fcntl::OFlag, sys::stat::Mode}; use libc::{self, c_int, c_void, off_t, size_t}; -use std::{os::unix::io::RawFd, num::NonZeroUsize}; +use std::ptr::NonNull; +use std::{ + num::NonZeroUsize, + os::unix::io::{AsFd, AsRawFd}, +}; libc_bitflags! { /// Desired memory protection of a memory mapping. @@ -22,12 +26,10 @@ libc_bitflags! { /// Pages can be executed PROT_EXEC; /// Apply protection up to the end of a mapping that grows upwards. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] PROT_GROWSUP; } } @@ -36,154 +38,161 @@ libc_bitflags! { /// Additional parameters for [`mmap`]. pub struct MapFlags: c_int { /// Compatibility flag. Ignored. + #[cfg(not(any(target_os = "solaris", target_os = "redox")))] MAP_FILE; /// Share this mapping. Mutually exclusive with `MAP_PRIVATE`. MAP_SHARED; + /// Force mmap to check and fail on unknown flags. This also enables `MAP_SYNC`. + #[cfg(target_os = "linux")] + MAP_SHARED_VALIDATE; /// Create a private copy-on-write mapping. Mutually exclusive with `MAP_SHARED`. MAP_PRIVATE; /// Place the mapping at exactly the address specified in `addr`. MAP_FIXED; /// Place the mapping at exactly the address specified in `addr`, but never clobber an existing range. #[cfg(target_os = "linux")] - #[cfg_attr(docsrs, doc(cfg(all())))] MAP_FIXED_NOREPLACE; /// To be used with `MAP_FIXED`, to forbid the system /// to select a different address than the one specified. #[cfg(target_os = "freebsd")] - #[cfg_attr(docsrs, doc(cfg(all())))] MAP_EXCL; /// Synonym for `MAP_ANONYMOUS`. MAP_ANON; /// The mapping is not backed by any file. MAP_ANONYMOUS; /// Put the mapping into the first 2GB of the process address space. - #[cfg(any(all(any(target_os = "android", target_os = "linux"), + #[cfg(any(all(linux_android, any(target_arch = "x86", target_arch = "x86_64")), - all(target_os = "linux", any(target_env = "musl", target_env = "ohos"), 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 = "linux", target_env = "ohos", 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())))] + #[cfg(linux_android)] MAP_GROWSDOWN; /// Compatibility flag. Ignored. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] MAP_DENYWRITE; /// Compatibility flag. Ignored. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] MAP_LOCKED; /// Do not reserve swap space for this mapping. /// /// This was removed in FreeBSD 11 and is unused in DragonFlyBSD. - #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(freebsdlike, target_os = "aix", target_os = "hurd", target_os = "redox")))] MAP_NORESERVE; /// Populate page tables for a mapping. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] MAP_NONBLOCK; /// Allocate the mapping using "huge pages." - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(freebsdlike)] 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())))] + #[cfg(netbsdlike)] 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())))] + #[cfg(any(freebsdlike, netbsdlike))] 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())))] + #[cfg(any(linux_android, freebsdlike, netbsdlike))] MAP_STACK; + /// Do not write through the page caches, write directly to the file. Used for Direct Access (DAX) enabled file systems. + // Available on Linux glibc and musl, MIPS* target excluded. + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", target_arch = "mips64", target_arch = "mips32r6", target_arch = "mips64r6")), not(target_env = "uclibc")))] + MAP_SYNC; /// 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())))] + #[cfg(apple_targets)] 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())))] + #[cfg(apple_targets)] 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; + /// Attempt to place the mapping at exactly the address specified in `addr`. + /// it's a default behavior on OpenBSD. + #[cfg(netbsdlike)] + MAP_TRYFIXED; + } +} + +impl MapFlags { + /// Create `MAP_HUGETLB` with provided size of huge page. + /// + /// Under the hood it computes `MAP_HUGETLB | (huge_page_size_log2 << libc::MAP_HUGE_SHIFT`). + /// `huge_page_size_log2` denotes logarithm of huge page size to use and should be + /// between 16 and 63 (inclusively). + /// + /// ``` + /// # use nix::sys::mman::MapFlags; + /// let f = MapFlags::map_hugetlb_with_size_log2(30).unwrap(); + /// assert_eq!(f, MapFlags::MAP_HUGETLB | MapFlags::MAP_HUGE_1GB); + /// ``` + #[cfg(any(linux_android, target_os = "fuchsia"))] + pub fn map_hugetlb_with_size_log2( + huge_page_size_log2: u32, + ) -> Option { + if (16..=63).contains(&huge_page_size_log2) { + let flag = libc::MAP_HUGETLB + | (huge_page_size_log2 << libc::MAP_HUGE_SHIFT) as i32; + Some(Self(flag.into())) + } else { + None + } } } @@ -193,19 +202,19 @@ libc_bitflags! { 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; + /// Works in conjunction with `MREMAP_MAYMOVE` but does not unmap `old_address`. + /// Note that, in this case, `old_size` and `new_size` must be the same. + #[cfg(target_os = "linux")] + MREMAP_DONTUNMAP; /// 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; } } @@ -228,30 +237,24 @@ libc_enum! { /// Do not expect access in the near future. MADV_DONTNEED, /// Free up a given range of pages and its associated backing store. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] MADV_DONTFORK, /// Undo the effect of `MADV_DONTFORK`. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] MADV_MERGEABLE, /// Undo the effect of `MADV_MERGEABLE` - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] MADV_UNMERGEABLE, /// Preserve the memory of each page but offline the original page. #[cfg(any(target_os = "android", @@ -266,68 +269,73 @@ libc_enum! { target_arch = "sparc64"))))] MADV_SOFT_OFFLINE, /// Enable Transparent Huge Pages (THP) for pages in the given range. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] MADV_HUGEPAGE, /// Undo the effect of `MADV_HUGEPAGE`. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] MADV_NOHUGEPAGE, /// Exclude the given range from a core dump. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] MADV_DONTDUMP, /// Undo the effect of an earlier `MADV_DONTDUMP`. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] MADV_DODUMP, /// Specify that the application no longer needs the pages in the given range. + #[cfg(not(any(target_os = "aix", target_os = "hurd", target_os = "cygwin", target_os = "redox")))] 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())))] + #[cfg(freebsdlike)] 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())))] + #[cfg(freebsdlike)] MADV_AUTOSYNC, /// Region is not included in a core file. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] MADV_NOCORE, /// Include region in a core file - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] 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())))] + #[cfg(apple_targets)] MADV_ZERO_WIRED_PAGES, /// Pages can be reused (by anyone). - #[cfg(any(target_os = "ios", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] MADV_FREE_REUSABLE, /// Caller wants to reuse those pages. - #[cfg(any(target_os = "ios", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] 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())))] + #[cfg(apple_targets)] #[allow(missing_docs)] MADV_CAN_REUSE, + /// Reclaim the address range when applicable. + #[cfg(linux_android)] + MADV_PAGEOUT, + /// Deactivate the address range when applicable. + #[cfg(linux_android)] + MADV_COLD, + /// After fork, the adress range is zero filled. + #[cfg(linux_android)] + MADV_WIPEONFORK, + /// Undo `MADV_WIPEONFORK` when it applied. + #[cfg(linux_android)] + MADV_KEEPONFORK, + /// Pre-load the address range for reading to reduce page-fault latency. + #[cfg(linux_android)] + MADV_POPULATE_READ, + /// Pre-fault the address range for writing to reduce page-fault + /// latency on subsequent writes. + #[cfg(linux_android)] + MADV_POPULATE_WRITE, } } @@ -339,19 +347,17 @@ libc_bitflags! { /// Invalidate all cached data. MS_INVALIDATE; /// Invalidate pages, but leave them mapped. - #[cfg(any(target_os = "ios", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] MS_KILLPAGES; /// Deactivate pages, but leave them mapped. - #[cfg(any(target_os = "ios", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] MS_DEACTIVATE; /// Perform an update and wait for it to complete. MS_SYNC; } } -#[cfg(not(target_os = "haiku"))] +#[cfg(not(any(target_os = "haiku", target_os = "cygwin", target_os = "redox")))] libc_bitflags! { /// Flags for [`mlockall`]. pub struct MlockAllFlags: c_int { @@ -372,8 +378,8 @@ libc_bitflags! { /// `addr` must meet all the requirements described in the [`mlock(2)`] man page. /// /// [`mlock(2)`]: https://man7.org/linux/man-pages/man2/mlock.2.html -pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> { - Errno::result(libc::mlock(addr, length)).map(drop) +pub unsafe fn mlock(addr: NonNull, length: size_t) -> Result<()> { + unsafe { Errno::result(libc::mlock(addr.as_ptr(), length)).map(drop) } } /// Unlocks all memory pages that contain part of the address range with @@ -385,8 +391,8 @@ pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> { /// page. /// /// [`munlock(2)`]: https://man7.org/linux/man-pages/man2/munlock.2.html -pub unsafe fn munlock(addr: *const c_void, length: size_t) -> Result<()> { - Errno::result(libc::munlock(addr, length)).map(drop) +pub unsafe fn munlock(addr: NonNull, length: size_t) -> Result<()> { + unsafe { Errno::result(libc::munlock(addr.as_ptr(), length)).map(drop) } } /// Locks all memory pages mapped into this process' address space. @@ -394,7 +400,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"))] +#[cfg(not(any(target_os = "haiku", target_os = "cygwin", target_os = "redox")))] pub fn mlockall(flags: MlockAllFlags) -> Result<()> { unsafe { Errno::result(libc::mlockall(flags.bits())) }.map(drop) } @@ -404,37 +410,73 @@ 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"))] +#[cfg(not(any(target_os = "haiku", target_os = "cygwin", target_os = "redox")))] pub fn munlockall() -> Result<()> { unsafe { Errno::result(libc::munlockall()) }.map(drop) } -/// allocate memory, or map files or devices into memory +/// Allocate memory, or map files or devices into memory +/// +/// For anonymous mappings (`MAP_ANON`/`MAP_ANONYMOUS`), see [mmap_anonymous]. /// /// # Safety /// /// See the [`mmap(2)`] man page for detailed requirements. /// /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html -pub unsafe fn mmap( +pub unsafe fn mmap( addr: Option, length: NonZeroUsize, prot: ProtFlags, flags: MapFlags, - fd: RawFd, + f: F, offset: off_t, -) -> Result<*mut c_void> { - let ptr = addr.map_or( - std::ptr::null_mut(), - |a| usize::from(a) as *mut c_void - ); - - let ret = libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset); +) -> Result> { + let ptr = addr.map_or(std::ptr::null_mut(), |a| a.get() as *mut c_void); - if ret == libc::MAP_FAILED { + let fd = f.as_fd().as_raw_fd(); + let ret = unsafe { + libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), fd, offset) + }; + + if std::ptr::eq(ret, libc::MAP_FAILED) { Err(Errno::last()) } else { - Ok(ret) + // SAFETY: `libc::mmap` returns a valid non-null pointer or `libc::MAP_FAILED`, thus `ret` + // will be non-null here. + Ok(unsafe { NonNull::new_unchecked(ret) }) + } +} + +/// Create an anonymous memory mapping. +/// +/// This function is a wrapper around [`mmap`]: +/// `mmap(ptr, len, prot, MAP_ANONYMOUS | flags, -1, 0)`. +/// +/// # Safety +/// +/// See the [`mmap(2)`] man page for detailed requirements. +/// +/// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html +pub unsafe fn mmap_anonymous( + addr: Option, + length: NonZeroUsize, + prot: ProtFlags, + flags: MapFlags, +) -> Result> { + let ptr = addr.map_or(std::ptr::null_mut(), |a| a.get() as *mut c_void); + + let flags = MapFlags::MAP_ANONYMOUS | flags; + let ret = unsafe { + libc::mmap(ptr, length.into(), prot.bits(), flags.bits(), -1, 0) + }; + + if std::ptr::eq(ret, libc::MAP_FAILED) { + Err(Errno::last()) + } else { + // SAFETY: `libc::mmap` returns a valid non-null pointer or `libc::MAP_FAILED`, thus `ret` + // will be non-null here. + Ok(unsafe { NonNull::new_unchecked(ret) }) } } @@ -447,33 +489,43 @@ pub unsafe fn mmap( /// detailed requirements. #[cfg(any(target_os = "linux", target_os = "netbsd"))] pub unsafe fn mremap( - addr: *mut c_void, + addr: NonNull, old_size: size_t, new_size: size_t, flags: MRemapFlags, - new_address: Option<*mut c_void>, -) -> Result<*mut c_void> { + new_address: Option>, +) -> Result> { #[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 = unsafe { + libc::mremap( + addr.as_ptr(), + old_size, + new_size, + flags.bits(), + new_address + .map(NonNull::as_ptr) + .unwrap_or(std::ptr::null_mut()), + ) + }; #[cfg(target_os = "netbsd")] - let ret = libc::mremap( - addr, - old_size, - new_address.unwrap_or(std::ptr::null_mut()), - new_size, - flags.bits(), - ); + let ret = unsafe { + libc::mremap( + addr.as_ptr(), + old_size, + new_address + .map(NonNull::as_ptr) + .unwrap_or(std::ptr::null_mut()), + new_size, + flags.bits(), + ) + }; - if ret == libc::MAP_FAILED { + if std::ptr::eq(ret, libc::MAP_FAILED) { Err(Errno::last()) } else { - Ok(ret) + // SAFETY: `libc::mremap` returns a valid non-null pointer or `libc::MAP_FAILED`, thus `ret` + // will be non-null here. + Ok(unsafe { NonNull::new_unchecked(ret) }) } } @@ -485,8 +537,8 @@ pub unsafe fn mremap( /// page. /// /// [`munmap(2)`]: https://man7.org/linux/man-pages/man2/munmap.2.html -pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> { - Errno::result(libc::munmap(addr, len)).map(drop) +pub unsafe fn munmap(addr: NonNull, len: size_t) -> Result<()> { + unsafe { Errno::result(libc::munmap(addr.as_ptr(), len)).map(drop) } } /// give advice about use of memory @@ -497,12 +549,16 @@ 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 +#[allow(rustdoc::broken_intra_doc_links)] // For Hurd as `MADV_FREE` is not available on it pub unsafe fn madvise( - addr: *mut c_void, + addr: NonNull, length: size_t, advise: MmapAdvise, ) -> Result<()> { - Errno::result(libc::madvise(addr, length, advise as i32)).map(drop) + unsafe { + Errno::result(libc::madvise(addr.as_ptr(), length, advise as i32)) + .map(drop) + } } /// Set protection of memory mapping. @@ -517,26 +573,30 @@ pub unsafe fn madvise( /// /// ``` /// # use nix::libc::size_t; -/// # use nix::sys::mman::{mmap, mprotect, MapFlags, ProtFlags}; +/// # use nix::sys::mman::{mmap_anonymous, 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(None, one_k_non_zero, ProtFlags::PROT_NONE, -/// MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, -1, 0).unwrap(); +/// let mem = mmap_anonymous(None, one_k_non_zero, ProtFlags::PROT_NONE, MapFlags::MAP_PRIVATE) +/// .unwrap(); /// mprotect(mem, ONE_K, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE).unwrap(); -/// std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) +/// std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) /// }; /// assert_eq!(slice[0], 0x00); /// slice[0] = 0xFF; /// assert_eq!(slice[0], 0xFF); /// ``` pub unsafe fn mprotect( - addr: *mut c_void, + addr: NonNull, length: size_t, prot: ProtFlags, ) -> Result<()> { - Errno::result(libc::mprotect(addr, length, prot.bits())).map(drop) + unsafe { + Errno::result(libc::mprotect(addr.as_ptr(), length, prot.bits())) + .map(drop) + } } /// synchronize a mapped region @@ -548,14 +608,17 @@ pub unsafe fn mprotect( /// /// [`msync(2)`]: https://man7.org/linux/man-pages/man2/msync.2.html pub unsafe fn msync( - addr: *mut c_void, + addr: NonNull, length: size_t, flags: MsFlags, ) -> Result<()> { - Errno::result(libc::msync(addr, length, flags.bits())).map(drop) + unsafe { + Errno::result(libc::msync(addr.as_ptr(), length, flags.bits())) + .map(drop) + } } -#[cfg(not(any(target_os = "android", target_env = "ohos")))] +#[cfg(not(target_os = "android"))] feature! { #![feature = "fs"] /// Creates and opens a new, or opens an existing, POSIX shared memory object. @@ -567,21 +630,26 @@ pub fn shm_open

    ( name: &P, flag: OFlag, mode: Mode - ) -> Result + ) -> 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"))] + #[cfg(apple_targets)] unsafe { libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::c_uint) } - #[cfg(not(any(target_os = "macos", target_os = "ios")))] + #[cfg(not(apple_targets))] unsafe { libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::mode_t) } })?; - Errno::result(ret) + match ret { + -1 => Err(Errno::last()), + fd => Ok(unsafe{ OwnedFd::from_raw_fd(fd) }) + } } } @@ -590,7 +658,7 @@ pub fn shm_open

    ( /// For more information, see [`shm_unlink(3)`]. /// /// [`shm_unlink(3)`]: https://man7.org/linux/man-pages/man3/shm_unlink.3.html -#[cfg(not(any(target_os = "android", target_env = "ohos")))] +#[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()) })?; diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 2065059d..c17c7be3 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -1,10 +1,11 @@ //! Mostly platform-specific functionality #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - all(target_os = "linux", not(target_env = "uclibc")), - target_os = "macos", + freebsdlike, + all( + target_os = "linux", + not(any(target_env = "uclibc", target_env = "ohos")) + ), + apple_targets, target_os = "netbsd" ))] feature! { @@ -15,48 +16,42 @@ feature! { feature! { #![feature = "event"] - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] #[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)] + #[cfg(bsd)] pub mod event; - #[cfg(any(target_os = "android", target_os = "linux"))] - #[allow(missing_docs)] + /// Event file descriptor. + #[cfg(any(linux_android, target_os = "freebsd"))] pub mod eventfd; } +#[cfg(target_os = "linux")] +feature! { + #![feature = "fanotify"] + pub mod fanotify; +} + #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", + bsd, + linux_android, + solarish, + target_os = "fuchsia", target_os = "redox", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd" ))] #[cfg(feature = "ioctl")] #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] #[macro_use] pub mod ioctl; -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] feature! { #![feature = "fs"] pub mod memfd; } -#[cfg(not(target_os = "redox"))] feature! { #![feature = "mman"] pub mod mman; @@ -68,20 +63,18 @@ feature! { pub mod personality; } +#[cfg(target_os = "linux")] +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" -))] +#[cfg(any(linux_android, bsd))] feature! { #![feature = "ptrace"] #[allow(missing_docs)] @@ -94,7 +87,7 @@ feature! { pub mod quota; } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", netbsdlike))] feature! { #![feature = "reboot"] pub mod reboot; @@ -103,7 +96,7 @@ feature! { #[cfg(not(any( target_os = "redox", target_os = "fuchsia", - target_os = "illumos", + solarish, target_os = "haiku" )))] feature! { @@ -111,20 +104,12 @@ feature! { pub mod resource; } -#[cfg(not(target_os = "redox"))] feature! { #![feature = "poll"] pub mod select; } -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos" -))] +#[cfg(any(linux_android, freebsdlike, apple_targets, solarish))] feature! { #![feature = "zerocopy"] pub mod sendfile; @@ -132,14 +117,13 @@ feature! { pub mod signal; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] feature! { #![feature = "signal"] #[allow(missing_docs)] pub mod signalfd; } -#[cfg(not(target_os = "redox"))] feature! { #![feature = "socket"] #[allow(missing_docs)] @@ -153,13 +137,11 @@ feature! { } #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "openbsd" + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd", + target_os = "cygwin" ))] feature! { #![feature = "fs"] @@ -171,8 +153,7 @@ feature! { pub mod statvfs; } -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(linux_android)] #[allow(missing_docs)] pub mod sysinfo; @@ -200,13 +181,13 @@ feature! { pub mod wait; } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] feature! { #![feature = "inotify"] pub mod inotify; } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] feature! { #![feature = "time"] pub mod timerfd; @@ -215,7 +196,7 @@ feature! { #[cfg(all( any( target_os = "freebsd", - target_os = "illumos", + solarish, target_os = "linux", target_os = "netbsd" ), diff --git a/src/sys/personality.rs b/src/sys/personality.rs index 3fd8578c..9623fb86 100644 --- a/src/sys/personality.rs +++ b/src/sys/personality.rs @@ -20,8 +20,7 @@ libc_bitflags! { /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html ADDR_LIMIT_3GB; /// User-space function pointers to signal handlers point to descriptors. - #[cfg(not(any(target_env = "musl", target_env = "ohos", target_env = "uclibc")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_env = "musl", target_env = "uclibc", target_env = "ohos")))] FDPIC_FUNCPTRS; /// Map page 0 as read-only. MMAP_PAGE_ZERO; @@ -42,8 +41,7 @@ libc_bitflags! { /// version number. /// /// [`uname(2)`]: https://man7.org/linux/man-pages/man2/uname.2.html - #[cfg(not(any(target_env = "musl", target_env = "ohos", target_env = "uclibc")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_env = "musl", target_env = "uclibc", target_env = "ohos")))] UNAME26; /// No effects. WHOLE_SECONDS; diff --git a/src/sys/prctl.rs b/src/sys/prctl.rs new file mode 100644 index 00000000..d183e105 --- /dev/null +++ b/src/sys/prctl.rs @@ -0,0 +1,228 @@ +//! 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, c_void}; +use std::convert::TryFrom; +use std::ffi::{CStr, CString}; +use std::num::NonZeroUsize; +use std::ptr::NonNull; + +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) }; + + Errno::result(res).and_then(|_| { + CStr::from_bytes_until_nul(&buf) + .map(CStr::to_owned) + .map_err(|_| Errno::EINVAL) + }) +} + +/// 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: c_ulong) -> 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) +} + +/// Set an identifier (or reset it) to the address memory range. +pub fn set_vma_anon_name(addr: NonNull, length: NonZeroUsize, name: Option<&CStr>) -> Result<()> { + let nameref = match name { + Some(n) => n.as_ptr(), + _ => std::ptr::null() + }; + let res = unsafe { libc::prctl(libc::PR_SET_VMA, libc::PR_SET_VMA_ANON_NAME, addr.as_ptr(), length, nameref) }; + + Errno::result(res).map(drop) +} diff --git a/src/sys/ptrace/bsd.rs b/src/sys/ptrace/bsd.rs index ba267c65..3dd48621 100644 --- a/src/sys/ptrace/bsd.rs +++ b/src/sys/ptrace/bsd.rs @@ -9,10 +9,7 @@ use std::ptr; pub type RequestType = c_int; cfg_if! { - if #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "openbsd"))] { + if #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))] { #[doc(hidden)] pub type AddressType = *mut ::libc::c_char; } else { @@ -29,33 +26,26 @@ libc_enum! { PT_TRACE_ME, PT_READ_I, PT_READ_D, - #[cfg(target_os = "macos")] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] PT_READ_U, PT_WRITE_I, PT_WRITE_D, - #[cfg(target_os = "macos")] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] PT_WRITE_U, PT_CONTINUE, PT_KILL, - #[cfg(any(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos"), + #[cfg(any(any(freebsdlike, apple_targets), all(target_os = "openbsd", target_arch = "x86_64"), all(target_os = "netbsd", any(target_arch = "x86_64", target_arch = "powerpc"))))] PT_STEP, PT_ATTACH, PT_DETACH, - #[cfg(target_os = "macos")] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] PT_SIGEXC, - #[cfg(target_os = "macos")] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] PT_THUPDATE, - #[cfg(target_os = "macos")] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] PT_ATTACHEXC } } @@ -66,13 +56,15 @@ unsafe fn ptrace_other( addr: AddressType, data: c_int, ) -> Result { - Errno::result(libc::ptrace( - request as RequestType, - libc::pid_t::from(pid), - addr, - data, - )) - .map(|_| 0) + unsafe { + Errno::result(libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data, + )) + .map(|_| 0) + } } /// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)` @@ -157,7 +149,7 @@ pub fn kill(pid: Pid) -> Result<()> { /// } /// ``` #[cfg(any( - any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"), + any(freebsdlike, apple_targets), all(target_os = "openbsd", target_arch = "x86_64"), all( target_os = "netbsd", diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index a5880cdd..8c1a46ba 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -14,11 +14,12 @@ pub type AddressType = *mut ::libc::c_void; target_os = "linux", any( all( - target_arch = "x86_64", - any(target_env = "gnu", target_env = "musl", target_env = "ohos") + any(target_arch = "x86_64", target_arch = "aarch64"), + any(target_env = "gnu", target_env = "musl") ), - all(target_arch = "x86", target_env = "gnu") - ) + all(target_arch = "x86", target_env = "gnu"), + all(target_arch = "riscv64", target_env = "gnu"), + ), ))] use libc::user_regs_struct; @@ -35,8 +36,8 @@ cfg_if! { } libc_enum! { - #[cfg_attr(not(any(target_env = "musl", target_env = "ohos", target_env = "uclibc", target_os = "android")), repr(u32))] - #[cfg_attr(any(target_env = "musl", target_env = "ohos", target_env = "uclibc", target_os = "android"), repr(i32))] + #[cfg_attr(not(any(target_env = "musl", target_env = "uclibc", target_os = "android", target_env = "ohos")), repr(u32))] + #[cfg_attr(any(target_env = "musl", target_env = "uclibc", target_os = "android", target_env = "ohos"), repr(i32))] /// Ptrace Request enum defining the action to be taken. #[non_exhaustive] pub enum Request { @@ -54,7 +55,9 @@ libc_enum! { all(target_os = "linux", any(target_env = "musl", target_env = "ohos", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "x86_64", target_pointer_width = "32"))))] PTRACE_GETREGS, @@ -62,7 +65,9 @@ libc_enum! { all(target_os = "linux", any(target_env = "musl", target_env = "ohos", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "x86_64", target_pointer_width = "32"))))] PTRACE_SETREGS, @@ -70,7 +75,9 @@ libc_enum! { all(target_os = "linux", any(target_env = "musl", target_env = "ohos", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "x86_64", target_pointer_width = "32"))))] PTRACE_GETFPREGS, @@ -78,7 +85,9 @@ libc_enum! { all(target_os = "linux", any(target_env = "musl", target_env = "ohos", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "x86_64", target_pointer_width = "32"))))] PTRACE_SETFPREGS, @@ -87,14 +96,18 @@ libc_enum! { #[cfg(all(target_os = "linux", any(target_env = "musl", target_env = "ohos", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "x86", target_arch = "x86_64")))] PTRACE_GETFPXREGS, #[cfg(all(target_os = "linux", any(target_env = "musl", target_env = "ohos", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "x86", target_arch = "x86_64")))] PTRACE_SETFPXREGS, @@ -104,22 +117,28 @@ libc_enum! { PTRACE_GETSIGINFO, PTRACE_SETSIGINFO, #[cfg(all(target_os = "linux", not(any(target_arch = "mips", - target_arch = "mips64"))))] + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6"))))] PTRACE_GETREGSET, #[cfg(all(target_os = "linux", not(any(target_arch = "mips", - target_arch = "mips64"))))] + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6"))))] 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"))))] + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6"))))] PTRACE_LISTEN, #[cfg(all(target_os = "linux", not(any(target_arch = "mips", - target_arch = "mips64"))))] + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6"))))] PTRACE_PEEKSIGINFO, #[cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86", target_arch = "x86_64")))] @@ -127,6 +146,8 @@ libc_enum! { #[cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86", target_arch = "x86_64")))] PTRACE_SYSEMU_SINGLESTEP, + #[cfg(all(target_os = "linux", target_env = "gnu"))] + PTRACE_GET_SYSCALL_INFO, } } @@ -158,6 +179,117 @@ libc_enum! { } } +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) + ), + all( + target_env = "musl", + target_arch = "aarch64", + ) + ), +))] +libc_enum! { + #[repr(i32)] + /// Defines a specific register set, as used in `PTRACE_GETREGSET` and `PTRACE_SETREGSET`. + #[non_exhaustive] + pub enum RegisterSetValue { + NT_PRSTATUS, + NT_PRFPREG, + NT_PRPSINFO, + NT_TASKSTRUCT, + NT_AUXV, + } +} + +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) + ), + all( + target_env = "musl", + target_arch = "aarch64", + ) + ), +))] +/// Represents register set areas, such as general-purpose registers or +/// floating-point registers. +/// +/// # Safety +/// +/// This trait is marked unsafe, since implementation of the trait must match +/// ptrace's request `VALUE` and return data type `Regs`. +pub unsafe trait RegisterSet { + /// Corresponding type of registers in the kernel. + const VALUE: RegisterSetValue; + + /// Struct representing the register space. + type Regs; +} + + +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) + ), + all( + target_env = "musl", + target_arch = "aarch64", + ) + ), +))] +/// Register sets used in [`getregset`] and [`setregset`] +pub mod regset { + use super::*; + + #[derive(Debug, Clone, Copy)] + /// General-purpose registers. + pub enum NT_PRSTATUS {} + + unsafe impl RegisterSet for NT_PRSTATUS { + const VALUE: RegisterSetValue = RegisterSetValue::NT_PRSTATUS; + type Regs = user_regs_struct; + } + + #[derive(Debug, Clone, Copy)] + /// Floating-point registers. + pub enum NT_PRFPREG {} + + unsafe impl RegisterSet for NT_PRFPREG { + const VALUE: RegisterSetValue = RegisterSetValue::NT_PRFPREG; + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + type Regs = libc::user_fpregs_struct; + #[cfg(target_arch = "aarch64")] + type Regs = libc::user_fpsimd_struct; + #[cfg(target_arch = "riscv64")] + type Regs = libc::__riscv_mc_d_ext_state; + } +} + libc_bitflags! { /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request. /// See `man ptrace` for more details. @@ -205,12 +337,18 @@ fn ptrace_peek( } /// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)` +/// +/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html #[cfg(all( target_os = "linux", any( all( target_arch = "x86_64", - any(target_env = "gnu", target_env = "musl", target_env = "ohos") + any(target_env = "gnu", target_env = "musl") ), all(target_arch = "x86", target_env = "gnu") ) @@ -219,13 +357,74 @@ pub fn getregs(pid: Pid) -> Result { ptrace_get_data::(Request::PTRACE_GETREGS, pid) } +/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)` +/// +/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html +#[cfg(all( + target_os = "linux", + any( + all( + target_arch = "aarch64", + any(target_env = "gnu", target_env = "musl") + ), + all(target_arch = "riscv64", target_env = "gnu") + ) +))] +pub fn getregs(pid: Pid) -> Result { + getregset::(pid) +} + +/// Get a particular set of user registers, as with `ptrace(PTRACE_GETREGSET, ...)` +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64" + ) + ), + all(target_env = "musl", target_arch = "aarch64") + ) +))] +pub fn getregset(pid: Pid) -> Result { + let request = Request::PTRACE_GETREGSET; + let mut data = mem::MaybeUninit::::uninit(); + let mut iov = libc::iovec { + iov_base: data.as_mut_ptr().cast(), + iov_len: mem::size_of::(), + }; + unsafe { + ptrace_other( + request, + pid, + S::VALUE as i32 as AddressType, + (&mut iov as *mut libc::iovec).cast(), + )?; + }; + Ok(unsafe { data.assume_init() }) +} + /// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)` +/// +/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html #[cfg(all( target_os = "linux", any( all( target_arch = "x86_64", - any(target_env = "gnu", target_env = "musl", target_env = "ohos") + any(target_env = "gnu", target_env = "musl") ), all(target_arch = "x86", target_env = "gnu") ) @@ -236,24 +435,77 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { Request::PTRACE_SETREGS as RequestType, libc::pid_t::from(pid), ptr::null_mut::(), - ®s as *const _ as *const c_void, + ®s as *const user_regs_struct as *const c_void, ) }; Errno::result(res).map(drop) } +/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)` +/// +/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any(target_arch = "aarch64", target_arch = "riscv64") + ), + all(target_env = "musl", target_arch = "aarch64") + ) +))] +pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { + setregset::(pid, regs) +} + +/// Set a particular set of user registers, as with `ptrace(PTRACE_SETREGSET, ...)` +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64" + ) + ), + all(target_env = "musl", target_arch = "aarch64") + ) +))] +pub fn setregset(pid: Pid, mut regs: S::Regs) -> Result<()> { + let mut iov = libc::iovec { + iov_base: (&mut regs as *mut S::Regs).cast(), + iov_len: mem::size_of::(), + }; + unsafe { + ptrace_other( + Request::PTRACE_SETREGSET, + pid, + S::VALUE as i32 as AddressType, + (&mut iov as *mut libc::iovec).cast(), + )?; + } + Ok(()) +} + /// Function for ptrace requests that return values from the data field. /// Some ptrace get requests populate structs or larger elements than `c_long` /// and therefore use the data field to return values. This function handles these /// requests. fn ptrace_get_data(request: Request, pid: Pid) -> Result { - let mut data = mem::MaybeUninit::uninit(); + let 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, + data.as_mut_ptr(), ) }; Errno::result(res)?; @@ -266,16 +518,18 @@ unsafe fn ptrace_other( addr: AddressType, data: *mut c_void, ) -> Result { - Errno::result(libc::ptrace( - request as RequestType, - libc::pid_t::from(pid), - addr, - data, - )) - .map(|_| 0) + unsafe { + 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( @@ -288,17 +542,17 @@ pub fn setoptions(pid: Pid, options: Options) -> Result<()> { 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 { Errno::clear(); @@ -315,6 +569,13 @@ pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { } } +/// Get the informations of the syscall that caused the stop, as with +/// `ptrace(PTRACE_GET_SYSCALL_INFO, ...`. +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub fn syscall_info(pid: Pid) -> Result { + ptrace_get_data::(Request::PTRACE_GET_SYSCALL_INFO, pid) +} + /// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)` /// /// Indicates that this process is to be traced by its parent. @@ -387,7 +648,6 @@ 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( @@ -434,7 +694,6 @@ 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( @@ -523,42 +782,40 @@ 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 -/// -/// # Safety -/// -/// The `data` argument is passed directly to `ptrace(2)`. Read that man page -/// for guidance. -pub unsafe fn write( - pid: Pid, - addr: AddressType, - data: *mut c_void, -) -> Result<()> { - ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(drop) +/// Writes a word into the processes memory at the given address, as with +/// ptrace(PTRACE_POKEDATA, ...) +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn write(pid: Pid, addr: AddressType, data: c_long) -> Result<()> { + unsafe { + // Safety(not_unsafe_ptr_arg_deref): + // `ptrace_other` is a common abstract + // but in `PTRACE_POKEDATA` situation, `data` is exactly what will be wtitten + ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data as *mut c_void) + .map(drop) + } } -/// Reads a word from a user area at `offset`. +/// 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`. +/// 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) +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn write_user(pid: Pid, offset: AddressType, data: c_long) -> Result<()> { + unsafe { + // Safety(not_unsafe_ptr_arg_deref): + // `ptrace_other` is a common abstract + // but in `PTRACE_POKEDATA` situation, `data` is exactly what will be wtitten + ptrace_other(Request::PTRACE_POKEUSER, pid, offset, data as *mut c_void) + .map(drop) + } } diff --git a/src/sys/ptrace/mod.rs b/src/sys/ptrace/mod.rs index 88648aca..c059797d 100644 --- a/src/sys/ptrace/mod.rs +++ b/src/sys/ptrace/mod.rs @@ -1,25 +1,13 @@ //! Provides helpers for making ptrace system calls -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] mod linux; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] pub use self::linux::*; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" -))] +#[cfg(bsd)] mod bsd; -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" -))] +#[cfg(bsd)] pub use self::bsd::*; diff --git a/src/sys/quota.rs b/src/sys/quota.rs index b3c44ca7..2d12b858 100644 --- a/src/sys/quota.rs +++ b/src/sys/quota.rs @@ -21,9 +21,8 @@ use std::{mem, ptr}; struct QuotaCmd(QuotaSubCmd, QuotaType); impl QuotaCmd { - #[allow(unused_unsafe)] fn as_int(&self) -> c_int { - unsafe { libc::QCMD(self.0 as i32, self.1 as i32) } + libc::QCMD(self.0 as i32, self.1 as i32) } } @@ -265,7 +264,7 @@ pub fn quotactl_on( ) -> 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; + let p: *mut c_char = path_copy.as_mut_ptr().cast(); quotactl( QuotaCmd(QuotaSubCmd::Q_QUOTAON, which), Some(special), @@ -309,12 +308,12 @@ pub fn quotactl_get( special: &P, id: c_int, ) -> Result { - let mut dqblk = mem::MaybeUninit::uninit(); + let mut dqblk = mem::MaybeUninit::::uninit(); quotactl( QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which), Some(special), id, - dqblk.as_mut_ptr() as *mut c_char, + dqblk.as_mut_ptr().cast(), )?; Ok(unsafe { Dqblk(dqblk.assume_init()) }) } diff --git a/src/sys/reboot.rs b/src/sys/reboot.rs index 02d98162..2e4d888d 100644 --- a/src/sys/reboot.rs +++ b/src/sys/reboot.rs @@ -1,48 +1,141 @@ -//! Reboot/shutdown or enable/disable Ctrl-Alt-Delete. +//! Reboot/shutdown +//! +//! On Linux, This can also be used to enable/disable Ctrl-Alt-Delete. use crate::errno::Errno; use crate::Result; +use cfg_if::cfg_if; use std::convert::Infallible; -use std::mem::drop; -libc_enum! { - /// How exactly should the system be rebooted. - /// - /// See [`set_cad_enabled()`](fn.set_cad_enabled.html) for - /// enabling/disabling Ctrl-Alt-Delete. - #[repr(i32)] - #[non_exhaustive] - pub enum RebootMode { - /// Halt the system. - RB_HALT_SYSTEM, - /// Execute a kernel that has been loaded earlier with - /// [`kexec_load(2)`](https://man7.org/linux/man-pages/man2/kexec_load.2.html). - RB_KEXEC, - /// Stop the system and switch off power, if possible. - RB_POWER_OFF, - /// Restart the system. - RB_AUTOBOOT, - // we do not support Restart2. - /// Suspend the system using software suspend. - RB_SW_SUSPEND, +cfg_if! { + if #[cfg(target_os = "linux")] { + use std::mem::drop; + + libc_enum! { + /// How exactly should the system be rebooted. + /// + /// See [`set_cad_enabled()`](fn.set_cad_enabled.html) for + /// enabling/disabling Ctrl-Alt-Delete. + #[repr(i32)] + #[non_exhaustive] + pub enum RebootMode { + /// Halt the system. + RB_HALT_SYSTEM, + /// Execute a kernel that has been loaded earlier with + /// [`kexec_load(2)`](https://man7.org/linux/man-pages/man2/kexec_load.2.html). + RB_KEXEC, + /// Stop the system and switch off power, if possible. + RB_POWER_OFF, + /// Restart the system. + RB_AUTOBOOT, + // we do not support Restart2. + /// Suspend the system using software suspend. + RB_SW_SUSPEND, + } + } + + /// Reboots or shuts down the system. + pub fn reboot(how: RebootMode) -> Result { + unsafe { libc::reboot(how as libc::c_int) }; + Err(Errno::last()) + } + + /// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). + /// + /// Corresponds to calling `reboot(RB_ENABLE_CAD)` or `reboot(RB_DISABLE_CAD)` in C. + pub fn set_cad_enabled(enable: bool) -> Result<()> { + let cmd = if enable { + libc::RB_ENABLE_CAD + } else { + libc::RB_DISABLE_CAD + }; + let res = unsafe { libc::reboot(cmd) }; + Errno::result(res).map(drop) + } + } else if #[cfg(netbsdlike)] { + use libc::c_int; + + libc_bitflags! { + /// How exactly should the system be rebooted. + pub struct RebootMode: c_int { + /// The default, causing the system to reboot in its usual fashion. + RB_AUTOBOOT; + /// Interpreted by the bootstrap program itself, causing it to + /// prompt on the console as to what file should be booted. + /// Normally, the system is booted from the file “xx(0,0)bsd”, + /// where xx is the default disk name, without prompting for + /// the file name. + RB_ASKNAME; + /// Dump kernel memory before rebooting; see `savecore(8)` for + /// more information. + RB_DUMP; + /// The processor is simply halted; no reboot takes place. + RB_HALT; + /// Power off the system if the system hardware supports the + /// function, otherwise it has no effect. + /// + /// Should be used in conjunction with `RB_HALT`. + RB_POWERDOWN; + /// By default, the system will halt if `reboot()` is called during + /// startup (before the system has finished autoconfiguration), even + /// if `RB_HALT` is not specified. This is because `panic(9)`s + /// during startup will probably just repeat on the next boot. + /// Use of this option implies that the user has requested the + /// action specified (for example, using the `ddb(4)` boot reboot + /// command), so the system will reboot if a halt is not explicitly + /// requested. + #[cfg(target_os = "openbsd")] + RB_USERREQ; + /// Load the symbol table and enable a built-in debugger in the + /// system. This option will have no useful function if the kernel + /// is not configured for debugging. Several other options have + /// different meaning if combined with this option, although their + /// use may not be possible via the `reboot()` call. See `ddb(4)` for + /// more information. + RB_KDB; + /// Normally, the disks are sync'd (see `sync(8)`) before the + /// processor is halted or rebooted. This option may be useful + /// if file system changes have been made manually or if the + /// processor is on fire. + RB_NOSYNC; + /// Normally, the reboot procedure involves an automatic disk + /// consistency check and then multi-user operations. `RB_SINGLE` + /// prevents this, booting the system with a single-user shell on + /// the console. `RB_SINGLE` is actually interpreted by the `init(8)` + /// program in the newly booted system. + /// + /// When no options are given (i.e., `RB_AUTOBOOT` is used), the + /// system is rebooted from file /bsd in the root file system of + /// unit 0 of a disk chosen in a processor specific way. An automatic + /// consistency check of the disks is normally performed (see `fsck(8)`). + RB_SINGLE; + /// Initially invoke the `userconf(4)` facility when the system + /// starts up again, if it has been compiled into the kernel + /// that is loaded. + #[cfg(target_os = "netbsd")] + RB_USERCONF; + /// Don't update the hardware clock from the system clock, presumably + /// because the system clock is suspect. + #[cfg(target_os = "openbsd")] + RB_TIMEBAD; + } + } + + /// Reboot system or halt processor + /// + /// For more information, see the man pages: + /// + /// * [NetBSD](https://man.netbsd.org/reboot.2) + /// * [OpenBSD](https://man.openbsd.org/reboot.2) + #[cfg(netbsdlike)] + pub fn reboot(how: RebootMode) -> Result { + #[cfg(target_os = "openbsd")] + unsafe { libc::reboot(how.bits()) }; + #[cfg(target_os = "netbsd")] + unsafe { libc::reboot(how.bits(), std::ptr::null_mut()) }; + + Err(Errno::last()) + } } } -/// Reboots or shuts down the system. -pub fn reboot(how: RebootMode) -> Result { - unsafe { libc::reboot(how as libc::c_int) }; - Err(Errno::last()) -} - -/// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). -/// -/// Corresponds to calling `reboot(RB_ENABLE_CAD)` or `reboot(RB_DISABLE_CAD)` in C. -pub fn set_cad_enabled(enable: bool) -> Result<()> { - let cmd = if enable { - libc::RB_ENABLE_CAD - } else { - libc::RB_DISABLE_CAD - }; - let res = unsafe { libc::reboot(cmd) }; - Errno::result(res).map(drop) -} diff --git a/src/sys/resource.rs b/src/sys/resource.rs index 89277377..449698db 100644 --- a/src/sys/resource.rs +++ b/src/sys/resource.rs @@ -10,17 +10,17 @@ pub use libc::RLIM_INFINITY; use std::mem; cfg_if! { - if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + if #[cfg(any( + all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), + target_os = "hurd" + ))]{ use libc::{__rlimit_resource_t, rlimit}; } else if #[cfg(any( - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "macos", - target_os = "ios", + bsd, target_os = "android", - target_os = "dragonfly", - all(target_os = "linux", not(target_env = "gnu")) + target_os = "aix", + all(target_os = "linux", not(target_env = "gnu")), + target_os = "cygwin" ))]{ use libc::rlimit; } @@ -42,21 +42,20 @@ libc_enum! { // // https://gcc.gnu.org/legacy-ml/gcc/2015-08/msg00441.html // https://github.com/rust-lang/libc/blob/master/src/unix/linux_like/linux/gnu/mod.rs - #[cfg_attr(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), repr(u32))] #[cfg_attr(any( - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "macos", - target_os = "ios", + all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), + target_os = "hurd" + ), repr(u32))] + #[cfg_attr(any( + bsd, target_os = "android", - target_os = "dragonfly", - all(target_os = "linux", not(any(target_env = "gnu", target_env = "uclibc"))) + target_os = "aix", + all(target_os = "linux", not(any(target_env = "gnu", target_env = "uclibc"))), + target_os = "cygwin" ), repr(i32))] #[non_exhaustive] pub enum Resource { - #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_os = "freebsd", netbsdlike)))] /// The maximum amount (in bytes) of virtual memory the process is /// allowed to map. RLIMIT_AS, @@ -75,100 +74,78 @@ 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())))] + #[cfg(linux_android)] /// A limit on the combined number of flock locks and fcntl leases that /// this process may establish. RLIMIT_LOCKS, - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "openbsd", - target_os = "linux", - target_os = "netbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, target_os = "freebsd", netbsdlike))] /// 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())))] + #[cfg(linux_android)] /// 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())))] + #[cfg(linux_android)] /// A ceiling to which the process's nice value can be raised using /// setpriority or nice. RLIMIT_NICE, #[cfg(any( - target_os = "android", + linux_android, target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "linux", + netbsdlike, + 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", + #[cfg(any(linux_android, target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "linux", + netbsdlike, + 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())))] + #[cfg(linux_android)] /// 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())))] + #[cfg(linux_android)] /// 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())))] + #[cfg(freebsdlike)] /// 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, } @@ -202,7 +179,10 @@ pub fn getrlimit(resource: Resource) -> Result<(rlim_t, rlim_t)> { let mut old_rlim = mem::MaybeUninit::::uninit(); cfg_if! { - if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + if #[cfg(any( + all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), + target_os = "hurd" + ))] { let res = unsafe { libc::getrlimit(resource as __rlimit_resource_t, old_rlim.as_mut_ptr()) }; } else { let res = unsafe { libc::getrlimit(resource as c_int, old_rlim.as_mut_ptr()) }; @@ -255,7 +235,10 @@ pub fn setrlimit( rlim_max: hard_limit, }; cfg_if! { - if #[cfg(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))]{ + if #[cfg(any( + all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")), + target_os = "hurd", + ))]{ 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) }; @@ -277,7 +260,6 @@ libc_enum! { 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, } @@ -313,7 +295,9 @@ impl Usage { TimeVal::from(self.0.ru_stime) } - /// The resident set size at its peak, in kilobytes. + /// The resident set size at its peak, + #[cfg_attr(apple_targets, doc = " in bytes.")] + #[cfg_attr(not(apple_targets), doc = " in kilobytes.")] pub fn max_rss(&self) -> c_long { self.0.ru_maxrss } @@ -416,28 +400,3 @@ pub fn getrusage(who: UsageWho) -> Result { 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 7a94cff8..29f6bd25 100644 --- a/src/sys/select.rs +++ b/src/sys/select.rs @@ -7,7 +7,7 @@ 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::{AsRawFd, BorrowedFd, RawFd}; use std::ptr::{null, null_mut}; pub use libc::FD_SETSIZE; @@ -15,7 +15,10 @@ 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!( @@ -24,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: BorrowedFd<'fd>) { + assert_fd_valid(fd.as_raw_fd()); + unsafe { libc::FD_SET(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: BorrowedFd<'fd>) { + assert_fd_valid(fd.as_raw_fd()); + unsafe { libc::FD_CLR(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: BorrowedFd<'fd>) -> bool { + assert_fd_valid(fd.as_raw_fd()); + unsafe { libc::FD_ISSET(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. @@ -66,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() } @@ -88,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] @@ -104,7 +115,7 @@ impl FdSet { } } -impl Default for FdSet { +impl Default for FdSet<'_> { fn default() -> Self { Self::new() } @@ -112,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<'fd> Iterator for Fds<'_, '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 @@ -136,19 +148,20 @@ impl<'a> Iterator for Fds<'a> { } } -impl<'a> DoubleEndedIterator for Fds<'a> { +impl<'fd> DoubleEndedIterator for Fds<'_, '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 FusedIterator for Fds<'_, '_> {} /// Monitors file descriptors for readiness /// @@ -173,7 +186,7 @@ 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>( +pub fn select<'a, 'fd, N, R, W, E, T>( nfds: N, readfds: R, writefds: W, @@ -181,10 +194,11 @@ pub fn select<'a, N, R, W, E, T>( 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(); @@ -197,7 +211,11 @@ where .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 @@ -256,17 +274,18 @@ use crate::sys::signal::SigSet; /// [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>, { @@ -280,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 }); @@ -298,158 +317,3 @@ where Errno::result(res) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::sys::time::{TimeVal, TimeValLike}; - use crate::unistd::{pipe, write}; - use std::os::unix::io::RawFd; - - #[test] - fn fdset_insert() { - let mut fd_set = FdSet::new(); - - for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); - } - - fd_set.insert(7); - - assert!(fd_set.contains(7)); - } - - #[test] - fn fdset_remove() { - let mut fd_set = FdSet::new(); - - for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); - } - - fd_set.insert(7); - fd_set.remove(7); - - for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); - } - } - - #[test] - fn fdset_clear() { - let mut fd_set = FdSet::new(); - fd_set.insert(1); - fd_set.insert((FD_SETSIZE / 2) as RawFd); - fd_set.insert((FD_SETSIZE - 1) as RawFd); - - fd_set.clear(); - - for i in 0..FD_SETSIZE { - assert!(!fd_set.contains(i as RawFd)); - } - } - - #[test] - fn fdset_highest() { - let mut set = FdSet::new(); - assert_eq!(set.highest(), None); - set.insert(0); - assert_eq!(set.highest(), Some(0)); - set.insert(90); - assert_eq!(set.highest(), Some(90)); - set.remove(0); - assert_eq!(set.highest(), Some(90)); - set.remove(90); - assert_eq!(set.highest(), None); - - set.insert(4); - set.insert(5); - set.insert(7); - assert_eq!(set.highest(), Some(7)); - } - - #[test] - fn fdset_fds() { - let mut set = FdSet::new(); - assert_eq!(set.fds(None).collect::>(), vec![]); - set.insert(0); - assert_eq!(set.fds(None).collect::>(), vec![0]); - set.insert(90); - assert_eq!(set.fds(None).collect::>(), vec![0, 90]); - - // highest limit - assert_eq!(set.fds(Some(89)).collect::>(), vec![0]); - assert_eq!(set.fds(Some(90)).collect::>(), vec![0, 90]); - } - - #[test] - fn test_select() { - let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); - let (r2, _w2) = pipe().unwrap(); - - let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); - - let mut timeout = TimeVal::seconds(10); - assert_eq!( - 1, - select(None, &mut fd_set, None, None, &mut timeout).unwrap() - ); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); - } - - #[test] - fn test_select_nfds() { - let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); - let (r2, _w2) = pipe().unwrap(); - - let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); - - let mut timeout = TimeVal::seconds(10); - assert_eq!( - 1, - select( - Some(fd_set.highest().unwrap() + 1), - &mut fd_set, - None, - None, - &mut timeout - ) - .unwrap() - ); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); - } - - #[test] - fn test_select_nfds2() { - let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); - let (r2, _w2) = pipe().unwrap(); - - let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); - - let mut timeout = TimeVal::seconds(10); - assert_eq!( - 1, - select( - ::std::cmp::max(r1, r2) + 1, - &mut fd_set, - None, - None, - &mut timeout - ) - .unwrap() - ); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); - } -} diff --git a/src/sys/sendfile.rs b/src/sys/sendfile.rs index fb293a4e..d7452edd 100644 --- a/src/sys/sendfile.rs +++ b/src/sys/sendfile.rs @@ -1,7 +1,7 @@ //! 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}; @@ -20,19 +20,26 @@ use crate::Result; /// /// `in_fd` must support `mmap`-like operations and therefore cannot be a socket. /// -/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn sendfile( - out_fd: RawFd, - in_fd: RawFd, +/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html) for Linux, +/// see [the sendfile(2) man page.](https://docs.oracle.com/cd/E88353_01/html/E37843/sendfile-3c.html) for Solaris. +#[cfg(any(linux_android, solarish))] +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) } @@ -49,61 +56,103 @@ 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")] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn sendfile64( - out_fd: RawFd, - in_fd: RawFd, +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 = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos"))] { + if #[cfg(any(freebsdlike, apple_targets))] { use std::io::IoSlice; #[derive(Clone, Debug)] - struct SendfileHeaderTrailer<'a>( - libc::sf_hdtr, - Option>>, - Option>>, - ); + struct SendfileHeaderTrailer<'a> { + raw: libc::sf_hdtr, + _headers: Option>>, + _trailers: Option>>, + } impl<'a> SendfileHeaderTrailer<'a> { fn new( headers: Option<&'a [&'a [u8]]>, trailers: Option<&'a [&'a [u8]]> ) -> SendfileHeaderTrailer<'a> { - let header_iovecs: Option>> = + let mut header_iovecs: Option>> = headers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect()); - let trailer_iovecs: Option>> = + let mut trailer_iovecs: Option>> = trailers.map(|s| s.iter().map(|b| IoSlice::new(b)).collect()); - SendfileHeaderTrailer( - libc::sf_hdtr { + + SendfileHeaderTrailer { + raw: libc::sf_hdtr { headers: { header_iovecs - .as_ref() - .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec + .as_mut() + .map_or(ptr::null_mut(), |v| v.as_mut_ptr()) + .cast() }, hdr_cnt: header_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32, trailers: { trailer_iovecs - .as_ref() - .map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec + .as_mut() + .map_or(ptr::null_mut(), |v| v.as_mut_ptr()) + .cast() }, trl_cnt: trailer_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32 }, - header_iovecs, - trailer_iovecs, - ) + _headers: header_iovecs, + _trailers: trailer_iovecs, + } + } + } + } else if #[cfg(solarish)] { + use std::os::unix::io::BorrowedFd; + use std::marker::PhantomData; + + #[derive(Debug, Copy, Clone)] + /// Mapping of the raw C sendfilevec_t struct + pub struct SendfileVec<'fd> { + raw: libc::sendfilevec_t, + phantom: PhantomData> + } + + impl<'fd> SendfileVec<'fd> { + /// initialises SendfileVec to send data directly from the process's address space + /// same in C with sfv_fd set to SFV_FD_SELF. + pub fn newself( + off: off_t, + len: usize + ) -> Self { + Self{raw: libc::sendfilevec_t{sfv_fd: libc::SFV_FD_SELF, sfv_flag: 0, sfv_off: off, sfv_len: len}, phantom: PhantomData} + } + + /// initialises SendfileVec to send data from `fd`. + pub fn new( + fd: BorrowedFd<'fd>, + off: off_t, + len: usize + ) -> SendfileVec<'fd> { + Self{raw: libc::sendfilevec_t{sfv_fd: fd.as_raw_fd(), sfv_flag: 0, sfv_off:off, sfv_len: len}, phantom: PhantomData} + } + } + + impl From> for libc::sendfilevec_t { + fn from<'fd>(vec: SendfileVec) -> libc::sendfilevec_t { + vec.raw } } } @@ -156,9 +205,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]]>, @@ -173,10 +222,10 @@ cfg_if! { let flags: u32 = (ra32 << 16) | (flags.bits() as u32); let mut bytes_sent: off_t = 0; let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); - let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw 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, @@ -206,9 +255,9 @@ cfg_if! { /// /// For more information, see /// [the sendfile(2) man page.](https://leaf.dragonflybsd.org/cgi/web-man?command=sendfile§ion=2) - pub fn sendfile( - in_fd: RawFd, - out_sock: RawFd, + pub fn sendfile( + in_fd: F1, + out_sock: F2, offset: off_t, count: Option, headers: Option<&[&[u8]]>, @@ -216,10 +265,10 @@ cfg_if! { ) -> (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 hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw 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, @@ -228,7 +277,7 @@ cfg_if! { }; (Errno::result(return_code).and(Ok(())), bytes_sent) } - } else if #[cfg(any(target_os = "ios", target_os = "macos"))] { + } else if #[cfg(apple_targets)] { /// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to /// `out_sock`. /// @@ -252,9 +301,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]]>, @@ -262,10 +311,10 @@ cfg_if! { ) -> (Result<()>, off_t) { let mut len = count.unwrap_or(0); let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers)); - let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr); + let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.raw 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, @@ -273,5 +322,30 @@ cfg_if! { }; (Errno::result(return_code).and(Ok(())), len) } + } else if #[cfg(solarish)] { + /// Write data from the vec arrays to `out_sock` and returns a `Result` and a + /// count of bytes written. + /// + /// Each `SendfileVec` set needs to be instantiated either with `SendfileVec::new` or + /// `SendfileVec::newself`. + /// + /// The former allows to send data from a file descriptor through `fd`, + /// from an offset `off` and for a given amount of data `len`. + /// + /// The latter allows to send data from the process's address space, from an offset `off` + /// and for a given amount of data `len`. + /// + /// For more information, see + /// [the sendfilev(3) man page.](https://illumos.org/man/3EXT/sendfilev) + pub fn sendfilev( + out_sock: F, + vec: &[SendfileVec] + ) -> (Result<()>, usize) { + let mut len = 0usize; + let return_code = unsafe { + libc::sendfilev(out_sock.as_fd().as_raw_fd(), vec.as_ptr() as *const libc::sendfilevec_t, vec.len() as i32, &mut len) + }; + (Errno::result(return_code).and(Ok(())), len) + } } } diff --git a/src/sys/signal.rs b/src/sys/signal.rs index f23bf1f4..2b0c779b 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -1,19 +1,21 @@ -// Portions of this file are Copyright 2014 The Rust Project Developers. -// See https://www.rust-lang.org/policies/licenses. - //! Operating system signals. use crate::errno::Errno; use crate::{Error, Result}; use cfg_if::cfg_if; use std::fmt; +use std::hash::{Hash, Hasher}; use std::mem; -#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] -use std::os::unix::io::RawFd; +use std::ops::BitOr; use std::ptr; use std::str::FromStr; -#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] +#[cfg(not(any( + target_os = "fuchsia", + target_os = "hurd", + target_os = "openbsd", + target_os = "redox" +)))] #[cfg(any(feature = "aio", feature = "signal"))] pub use self::sigevent::*; @@ -59,9 +61,13 @@ libc_enum! { /// Software termination signal from kill SIGTERM, /// Stack fault (obsolete) - #[cfg(all(any(target_os = "android", target_os = "emscripten", - target_os = "fuchsia", target_os = "linux"), - not(any(target_arch = "mips", target_arch = "mips64", + #[cfg(all(any(linux_android, target_os = "emscripten", + target_os = "fuchsia"), + not(any(target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "sparc", target_arch = "sparc64"))))] SIGSTKFLT, /// To parent on child stop or exit @@ -90,25 +96,22 @@ libc_enum! { SIGWINCH, /// Input/output possible signal #[cfg(not(target_os = "haiku"))] - #[cfg_attr(docsrs, doc(cfg(all())))] SIGIO, - #[cfg(any(target_os = "android", target_os = "emscripten", - target_os = "fuchsia", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, target_os = "emscripten", + target_os = "fuchsia", target_os = "aix"))] /// Power failure imminent. SIGPWR, /// Bad system call SIGSYS, - #[cfg(not(any(target_os = "android", target_os = "emscripten", - target_os = "fuchsia", target_os = "linux", + #[cfg(not(any(linux_android, target_os = "emscripten", + target_os = "fuchsia", target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] /// Emulator trap SIGEMT, - #[cfg(not(any(target_os = "android", target_os = "emscripten", - target_os = "fuchsia", target_os = "linux", - target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(linux_android, target_os = "emscripten", + target_os = "fuchsia", target_os = "redox", + target_os = "haiku", target_os = "aix", + target_os = "solaris", target_os = "cygwin")))] /// Information request SIGINFO, } @@ -137,14 +140,16 @@ impl FromStr for Signal { "SIGTERM" => Signal::SIGTERM, #[cfg(all( any( - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", - target_os = "linux" ), not(any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "sparc", target_arch = "sparc64" )) ))] @@ -164,29 +169,29 @@ impl FromStr for Signal { #[cfg(not(target_os = "haiku"))] "SIGIO" => Signal::SIGIO, #[cfg(any( - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", - target_os = "linux" ))] "SIGPWR" => Signal::SIGPWR, "SIGSYS" => Signal::SIGSYS, #[cfg(not(any( - target_os = "android", + linux_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", + linux_android, target_os = "emscripten", target_os = "fuchsia", - target_os = "linux", target_os = "redox", - target_os = "haiku" + target_os = "aix", + target_os = "haiku", + target_os = "solaris", + target_os = "cygwin" )))] "SIGINFO" => Signal::SIGINFO, _ => return Err(Errno::EINVAL), @@ -220,14 +225,16 @@ impl Signal { Signal::SIGTERM => "SIGTERM", #[cfg(all( any( - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", - target_os = "linux" ), not(any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "sparc", target_arch = "sparc64" )) ))] @@ -247,29 +254,30 @@ impl Signal { #[cfg(not(target_os = "haiku"))] Signal::SIGIO => "SIGIO", #[cfg(any( - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", - target_os = "linux" + target_os = "aix", ))] Signal::SIGPWR => "SIGPWR", Signal::SIGSYS => "SIGSYS", #[cfg(not(any( - target_os = "android", + linux_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", + linux_android, target_os = "emscripten", target_os = "fuchsia", - target_os = "linux", target_os = "redox", - target_os = "haiku" + target_os = "aix", + target_os = "haiku", + target_os = "solaris", + target_os = "cygwin" )))] Signal::SIGINFO => "SIGINFO", } @@ -310,15 +318,13 @@ const SIGNALS: [Signal; 28] = [ SIGPROF, SIGWINCH, SIGSYS, ]; #[cfg(all( - any( - target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia" - ), + any(linux_android, target_os = "emscripten", target_os = "fuchsia"), not(any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "sparc", target_arch = "sparc64" )) ))] @@ -330,13 +336,15 @@ const SIGNALS: [Signal; 31] = [ SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, ]; #[cfg(all( + any(linux_android, target_os = "emscripten", target_os = "fuchsia"), any( - target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia" - ), - any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64") + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "sparc", + target_arch = "sparc64" + ) ))] #[cfg(feature = "signal")] const SIGNALS: [Signal; 30] = [ @@ -345,13 +353,31 @@ const SIGNALS: [Signal; 30] = [ 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, 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(any(target_os = "solaris", target_os = "cygwin"))] +#[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, SIGSYS, SIGEMT, +]; #[cfg(not(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "fuchsia", target_os = "emscripten", + target_os = "aix", target_os = "redox", - target_os = "haiku" + target_os = "haiku", + target_os = "solaris", + target_os = "cygwin" )))] #[cfg(feature = "signal")] const SIGNALS: [Signal; 31] = [ @@ -421,6 +447,7 @@ libc_bitflags! { SA_NOCLDSTOP; /// When catching a [`Signal::SIGCHLD`] signal, the system will not /// create zombie processes when children of the calling process exit. + #[cfg(not(target_os = "hurd"))] SA_NOCLDWAIT; /// Further occurrences of the delivered signal are not masked during /// the execution of the handler. @@ -468,7 +495,7 @@ use std::iter::IntoIterator; // 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)] +#[derive(Clone, Copy, Debug, Eq)] pub struct SigSet { sigset: libc::sigset_t } @@ -559,7 +586,6 @@ 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; @@ -571,6 +597,35 @@ impl SigSet { }) } + /// Wait for a signal + /// + /// # Return value + /// If `sigsuspend(2)` is interrupted (EINTR), this function returns `Ok`. + /// If `sigsuspend(2)` set other error, this function returns `Err`. + /// + /// For more information see the + /// [`sigsuspend(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigsuspend.html). + #[cfg(any( + bsd, + linux_android, + solarish, + target_os = "haiku", + target_os = "hurd", + target_os = "aix", + target_os = "fuchsia" + ))] + #[doc(alias("sigsuspend"))] + pub fn suspend(&self) -> Result<()> { + let res = unsafe { + libc::sigsuspend(&self.sigset as *const libc::sigset_t) + }; + match Errno::result(res).map(drop) { + Err(Errno::EINTR) => Ok(()), + Err(e) => Err(e), + Ok(_) => unreachable!("because this syscall always returns -1 if returns"), + } + } + /// Converts a `libc::sigset_t` object to a [`SigSet`] without checking whether the /// `libc::sigset_t` is already initialized. /// @@ -585,6 +640,42 @@ impl SigSet { } } +impl From for SigSet { + fn from(signal: Signal) -> SigSet { + let mut sigset = SigSet::empty(); + sigset.add(signal); + sigset + } +} + +impl BitOr for Signal { + type Output = SigSet; + + fn bitor(self, rhs: Self) -> Self::Output { + let mut sigset = SigSet::empty(); + sigset.add(self); + sigset.add(rhs); + sigset + } +} + +impl BitOr for SigSet { + type Output = SigSet; + + fn bitor(mut self, rhs: Signal) -> Self::Output { + self.add(rhs); + self + } +} + +impl BitOr for SigSet { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + self.iter().chain(rhs.iter()).collect() + } +} + impl AsRef for SigSet { fn as_ref(&self) -> &libc::sigset_t { &self.sigset @@ -610,6 +701,27 @@ impl FromIterator for SigSet { } } +impl PartialEq for SigSet { + fn eq(&self, other: &Self) -> bool { + for signal in Signal::iterator() { + if self.contains(signal) != other.contains(signal) { + return false; + } + } + true + } +} + +impl Hash for SigSet { + fn hash(&self, state: &mut H) { + for signal in Signal::iterator() { + if self.contains(signal) { + signal.hash(state); + } + } + } +} + /// Iterator for a [`SigSet`]. /// /// Call [`SigSet::iter`] to create an iterator. @@ -641,7 +753,6 @@ impl<'a> IntoIterator for &'a SigSet { } /// A signal handler. -#[allow(unknown_lints)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SigHandler { /// Default signal handling. @@ -649,20 +760,26 @@ pub enum SigHandler { /// Request that the signal be ignored. SigIgn, /// Use the given signal-catching function, which takes in the signal. - Handler(extern fn(libc::c_int)), + Handler(extern "C" fn(libc::c_int)), /// Use the given signal-catching function, which takes in the signal, information about how /// the signal was generated, and a pointer to the threads `ucontext_t`. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - SigAction(extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void)) + SigAction(extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void)) } /// Action to take on receipt of a signal. Corresponds to `sigaction`. +#[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct SigAction { sigaction: libc::sigaction } +impl From for libc::sigaction { + fn from(value: SigAction) -> libc::sigaction { + value.sigaction + } +} + impl SigAction { /// Creates a new action. /// @@ -671,13 +788,15 @@ impl SigAction { /// the signal-catching function. pub fn new(handler: SigHandler, flags: SaFlags, mask: SigSet) -> SigAction { unsafe fn install_sig(p: *mut libc::sigaction, handler: SigHandler) { - (*p).sa_sigaction = match handler { - SigHandler::SigDfl => libc::SIG_DFL, - SigHandler::SigIgn => libc::SIG_IGN, - SigHandler::Handler(f) => f as *const extern fn(libc::c_int) as usize, - #[cfg(not(target_os = "redox"))] - SigHandler::SigAction(f) => f as *const extern fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void) as usize, - }; + unsafe { + (*p).sa_sigaction = match handler { + SigHandler::SigDfl => libc::SIG_DFL, + SigHandler::SigIgn => libc::SIG_IGN, + SigHandler::Handler(f) => f as *const extern "C" fn(libc::c_int) as usize, + #[cfg(not(target_os = "redox"))] + SigHandler::SigAction(f) => f as *const extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void) as usize, + }; + } } let mut s = mem::MaybeUninit::::uninit(); @@ -722,9 +841,9 @@ impl SigAction { // ensured that it is correctly initialized. unsafe{ *(&p as *const usize - as *const extern fn(_, _, _)) + as *const extern "C" fn(_, _, _)) } - as extern fn(_, _, _)), + as extern "C" fn(_, _, _)), p => SigHandler::Handler( // Safe for one of two reasons: // * The SigHandler was created by SigHandler::new, in which @@ -734,9 +853,9 @@ impl SigAction { // ensured that it is correctly initialized. unsafe{ *(&p as *const usize - as *const extern fn(libc::c_int)) + as *const extern "C" fn(libc::c_int)) } - as extern fn(libc::c_int)), + as extern "C" fn(libc::c_int)), } } } @@ -761,11 +880,11 @@ impl SigAction { pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result { let mut oldact = mem::MaybeUninit::::uninit(); - let res = libc::sigaction(signal as libc::c_int, + let res = unsafe { libc::sigaction(signal as libc::c_int, &sigaction.sigaction as *const libc::sigaction, - oldact.as_mut_ptr()); + oldact.as_mut_ptr()) }; - Errno::result(res).map(|_| SigAction { sigaction: oldact.assume_init() }) + Errno::result(res).map(|_| SigAction { sigaction: unsafe { oldact.assume_init() } }) } /// Signal management (see [signal(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html)) @@ -792,15 +911,12 @@ pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result Result Result { let signal = signal as libc::c_int; let res = match handler { - SigHandler::SigDfl => libc::signal(signal, libc::SIG_DFL), - SigHandler::SigIgn => libc::signal(signal, libc::SIG_IGN), - SigHandler::Handler(handler) => libc::signal(signal, handler as libc::sighandler_t), + SigHandler::SigDfl => unsafe { libc::signal(signal, libc::SIG_DFL) }, + SigHandler::SigIgn => unsafe { libc::signal(signal, libc::SIG_IGN) }, + SigHandler::Handler(handler) => unsafe { libc::signal(signal, handler as libc::sighandler_t) }, #[cfg(not(target_os = "redox"))] SigHandler::SigAction(_) => return Err(Errno::ENOTSUP), }; @@ -837,9 +953,7 @@ pub unsafe fn signal(signal: Signal, handler: SigHandler) -> Result libc::SIG_DFL => SigHandler::SigDfl, libc::SIG_IGN => SigHandler::SigIgn, p => SigHandler::Handler( - *(&p as *const usize - as *const extern fn(libc::c_int)) - as extern fn(libc::c_int)), + unsafe { *(&p as *const usize as *const extern "C" fn(libc::c_int)) } as extern "C" fn(libc::c_int)), } }) } @@ -973,16 +1087,16 @@ feature! { #[cfg(target_os = "freebsd")] pub type type_of_thread_id = libc::lwpid_t; /// Identifies a thread for [`SigevNotify::SigevThreadId`] -#[cfg(target_os = "linux")] +#[cfg(all(not(target_os = "hurd"), 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")))] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum SigevNotify { +#[cfg(not(any(target_os = "fuchsia", target_os = "hurd", target_os = "openbsd", target_os = "redox")))] +#[derive(Clone, Copy, Debug)] +pub enum SigevNotify<'fd> { /// No notification will be delivered SigevNone, /// Notify by delivering a signal to the process. @@ -993,20 +1107,32 @@ 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())))] + #[cfg(freebsdlike)] SigevKevent { /// File descriptor of the kqueue to notify. - kq: RawFd, + kq: std::os::fd::BorrowedFd<'fd>, /// 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(feature = "event")] + SigevKeventFlags { + /// File descriptor of the kqueue to notify. + kq: std::os::fd::BorrowedFd<'fd>, + /// 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::EvFlags + }, /// Notify by delivering a signal to a thread. - #[cfg(any(target_os = "freebsd", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + target_os = "freebsd", + target_env = "gnu", + target_env = "uclibc", + ))] SigevThreadId { /// Signal to send signal: Signal, @@ -1016,27 +1142,161 @@ pub enum SigevNotify { /// structure of the queued signal. si_value: libc::intptr_t }, + /// A helper variant to resolve the unused parameter (`'fd`) problem on + /// platforms other than FreeBSD and DragonFlyBSD. + /// + /// This variant can never be constructed due to the usage of an enum with 0 + /// variants. + #[doc(hidden)] + #[cfg(not(freebsdlike))] + _Unreachable(&'fd std::convert::Infallible), } } -#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(not(any( + target_os = "fuchsia", + target_os = "hurd", + target_os = "openbsd", + target_os = "redox" +)))] 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 { @@ -1053,296 +1313,100 @@ 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, - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT, + 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(freebsdlike)] + SigevNotify::SigevKevent{kq, udata} => { + use std::os::fd::AsRawFd; + + sev.sigev_notify = libc::SIGEV_KEVENT; + sev.sigev_signo = kq.as_raw_fd(); + 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(all(target_os = "linux", target_env = "uclibc"))] - SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID, - #[cfg(any(all(target_os = "linux", any(target_env = "musl", target_env = "ohos")), 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} => { + use std::os::fd::AsRawFd; + + sev.sigev_notify = libc::SIGEV_KEVENT; + sev.sigev_signo = kq.as_raw_fd(); + 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; + } + #[cfg(not(freebsdlike))] + SigevNotify::_Unreachable(_) => unreachable!("This variant could never be constructed") + } 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 { + impl From<&'_ 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; - - #[test] - fn test_contains() { - let mut mask = SigSet::empty(); - mask.add(SIGUSR1); - - assert!(mask.contains(SIGUSR1)); - assert!(!mask.contains(SIGUSR2)); - - let all = SigSet::all(); - assert!(all.contains(SIGUSR1)); - assert!(all.contains(SIGUSR2)); - } - - #[test] - fn test_clear() { - let mut set = SigSet::all(); - set.clear(); - for signal in Signal::iterator() { - assert!(!set.contains(signal)); - } - } - - #[test] - fn test_from_str_round_trips() { - for signal in Signal::iterator() { - assert_eq!(signal.as_ref().parse::().unwrap(), signal); - assert_eq!(signal.to_string().parse::().unwrap(), signal); - } - } - - #[test] - fn test_from_str_invalid_value() { - let errval = Err(Errno::EINVAL); - assert_eq!("NOSIGNAL".parse::(), errval); - assert_eq!("kill".parse::(), errval); - assert_eq!("9".parse::(), errval); - } - - #[test] - fn test_extend() { - let mut one_signal = SigSet::empty(); - one_signal.add(SIGUSR1); - - let mut two_signals = SigSet::empty(); - two_signals.add(SIGUSR2); - two_signals.extend(&one_signal); - - assert!(two_signals.contains(SIGUSR1)); - assert!(two_signals.contains(SIGUSR2)); - } - - #[test] - #[cfg(not(target_os = "redox"))] - fn test_thread_signal_set_mask() { - thread::spawn(|| { - let prev_mask = SigSet::thread_get_mask() - .expect("Failed to get existing signal mask!"); - - let mut test_mask = prev_mask; - test_mask.add(SIGUSR1); - - test_mask.thread_set_mask().expect("assertion failed"); - let new_mask = - SigSet::thread_get_mask().expect("Failed to get new mask!"); - - assert!(new_mask.contains(SIGUSR1)); - assert!(!new_mask.contains(SIGUSR2)); - - prev_mask - .thread_set_mask() - .expect("Failed to revert signal mask!"); - }) - .join() - .unwrap(); - } - - #[test] - #[cfg(not(target_os = "redox"))] - fn test_thread_signal_block() { - thread::spawn(|| { - let mut mask = SigSet::empty(); - mask.add(SIGUSR1); - - mask.thread_block().expect("assertion failed"); - - assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); - }) - .join() - .unwrap(); - } - - #[test] - #[cfg(not(target_os = "redox"))] - fn test_thread_signal_unblock() { - thread::spawn(|| { - let mut mask = SigSet::empty(); - mask.add(SIGUSR1); - - mask.thread_unblock().expect("assertion failed"); - - assert!(!SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); - }) - .join() - .unwrap(); - } - - #[test] - #[cfg(not(target_os = "redox"))] - fn test_thread_signal_swap() { - thread::spawn(|| { - let mut mask = SigSet::empty(); - mask.add(SIGUSR1); - mask.thread_block().unwrap(); - - assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); - - let mut mask2 = SigSet::empty(); - mask2.add(SIGUSR2); - - let oldmask = - mask2.thread_swap_mask(SigmaskHow::SIG_SETMASK).unwrap(); - - assert!(oldmask.contains(SIGUSR1)); - assert!(!oldmask.contains(SIGUSR2)); - - assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR2)); - }) - .join() - .unwrap(); - } - - #[test] - fn test_from_and_into_iterator() { - let sigset = SigSet::from_iter(vec![Signal::SIGUSR1, Signal::SIGUSR2]); - let signals = sigset.into_iter().collect::>(); - assert_eq!(signals, [Signal::SIGUSR1, Signal::SIGUSR2]); - } - - #[test] - #[cfg(not(target_os = "redox"))] - fn test_sigaction() { - thread::spawn(|| { - extern "C" fn test_sigaction_handler(_: libc::c_int) {} - extern "C" fn test_sigaction_action( - _: libc::c_int, - _: *mut libc::siginfo_t, - _: *mut libc::c_void, - ) { - } - - let handler_sig = SigHandler::Handler(test_sigaction_handler); - - let flags = - SaFlags::SA_ONSTACK | SaFlags::SA_RESTART | SaFlags::SA_SIGINFO; - - let mut mask = SigSet::empty(); - mask.add(SIGUSR1); - - let action_sig = SigAction::new(handler_sig, flags, mask); - - assert_eq!( - action_sig.flags(), - SaFlags::SA_ONSTACK | SaFlags::SA_RESTART - ); - assert_eq!(action_sig.handler(), handler_sig); - - mask = action_sig.mask(); - assert!(mask.contains(SIGUSR1)); - assert!(!mask.contains(SIGUSR2)); - - let handler_act = SigHandler::SigAction(test_sigaction_action); - let action_act = SigAction::new(handler_act, flags, mask); - assert_eq!(action_act.handler(), handler_act); - - let action_dfl = SigAction::new(SigHandler::SigDfl, flags, mask); - assert_eq!(action_dfl.handler(), SigHandler::SigDfl); - - let action_ign = SigAction::new(SigHandler::SigIgn, flags, mask); - assert_eq!(action_ign.handler(), SigHandler::SigIgn); - }) - .join() - .unwrap(); - } - - #[test] - #[cfg(not(target_os = "redox"))] - fn test_sigwait() { - thread::spawn(|| { - let mut mask = SigSet::empty(); - mask.add(SIGUSR1); - mask.add(SIGUSR2); - mask.thread_block().unwrap(); - - raise(SIGUSR1).unwrap(); - assert_eq!(mask.wait().unwrap(), SIGUSR1); - }) - .join() - .unwrap(); - } - - #[test] - fn test_from_sigset_t_unchecked() { - let src_set = SigSet::empty(); - let set = unsafe { SigSet::from_sigset_t_unchecked(src_set.sigset) }; - - for signal in Signal::iterator() { - assert!(!set.contains(signal)); - } - - let src_set = SigSet::all(); - let set = unsafe { SigSet::from_sigset_t_unchecked(src_set.sigset) }; - - for signal in Signal::iterator() { - assert!(set.contains(signal)); - } - } -} diff --git a/src/sys/signalfd.rs b/src/sys/signalfd.rs index 095e5908..36148812 100644 --- a/src/sys/signalfd.rs +++ b/src/sys/signalfd.rs @@ -17,12 +17,13 @@ //! signal handlers. use crate::errno::Errno; pub use crate::sys::signal::{self, SigSet}; -use crate::unistd; use crate::Result; + +/// Information of a received signal, the return type of [`SignalFd::read_signal()`]. pub use libc::signalfd_siginfo as siginfo; use std::mem; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; libc_bitflags! { pub struct SfdFlags: libc::c_int { @@ -31,7 +32,6 @@ libc_bitflags! { } } -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::(); @@ -46,13 +46,24 @@ 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)) } } @@ -82,8 +93,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 { @@ -91,21 +102,21 @@ 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) + pub fn set_mask(&self, mask: &SigSet) -> Result<()> { + self.update(mask, SfdFlags::empty()) } - pub fn read_signal(&mut self) -> Result> { + pub fn read_signal(&self) -> Result> { let mut buffer = mem::MaybeUninit::::uninit(); let size = mem::size_of_val(&buffer); let res = Errno::result(unsafe { - libc::read(self.0, buffer.as_mut_ptr() as *mut libc::c_void, size) + libc::read(self.0.as_raw_fd(), buffer.as_mut_ptr().cast(), size) }) .map(|r| r as usize); match res { @@ -115,20 +126,39 @@ impl SignalFd { 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!"); - }; + /// Constructs a `SignalFd` wrapping an existing `OwnedFd`. + /// + /// # Safety + /// + /// `OwnedFd` is a valid `SignalFd`. + pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { + Self(fd) + } + + fn update(&self, mask: &SigSet, flags: SfdFlags) -> Result<()> { + let raw_fd = self.0.as_raw_fd(); + unsafe { + Errno::result(libc::signalfd(raw_fd, mask.as_ref(), flags.bits())) + .map(drop) + } } } +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() + } +} + +impl From for OwnedFd { + fn from(value: SignalFd) -> Self { + value.0 } } @@ -142,34 +172,3 @@ impl Iterator for SignalFd { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn create_signalfd() { - let mask = SigSet::empty(); - SignalFd::new(&mask).unwrap(); - } - - #[test] - fn create_signalfd_with_opts() { - let mask = SigSet::empty(); - SignalFd::with_flags( - &mask, - SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK, - ) - .unwrap(); - } - - #[test] - fn read_empty_signalfd() { - let mask = SigSet::empty(); - let mut fd = - SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); - - let res = fd.read_signal(); - assert!(res.unwrap().is_none()); - } -} diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs index 2f30bff4..09be1d68 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -1,30 +1,22 @@ #[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", + bsd, + linux_android, + solarish, target_os = "haiku", - target_os = "fuchsia" + target_os = "fuchsia", + target_os = "aix", ))] #[cfg(feature = "net")] pub use self::datalink::LinkAddr; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(any(linux_android, apple_targets))] pub use self::vsock::VsockAddr; use super::sa_family_t; use crate::errno::Errno; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] use crate::sys::socket::addr::alg::AlgAddr; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] use crate::sys::socket::addr::netlink::NetlinkAddr; -#[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") -))] +#[cfg(all(feature = "ioctl", apple_targets))] use crate::sys::socket::addr::sys_control::SysControlAddr; use crate::{NixPath, Result}; use cfg_if::cfg_if; @@ -32,9 +24,8 @@ use memoffset::offset_of; use std::convert::TryInto; use std::ffi::OsStr; use std::hash::{Hash, Hasher}; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::os::unix::ffi::OsStrExt; -#[cfg(any(target_os = "ios", target_os = "macos"))] -use std::os::unix::io::RawFd; use std::path::Path; use std::{fmt, mem, net, ptr, slice}; @@ -42,7 +33,7 @@ use std::{fmt, mem, net, ptr, slice}; #[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()) + s_addr: u32::from_ne_bytes(addr.octets()), } } @@ -50,7 +41,7 @@ pub(crate) const fn ipv4addr_to_libc(addr: net::Ipv4Addr) -> libc::in_addr { #[cfg(feature = "net")] pub(crate) const fn ipv6addr_to_libc(addr: &net::Ipv6Addr) -> libc::in6_addr { libc::in6_addr { - s6_addr: addr.octets() + s6_addr: addr.octets(), } } @@ -72,332 +63,190 @@ pub enum AddressFamily { /// IPv6 Internet protocols (see [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html)) Inet6 = libc::AF_INET6, /// Kernel user interface device (see [`netlink(7)`](https://man7.org/linux/man-pages/man7/netlink.7.html)) - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Netlink = libc::AF_NETLINK, + /// Kernel interface for interacting with the routing table + #[cfg(not(any(linux_android, target_os = "redox", target_os = "cygwin")))] + 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_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, solarish, target_os = "fuchsia"))] Packet = libc::AF_PACKET, /// KEXT Controls and Notifications - #[cfg(any(target_os = "ios", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] System = libc::AF_SYSTEM, /// Amateur radio AX.25 protocol - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Ax25 = libc::AF_AX25, /// IPX - Novell protocols + #[cfg(not(any(target_os = "aix", target_os = "redox", target_os = "cygwin")))] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] Bridge = libc::AF_BRIDGE, /// Access to raw ATM PVCs - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] Rose = libc::AF_ROSE, /// DECet protocol sockets. - #[cfg(not(target_os = "haiku"))] + #[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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] Security = libc::AF_SECURITY, /// Key management protocol. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] Ash = libc::AF_ASH, /// Acorn Econet protocol - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Econet = libc::AF_ECONET, /// Access to ATM Switched Virtual Circuits - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] AtmSvc = libc::AF_ATMSVC, /// Reliable Datagram Sockets (RDS) protocol - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Rds = libc::AF_RDS, /// IBM SNA - #[cfg(not(target_os = "haiku"))] + #[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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] Llc = libc::AF_LLC, /// InfiniBand native addressing #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] - #[cfg_attr(docsrs, doc(cfg(all())))] Ib = libc::AF_IB, /// Multiprotocol Label Switching #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] - #[cfg_attr(docsrs, doc(cfg(all())))] Mpls = libc::AF_MPLS, /// Controller Area Network automotive bus protocol - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Can = libc::AF_CAN, /// TIPC, "cluster domain sockets" protocol - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Tipc = libc::AF_TIPC, /// Bluetooth low-level socket protocol #[cfg(not(any( - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "solaris" + target_os = "aix", + solarish, + apple_targets, + target_os = "hurd", + target_os = "redox", + target_os = "cygwin", )))] - #[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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] RxRpc = libc::AF_RXRPC, /// New "modular ISDN" driver interface protocol #[cfg(not(any( - target_os = "illumos", - target_os = "solaris", - target_os = "haiku" + target_os = "aix", + solarish, + target_os = "haiku", + target_os = "hurd", + target_os = "redox", + target_os = "cygwin", )))] - #[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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] Caif = libc::AF_CAIF, /// Interface to kernel crypto API - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] Alg = libc::AF_ALG, /// Near field communication #[cfg(target_os = "linux")] - #[cfg_attr(docsrs, doc(cfg(all())))] Nfc = libc::AF_NFC, /// VMWare VSockets protocol for hypervisor-guest interaction. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, apple_targets))] Vsock = libc::AF_VSOCK, /// ARPANet IMP addresses - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] ImpLink = libc::AF_IMPLINK, /// PUP protocols, e.g. BSP - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Pup = libc::AF_PUP, /// MIT CHAOS protocols - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Chaos = libc::AF_CHAOS, /// Novell and Xerox protocol - #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(apple_targets, netbsdlike))] Ns = libc::AF_NS, #[allow(missing_docs)] // Not documented anywhere that I can find - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Iso = libc::AF_ISO, /// Bell Labs virtual circuit switch ? - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Datakit = libc::AF_DATAKIT, /// CCITT protocols, X.25 etc - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Ccitt = libc::AF_CCITT, /// DEC Direct data link interface - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Dli = libc::AF_DLI, #[allow(missing_docs)] // Not documented anywhere that I can find - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Lat = libc::AF_LAT, /// NSC Hyperchannel - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Hylink = libc::AF_HYLINK, /// Link layer interface - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "illumos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, solarish))] Link = libc::AF_LINK, /// connection-oriented IP, aka ST II - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Coip = libc::AF_COIP, /// Computer Network Technology - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] Cnt = libc::AF_CNT, /// Native ATM access - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] 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())))] + #[cfg(linux_android)] Unspec = libc::AF_UNSPEC, } @@ -412,331 +261,23 @@ impl AddressFamily { libc::AF_UNIX => Some(AddressFamily::Unix), libc::AF_INET => Some(AddressFamily::Inet), libc::AF_INET6 => Some(AddressFamily::Inet6), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] libc::AF_NETLINK => Some(AddressFamily::Netlink), - #[cfg(any(target_os = "macos", target_os = "macos"))] + #[cfg(apple_targets)] libc::AF_SYSTEM => Some(AddressFamily::System), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(not(any(linux_android, target_os = "redox", target_os = "cygwin")))] + libc::PF_ROUTE => Some(AddressFamily::Route), + #[cfg(linux_android)] 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(bsd, solarish))] libc::AF_LINK => Some(AddressFamily::Link), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(any(linux_android, apple_targets))] libc::AF_VSOCK => Some(AddressFamily::Vsock), _ => None, } } } -feature! { -#![feature = "net"] - -#[deprecated( - since = "0.24.0", - note = "use SockaddrIn, SockaddrIn6, or SockaddrStorage instead" -)] -#[allow(missing_docs)] // Since they're all deprecated anyway -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum InetAddr { - V4(libc::sockaddr_in), - V6(libc::sockaddr_in6), -} - -#[allow(missing_docs)] // It's deprecated anyway -#[allow(deprecated)] -impl InetAddr { - #[allow(clippy::needless_update)] // It isn't needless on all OSes - pub fn from_std(std: &net::SocketAddr) -> InetAddr { - match *std { - net::SocketAddr::V4(ref addr) => { - InetAddr::V4(libc::sockaddr_in { - #[cfg(any(target_os = "dragonfly", target_os = "freebsd", - target_os = "haiku", target_os = "hermit", - target_os = "ios", target_os = "macos", - target_os = "netbsd", target_os = "openbsd"))] - sin_len: mem::size_of::() as u8, - sin_family: AddressFamily::Inet as sa_family_t, - sin_port: addr.port().to_be(), // network byte order - sin_addr: Ipv4Addr::from_std(addr.ip()).0, - .. unsafe { mem::zeroed() } - }) - } - net::SocketAddr::V6(ref addr) => { - InetAddr::V6(libc::sockaddr_in6 { - #[cfg(any(target_os = "dragonfly", target_os = "freebsd", - target_os = "haiku", target_os = "hermit", - target_os = "ios", target_os = "macos", - target_os = "netbsd", target_os = "openbsd"))] - sin6_len: mem::size_of::() as u8, - sin6_family: AddressFamily::Inet6 as sa_family_t, - sin6_port: addr.port().to_be(), // network byte order - sin6_addr: Ipv6Addr::from_std(addr.ip()).0, - sin6_flowinfo: addr.flowinfo(), // host byte order - sin6_scope_id: addr.scope_id(), // host byte order - .. unsafe { mem::zeroed() } - }) - } - } - } - - #[allow(clippy::needless_update)] // It isn't needless on all OSes - pub fn new(ip: IpAddr, port: u16) -> InetAddr { - match ip { - IpAddr::V4(ref ip) => { - InetAddr::V4(libc::sockaddr_in { - sin_family: AddressFamily::Inet as sa_family_t, - sin_port: port.to_be(), - sin_addr: ip.0, - .. unsafe { mem::zeroed() } - }) - } - IpAddr::V6(ref ip) => { - InetAddr::V6(libc::sockaddr_in6 { - sin6_family: AddressFamily::Inet6 as sa_family_t, - sin6_port: port.to_be(), - sin6_addr: ip.0, - .. unsafe { mem::zeroed() } - }) - } - } - } - /// Gets the IP address associated with this socket address. - pub const fn ip(&self) -> IpAddr { - match *self { - InetAddr::V4(ref sa) => IpAddr::V4(Ipv4Addr(sa.sin_addr)), - InetAddr::V6(ref sa) => IpAddr::V6(Ipv6Addr(sa.sin6_addr)), - } - } - - /// Gets the port number associated with this socket address - pub const fn port(&self) -> u16 { - match *self { - InetAddr::V6(ref sa) => u16::from_be(sa.sin6_port), - InetAddr::V4(ref sa) => u16::from_be(sa.sin_port), - } - } - - pub fn to_std(&self) -> net::SocketAddr { - match *self { - InetAddr::V4(ref sa) => net::SocketAddr::V4( - net::SocketAddrV4::new( - Ipv4Addr(sa.sin_addr).to_std(), - self.port())), - InetAddr::V6(ref sa) => net::SocketAddr::V6( - net::SocketAddrV6::new( - Ipv6Addr(sa.sin6_addr).to_std(), - self.port(), - sa.sin6_flowinfo, - sa.sin6_scope_id)), - } - } - - #[deprecated(since = "0.23.0", note = "use .to_string() instead")] - pub fn to_str(&self) -> String { - format!("{}", self) - } -} - -#[allow(deprecated)] -impl fmt::Display for InetAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - InetAddr::V4(_) => write!(f, "{}:{}", self.ip(), self.port()), - InetAddr::V6(_) => write!(f, "[{}]:{}", self.ip(), self.port()), - } - } -} - -/* - * - * ===== IpAddr ===== - * - */ -#[allow(missing_docs)] // Since they're all deprecated anyway -#[allow(deprecated)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[deprecated( - since = "0.24.0", - note = "Use std::net::IpAddr instead" -)] -pub enum IpAddr { - V4(Ipv4Addr), - V6(Ipv6Addr), -} - -#[allow(deprecated)] -#[allow(missing_docs)] // Since they're all deprecated anyway -impl IpAddr { - /// Create a new IpAddr that contains an IPv4 address. - /// - /// The result will represent the IP address a.b.c.d - pub const fn new_v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr { - IpAddr::V4(Ipv4Addr::new(a, b, c, d)) - } - - /// Create a new IpAddr that contains an IPv6 address. - /// - /// The result will represent the IP address a:b:c:d:e:f - #[allow(clippy::many_single_char_names)] - #[allow(clippy::too_many_arguments)] - pub const fn new_v6(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> IpAddr { - IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h)) - } - - pub fn from_std(std: &net::IpAddr) -> IpAddr { - match *std { - net::IpAddr::V4(ref std) => IpAddr::V4(Ipv4Addr::from_std(std)), - net::IpAddr::V6(ref std) => IpAddr::V6(Ipv6Addr::from_std(std)), - } - } - - pub const fn to_std(&self) -> net::IpAddr { - match *self { - IpAddr::V4(ref ip) => net::IpAddr::V4(ip.to_std()), - IpAddr::V6(ref ip) => net::IpAddr::V6(ip.to_std()), - } - } -} - -#[allow(deprecated)] -impl fmt::Display for IpAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - IpAddr::V4(ref v4) => v4.fmt(f), - IpAddr::V6(ref v6) => v6.fmt(f) - } - } -} - -/* - * - * ===== Ipv4Addr ===== - * - */ - -#[deprecated( - since = "0.24.0", - note = "Use std::net::Ipv4Addr instead" -)] -#[allow(missing_docs)] // Since they're all deprecated anyway -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[repr(transparent)] -pub struct Ipv4Addr(pub libc::in_addr); - -#[allow(deprecated)] -#[allow(missing_docs)] // Since they're all deprecated anyway -impl Ipv4Addr { - #[allow(clippy::identity_op)] // More readable this way - pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { - let ip = (((a as u32) << 24) | - ((b as u32) << 16) | - ((c as u32) << 8) | - ((d as u32) << 0)).to_be(); - - Ipv4Addr(libc::in_addr { s_addr: ip }) - } - - // Use pass by reference for symmetry with Ipv6Addr::from_std - #[allow(clippy::trivially_copy_pass_by_ref)] - pub fn from_std(std: &net::Ipv4Addr) -> Ipv4Addr { - let bits = std.octets(); - Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) - } - - pub const fn any() -> Ipv4Addr { - Ipv4Addr(libc::in_addr { s_addr: libc::INADDR_ANY }) - } - - pub const fn octets(self) -> [u8; 4] { - let bits = u32::from_be(self.0.s_addr); - [(bits >> 24) as u8, (bits >> 16) as u8, (bits >> 8) as u8, bits as u8] - } - - pub const fn to_std(self) -> net::Ipv4Addr { - let bits = self.octets(); - net::Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) - } -} - -#[allow(deprecated)] -impl fmt::Display for Ipv4Addr { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let octets = self.octets(); - write!(fmt, "{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]) - } -} - -/* - * - * ===== Ipv6Addr ===== - * - */ - -#[deprecated( - since = "0.24.0", - note = "Use std::net::Ipv6Addr instead" -)] -#[allow(missing_docs)] // Since they're all deprecated anyway -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[repr(transparent)] -pub struct Ipv6Addr(pub libc::in6_addr); - -// Note that IPv6 addresses are stored in big endian order on all architectures. -// See https://tools.ietf.org/html/rfc1700 or consult your favorite search -// engine. - -macro_rules! to_u8_array { - ($($num:ident),*) => { - [ $(($num>>8) as u8, ($num&0xff) as u8,)* ] - } -} - -macro_rules! to_u16_array { - ($slf:ident, $($first:expr, $second:expr),*) => { - [$( (($slf.0.s6_addr[$first] as u16) << 8) + $slf.0.s6_addr[$second] as u16,)*] - } -} - -#[allow(deprecated)] -#[allow(missing_docs)] // Since they're all deprecated anyway -impl Ipv6Addr { - #[allow(clippy::many_single_char_names)] - #[allow(clippy::too_many_arguments)] - pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { - Ipv6Addr(libc::in6_addr{s6_addr: to_u8_array!(a,b,c,d,e,f,g,h)}) - } - - pub fn from_std(std: &net::Ipv6Addr) -> Ipv6Addr { - let s = std.segments(); - Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) - } - - /// Return the eight 16-bit segments that make up this address - pub const fn segments(&self) -> [u16; 8] { - to_u16_array!(self, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) - } - - pub const fn to_std(&self) -> net::Ipv6Addr { - let s = self.segments(); - net::Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) - } -} - -#[allow(deprecated)] -impl fmt::Display for Ipv6Addr { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - self.to_std().fmt(fmt) - } -} -} - /// A wrapper around `sockaddr_un`. #[derive(Clone, Copy, Debug)] #[repr(C)] @@ -746,12 +287,7 @@ pub struct UnixAddr { /// 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" - ))] + #[cfg(not(any(bsd, target_os = "haiku", target_os = "hurd")))] sun_len: u8, } @@ -765,12 +301,12 @@ pub struct UnixAddr { enum UnixAddrKind<'a> { Pathname(&'a Path), Unnamed, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] Abstract(&'a [u8]), } impl<'a> UnixAddrKind<'a> { /// Safety: sun & sun_len must be valid - #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms + #[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 = @@ -778,16 +314,19 @@ impl<'a> UnixAddrKind<'a> { if path_len == 0 { return Self::Unnamed; } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] 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 = unsafe { + slice::from_raw_parts( + sun.sun_path.as_ptr().add(1).cast(), + path_len - 1, + ) + }; return Self::Abstract(name); } - let pathname = - slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, path_len); + let pathname = unsafe { + slice::from_raw_parts(sun.sun_path.as_ptr().cast(), 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 @@ -807,7 +346,7 @@ impl<'a> UnixAddrKind<'a> { impl UnixAddr { /// Create a new sockaddr_un representing a filesystem path. - #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms + #[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 { @@ -826,20 +365,13 @@ impl UnixAddr { .try_into() .unwrap(); - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ))] + #[cfg(any(bsd, target_os = "haiku", target_os = "hurd"))] { ret.sun_len = sun_len; } ptr::copy_nonoverlapping( bytes.as_ptr(), - ret.sun_path.as_mut_ptr() as *mut u8, + ret.sun_path.as_mut_ptr().cast(), bytes.len(), ); @@ -853,9 +385,8 @@ impl UnixAddr { /// 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 + #[cfg(linux_android)] + #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms pub fn new_abstract(path: &[u8]) -> Result { unsafe { let mut ret = libc::sockaddr_un { @@ -875,7 +406,7 @@ impl UnixAddr { // 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, + ret.sun_path.as_mut_ptr().offset(1).cast(), path.len(), ); @@ -884,15 +415,15 @@ impl UnixAddr { } /// 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())))] + #[cfg(linux_android)] pub fn new_unnamed() -> UnixAddr { let ret = libc::sockaddr_un { sun_family: AddressFamily::Unix as sa_family_t, - .. unsafe { mem::zeroed() } + ..unsafe { mem::zeroed() } }; - let sun_len: u8 = offset_of!(libc::sockaddr_un, sun_path).try_into().unwrap(); + let sun_len: u8 = + offset_of!(libc::sockaddr_un, sun_path).try_into().unwrap(); unsafe { UnixAddr::from_raw_parts(ret, sun_len) } } @@ -913,10 +444,11 @@ impl UnixAddr { sun_len: u8, ) -> UnixAddr { cfg_if! { - if #[cfg(any(target_os = "android", + if #[cfg(any(linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "redox", + target_os = "cygwin", ))] { UnixAddr { sun, sun_len } @@ -944,8 +476,7 @@ impl UnixAddr { /// /// For abstract sockets only the bare name is returned, without the /// leading NUL byte. `None` is returned for unnamed or path-backed sockets. - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] pub fn as_abstract(&self) -> Option<&[u8]> { match self.kind() { UnixAddrKind::Abstract(name) => Some(name), @@ -954,8 +485,7 @@ 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())))] + #[cfg(linux_android)] #[inline] pub fn is_unnamed(&self) -> bool { matches!(self.kind(), UnixAddrKind::Unnamed) @@ -979,10 +509,11 @@ impl UnixAddr { fn sun_len(&self) -> u8 { cfg_if! { - if #[cfg(any(target_os = "android", + if #[cfg(any(linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "redox", + target_os = "cygwin", ))] { self.sun_len @@ -996,10 +527,11 @@ impl UnixAddr { impl private::SockaddrLikePriv for UnixAddr {} impl SockaddrLike for UnixAddr { #[cfg(any( - target_os = "android", + linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "redox", + target_os = "cygwin", ))] fn len(&self) -> libc::socklen_t { self.sun_len.into() @@ -1019,26 +551,27 @@ impl SockaddrLike for UnixAddr { return None; } } - if (*addr).sa_family as i32 != libc::AF_UNIX { + if unsafe { (*addr).sa_family as i32 != libc::AF_UNIX } { return None; } - let mut su: libc::sockaddr_un = mem::zeroed(); + let mut su: libc::sockaddr_un = unsafe { mem::zeroed() }; let sup = &mut su as *mut libc::sockaddr_un as *mut u8; cfg_if! { - if #[cfg(any(target_os = "android", + if #[cfg(any(linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "redox", + target_os = "cygwin", ))] { 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); + let su_len = unsafe { 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)) + } + unsafe { ptr::copy(addr as *const u8, sup, su_len as usize) }; + Some(unsafe { Self::from_raw_parts(su, su_len as u8) }) } fn size() -> libc::socklen_t @@ -1048,14 +581,16 @@ impl SockaddrLike for UnixAddr { mem::size_of::() as libc::socklen_t } - unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> { + 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", + if #[cfg(any(linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux", + solarish, target_os = "redox", ))] { self.sun_len = new_length as u8; @@ -1071,7 +606,7 @@ impl AsRef for UnixAddr { } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn fmt_abstract(abs: &[u8], f: &mut fmt::Formatter) -> fmt::Result { use fmt::Write; f.write_str("@\"")?; @@ -1088,7 +623,7 @@ impl fmt::Display for UnixAddr { match self.kind() { UnixAddrKind::Pathname(path) => path.display().fmt(f), UnixAddrKind::Unnamed => f.pad(""), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] UnixAddrKind::Abstract(name) => fmt_abstract(name, f), } } @@ -1126,15 +661,13 @@ pub trait SockaddrLike: private::SockaddrLikePriv { /// /// # 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. + /// - `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 /// @@ -1153,9 +686,10 @@ pub trait SockaddrLike: private::SockaddrLikePriv { /// 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).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()), @@ -1171,12 +705,7 @@ pub trait SockaddrLike: private::SockaddrLikePriv { } cfg_if! { - if #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] { + if #[cfg(bsd)] { /// Return the length of valid data in the sockaddr structure. /// /// For fixed-size sockaddrs, this should be the size of the @@ -1223,7 +752,10 @@ pub trait SockaddrLike: private::SockaddrLikePriv { /// `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> { + unsafe fn set_length( + &mut self, + _new_length: usize, + ) -> std::result::Result<(), SocketAddressLengthNotDynamic> { Err(SocketAddressLengthNotDynamic) } } @@ -1283,21 +815,20 @@ pub struct SockaddrIn(libc::sockaddr_in); 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) + pub const fn ip(&self) -> net::Ipv4Addr { + let bytes = self.0.sin_addr.s_addr.to_ne_bytes(); + let (a, b, c, d) = (bytes[0], bytes[1], bytes[2], bytes[3]); + Ipv4Addr::new(a, b, c, d) } /// 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", + bsd, + target_os = "aix", target_os = "haiku", - target_os = "openbsd" + target_os = "hurd" ))] sin_len: Self::size() as u8, sin_family: AddressFamily::Inet as sa_family_t, @@ -1332,10 +863,10 @@ impl SockaddrLike for SockaddrIn { return None; } } - if (*addr).sa_family as i32 != libc::AF_INET { + if unsafe { (*addr).sa_family as i32 != libc::AF_INET } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) })) } } @@ -1368,14 +899,10 @@ impl From for SockaddrIn { fn from(addr: net::SocketAddrV4) -> Self { Self(libc::sockaddr_in { #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", + bsd, target_os = "haiku", target_os = "hermit", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" + target_os = "hurd" ))] sin_len: mem::size_of::() as u8, sin_family: AddressFamily::Inet as sa_family_t, @@ -1396,6 +923,26 @@ impl From for net::SocketAddrV4 { } } +#[cfg(feature = "net")] +impl From for net::SocketAddr { + fn from(addr: SockaddrIn) -> Self { + net::SocketAddr::from(net::SocketAddrV4::from(addr)) + } +} + +#[cfg(feature = "net")] +impl From for libc::sockaddr_in { + fn from(sin: SockaddrIn) -> libc::sockaddr_in { + sin.0 + } +} +#[cfg(feature = "net")] +impl From for SockaddrIn { + fn from(sin: libc::sockaddr_in) -> SockaddrIn { + SockaddrIn(sin) + } +} + #[cfg(feature = "net")] impl std::str::FromStr for SockaddrIn { type Err = net::AddrParseError; @@ -1419,8 +966,19 @@ impl SockaddrIn6 { } /// Returns the IP address associated with this socket address. - pub fn ip(&self) -> net::Ipv6Addr { - net::Ipv6Addr::from(self.0.sin6_addr.s6_addr) + pub const fn ip(&self) -> net::Ipv6Addr { + let bytes = self.0.sin6_addr.s6_addr; + let (a, b, c, d, e, f, g, h) = ( + ((bytes[0] as u16) << 8) | bytes[1] as u16, + ((bytes[2] as u16) << 8) | bytes[3] as u16, + ((bytes[4] as u16) << 8) | bytes[5] as u16, + ((bytes[6] as u16) << 8) | bytes[7] as u16, + ((bytes[8] as u16) << 8) | bytes[9] as u16, + ((bytes[10] as u16) << 8) | bytes[11] as u16, + ((bytes[12] as u16) << 8) | bytes[13] as u16, + ((bytes[14] as u16) << 8) | bytes[15] as u16, + ); + Ipv6Addr::new(a, b, c, d, e, f, g, h) } /// Returns the port number associated with this socket address, in native @@ -1435,6 +993,20 @@ impl SockaddrIn6 { } } +#[cfg(feature = "net")] +impl From for libc::sockaddr_in6 { + fn from(sin6: SockaddrIn6) -> libc::sockaddr_in6 { + sin6.0 + } +} + +#[cfg(feature = "net")] +impl From for SockaddrIn6 { + fn from(sin6: libc::sockaddr_in6) -> SockaddrIn6 { + SockaddrIn6(sin6) + } +} + #[cfg(feature = "net")] impl private::SockaddrLikePriv for SockaddrIn6 {} #[cfg(feature = "net")] @@ -1451,10 +1023,10 @@ impl SockaddrLike for SockaddrIn6 { return None; } } - if (*addr).sa_family as i32 != libc::AF_INET6 { + if unsafe { (*addr).sa_family as i32 != libc::AF_INET6 } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) })) } } @@ -1486,14 +1058,10 @@ impl From for SockaddrIn6 { #[allow(clippy::needless_update)] // It isn't needless on Illumos Self(libc::sockaddr_in6 { #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", + bsd, target_os = "haiku", target_os = "hermit", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" + target_os = "hurd" ))] sin6_len: mem::size_of::() as u8, sin6_family: AddressFamily::Inet6 as sa_family_t, @@ -1518,6 +1086,13 @@ impl From for net::SocketAddrV6 { } } +#[cfg(feature = "net")] +impl From for net::SocketAddr { + fn from(addr: SockaddrIn6) -> Self { + net::SocketAddr::from(net::SocketAddrV6::from(addr)) + } +} + #[cfg(feature = "net")] impl std::str::FromStr for SockaddrIn6 { type Err = net::AddrParseError; @@ -1538,28 +1113,28 @@ impl std::str::FromStr for SockaddrIn6 { /// ``` /// # 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, &localhost).expect("bind"); -/// let ss: SockaddrStorage = getsockname(fd).expect("getsockname"); +/// 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())))] + #[cfg(linux_android)] alg: AlgAddr, - #[cfg(feature = "net")] + #[cfg(all( + feature = "net", + not(any(target_os = "hurd", target_os = "redox", target_os = "cygwin")) + ))] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] dl: LinkAddr, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] nl: NetlinkAddr, - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] + #[cfg(all(feature = "ioctl", apple_targets))] #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] sctl: SysControlAddr, #[cfg(feature = "net")] @@ -1568,8 +1143,7 @@ pub union SockaddrStorage { sin6: SockaddrIn6, ss: libc::sockaddr_storage, su: UnixAddr, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, apple_targets))] vsock: VsockAddr, } impl private::SockaddrLikePriv for SockaddrStorage {} @@ -1591,19 +1165,23 @@ impl SockaddrLike for SockaddrStorage { { None } else { - let mut ss: libc::sockaddr_storage = mem::zeroed(); + let mut ss: libc::sockaddr_storage = unsafe { mem::zeroed() }; let ssp = &mut ss as *mut libc::sockaddr_storage as *mut u8; - ptr::copy(addr as *const u8, ssp, len as usize); + unsafe { ptr::copy(addr as *const u8, ssp, len as usize) }; #[cfg(any( - target_os = "android", + linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "cygwin", ))] 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; + unsafe { + (*(&mut ss as *mut libc::sockaddr_storage + as *mut UnixAddr)) + .sun_len = len as u8; + } } Some(Self { ss }) } @@ -1611,82 +1189,62 @@ impl SockaddrLike for SockaddrStorage { // 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 => { + match unsafe { (*addr).sa_family as i32 } { + #[cfg(linux_android)] + libc::AF_ALG => unsafe { AlgAddr::from_raw(addr, l).map(|alg| Self { alg }) - } + }, #[cfg(feature = "net")] - libc::AF_INET => { + libc::AF_INET => unsafe { SockaddrIn::from_raw(addr, l).map(|sin| Self { sin }) - } + }, #[cfg(feature = "net")] - libc::AF_INET6 => { + libc::AF_INET6 => unsafe { 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(any(bsd, solarish, target_os = "haiku"))] #[cfg(feature = "net")] - libc::AF_LINK => { + libc::AF_LINK => unsafe { LinkAddr::from_raw(addr, l).map(|dl| Self { dl }) - } - #[cfg(any(target_os = "android", target_os = "linux"))] - libc::AF_NETLINK => { + }, + #[cfg(linux_android)] + libc::AF_NETLINK => unsafe { NetlinkAddr::from_raw(addr, l).map(|nl| Self { nl }) - } - #[cfg(any( - target_os = "android", - target_os = "fuchsia", - target_os = "linux" - ))] + }, + #[cfg(any(linux_android, target_os = "fuchsia"))] #[cfg(feature = "net")] - libc::AF_PACKET => { + libc::AF_PACKET => unsafe { LinkAddr::from_raw(addr, l).map(|dl| Self { dl }) - } - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] - libc::AF_SYSTEM => { + }, + #[cfg(all(feature = "ioctl", apple_targets))] + libc::AF_SYSTEM => unsafe { SysControlAddr::from_raw(addr, l).map(|sctl| Self { sctl }) - } - #[cfg(any(target_os = "android", target_os = "linux"))] - libc::AF_VSOCK => { + }, + #[cfg(any(linux_android, apple_targets))] + libc::AF_VSOCK => unsafe { VsockAddr::from_raw(addr, l).map(|vsock| Self { vsock }) - } + }, _ => None, } } } - #[cfg(any( - target_os = "android", - target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" - ))] + #[cfg(any(linux_android, target_os = "fuchsia", solarish, target_os = "cygwin"))] 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 + None => mem::size_of_val(self) as libc::socklen_t, } } - unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> { + 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) - }, + Some(addr) => unsafe { addr.set_length(new_length) }, None => Err(SocketAddressLengthNotDynamic), } } @@ -1730,10 +1288,10 @@ impl SockaddrStorage { /// Downcast to an immutable `[UnixAddr]` reference. pub fn as_unix_addr(&self) -> Option<&UnixAddr> { cfg_if! { - if #[cfg(any(target_os = "android", + if #[cfg(any(linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "cygwin", ))] { let p = unsafe{ &self.ss as *const libc::sockaddr_storage }; @@ -1747,22 +1305,23 @@ impl SockaddrStorage { } } // Sanity checks - if self.family() != Some(AddressFamily::Unix) || - len < offset_of!(libc::sockaddr_un, sun_path) || - len > mem::size_of::() { + if self.family() != Some(AddressFamily::Unix) + || len < offset_of!(libc::sockaddr_un, sun_path) + || len > mem::size_of::() + { None } else { - Some(unsafe{&self.su}) + Some(unsafe { &self.su }) } } /// Downcast to a mutable `[UnixAddr]` reference. pub fn as_unix_addr_mut(&mut self) -> Option<&mut UnixAddr> { cfg_if! { - if #[cfg(any(target_os = "android", + if #[cfg(any(linux_android, target_os = "fuchsia", - target_os = "illumos", - target_os = "linux" + solarish, + target_os = "cygwin", ))] { let p = unsafe{ &self.ss as *const libc::sockaddr_storage }; @@ -1776,38 +1335,27 @@ impl SockaddrStorage { } } // Sanity checks - if self.family() != Some(AddressFamily::Unix) || - len < offset_of!(libc::sockaddr_un, sun_path) || - len > mem::size_of::() { + 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}) + Some(unsafe { &mut self.su }) } } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] 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(any(linux_android, target_os = "fuchsia"))] #[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(any(bsd, solarish))] #[cfg(feature = "net")] accessors! { as_link_addr, as_link_addr_mut, LinkAddr, @@ -1823,17 +1371,16 @@ impl SockaddrStorage { as_sockaddr_in6, as_sockaddr_in6_mut, SockaddrIn6, AddressFamily::Inet6, libc::sockaddr_in6, sin6} - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] 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(all(feature = "ioctl", apple_targets))] #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] accessors! {as_sys_control_addr, as_sys_control_addr_mut, SysControlAddr, AddressFamily::System, libc::sockaddr_ctl, sctl} - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, apple_targets))] accessors! {as_vsock_addr, as_vsock_addr_mut, VsockAddr, AddressFamily::Vsock, libc::sockaddr_vm, vsock} } @@ -1852,37 +1399,25 @@ 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"))] + #[cfg(linux_android)] 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(any(bsd, solarish))] #[cfg(feature = "net")] libc::AF_LINK => self.dl.fmt(f), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] libc::AF_NETLINK => self.nl.fmt(f), - #[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "fuchsia" - ))] + #[cfg(any(linux_android, target_os = "fuchsia"))] #[cfg(feature = "net")] libc::AF_PACKET => self.dl.fmt(f), - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] #[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"))] + #[cfg(any(linux_android, apple_targets))] libc::AF_VSOCK => self.vsock.fmt(f), _ => "

    ".fmt(f), } @@ -1926,37 +1461,25 @@ 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"))] + #[cfg(linux_android)] 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(any(bsd, solarish))] #[cfg(feature = "net")] libc::AF_LINK => self.dl.hash(s), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] libc::AF_NETLINK => self.nl.hash(s), - #[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "fuchsia" - ))] + #[cfg(any(linux_android, target_os = "fuchsia"))] #[cfg(feature = "net")] libc::AF_PACKET => self.dl.hash(s), - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] #[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"))] + #[cfg(any(linux_android, apple_targets))] libc::AF_VSOCK => self.vsock.hash(s), _ => self.ss.hash(s), } @@ -1968,37 +1491,25 @@ 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"))] + #[cfg(linux_android)] (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(any(bsd, solarish))] #[cfg(feature = "net")] (libc::AF_LINK, libc::AF_LINK) => self.dl == other.dl, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] (libc::AF_NETLINK, libc::AF_NETLINK) => self.nl == other.nl, - #[cfg(any( - target_os = "android", - target_os = "fuchsia", - target_os = "linux" - ))] + #[cfg(any(linux_android, target_os = "fuchsia"))] #[cfg(feature = "net")] (libc::AF_PACKET, libc::AF_PACKET) => self.dl == other.dl, - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] #[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"))] + #[cfg(any(linux_android, apple_targets))] (libc::AF_VSOCK, libc::AF_VSOCK) => self.vsock == other.vsock, _ => false, } @@ -2022,360 +1533,7 @@ pub(super) mod private { } } -/// Represents a socket address -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[deprecated( - since = "0.24.0", - note = "use SockaddrLike or SockaddrStorage instead" -)] -#[allow(missing_docs)] // Since they're all deprecated anyway -#[allow(deprecated)] -#[non_exhaustive] -pub enum SockAddr { - #[cfg(feature = "net")] - #[cfg_attr(docsrs, doc(cfg(feature = "net")))] - Inet(InetAddr), - Unix(UnixAddr), - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - Netlink(NetlinkAddr), - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - Alg(AlgAddr), - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] - #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] - SysControl(SysControlAddr), - /// Datalink address (MAC) - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "illumos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg(feature = "net")] - #[cfg_attr(docsrs, doc(cfg(feature = "net")))] - Link(LinkAddr), - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - Vsock(VsockAddr), -} - -#[allow(missing_docs)] // Since they're all deprecated anyway -#[allow(deprecated)] -impl SockAddr { - feature! { - #![feature = "net"] - pub fn new_inet(addr: InetAddr) -> SockAddr { - SockAddr::Inet(addr) - } - } - - pub fn new_unix(path: &P) -> Result { - Ok(SockAddr::Unix(UnixAddr::new(path)?)) - } - - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new_netlink(pid: u32, groups: u32) -> SockAddr { - SockAddr::Netlink(NetlinkAddr::new(pid, groups)) - } - - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new_alg(alg_type: &str, alg_name: &str) -> SockAddr { - SockAddr::Alg(AlgAddr::new(alg_type, alg_name)) - } - - feature! { - #![feature = "ioctl"] - #[cfg(any(target_os = "ios", target_os = "macos"))] - pub fn new_sys_control(sockfd: RawFd, name: &str, unit: u32) -> Result { - SysControlAddr::from_name(sockfd, name, unit).map(SockAddr::SysControl) - } - } - - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - pub fn new_vsock(cid: u32, port: u32) -> SockAddr { - SockAddr::Vsock(VsockAddr::new(cid, port)) - } - - pub fn family(&self) -> AddressFamily { - match *self { - #[cfg(feature = "net")] - SockAddr::Inet(InetAddr::V4(..)) => AddressFamily::Inet, - #[cfg(feature = "net")] - SockAddr::Inet(InetAddr::V6(..)) => AddressFamily::Inet6, - SockAddr::Unix(..) => AddressFamily::Unix, - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Netlink(..) => AddressFamily::Netlink, - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Alg(..) => AddressFamily::Alg, - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] - SockAddr::SysControl(..) => AddressFamily::System, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg(feature = "net")] - SockAddr::Link(..) => AddressFamily::Packet, - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd" - ))] - #[cfg(feature = "net")] - SockAddr::Link(..) => AddressFamily::Link, - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Vsock(..) => AddressFamily::Vsock, - } - } - - #[deprecated(since = "0.23.0", note = "use .to_string() instead")] - pub fn to_str(&self) -> String { - format!("{}", self) - } - - /// Creates a `SockAddr` struct from libc's sockaddr. - /// - /// Supports only the following address families: Unix, Inet (v4 & v6), Netlink and System. - /// Returns None for unsupported families. - /// - /// # Safety - /// - /// unsafe because it takes a raw pointer as argument. The caller must - /// ensure that the pointer is valid. - #[cfg(not(target_os = "fuchsia"))] - #[cfg(feature = "net")] - pub(crate) unsafe fn from_libc_sockaddr( - addr: *const libc::sockaddr, - ) -> Option { - if addr.is_null() { - None - } else { - match AddressFamily::from_i32(i32::from((*addr).sa_family)) { - Some(AddressFamily::Unix) => None, - #[cfg(feature = "net")] - Some(AddressFamily::Inet) => Some(SockAddr::Inet( - InetAddr::V4(ptr::read_unaligned(addr as *const _)), - )), - #[cfg(feature = "net")] - Some(AddressFamily::Inet6) => Some(SockAddr::Inet( - InetAddr::V6(ptr::read_unaligned(addr as *const _)), - )), - #[cfg(any(target_os = "android", target_os = "linux"))] - Some(AddressFamily::Netlink) => Some(SockAddr::Netlink( - NetlinkAddr(ptr::read_unaligned(addr as *const _)), - )), - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] - Some(AddressFamily::System) => Some(SockAddr::SysControl( - SysControlAddr(ptr::read_unaligned(addr as *const _)), - )), - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg(feature = "net")] - Some(AddressFamily::Packet) => Some(SockAddr::Link(LinkAddr( - ptr::read_unaligned(addr as *const _), - ))), - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd" - ))] - #[cfg(feature = "net")] - Some(AddressFamily::Link) => { - let ether_addr = - LinkAddr(ptr::read_unaligned(addr as *const _)); - if ether_addr.is_empty() { - None - } else { - Some(SockAddr::Link(ether_addr)) - } - } - #[cfg(any(target_os = "android", target_os = "linux"))] - Some(AddressFamily::Vsock) => Some(SockAddr::Vsock(VsockAddr( - ptr::read_unaligned(addr as *const _), - ))), - // Other address families are currently not supported and simply yield a None - // entry instead of a proper conversion to a `SockAddr`. - Some(_) | None => None, - } - } - } - - /// Conversion from nix's SockAddr type to the underlying libc sockaddr type. - /// - /// This is useful for interfacing with other libc functions that don't yet have nix wrappers. - /// Returns a reference to the underlying data type (as a sockaddr reference) along - /// with the size of the actual data type. sockaddr is commonly used as a proxy for - /// a superclass as C doesn't support inheritance, so many functions that take - /// a sockaddr * need to take the size of the underlying type as well and then internally cast it back. - pub fn as_ffi_pair(&self) -> (&libc::sockaddr, libc::socklen_t) { - match *self { - #[cfg(feature = "net")] - SockAddr::Inet(InetAddr::V4(ref addr)) => ( - // This cast is always allowed in C - unsafe { - &*(addr as *const libc::sockaddr_in - as *const libc::sockaddr) - }, - mem::size_of_val(addr) as libc::socklen_t, - ), - #[cfg(feature = "net")] - SockAddr::Inet(InetAddr::V6(ref addr)) => ( - // This cast is always allowed in C - unsafe { - &*(addr as *const libc::sockaddr_in6 - as *const libc::sockaddr) - }, - mem::size_of_val(addr) as libc::socklen_t, - ), - SockAddr::Unix(ref unix_addr) => ( - // This cast is always allowed in C - unsafe { - &*(&unix_addr.sun as *const libc::sockaddr_un - as *const libc::sockaddr) - }, - unix_addr.sun_len() as libc::socklen_t, - ), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Netlink(NetlinkAddr(ref sa)) => ( - // This cast is always allowed in C - unsafe { - &*(sa as *const libc::sockaddr_nl as *const libc::sockaddr) - }, - mem::size_of_val(sa) as libc::socklen_t, - ), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Alg(AlgAddr(ref sa)) => ( - // This cast is always allowed in C - unsafe { - &*(sa as *const libc::sockaddr_alg as *const libc::sockaddr) - }, - mem::size_of_val(sa) as libc::socklen_t, - ), - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] - SockAddr::SysControl(SysControlAddr(ref sa)) => ( - // This cast is always allowed in C - unsafe { - &*(sa as *const libc::sockaddr_ctl as *const libc::sockaddr) - }, - mem::size_of_val(sa) as libc::socklen_t, - ), - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg(feature = "net")] - SockAddr::Link(LinkAddr(ref addr)) => ( - // This cast is always allowed in C - unsafe { - &*(addr as *const libc::sockaddr_ll - as *const libc::sockaddr) - }, - mem::size_of_val(addr) as libc::socklen_t, - ), - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "illumos", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[cfg(feature = "net")] - SockAddr::Link(LinkAddr(ref addr)) => ( - // This cast is always allowed in C - unsafe { - &*(addr as *const libc::sockaddr_dl - as *const libc::sockaddr) - }, - mem::size_of_val(addr) as libc::socklen_t, - ), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Vsock(VsockAddr(ref sa)) => ( - // This cast is always allowed in C - unsafe { - &*(sa as *const libc::sockaddr_vm as *const libc::sockaddr) - }, - mem::size_of_val(sa) as libc::socklen_t, - ), - } - } -} - -#[allow(deprecated)] -impl fmt::Display for SockAddr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - #[cfg(feature = "net")] - SockAddr::Inet(ref inet) => inet.fmt(f), - SockAddr::Unix(ref unix) => unix.fmt(f), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Netlink(ref nl) => nl.fmt(f), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Alg(ref nl) => nl.fmt(f), - #[cfg(all( - feature = "ioctl", - any(target_os = "ios", target_os = "macos") - ))] - SockAddr::SysControl(ref sc) => sc.fmt(f), - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd" - ))] - #[cfg(feature = "net")] - SockAddr::Link(ref ether_addr) => ether_addr.fmt(f), - #[cfg(any(target_os = "android", target_os = "linux"))] - SockAddr::Vsock(ref svm) => svm.fmt(f), - } - } -} - -#[cfg(not(target_os = "fuchsia"))] -#[cfg(feature = "net")] -#[allow(deprecated)] -impl private::SockaddrLikePriv for SockAddr {} -#[cfg(not(target_os = "fuchsia"))] -#[cfg(feature = "net")] -#[allow(deprecated)] -impl SockaddrLike for SockAddr { - unsafe fn from_raw( - addr: *const libc::sockaddr, - _len: Option, - ) -> Option { - Self::from_libc_sockaddr(addr) - } -} - -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(linux_android)] pub mod netlink { use super::*; use crate::sys::socket::addr::AddressFamily; @@ -2428,10 +1586,10 @@ pub mod netlink { return None; } } - if (*addr).sa_family as i32 != libc::AF_NETLINK { + if unsafe { (*addr).sa_family as i32 != libc::AF_NETLINK } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) })) } } @@ -2448,11 +1606,10 @@ pub mod netlink { } } -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(linux_android)] pub mod alg { use super::*; - use libc::{c_char, sockaddr_alg, AF_ALG}; + use libc::{sockaddr_alg, AF_ALG}; use std::ffi::CStr; use std::hash::{Hash, Hasher}; use std::{fmt, mem, str}; @@ -2477,10 +1634,10 @@ pub mod alg { return None; } } - if (*addr).sa_family as i32 != libc::AF_ALG { + if unsafe { (*addr).sa_family as i32 != libc::AF_ALG } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) })) } } @@ -2541,16 +1698,12 @@ pub mod alg { /// 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().cast()) } } /// 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().cast()) } } } @@ -2574,7 +1727,7 @@ pub mod alg { feature! { #![feature = "ioctl"] -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(apple_targets)] pub mod sys_control { use crate::sys::socket::addr::AddressFamily; use libc::{self, c_uchar}; @@ -2617,10 +1770,10 @@ pub mod sys_control { return None; } } - if (*addr).sa_family as i32 != libc::AF_SYSTEM { + if unsafe { (*addr).sa_family as i32 != libc::AF_SYSTEM } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) } )) } } @@ -2681,8 +1834,7 @@ pub mod sys_control { } } -#[cfg(any(target_os = "android", target_os = "linux", target_os = "fuchsia"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(any(linux_android, target_os = "fuchsia"))] mod datalink { feature! { #![feature = "net"] @@ -2759,10 +1911,10 @@ mod datalink { return None; } } - if (*addr).sa_family as i32 != libc::AF_PACKET { + if unsafe { (*addr).sa_family as i32 != libc::AF_PACKET } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) })) } } @@ -2775,17 +1927,7 @@ mod datalink { } } -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "illumos", - target_os = "netbsd", - target_os = "haiku", - target_os = "openbsd" -))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(any(bsd, solarish, target_os = "haiku", target_os = "aix"))] mod datalink { feature! { #![feature = "net"] @@ -2883,10 +2025,10 @@ mod datalink { return None; } } - if (*addr).sa_family as i32 != libc::AF_LINK { + if unsafe { (*addr).sa_family as i32 != libc::AF_LINK } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + Some(Self(unsafe { ptr::read_unaligned(addr as *const _) })) } } @@ -2898,8 +2040,7 @@ mod datalink { } } -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(any(linux_android, apple_targets))] pub mod vsock { use super::*; use crate::sys::socket::addr::AddressFamily; @@ -2930,10 +2071,10 @@ pub mod vsock { return None; } } - if (*addr).sa_family as i32 != libc::AF_VSOCK { + if unsafe { (*addr).sa_family as i32 != libc::AF_VSOCK } { return None; } - Some(Self(ptr::read_unaligned(addr as *const _))) + unsafe { Some(Self(ptr::read_unaligned(addr as *const _))) } } } @@ -2944,20 +2085,48 @@ pub mod vsock { } impl PartialEq for VsockAddr { + #[cfg(linux_android)] 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) } + #[cfg(apple_targets)] + 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(linux_android)] fn hash(&self, s: &mut H) { let inner = self.0; (inner.svm_family, inner.svm_cid, inner.svm_port).hash(s); } + #[cfg(apple_targets)] + 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 @@ -2972,6 +2141,10 @@ pub mod vsock { addr.svm_cid = cid; addr.svm_port = port; + #[cfg(apple_targets)] + { + addr.svm_len = std::mem::size_of::() as u8; + } VsockAddr(addr) } @@ -3024,26 +2197,15 @@ mod tests { } } + #[cfg(not(any(target_os = "hurd", target_os = "redox", target_os = "cygwin")))] + #[allow(clippy::cast_ptr_alignment)] mod link { - #![allow(clippy::cast_ptr_alignment)] - - #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "illumos" - ))] + #[cfg(any(apple_targets, solarish))] 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" - ))] + #[cfg(bsd)] #[test] fn test_datalink_display() { use super::super::LinkAddr; @@ -3059,15 +2221,11 @@ mod tests { sdl_slen: 0, ..unsafe { mem::zeroed() } }); - format!("{}", la); + let _ = format!("{la}"); } #[cfg(all( - any( - target_os = "android", - target_os = "fuchsia", - target_os = "linux" - ), + any(linux_android, target_os = "fuchsia"), target_endian = "little" ))] #[test] @@ -3078,7 +2236,7 @@ mod tests { 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 sa = bytes.0.as_ptr().cast(); let len = None; let sock_addr = unsafe { SockaddrStorage::from_raw(sa, len) }.unwrap(); @@ -3089,12 +2247,12 @@ mod tests { } } - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] #[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 sa = bytes.as_ptr().cast(); let len = Some(bytes.len() as socklen_t); let sock_addr = unsafe { SockaddrStorage::from_raw(sa, len) }.unwrap(); @@ -3107,7 +2265,7 @@ mod tests { } } - #[cfg(any(target_os = "ios", target_os = "macos"))] + #[cfg(apple_targets)] #[test] fn macos_tap() { let bytes = [ @@ -3129,9 +2287,9 @@ mod tests { } } - #[cfg(target_os = "illumos")] + #[cfg(solarish)] #[test] - fn illumos_tap() { + fn solarish_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; @@ -3152,22 +2310,9 @@ mod tests { #[test] fn size() { - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd", - target_os = "haiku" - ))] + #[cfg(any(bsd, target_os = "aix", solarish, target_os = "haiku"))] let l = mem::size_of::(); - #[cfg(any( - target_os = "android", - target_os = "fuchsia", - target_os = "linux" - ))] + #[cfg(any(linux_android, target_os = "fuchsia"))] let l = mem::size_of::(); assert_eq!(LinkAddr::size() as usize, l); } @@ -3181,7 +2326,7 @@ mod tests { fn display() { let s = "127.0.0.1:8080"; let addr = SockaddrIn::from_str(s).unwrap(); - assert_eq!(s, format!("{}", addr)); + assert_eq!(s, format!("{addr}")); } #[test] @@ -3191,6 +2336,13 @@ mod tests { SockaddrIn::size() as usize ); } + + #[test] + fn ip() { + let s = "127.0.0.1:8082"; + let ip = SockaddrIn::from_str(s).unwrap().ip(); + assert_eq!("127.0.0.1", format!("{ip}")); + } } mod sockaddr_in6 { @@ -3201,7 +2353,7 @@ mod tests { fn display() { let s = "[1234:5678:90ab:cdef::1111:2222]:8080"; let addr = SockaddrIn6::from_str(s).unwrap(); - assert_eq!(s, format!("{}", addr)); + assert_eq!(s, format!("{addr}")); } #[test] @@ -3212,6 +2364,13 @@ mod tests { ); } + #[test] + fn ip() { + let s = "[1234:5678:90ab:cdef::1111:2222]:8080"; + let ip = SockaddrIn6::from_str(s).unwrap().ip(); + assert_eq!("1234:5678:90ab:cdef::1111:2222", format!("{ip}")); + } + #[test] // Ensure that we can convert to-and-from std::net variants without change. fn to_and_from() { @@ -3220,7 +2379,7 @@ mod tests { nix_sin6.0.sin6_flowinfo = 0x12345678; nix_sin6.0.sin6_scope_id = 0x9abcdef0; - let std_sin6 : std::net::SocketAddrV6 = nix_sin6.into(); + let std_sin6: std::net::SocketAddrV6 = nix_sin6.into(); assert_eq!(nix_sin6, std_sin6.into()); } } @@ -3231,33 +2390,30 @@ mod tests { #[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(); + let ptr = ua.as_ptr().cast(); + 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"))] + #[cfg(linux_android)] #[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(); + let ptr = ua.as_ptr().cast(); + 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"))] + #[cfg(linux_android)] #[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(); + let ptr = ua.as_ptr().cast(); + let ss = unsafe { SockaddrStorage::from_raw(ptr, Some(ua.len())) } + .unwrap(); assert_eq!(ss.len(), ua.len()); } } @@ -3265,14 +2421,14 @@ mod tests { mod unixaddr { use super::*; - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] #[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()] }; + 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, diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 73fe0620..1b38646e 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -1,23 +1,26 @@ //! Socket interface functions //! //! [Further reading](https://man7.org/linux/man-pages/man7/socket.7.html) -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "freebsd", linux_android))] #[cfg(feature = "uio")] use crate::sys::time::TimeSpec; +#[cfg(not(target_os = "redox"))] #[cfg(feature = "uio")] use crate::sys::time::TimeVal; use crate::{errno::Errno, Result}; use cfg_if::cfg_if; +use libc::{self, c_int, size_t, socklen_t}; +#[cfg(all(feature = "uio", not(target_os = "redox")))] use libc::{ - self, c_int, c_void, iovec, size_t, socklen_t, CMSG_DATA, CMSG_FIRSTHDR, - CMSG_LEN, CMSG_NXTHDR, + c_void, iovec, CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_NXTHDR, CMSG_SPACE, + MSG_CTRUNC, }; -use std::convert::{TryFrom, TryInto}; +#[cfg(not(target_os = "redox"))] use std::io::{IoSlice, IoSliceMut}; #[cfg(feature = "net")] use std::net; -use std::os::unix::io::RawFd; -use std::{mem, ptr, slice}; +use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::{mem, ptr}; #[deny(missing_docs)] mod addr; @@ -32,53 +35,45 @@ pub mod sockopt; pub use self::addr::{SockaddrLike, SockaddrStorage}; -#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] -#[allow(deprecated)] -pub use self::addr::{AddressFamily, SockAddr, UnixAddr}; -#[cfg(any(target_os = "illumos", target_os = "solaris"))] -#[allow(deprecated)] -pub use self::addr::{AddressFamily, SockAddr, UnixAddr}; -#[allow(deprecated)] +#[cfg(solarish)] +pub use self::addr::{AddressFamily, UnixAddr}; +#[cfg(not(solarish))] +pub use self::addr::{AddressFamily, UnixAddr}; #[cfg(not(any( - target_os = "illumos", - target_os = "solaris", - target_os = "haiku" + solarish, + target_os = "haiku", + target_os = "hurd", + target_os = "redox", + target_os = "cygwin", )))] #[cfg(feature = "net")] -pub use self::addr::{ - InetAddr, IpAddr, Ipv4Addr, Ipv6Addr, LinkAddr, SockaddrIn, SockaddrIn6, -}; -#[allow(deprecated)] +pub use self::addr::{LinkAddr, SockaddrIn, SockaddrIn6}; #[cfg(any( - target_os = "illumos", - target_os = "solaris", - target_os = "haiku" + solarish, + target_os = "haiku", + target_os = "hurd", + target_os = "redox", + target_os = "cygwin", ))] #[cfg(feature = "net")] -pub use self::addr::{ - InetAddr, IpAddr, Ipv4Addr, Ipv6Addr, SockaddrIn, SockaddrIn6, -}; +pub use self::addr::{SockaddrIn, SockaddrIn6}; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] pub use crate::sys::socket::addr::alg::AlgAddr; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] pub use crate::sys::socket::addr::netlink::NetlinkAddr; -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(apple_targets)] #[cfg(feature = "ioctl")] pub use crate::sys::socket::addr::sys_control::SysControlAddr; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(any(linux_android, apple_targets))] pub use crate::sys::socket::addr::vsock::VsockAddr; -#[cfg(feature = "uio")] +#[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}; @@ -101,10 +96,11 @@ 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")))] + #[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 @@ -118,10 +114,11 @@ impl TryFrom for SockType { 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")))] + #[cfg(not(any(target_os = "haiku", target_os = "redox")))] libc::SOCK_RDM => Ok(Self::Rdm), - _ => Err(Errno::EINVAL) + _ => Err(Errno::EINVAL), } } } @@ -138,116 +135,156 @@ pub enum SockProtocol { 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())))] + #[cfg(apple_targets)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] NetlinkSockDiag = libc::NETLINK_SOCK_DIAG, + /// Netfilter/iptables ULOG. + /// ([ref](https://www.man7.org/linux/man-pages/man7/netlink.7.html)) + #[cfg(linux_android)] + 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] 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(linux_android)] + 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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] EthAll = (libc::ETH_P_ALL as u16).to_be() as i32, + #[cfg(linux_android)] + /// Packet filter on loopback traffic + EthLoop = (libc::ETH_P_LOOP as u16).to_be() as i32, + /// Packet filter on IPv4 traffic + #[cfg(linux_android)] + #[cfg(target_endian = "big")] + EthIp = libc::ETH_P_IP, + /// Packet filter on IPv6 traffic + #[cfg(linux_android)] + EthIpv6 = (libc::ETH_P_IPV6 as u16).to_be() as i32, + /// ICMP protocol ([icmp(7)](https://man7.org/linux/man-pages/man7/icmp.7.html)) + Icmp = libc::IPPROTO_ICMP, + /// ICMPv6 protocol (ICMP over IPv6) + IcmpV6 = libc::IPPROTO_ICMPV6, + /// SCTP ([sctp(7)](https://man7.org/linux/man-pages/man7/sctp.7.html)) + #[cfg(any( + apple_targets, + linux_android, + target_os = "freebsd", + target_os = "netbsd" + ))] + Sctp = libc::IPPROTO_SCTP, } -#[cfg(target_os = "linux")] +impl SockProtocol { + /// The Controller Area Network raw socket protocol + /// ([ref](https://docs.kernel.org/networking/can.html#how-to-use-socketcan)) + #[cfg(target_os = "linux")] + #[allow(non_upper_case_globals)] + pub const CanRaw: SockProtocol = SockProtocol::Icmp; // Matches libc::CAN_RAW + + /// The Controller Area Network broadcast manager protocol + /// ([ref](https://docs.kernel.org/networking/can.html#how-to-use-socketcan)) + #[cfg(target_os = "linux")] + #[allow(non_upper_case_globals)] + pub const CanBcm: SockProtocol = SockProtocol::NetlinkUserSock; // Matches libc::CAN_BCM + + /// 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(apple_targets)] + #[allow(non_upper_case_globals)] + pub const KextEvent: SockProtocol = SockProtocol::Icmp; // Matches libc::SYSPROTO_EVENT + + /// Packet filter on IPv4 traffic + // NOTE: placed here due to conflict (little endian arch) with SockProtocol::NetLinkISCI + #[cfg(linux_android)] + #[allow(non_upper_case_globals)] + #[cfg(target_endian = "little")] + pub const EthIp: SockProtocol = unsafe { std::mem::transmute::((libc::ETH_P_IP as u16).to_be() as i32) }; + +} +#[cfg(linux_android)] libc_bitflags! { /// Configuration flags for `SO_TIMESTAMPING` interface /// /// For use with [`Timestamping`][sockopt::Timestamping]. /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) - pub struct TimestampingFlag: c_uint { + 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 transmiting timestamps as reported by hardware + /// Collect transmitting timestamps as reported by hardware SOF_TIMESTAMPING_TX_HARDWARE; - /// Collect transmiting timestamps as reported by software + /// 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; } } @@ -255,33 +292,23 @@ libc_bitflags! { /// Additional socket options pub struct SockFlag: c_int { /// Set non-blocking mode on the new socket - #[cfg(any(target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, + freebsdlike, + netbsdlike, + solarish))] SOCK_NONBLOCK; /// Set close-on-exec on the new descriptor - #[cfg(any(target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, + freebsdlike, + netbsdlike, + solarish))] 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; } } @@ -292,17 +319,14 @@ libc_bitflags! { /// Sends or requests out-of-band data on sockets that support this notion /// (e.g., of type [`Stream`](enum.SockType.html)); the underlying protocol must also /// support out-of-band data. - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 MSG_OOB; /// Peeks at an incoming message. The data is treated as unread and the next /// [`recv()`](fn.recv.html) /// or similar function shall still return this data. - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 MSG_PEEK; /// Receive operation blocks until the full amount of data can be /// returned. The function may return smaller amount of data if a signal /// is caught, an error or disconnect occurs. - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 MSG_WAITALL; /// Enables nonblocking operation; if the operation would block, /// `EAGAIN` or `EWOULDBLOCK` is returned. This provides similar @@ -314,10 +338,9 @@ 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. - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + #[cfg(not(target_os = "aix"))] MSG_DONTWAIT; /// Receive flags: Control Data was discarded (buffer too small) - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 MSG_CTRUNC; /// For raw ([`Packet`](addr/enum.AddressFamily.html)), Internet datagram /// (since Linux 2.4.27/2.6.8), @@ -327,18 +350,14 @@ libc_bitflags! { /// domain ([unix(7)](https://linux.die.net/man/7/unix)) sockets. /// /// For use with Internet stream sockets, see [tcp(7)](https://linux.die.net/man/7/tcp). - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 MSG_TRUNC; /// Terminates a record (when this notion is supported, as for /// sockets of type [`SeqPacket`](enum.SockType.html)). - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 MSG_EOR; /// This flag specifies that queued errors should be received from /// the socket error queue. (For more details, see /// [recvfrom(2)](https://linux.die.net/man/2/recvfrom)) - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + #[cfg(linux_android)] 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 @@ -347,35 +366,51 @@ libc_bitflags! { /// [open(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html). /// /// Only used in [`recvmsg`](fn.recvmsg.html) function. - #[cfg(any(target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + #[cfg(any(linux_android, freebsdlike, netbsdlike))] 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", + #[cfg(any(linux_android, + freebsdlike, + solarish, + netbsdlike, target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - #[allow(deprecated)] // Suppress useless warnings from libc PR 2963 + target_os = "haiku"))] MSG_NOSIGNAL; + /// Turns on [`MSG_DONTWAIT`] after the first message has been received (only for + /// `recvmmsg()`). + #[cfg(any(linux_android, + netbsdlike, + target_os = "fuchsia", + target_os = "freebsd"))] + MSG_WAITFORONE; + /// Indicates that this message is not a user message but an SCTP notification. + #[cfg(target_os = "linux")] + MSG_NOTIFICATION; + } +} + +#[cfg(target_os = "freebsd")] +libc_enum! { + /// A selector for which clock to use when generating packet timestamps. + /// Used when setting [`TsClock`](crate::sys::socket::sockopt::TsClock) on a socket. + /// (For more details, see [setsockopt(2)](https://man.freebsd.org/cgi/man.cgi?setsockopt)). + #[repr(i32)] + #[non_exhaustive] + pub enum SocketTimestamp { + /// Microsecond resolution, realtime. This is the default. + SO_TS_REALTIME_MICRO, + /// Sub-nanosecond resolution, realtime. + SO_TS_BINTIME, + /// Nanosecond resolution, realtime. + SO_TS_REALTIME, + /// Nanosecond resolution, monotonic. + SO_TS_MONOTONIC, } } cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(linux_android)] { /// Unix credentials of the sending process. /// /// This struct is used with the `SO_PEERCRED` ancillary message @@ -430,7 +465,7 @@ cfg_if! { uc.0 } } - } else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] { + } else if #[cfg(freebsdlike)] { /// Unix credentials of the sending process. /// /// This struct is used with the `SCM_CREDS` ancillary message for UNIX sockets. @@ -462,8 +497,8 @@ 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, + std::slice::from_raw_parts( + self.0.cmcred_groups.as_ptr(), self.0.cmcred_ngroups as _ ) } @@ -479,12 +514,7 @@ cfg_if! { } cfg_if! { - if #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "ios" - ))] { + if #[cfg(any(freebsdlike, apple_targets))] { /// Return type of [`LocalPeerCred`](crate::sys::socket::sockopt::LocalPeerCred) #[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -510,6 +540,63 @@ cfg_if! { } } +cfg_if! { + if #[cfg(apple_targets)] { + use std::fmt; + + /// Return type of [`LocalPeerToken`]. + /// + /// The audit token is an opaque token which identifies Mach tasks and + /// senders of Mach messages as subjects to the BSM audit system. Only + /// the appropriate BSM library routines should be used to interpret + /// the contents of the audit token as the representation of the + /// subject identity within the token may change over time. + /// + /// Starting with macOS 11, almost all audit functions have been + /// deprecated (see the system header `bsm/libbsm.h`), do not use them + /// if your program target more recent versions of macOS. + /// + /// [`LocalPeerToken`]: crate::sys::socket::sockopt::LocalPeerToken + #[repr(C)] + #[derive(Default, Copy, Clone, PartialEq, Eq, Hash)] + pub struct audit_token_t { + /// Value of the token. + /// + /// This is considered an opaque value, do not rely on its format. + pub val: [libc::c_uint; 8], + } + + // Make the debug representation a hex string to make it shorter and clearer. + impl fmt::Debug for audit_token_t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("audit_token_t") + .field(&format!("0x{:08X}", self)) + .finish() + } + } + + impl fmt::LowerHex for audit_token_t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for v in self.val { + fmt::LowerHex::fmt(&v, f)?; + } + + Ok(()) + } + } + + impl fmt::UpperHex for audit_token_t { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for v in self.val { + fmt::UpperHex::fmt(&v, f)?; + } + + Ok(()) + } + } + } +} + feature! { #![feature = "net"] /// Request for multicast socket operations @@ -555,6 +642,7 @@ impl Ipv6MembershipRequest { } } +#[cfg(not(target_os = "redox"))] feature! { #![feature = "uio"] @@ -578,24 +666,23 @@ feature! { /// let _ = cmsg_space!(RawFd, TimeVal); /// # } /// ``` -// Unfortunately, CMSG_SPACE isn't a const_fn, or else we could return a -// stack-allocated array. #[macro_export] macro_rules! cmsg_space { ( $( $x:ty ),* ) => { { - let mut space = 0; - $( - // CMSG_SPACE is always safe - space += unsafe { - $crate::sys::socket::CMSG_SPACE(::std::mem::size_of::<$x>() as $crate::sys::socket::c_uint) - } as usize; - )* - Vec::::with_capacity(space) + let space = 0 $(+ $crate::sys::socket::cmsg_space::<$x>())*; + vec![0u8; space] } } } +#[inline] +#[doc(hidden)] +pub const 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)] /// Contains outcome of sending or receiving a message /// @@ -610,14 +697,20 @@ pub struct RecvMsg<'a, 's, S> { mhdr: msghdr, } -impl<'a, S> RecvMsg<'a, '_, S> { - /// Iterate over the valid control messages pointed to by this - /// msghdr. - pub fn cmsgs(&self) -> CmsgIterator { - CmsgIterator { +impl RecvMsg<'_, '_, S> { + /// Iterate over the valid control messages pointed to by this msghdr. If + /// allocated space for CMSGs was too small it is not safe to iterate, + /// instead return an `Error::ENOBUFS` error. + pub fn cmsgs(&self) -> Result { + + if self.mhdr.msg_flags & MSG_CTRUNC == MSG_CTRUNC { + return Err(Errno::ENOBUFS); + } + + Ok(CmsgIterator { cmsghdr: self.cmsghdr, mhdr: &self.mhdr - } + }) } } @@ -628,7 +721,7 @@ pub struct CmsgIterator<'a> { mhdr: &'a msghdr } -impl<'a> Iterator for CmsgIterator<'a> { +impl Iterator for CmsgIterator<'_> { type Item = ControlMessageOwned; fn next(&mut self) -> Option { @@ -651,7 +744,7 @@ impl<'a> Iterator for CmsgIterator<'a> { } /// A type-safe wrapper around a single control message, as used with -/// [`recvmsg`](#fn.recvmsg). +/// [`recvmsg`]. /// /// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) // Nix version 0.13.0 and earlier used ControlMessage for both recvmsg and @@ -666,12 +759,10 @@ pub enum ControlMessageOwned { /// Received version of [`ControlMessage::ScmRights`] ScmRights(Vec), /// Received version of [`ControlMessage::ScmCredentials`] - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] ScmCredentials(UnixCredentials), /// Received version of [`ControlMessage::ScmCreds`] - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] ScmCreds(UnixCredentials), /// A message of type `SCM_TIMESTAMP`, containing the time the /// packet was received by the kernel. @@ -688,6 +779,7 @@ pub enum ControlMessageOwned { /// # 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(); @@ -696,24 +788,24 @@ pub enum ControlMessageOwned { /// SockType::Datagram, /// SockFlag::empty(), /// None).unwrap(); - /// setsockopt(in_socket, sockopt::ReceiveTimestamp, &true).unwrap(); + /// setsockopt(&in_socket, sockopt::ReceiveTimestamp, &true).unwrap(); /// let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); - /// bind(in_socket, &localhost).unwrap(); - /// let address: SockaddrIn = getsockname(in_socket).unwrap(); + /// 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 = [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 mut iov = [IoSliceMut::new(&mut buffer)]; - /// let r = recvmsg::(in_socket, &mut iov, Some(&mut cmsgspace), flags) + /// let r = recvmsg::(in_socket.as_raw_fd(), &mut iov, Some(&mut cmsgspace), flags) /// .unwrap(); - /// let rtime = match r.cmsgs().next() { + /// let rtime = match r.cmsgs().unwrap().next() { /// Some(ControlMessageOwned::ScmTimestamp(rtime)) => rtime, /// Some(_) => panic!("Unexpected control message"), /// None => panic!("No control message") @@ -727,73 +819,91 @@ 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(all(target_os = "linux"))] + #[cfg(linux_android)] ScmTimestampsns(Timestamps), /// Nanoseconds resolution timestamp /// /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) - #[cfg(all(target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] ScmTimestampns(TimeSpec), - #[cfg(any( - target_os = "android", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - ))] + /// Realtime clock timestamp + /// + /// [Further reading](https://man.freebsd.org/cgi/man.cgi?setsockopt) + #[cfg(target_os = "freebsd")] + ScmRealtime(TimeSpec), + /// Monotonic clock timestamp + /// + /// [Further reading](https://man.freebsd.org/cgi/man.cgi?setsockopt) + #[cfg(target_os = "freebsd")] + ScmMonotonic(TimeSpec), + #[cfg(any(linux_android, apple_targets, target_os = "netbsd"))] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv4PacketInfo(libc::in_pktinfo), - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "openbsd", - target_os = "netbsd", - ))] + #[cfg(any(linux_android, bsd))] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv6PacketInfo(libc::in6_pktinfo), - #[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - ))] + #[cfg(bsd)] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv4RecvIf(libc::sockaddr_dl), - #[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - ))] + #[cfg(bsd)] #[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(any(linux_android, target_os = "freebsd"))] #[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(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv6OrigDstAddr(libc::sockaddr_in6), + /// Time-to-Live (TTL) header field of the incoming IPv4 packet. + /// + /// [Further reading](https://www.man7.org/linux/man-pages/man7/ip.7.html) + #[cfg(linux_android)] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(i32), + + /// Time-to-Live (TTL) header field of the incoming IPv4 packet. + /// + /// [Further reading](https://datatracker.ietf.org/doc/html/rfc3542.html) + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(u8), + + /// Hop Limit header field of the incoming IPv6 packet. + /// + /// [Further reading for Linux](https://www.man7.org/linux/man-pages/man7/ip.7.html) + /// [Further reading for FreeBSD](https://datatracker.ietf.org/doc/html/rfc3542.html) + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6HopLimit(i32), + + /// Retrieve the DSCP (ToS) header field of the incoming IPv4 packet. + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Tos(u8), + + /// Retrieve the DSCP (Traffic Class) header field of the incoming IPv6 packet. + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6TClass(i32), + /// UDP Generic Receive Offload (GRO) allows receiving multiple UDP /// packets from a single sender. /// Fixed-size payloads are following one by one in a receive buffer. @@ -805,7 +915,7 @@ pub enum ControlMessageOwned { #[cfg(target_os = "linux")] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] - UdpGroSegments(u16), + UdpGroSegments(i32), /// SO_RXQ_OVFL indicates that an unsigned 32 bit value /// ancilliary msg (cmsg) should be attached to recieved @@ -815,28 +925,30 @@ 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())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] RxqOvfl(u32), /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] #[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(linux_android)] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv6RecvErr(libc::sock_extended_err, Option), + /// `SOL_TLS` messages of type `TLS_GET_RECORD_TYPE` + #[cfg(any(target_os = "linux"))] + TlsGetRecordType(TlsGetRecordType), + /// Catch-all variant for unimplemented cmsg types. - #[doc(hidden)] Unknown(UnknownCmsg), } /// For representing packet timestamps via `SO_TIMESTAMPING` interface -#[cfg(all(target_os = "linux"))] +#[cfg(linux_android)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Timestamps { /// software based timestamp, usually one containing data @@ -847,6 +959,33 @@ pub struct Timestamps { pub hw_raw: TimeSpec, } +/// These constants correspond to TLS 1.2 message types, as defined in +/// RFC 5246, Appendix A.1 +#[cfg(any(target_os = "linux"))] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(u8)] +#[non_exhaustive] +pub enum TlsGetRecordType { + ChangeCipherSpec , + Alert, + Handshake, + ApplicationData, + Unknown(u8), +} + +#[cfg(any(target_os = "linux"))] +impl From for TlsGetRecordType { + fn from(x: u8) -> Self { + match x { + 20 => TlsGetRecordType::ChangeCipherSpec, + 21 => TlsGetRecordType::Alert, + 22 => TlsGetRecordType::Handshake, + 23 => TlsGetRecordType::ApplicationData, + _ => TlsGetRecordType::Unknown(x), + } + } +} + impl ControlMessageOwned { /// Decodes a `ControlMessageOwned` from raw bytes. /// @@ -859,7 +998,7 @@ impl ControlMessageOwned { #[allow(clippy::cast_ptr_alignment)] unsafe fn decode_from(header: &cmsghdr) -> ControlMessageOwned { - let p = CMSG_DATA(header); + let p = unsafe { 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 @@ -869,158 +1008,190 @@ impl ControlMessageOwned { let n = len / mem::size_of::(); let mut fds = Vec::with_capacity(n); for i in 0..n { - let fdp = (p as *const RawFd).add(i); - fds.push(ptr::read_unaligned(fdp)); + unsafe { + let fdp = (p as *const RawFd).add(i); + fds.push(ptr::read_unaligned(fdp)); + } } ControlMessageOwned::ScmRights(fds) }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] (libc::SOL_SOCKET, libc::SCM_CREDENTIALS) => { - let cred: libc::ucred = ptr::read_unaligned(p as *const _); + let cred: libc::ucred = unsafe { ptr::read_unaligned(p as *const _) }; ControlMessageOwned::ScmCredentials(cred.into()) } - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] (libc::SOL_SOCKET, libc::SCM_CREDS) => { - let cred: libc::cmsgcred = ptr::read_unaligned(p as *const _); + let cred: libc::cmsgcred = unsafe { ptr::read_unaligned(p as *const _) }; ControlMessageOwned::ScmCreds(cred.into()) } - #[cfg(not(target_os = "haiku"))] + #[cfg(not(any(target_os = "aix", target_os = "haiku", target_os = "cygwin")))] (libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => { - let tv: libc::timeval = ptr::read_unaligned(p as *const _); + let tv: libc::timeval = unsafe { ptr::read_unaligned(p as *const _) }; ControlMessageOwned::ScmTimestamp(TimeVal::from(tv)) }, - #[cfg(all(target_os = "linux"))] + #[cfg(linux_android)] (libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => { - let ts: libc::timespec = ptr::read_unaligned(p as *const _); + let ts: libc::timespec = unsafe { ptr::read_unaligned(p as *const _) }; ControlMessageOwned::ScmTimestampns(TimeSpec::from(ts)) } - #[cfg(all(target_os = "linux"))] + #[cfg(target_os = "freebsd")] + (libc::SOL_SOCKET, libc::SCM_REALTIME) => { + let ts: libc::timespec = unsafe { ptr::read_unaligned(p as *const _) }; + ControlMessageOwned::ScmRealtime(TimeSpec::from(ts)) + } + #[cfg(target_os = "freebsd")] + (libc::SOL_SOCKET, libc::SCM_MONOTONIC) => { + let ts: libc::timespec = unsafe { ptr::read_unaligned(p as *const _) }; + ControlMessageOwned::ScmMonotonic(TimeSpec::from(ts)) + } + #[cfg(linux_android)] (libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => { let tp = p as *const libc::timespec; - let ts: libc::timespec = ptr::read_unaligned(tp); + let ts: libc::timespec = unsafe { ptr::read_unaligned(tp) }; let system = TimeSpec::from(ts); - let ts: libc::timespec = ptr::read_unaligned(tp.add(1)); + let ts: libc::timespec = unsafe { ptr::read_unaligned(tp.add(1)) }; let hw_trans = TimeSpec::from(ts); - let ts: libc::timespec = ptr::read_unaligned(tp.add(2)); + let ts: libc::timespec = unsafe { ptr::read_unaligned(tp.add(2)) }; let hw_raw = TimeSpec::from(ts); let timestamping = Timestamps { system, hw_trans, hw_raw }; ControlMessageOwned::ScmTimestampsns(timestamping) } - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos" - ))] + #[cfg(any(target_os = "freebsd", linux_android, apple_targets))] #[cfg(feature = "net")] (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => { - let info = ptr::read_unaligned(p as *const libc::in6_pktinfo); + let info = unsafe { ptr::read_unaligned(p as *const libc::in6_pktinfo) }; ControlMessageOwned::Ipv6PacketInfo(info) } - #[cfg(any( - target_os = "android", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - ))] + #[cfg(any(linux_android, apple_targets, target_os = "netbsd"))] #[cfg(feature = "net")] (libc::IPPROTO_IP, libc::IP_PKTINFO) => { - let info = ptr::read_unaligned(p as *const libc::in_pktinfo); + let info = unsafe { ptr::read_unaligned(p as *const libc::in_pktinfo) }; ControlMessageOwned::Ipv4PacketInfo(info) } - #[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - ))] + #[cfg(bsd)] #[cfg(feature = "net")] (libc::IPPROTO_IP, libc::IP_RECVIF) => { - let dl = ptr::read_unaligned(p as *const libc::sockaddr_dl); + let dl = unsafe { ptr::read_unaligned(p as *const libc::sockaddr_dl) }; ControlMessageOwned::Ipv4RecvIf(dl) }, - #[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - ))] + #[cfg(bsd)] #[cfg(feature = "net")] (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { - let dl = ptr::read_unaligned(p as *const libc::in_addr); + let dl = unsafe { ptr::read_unaligned(p as *const libc::in_addr) }; ControlMessageOwned::Ipv4RecvDstAddr(dl) }, - #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] (libc::IPPROTO_IP, libc::IP_ORIGDSTADDR) => { - let dl = ptr::read_unaligned(p as *const libc::sockaddr_in); + let dl = unsafe { 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 _); + let gso_size: i32 = unsafe { ptr::read_unaligned(p as *const _) }; ControlMessageOwned::UdpGroSegments(gso_size) }, - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg(any(linux_android, target_os = "fuchsia"))] (libc::SOL_SOCKET, libc::SO_RXQ_OVFL) => { - let drop_counter = ptr::read_unaligned(p as *const u32); + let drop_counter = unsafe { ptr::read_unaligned(p as *const u32) }; ControlMessageOwned::RxqOvfl(drop_counter) }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] #[cfg(feature = "net")] (libc::IPPROTO_IP, libc::IP_RECVERR) => { - let (err, addr) = Self::recv_err_helper::(p, len); + let (err, addr) = unsafe { Self::recv_err_helper::(p, len) }; ControlMessageOwned::Ipv4RecvErr(err, addr) }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] #[cfg(feature = "net")] (libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => { - let (err, addr) = Self::recv_err_helper::(p, len); + let (err, addr) = unsafe { Self::recv_err_helper::(p, len) }; ControlMessageOwned::Ipv6RecvErr(err, addr) }, - #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] + #[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] (libc::IPPROTO_IPV6, libc::IPV6_ORIGDSTADDR) => { - let dl = ptr::read_unaligned(p as *const libc::sockaddr_in6); + let dl = unsafe { ptr::read_unaligned(p as *const libc::sockaddr_in6) }; ControlMessageOwned::Ipv6OrigDstAddr(dl) }, + #[cfg(any(target_os = "linux"))] + (libc::SOL_TLS, libc::TLS_GET_RECORD_TYPE) => { + let content_type = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::TlsGetRecordType(content_type.into()) + }, + #[cfg(linux_android)] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_TTL) => { + let ttl = unsafe { ptr::read_unaligned(p as *const i32) }; + ControlMessageOwned::Ipv4Ttl(ttl) + }, + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVTTL) => { + let ttl: u8 = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::Ipv4Ttl(ttl) + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_HOPLIMIT) => { + let ttl = unsafe { ptr::read_unaligned(p as *const i32) }; + ControlMessageOwned::Ipv6HopLimit(ttl) + }, + #[cfg(linux_android)] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_TOS) => { + let tos = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::Ipv4Tos(tos) + }, + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVTOS) => { + let tos = unsafe { ptr::read_unaligned(p as *const u8) }; + ControlMessageOwned::Ipv4Tos(tos) + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => { + let tc = unsafe { ptr::read_unaligned(p as *const i32) }; + ControlMessageOwned::Ipv6TClass(tc) + }, (_, _) => { - let sl = slice::from_raw_parts(p, len); - let ucmsg = UnknownCmsg(*header, Vec::::from(sl)); + let sl = unsafe { std::slice::from_raw_parts(p, len) }; + let ucmsg = UnknownCmsg { + cmsg_header: *header, + data_bytes: Vec::::from(sl), + }; ControlMessageOwned::Unknown(ucmsg) } } } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] #[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); + let err = unsafe { ptr::read_unaligned(ee) }; // For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len] // CMSG_DATA buffer. For local errors, there is no address included in the control // message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to // validate that the address object is in-bounds before we attempt to copy it. - let addrp = libc::SO_EE_OFFENDER(ee) as *const T; + let addrp = unsafe { libc::SO_EE_OFFENDER(ee) as *const T }; - if addrp.offset(1) as usize - (p as usize) > len { + if unsafe { addrp.offset(1) } as usize - (p as usize) > len { (err, None) } else { - (err, Some(ptr::read_unaligned(addrp))) + (err, Some(unsafe { ptr::read_unaligned(addrp) })) } } } -/// A type-safe zero-copy wrapper around a single control message, as used wih -/// [`sendmsg`](#fn.sendmsg). More types may be added to this enum; do not -/// exhaustively pattern-match it. +/// A type-safe zero-copy wrapper around a single control message, as used with +/// [`sendmsg`]. More types may be added to this enum; do not exhaustively +/// pattern-match it. /// /// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -1048,8 +1219,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())))] + #[cfg(linux_android)] ScmCredentials(&'a UnixCredentials), /// A message of type `SCM_CREDS`, containing the pid, uid, euid, gid and groups of /// a process connected to the socket. @@ -1063,41 +1233,28 @@ 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())))] + #[cfg(freebsdlike)] ScmCreds, /// Set IV for `AF_ALG` crypto API. /// /// For further information, please refer to the /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) - #[cfg(any( - target_os = "android", - target_os = "linux", - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] AlgSetIv(&'a [u8]), /// Set crypto operation for `AF_ALG` crypto API. It may be one of /// `ALG_OP_ENCRYPT` or `ALG_OP_DECRYPT` /// /// For further information, please refer to the /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) - #[cfg(any( - target_os = "android", - target_os = "linux", - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] AlgSetOp(&'a libc::c_int), /// Set the length of associated authentication data (AAD) (applicable only to AEAD algorithms) /// for `AF_ALG` crypto API. /// /// For further information, please refer to the /// [`documentation`](https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html) - #[cfg(any( - target_os = "android", - target_os = "linux", - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] AlgSetAeadAssoclen(&'a u32), /// UDP GSO makes it possible for applications to generate network packets @@ -1113,51 +1270,64 @@ pub enum ControlMessage<'a> { #[cfg_attr(docsrs, doc(cfg(feature = "net")))] UdpGsoSegments(&'a u16), - /// Configure the sending addressing and interface for v4 + /// Configure the sending addressing and interface for v4. /// /// For further information, please refer to the /// [`ip(7)`](https://man7.org/linux/man-pages/man7/ip.7.html) man page. - #[cfg(any(target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "android", - target_os = "ios",))] + #[cfg(any(linux_android, target_os = "netbsd", apple_targets))] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv4PacketInfo(&'a libc::in_pktinfo), - /// Configure the sending addressing and interface for v6 + /// Configure the sending addressing and interface for v6. /// /// For further information, please refer to the /// [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html) man page. - #[cfg(any(target_os = "linux", - target_os = "macos", + #[cfg(any(linux_android, target_os = "netbsd", target_os = "freebsd", - target_os = "android", - target_os = "ios",))] + apple_targets))] #[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(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv4SendSrcAddr(&'a libc::in_addr), + /// Configure the Time-to-Live for v4 traffic. + #[cfg(linux_android)] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(&'a libc::c_int), + + /// Configure the Time-to-Live for v4 traffic. + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Ttl(&'a libc::c_uchar), + + /// Configure the hop limit for v6 multicast traffic. + /// + /// Set the IPv6 hop limit for this message. The argument is an integer + /// between 0 and 255. A value of -1 will set the hop limit to the route + /// default if possible on the interface. Without this cmsg, packets sent + /// with sendmsg have a hop limit of 1 and will not leave the local network. + /// For further information, please refer to the + /// [`ipv6(7)`](https://man7.org/linux/man-pages/man7/ipv6.7.html) man page. + #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6HopLimit(&'a libc::c_int), + /// SO_RXQ_OVFL indicates that an unsigned 32 bit value - /// ancilliary msg (cmsg) should be attached to recieved + /// ancillary msg (cmsg) should be attached to received /// skbs indicating the number of packets dropped by the - /// socket between the last recieved packet and this + /// socket between the last received packet and this /// received packet. - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, target_os = "fuchsia"))] RxqOvfl(&'a u32), /// Configure the transmission time of packets. @@ -1167,14 +1337,34 @@ pub enum ControlMessage<'a> { /// page. #[cfg(target_os = "linux")] TxTime(&'a u64), + + /// Configure DSCP / IP TOS for outgoing v4 packets. + /// + /// Further information can be found [here](https://en.wikipedia.org/wiki/Differentiated_services). + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4Tos(&'a u8), + + /// Configure DSCP / IPv6 TCLASS for outgoing v6 packets. + /// + /// Further information can be found [here](https://en.wikipedia.org/wiki/Differentiated_services). + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6TClass(&'a i32), } -// An opaque structure used to prevent cmsghdr from being a public type -#[doc(hidden)] +/// Control messages that are currently not supported by Nix. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct UnknownCmsg(cmsghdr, Vec); +pub struct UnknownCmsg { + /// Control message header. + pub cmsg_header: cmsghdr, + /// Bytes of the control message data. + pub data_bytes: Vec +} -impl<'a> ControlMessage<'a> { +impl ControlMessage<'_> { /// The value of CMSG_SPACE on this message. /// Safe because CMSG_SPACE is always safe fn space(&self) -> usize { @@ -1184,13 +1374,15 @@ impl<'a> ControlMessage<'a> { /// The value of CMSG_LEN on this message. /// Safe because CMSG_LEN is always safe #[cfg(any(target_os = "android", - all(target_os = "linux", not(any(target_env = "musl", target_env = "ohos")))))] + all(target_os = "linux", not(any(target_env = "musl", target_env = "ohos"))), + target_os = "cygwin"))] fn cmsg_len(&self) -> usize { unsafe{CMSG_LEN(self.len() as libc::c_uint) as usize} } #[cfg(not(any(target_os = "android", - all(target_os = "linux", not(any(target_env = "musl", target_env = "ohos"))))))] + all(target_os = "linux", not(any(target_env = "musl", target_env = "ohos"))), + target_os = "cygwin")))] fn cmsg_len(&self) -> libc::c_uint { unsafe{CMSG_LEN(self.len() as libc::c_uint)} } @@ -1201,18 +1393,18 @@ impl<'a> ControlMessage<'a> { ControlMessage::ScmRights(fds) => { fds as *const _ as *const u8 }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::ScmCredentials(creds) => { &creds.0 as *const libc::ucred as *const u8 } - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] ControlMessage::ScmCreds => { // The kernel overwrites the data, we just zero it // to make sure it's not uninitialized memory unsafe { ptr::write_bytes(cmsg_data, 0, self.len()) }; return } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetIv(iv) => { #[allow(deprecated)] // https://github.com/rust-lang/libc/issues/1501 let af_alg_iv = libc::af_alg_iv { @@ -1237,11 +1429,11 @@ impl<'a> ControlMessage<'a> { return }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetOp(op) => { op as *const _ as *const u8 }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetAeadAssoclen(len) => { len as *const _ as *const u8 }, @@ -1250,21 +1442,26 @@ impl<'a> ControlMessage<'a> { 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(any(linux_android, target_os = "netbsd", apple_targets))] #[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(any(linux_android, target_os = "netbsd", + target_os = "freebsd", apple_targets))] #[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(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(addr) => addr as *const _ as *const u8, - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg(linux_android)] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(ttl) => ttl as *const i32 as *const u8, + #[cfg(target_os = "freebsd")] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(ttl) => ttl as *const u8, + #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6HopLimit(limit) => limit as *const _ as *const u8, + #[cfg(any(linux_android, target_os = "fuchsia"))] ControlMessage::RxqOvfl(drop_count) => { drop_count as *const _ as *const u8 }, @@ -1272,6 +1469,16 @@ impl<'a> ControlMessage<'a> { ControlMessage::TxTime(tx_time) => { tx_time as *const _ as *const u8 }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(tos) => { + tos as *const _ + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(tclass) => { + tclass as *const _ as *const u8 + }, }; unsafe { ptr::copy_nonoverlapping( @@ -1288,23 +1495,23 @@ impl<'a> ControlMessage<'a> { ControlMessage::ScmRights(fds) => { mem::size_of_val(fds) }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::ScmCredentials(creds) => { mem::size_of_val(creds) } - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] ControlMessage::ScmCreds => { mem::size_of::() } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetIv(iv) => { mem::size_of::<&[u8]>() + iv.len() }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetOp(op) => { mem::size_of_val(op) }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetAeadAssoclen(len) => { mem::size_of_val(len) }, @@ -1313,21 +1520,27 @@ impl<'a> ControlMessage<'a> { 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(any(linux_android, target_os = "netbsd", apple_targets))] #[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(any(linux_android, target_os = "netbsd", + target_os = "freebsd", apple_targets))] #[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(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(addr) => mem::size_of_val(addr), - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(ttl) => { + mem::size_of_val(ttl) + }, + #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6HopLimit(limit) => { + mem::size_of_val(limit) + }, + #[cfg(any(linux_android, target_os = "fuchsia"))] ControlMessage::RxqOvfl(drop_count) => { mem::size_of_val(drop_count) }, @@ -1335,6 +1548,16 @@ impl<'a> ControlMessage<'a> { ControlMessage::TxTime(tx_time) => { mem::size_of_val(tx_time) }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(tos) => { + mem::size_of_val(tos) + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(tclass) => { + mem::size_of_val(tclass) + }, } } @@ -1342,34 +1565,42 @@ impl<'a> ControlMessage<'a> { fn cmsg_level(&self) -> libc::c_int { match *self { ControlMessage::ScmRights(_) => libc::SOL_SOCKET, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::ScmCredentials(_) => libc::SOL_SOCKET, - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] ControlMessage::ScmCreds => libc::SOL_SOCKET, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] 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(any(linux_android, target_os = "netbsd", apple_targets))] #[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(any(linux_android, target_os = "netbsd", + target_os = "freebsd", apple_targets))] #[cfg(feature = "net")] ControlMessage::Ipv6PacketInfo(_) => libc::IPPROTO_IPV6, - #[cfg(any(target_os = "netbsd", target_os = "freebsd", - target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(_) => libc::IPPROTO_IP, - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(_) => libc::IPPROTO_IP, + #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6HopLimit(_) => libc::IPPROTO_IPV6, + #[cfg(any(linux_android, target_os = "fuchsia"))] ControlMessage::RxqOvfl(_) => libc::SOL_SOCKET, #[cfg(target_os = "linux")] ControlMessage::TxTime(_) => libc::SOL_SOCKET, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(_) => libc::IPPROTO_IP, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(_) => libc::IPPROTO_IPV6, } } @@ -1377,19 +1608,19 @@ impl<'a> ControlMessage<'a> { fn cmsg_type(&self) -> libc::c_int { match *self { ControlMessage::ScmRights(_) => libc::SCM_RIGHTS, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::ScmCredentials(_) => libc::SCM_CREDENTIALS, - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] ControlMessage::ScmCreds => libc::SCM_CREDS, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetIv(_) => { libc::ALG_SET_IV }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetOp(_) => { libc::ALG_SET_OP }, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessage::AlgSetAeadAssoclen(_) => { libc::ALG_SET_AEAD_ASSOCLEN }, @@ -1398,21 +1629,23 @@ impl<'a> ControlMessage<'a> { ControlMessage::UdpGsoSegments(_) => { libc::UDP_SEGMENT }, - #[cfg(any(target_os = "linux", target_os = "macos", - target_os = "netbsd", target_os = "android", - target_os = "ios",))] + #[cfg(any(linux_android, target_os = "netbsd", apple_targets))] #[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(any(linux_android, target_os = "netbsd", + target_os = "freebsd", apple_targets))] #[cfg(feature = "net")] ControlMessage::Ipv6PacketInfo(_) => libc::IPV6_PKTINFO, - #[cfg(any(target_os = "netbsd", target_os = "freebsd", - target_os = "openbsd", target_os = "dragonfly"))] + #[cfg(any(freebsdlike, netbsdlike))] #[cfg(feature = "net")] ControlMessage::Ipv4SendSrcAddr(_) => libc::IP_SENDSRCADDR, - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Ttl(_) => libc::IP_TTL, + #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "haiku"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6HopLimit(_) => libc::IPV6_HOPLIMIT, + #[cfg(any(linux_android, target_os = "fuchsia"))] ControlMessage::RxqOvfl(_) => { libc::SO_RXQ_OVFL }, @@ -1420,16 +1653,28 @@ impl<'a> ControlMessage<'a> { ControlMessage::TxTime(_) => { libc::SCM_TXTIME }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv4Tos(_) => { + libc::IP_TOS + }, + #[cfg(any(linux_android, target_os = "freebsd"))] + #[cfg(feature = "net")] + ControlMessage::Ipv6TClass(_) => { + libc::IPV6_TCLASS + }, } } // Unsafe: cmsg must point to a valid cmsghdr with enough space to // encode self. unsafe fn encode_into(&self, cmsg: *mut cmsghdr) { - (*cmsg).cmsg_level = self.cmsg_level(); - (*cmsg).cmsg_type = self.cmsg_type(); - (*cmsg).cmsg_len = self.cmsg_len(); - self.copy_to_cmsg_data(CMSG_DATA(cmsg)); + unsafe { + (*cmsg).cmsg_level = self.cmsg_level(); + (*cmsg).cmsg_type = self.cmsg_type(); + (*cmsg).cmsg_len = self.cmsg_len(); + self.copy_to_cmsg_data( CMSG_DATA(cmsg) ); + } } } @@ -1446,31 +1691,35 @@ impl<'a> ControlMessage<'a> { /// # 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 fds = [r.as_raw_fd()]; /// let cmsg = ControlMessage::ScmRights(&fds); -/// sendmsg::<()>(fd1, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(); +/// sendmsg::<()>(fd1.as_raw_fd(), &iov, &[cmsg], MsgFlags::empty(), None).unwrap(); /// ``` /// When directing to a specific address, the generic type will be inferred. +/// Note that SCM_RIGHTS ancillary data are valid only for AF_UNIX sockets on Solaris. /// ``` /// # 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 fds = [r.as_raw_fd()]; /// let cmsg = ControlMessage::ScmRights(&fds); -/// sendmsg(fd, &iov, &[cmsg], MsgFlags::empty(), Some(&localhost)).unwrap(); +/// #[cfg(not(target_os = "solaris"))] +/// 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 @@ -1507,12 +1756,7 @@ pub fn sendmsg(fd: RawFd, iov: &[IoSlice<'_>], cmsgs: &[ControlMessage], /// /// # References /// [`sendmsg`](fn.sendmsg.html) -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", -))] +#[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] pub fn sendmmsg<'a, XS, AS, C, I, S>( fd: RawFd, data: &'a mut MultiHeaders, @@ -1528,7 +1772,7 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>( AS: AsRef<[Option]>, I: AsRef<[IoSlice<'a>]> + 'a, C: AsRef<[ControlMessage<'a>]> + 'a, - S: SockaddrLike + 'a + S: SockaddrLike + 'a, { let mut count = 0; @@ -1536,11 +1780,11 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>( 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_iov = slice.as_ref().as_ptr().cast_mut().cast(); p.msg_iovlen = slice.as_ref().len() as _; p.msg_namelen = addr.as_ref().map_or(0, S::len); - p.msg_name = addr.as_ref().map_or(ptr::null(), S::as_ptr) as _; + p.msg_name = addr.as_ref().map_or(ptr::null(), S::as_ptr).cast_mut().cast(); // Encode each cmsg. This must happen after initializing the header because // CMSG_NEXT_HDR and friends read the msg_control and msg_controllen fields. @@ -1555,9 +1799,16 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>( pmhdr = unsafe { CMSG_NXTHDR(p, pmhdr) }; } - count = i+1; + // Doing an unchecked addition is alright here, as the only way to obtain an instance of `MultiHeaders` + // is through the `preallocate` function, which takes an `usize` as an argument to define its size, + // which also provides an upper bound for the size of this zipped iterator. Thus, `i < usize::MAX` or in + // other words: `count` doesn't overflow + count = i + 1; } + // SAFETY: all pointers are guaranteed to be valid for the scope of this function. `count` does represent the + // maximum number of messages that can be sent safely (i.e. `count` is the minimum of the sizes of `slices`, + // `data.items` and `addrs`) let sent = Errno::result(unsafe { libc::sendmmsg( fd, @@ -1576,12 +1827,7 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>( } -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", -))] +#[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] #[derive(Debug)] /// Preallocated structures needed for [`recvmmsg`] and [`sendmmsg`] functions pub struct MultiHeaders { @@ -1590,17 +1836,11 @@ pub struct MultiHeaders { addresses: Box<[mem::MaybeUninit]>, // while we are not using it directly - this is used to store control messages // and we retain pointers to them inside items array - #[allow(dead_code)] - cmsg_buffers: Option>, + _cmsg_buffers: Option>, msg_controllen: usize, } -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", -))] +#[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] impl MultiHeaders { /// Preallocate structure used by [`recvmmsg`] and [`sendmmsg`] takes number of headers to preallocate /// @@ -1625,7 +1865,7 @@ impl MultiHeaders { .enumerate() .map(|(ix, address)| { let (ptr, cap) = match &mut cmsg_buffers { - Some(v) => ((&mut v[ix * msg_controllen] as *mut u8), msg_controllen), + 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()) }; @@ -1639,7 +1879,7 @@ impl MultiHeaders { Self { items: items.into_boxed_slice(), addresses, - cmsg_buffers, + _cmsg_buffers: cmsg_buffers, msg_controllen, } } @@ -1665,17 +1905,10 @@ impl MultiHeaders { /// 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", -))] +#[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] pub fn recvmmsg<'a, XS, S, I>( fd: RawFd, data: &'a mut MultiHeaders, @@ -1684,14 +1917,19 @@ pub fn recvmmsg<'a, XS, S, I>( mut timeout: Option, ) -> crate::Result> where - XS: IntoIterator, - I: AsRef<[IoSliceMut<'a>]> + 'a, + XS: IntoIterator, + I: AsMut<[IoSliceMut<'a>]> + 'a, { 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 _; + p.msg_iov = slice.as_mut().as_mut_ptr().cast(); + p.msg_iovlen = slice.as_mut().len() as _; + + // Doing an unchecked addition is alright here, as the only way to obtain an instance of `MultiHeaders` + // is through the `preallocate` function, which takes an `usize` as an argument to define its size, + // which also provides an upper bound for the size of this zipped iterator. Thus, `i < usize::MAX` or in + // other words: `count` doesn't overflow count = i + 1; } @@ -1699,6 +1937,8 @@ where .as_mut() .map_or_else(std::ptr::null_mut, |t| t as *mut _ as *mut libc::timespec); + // SAFETY: all pointers are guaranteed to be valid for the scope of this function. `count` does represent the + // maximum number of messages that can be received safely (i.e. `count` is the minimum of the sizes of `slices` and `data.items`) let received = Errno::result(unsafe { libc::recvmmsg( fd, @@ -1716,16 +1956,9 @@ where }) } -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", -))] -#[derive(Debug)] /// Iterator over results of [`recvmmsg`]/[`sendmmsg`] -/// -/// +#[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] +#[derive(Debug)] pub struct MultiResults<'a, S> { // preallocated structures rmm: &'a MultiHeaders, @@ -1733,12 +1966,7 @@ pub struct MultiResults<'a, S> { received: usize, } -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", -))] +#[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] impl<'a, S> Iterator for MultiResults<'a, S> where S: Copy + SockaddrLike, @@ -1811,107 +2039,6 @@ impl<'a> Iterator for IoSliceIterator<'a> { } } -// test contains both recvmmsg and timestaping which is linux only -// there are existing tests for recvmmsg only in tests/ -#[cfg(target_os = "linux")] -#[cfg(test)] -mod test { - use crate::sys::socket::{AddressFamily, ControlMessageOwned}; - use crate::*; - use std::str::FromStr; - - #[cfg_attr(qemu, ignore)] - #[test] - fn test_recvmm2() -> crate::Result<()> { - use crate::sys::socket::{ - sendmsg, setsockopt, socket, sockopt::Timestamping, MsgFlags, SockFlag, SockType, - SockaddrIn, TimestampingFlag, - }; - use std::io::{IoSlice, IoSliceMut}; - - let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); - - let ssock = socket( - AddressFamily::Inet, - SockType::Datagram, - SockFlag::empty(), - None, - )?; - - let rsock = socket( - AddressFamily::Inet, - SockType::Datagram, - SockFlag::SOCK_NONBLOCK, - None, - )?; - - crate::sys::socket::bind(rsock, &sock_addr)?; - - setsockopt(rsock, Timestamping, &TimestampingFlag::all())?; - - let sbuf = (0..400).map(|i| i as u8).collect::>(); - - let mut recv_buf = vec![0; 1024]; - - let mut recv_iovs = Vec::new(); - let mut pkt_iovs = Vec::new(); - - for (ix, chunk) in recv_buf.chunks_mut(256).enumerate() { - pkt_iovs.push(IoSliceMut::new(chunk)); - if ix % 2 == 1 { - recv_iovs.push(pkt_iovs); - pkt_iovs = Vec::new(); - } - } - drop(pkt_iovs); - - let flags = MsgFlags::empty(); - let iov1 = [IoSlice::new(&sbuf)]; - - let cmsg = cmsg_space!(crate::sys::socket::Timestamps); - sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap(); - - let mut data = super::MultiHeaders::<()>::preallocate(recv_iovs.len(), Some(cmsg)); - - let t = sys::time::TimeSpec::from_duration(std::time::Duration::from_secs(10)); - - let recv = super::recvmmsg(rsock, &mut data, recv_iovs.iter(), flags, Some(t))?; - - for rmsg in recv { - #[cfg(not(any(qemu, target_arch = "aarch64")))] - let mut saw_time = false; - let mut recvd = 0; - for cmsg in rmsg.cmsgs() { - if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg { - let ts = timestamps.system; - - let sys_time = - crate::time::clock_gettime(crate::time::ClockId::CLOCK_REALTIME)?; - let diff = if ts > sys_time { - ts - sys_time - } else { - sys_time - ts - }; - assert!(std::time::Duration::from(diff).as_secs() < 60); - #[cfg(not(any(qemu, target_arch = "aarch64")))] - { - saw_time = true; - } - } - } - - #[cfg(not(any(qemu, target_arch = "aarch64")))] - assert!(saw_time); - - for iov in rmsg.iovs() { - recvd += iov.len(); - } - assert_eq!(recvd, 400); - } - - Ok(()) - } -} unsafe fn read_mhdr<'a, 'i, S>( mhdr: msghdr, r: isize, @@ -1923,19 +2050,23 @@ unsafe fn read_mhdr<'a, 'i, S>( // The cast is not unnecessary on all platforms. #[allow(clippy::unnecessary_cast)] let cmsghdr = { - if mhdr.msg_controllen > 0 { + let ptr = if mhdr.msg_controllen > 0 { debug_assert!(!mhdr.msg_control.is_null()); debug_assert!(msg_controllen >= mhdr.msg_controllen as usize); - CMSG_FIRSTHDR(&mhdr as *const msghdr) + unsafe { CMSG_FIRSTHDR(&mhdr as *const msghdr) } } else { ptr::null() - }.as_ref() + }; + + unsafe { + ptr.as_ref() + } }; // 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); + let _ = unsafe { address.set_length(mhdr.msg_namelen as usize) }; RecvMsg { bytes: r as usize, @@ -1972,14 +2103,19 @@ unsafe fn pack_mhdr_to_receive( // 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() + unsafe { + // it is important to use as_mut_ptr() here since S can be + // a zero sized type representing by a dangling pointer. + // as_mut_ptr() handles this case and uses a null pointer instead + (*p).msg_name = (*address).as_mut_ptr() as *mut c_void; + (*p).msg_namelen = S::size(); + (*p).msg_iov = iov_buffer as *mut iovec; + (*p).msg_iovlen = iov_buffer_len as _; + (*p).msg_control = cmsg_buffer as *mut c_void; + (*p).msg_controllen = cmsg_capacity as _; + (*p).msg_flags = 0; + mhdr.assume_init() + } } fn pack_mhdr_to_send<'a, I, C, S>( @@ -1997,7 +2133,7 @@ fn pack_mhdr_to_send<'a, I, C, S>( // The message header must be initialized before the individual cmsgs. let cmsg_ptr = if capacity > 0 { - cmsg_buffer.as_mut_ptr() as *mut c_void + cmsg_buffer.as_mut_ptr().cast() } else { ptr::null_mut() }; @@ -2007,11 +2143,11 @@ fn pack_mhdr_to_send<'a, I, C, S>( // initialize it. let mut mhdr = mem::MaybeUninit::::zeroed(); let p = mhdr.as_mut_ptr(); - (*p).msg_name = addr.map(S::as_ptr).unwrap_or(ptr::null()) as *mut _; + (*p).msg_name = addr.map(S::as_ptr).unwrap_or(ptr::null()).cast_mut().cast(); (*p).msg_namelen = addr.map(S::len).unwrap_or(0); // transmute iov into a mutable pointer. sendmsg doesn't really mutate // the buffer, but the standard says that it takes a mutable pointer - (*p).msg_iov = iov.as_ref().as_ptr() as *mut _; + (*p).msg_iov = iov.as_ref().as_ptr().cast_mut().cast(); (*p).msg_iovlen = iov.as_ref().len() as _; (*p).msg_control = cmsg_ptr; (*p).msg_controllen = capacity as _; @@ -2050,7 +2186,7 @@ fn pack_mhdr_to_send<'a, I, C, S>( /// # References /// [recvmsg(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html) pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'inner>], - mut cmsg_buffer: Option<&'a mut Vec>, + mut cmsg_buffer: Option<&'a mut [u8]>, flags: MsgFlags) -> Result> where S: SockaddrLike + 'a, 'inner: 'outer @@ -2058,7 +2194,7 @@ pub fn recvmsg<'a, 'outer, 'inner, S>(fd: RawFd, iov: &'outer mut [IoSliceMut<'i let mut address = mem::MaybeUninit::uninit(); let (msg_control, msg_controllen) = cmsg_buffer.as_mut() - .map(|v| (v.as_mut_ptr(), v.capacity())) + .map(|v| (v.as_mut_ptr(), v.len())) .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()) @@ -2087,7 +2223,7 @@ pub fn socket>>( ty: SockType, flags: SockFlag, protocol: T, -) -> Result { +) -> Result { let protocol = match protocol.into() { None => 0, Some(p) => p as c_int, @@ -2101,7 +2237,13 @@ pub fn socket>>( 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 @@ -2112,7 +2254,7 @@ pub fn socketpair>>( ty: SockType, protocol: T, flags: SockFlag, -) -> Result<(RawFd, RawFd)> { +) -> Result<(OwnedFd, OwnedFd)> { let protocol = match protocol.into() { None => 0, Some(p) => p as c_int, @@ -2131,14 +2273,52 @@ pub fn socketpair>>( }; 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]))) } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Backlog(i32); + +impl Backlog { + /// Sets the listen queue size to system `SOMAXCONN` value + pub const MAXCONN: Self = Self(libc::SOMAXCONN); + /// Sets the listen queue size to -1 for system supporting it + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub const MAXALLOWABLE: Self = Self(-1); + + /// Create a `Backlog`, an `EINVAL` will be returned if `val` is invalid. + pub fn new>(val: I) -> Result { + cfg_if! { + if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { + const MIN: i32 = -1; + } else { + const MIN: i32 = 0; + } + } + + let val = val.into(); + + if !(MIN..=Self::MAXCONN.0).contains(&val) { + return Err(Errno::EINVAL); + } + + Ok(Self(val)) + } +} + +impl From for i32 { + fn from(backlog: Backlog) -> Self { + backlog.0 + } } /// 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: Backlog) -> Result<()> { + let fd = sock.as_fd().as_raw_fd(); + let res = unsafe { libc::listen(fd, backlog.into()) }; Errno::result(res).map(drop) } @@ -2173,14 +2353,12 @@ pub fn accept(sockfd: RawFd) -> Result { target_arch = "x86_64" ) ), - target_os = "dragonfly", + freebsdlike, + netbsdlike, target_os = "emscripten", - target_os = "freebsd", target_os = "fuchsia", - target_os = "illumos", + solarish, target_os = "linux", - target_os = "netbsd", - target_os = "openbsd" ))] pub fn accept4(sockfd: RawFd, flags: SockFlag) -> Result { let res = unsafe { @@ -2207,7 +2385,7 @@ pub fn recv(sockfd: RawFd, buf: &mut [u8], flags: MsgFlags) -> Result { unsafe { let ret = libc::recv( sockfd, - buf.as_mut_ptr() as *mut c_void, + buf.as_mut_ptr().cast(), buf.len() as size_t, flags.bits(), ); @@ -2231,20 +2409,14 @@ pub fn recvfrom( let ret = Errno::result(libc::recvfrom( sockfd, - buf.as_mut_ptr() as *mut c_void, + buf.as_mut_ptr().cast(), buf.len() as size_t, 0, - addr.as_mut_ptr() as *mut libc::sockaddr, + addr.as_mut_ptr().cast(), &mut len as *mut socklen_t, ))? as usize; - Ok(( - ret, - T::from_raw( - addr.assume_init().as_ptr(), - Some(len), - ), - )) + Ok((ret, T::from_raw(addr.assume_init().as_ptr(), Some(len)))) } } @@ -2260,7 +2432,7 @@ pub fn sendto( let ret = unsafe { libc::sendto( fd, - buf.as_ptr() as *const c_void, + buf.as_ptr().cast(), buf.len() as size_t, flags.bits(), addr.as_ptr(), @@ -2276,12 +2448,7 @@ pub fn sendto( /// [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().cast(), buf.len() as size_t, flags.bits()) }; Errno::result(ret).map(|r| r as usize) @@ -2298,21 +2465,21 @@ 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 { - type Val; + type Val: ?Sized; /// 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) } @@ -2326,15 +2493,14 @@ 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, +pub fn setsockopt( + fd: &F, opt: O, val: &O::Val, ) -> Result<()> { @@ -2349,11 +2515,7 @@ pub fn getpeername(fd: RawFd) -> Result { 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().cast(), &mut len); Errno::result(ret)?; @@ -2369,11 +2531,7 @@ pub fn getsockname(fd: RawFd) -> Result { 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().cast(), &mut len); Errno::result(ret)?; @@ -2381,81 +2539,6 @@ pub fn getsockname(fd: RawFd) -> Result { } } -/// Return the appropriate `SockAddr` type from a `sockaddr_storage` of a -/// certain size. -/// -/// In C this would usually be done by casting. The `len` argument -/// should be the number of bytes in the `sockaddr_storage` that are actually -/// allocated and valid. It must be at least as large as all the useful parts -/// of the structure. Note that in the case of a `sockaddr_un`, `len` need not -/// include the terminating null. -#[deprecated( - since = "0.24.0", - note = "use SockaddrLike or SockaddrStorage instead" -)] -#[allow(deprecated)] -pub fn sockaddr_storage_to_addr( - addr: &sockaddr_storage, - len: usize, -) -> Result { - assert!(len <= mem::size_of::()); - if len < mem::size_of_val(&addr.ss_family) { - return Err(Errno::ENOTCONN); - } - - match c_int::from(addr.ss_family) { - #[cfg(feature = "net")] - libc::AF_INET => { - assert!(len >= mem::size_of::()); - let sin = unsafe { - *(addr as *const sockaddr_storage as *const sockaddr_in) - }; - Ok(SockAddr::Inet(InetAddr::V4(sin))) - } - #[cfg(feature = "net")] - libc::AF_INET6 => { - assert!(len >= mem::size_of::()); - let sin6 = unsafe { *(addr as *const _ as *const sockaddr_in6) }; - Ok(SockAddr::Inet(InetAddr::V6(sin6))) - } - libc::AF_UNIX => unsafe { - let sun = *(addr as *const _ as *const sockaddr_un); - let sun_len = len.try_into().unwrap(); - Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, sun_len))) - }, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg(feature = "net")] - libc::AF_PACKET => { - use libc::sockaddr_ll; - // Don't assert anything about the size. - // Apparently the Linux kernel can return smaller sizes when - // the value in the last element of sockaddr_ll (`sll_addr`) is - // smaller than the declared size of that field - let sll = unsafe { *(addr as *const _ as *const sockaddr_ll) }; - Ok(SockAddr::Link(LinkAddr(sll))) - } - #[cfg(any(target_os = "android", target_os = "linux"))] - libc::AF_NETLINK => { - use libc::sockaddr_nl; - let snl = unsafe { *(addr as *const _ as *const sockaddr_nl) }; - Ok(SockAddr::Netlink(NetlinkAddr(snl))) - } - #[cfg(any(target_os = "android", target_os = "linux"))] - libc::AF_ALG => { - use libc::sockaddr_alg; - let salg = unsafe { *(addr as *const _ as *const sockaddr_alg) }; - Ok(SockAddr::Alg(AlgAddr(salg))) - } - #[cfg(any(target_os = "android", target_os = "linux"))] - libc::AF_VSOCK => { - use libc::sockaddr_vm; - let svm = unsafe { *(addr as *const _ as *const sockaddr_vm) }; - Ok(SockAddr::Vsock(VsockAddr(svm))) - } - af => panic!("unexpected address family {}", af), - } -} - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Shutdown { /// Further receptions will be disallowed. @@ -2482,11 +2565,3 @@ pub fn shutdown(df: RawFd, how: Shutdown) -> Result<()> { Errno::result(shutdown(df, how)).map(drop) } } - -#[cfg(test)] -mod tests { - #[test] - fn can_use_cmsg_space() { - let _ = cmsg_space!(u8); - } -} diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 2658deae..a821f5cb 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -1,18 +1,19 @@ //! Socket options as used by `setsockopt` and `getsockopt`. -use super::{GetSockOpt, SetSockOpt}; -use crate::errno::Errno; +#[cfg(any(linux_android, target_os = "illumos"))] +use super::SetSockOpt; use crate::sys::time::TimeVal; -use crate::Result; +#[cfg(any(linux_android, target_os = "illumos"))] +use crate::{errno::Errno, Result}; use cfg_if::cfg_if; use libc::{self, c_int, c_void, socklen_t}; -use std::ffi::{OsStr, OsString}; -use std::{ - convert::TryFrom, - mem::{self, MaybeUninit} -}; -#[cfg(target_family = "unix")] +#[cfg(apple_targets)] +use std::ffi::CString; +use std::ffi::{CStr, OsStr, OsString}; +use std::mem::{self, MaybeUninit}; +use std::os::fd::OwnedFd; use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::RawFd; +#[cfg(any(linux_android, target_os = "illumos"))] +use std::os::unix::io::{AsFd, AsRawFd}; // Constants // TCP_CA_NAME_MAX isn't defined in user space include files @@ -26,40 +27,49 @@ const TCP_CA_NAME_MAX: usize = 16; /// This macro aims to help implementing `SetSockOpt` for different socket options that accept /// different kinds of data to be used with `setsockopt`. /// -/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option -/// you are implementing represents a simple type. +/// Instead of using this macro directly consider using [`sockopt_impl!`](crate::sockopt_impl), +/// especially if the option you are implementing represents a simple type. /// /// # Arguments /// /// * `$name:ident`: name of the type you want to implement `SetSockOpt` for. /// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* -/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), -/// and more. Please refer to your system manual for more options. Will be passed as the second -/// argument (`level`) to the `setsockopt` call. +/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), +/// and more. Please refer to your system manual for more options. Will be passed as the second +/// argument (`level`) to the `setsockopt` call. /// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, -/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) -/// to the `setsockopt` call. +/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) +/// to the `setsockopt` call. /// * Type of the value that you are going to set. -/// * Type that implements the `Set` trait for the type from the previous item (like `SetBool` for -/// `bool`, `SetUsize` for `usize`, etc.). +/// * Type that implements the `Set` trait for the type from the previous item +/// (like `SetBool` for `bool`, `SetUsize` for `usize`, etc.). +#[macro_export] macro_rules! setsockopt_impl { ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { - impl SetSockOpt for $name { + #[allow(deprecated)] // to allow we have deprecated socket option + impl $crate::sys::socket::SetSockOpt for $name { type Val = $ty; - fn set(&self, fd: RawFd, val: &$ty) -> Result<()> { - unsafe { - let setter: $setter = Set::new(val); - - let res = libc::setsockopt( - fd, - $level, - $flag, + fn set( + &self, + fd: &F, + val: &$ty, + ) -> $crate::Result<()> { + use std::os::fd::AsRawFd; + use $crate::sys::socket::sockopt::Set; + let setter: $setter = Set::new(val); + let level = $level; + let flag = $flag; + let res = unsafe { + libc::setsockopt( + fd.as_fd().as_raw_fd(), + level, + flag, setter.ffi_ptr(), setter.ffi_len(), - ); - Errno::result(res).map(drop) - } + ) + }; + $crate::errno::Errno::result(res).map(drop) } } }; @@ -71,44 +81,68 @@ macro_rules! setsockopt_impl { /// This macro aims to help implementing `GetSockOpt` for different socket options that accept /// different kinds of data to be use with `getsockopt`. /// -/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option -/// you are implementing represents a simple type. +/// Instead of using this macro directly consider using [`sockopt_impl!`](crate::sockopt_impl), +/// especially if the option you are implementing represents a simple type. /// /// # Arguments /// /// * Name of the type you want to implement `GetSockOpt` for. /// * Socket layer, or a `protocol level`: could be *raw sockets* (`lic::SOL_SOCKET`), *ip -/// protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), and more. Please refer -/// to your system manual for more options. Will be passed as the second argument (`level`) to -/// the `getsockopt` call. +/// protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), and more. Please refer +/// to your system manual for more options. Will be passed as the second argument (`level`) to +/// the `getsockopt` call. /// * A flag to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, -/// `libc::SO_ORIGINAL_DST` and others. Will be passed as the third argument (`option_name`) to -/// the `getsockopt` call. +/// `libc::SO_ORIGINAL_DST` and others. Will be passed as the third argument (`option_name`) to +/// the `getsockopt` call. /// * Type of the value that you are going to get. /// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for -/// `bool`, `GetUsize` for `usize`, etc.). +/// `bool`, `GetUsize` for `usize`, etc.). +#[macro_export] macro_rules! getsockopt_impl { ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { - impl GetSockOpt for $name { + #[allow(deprecated)] // to allow we have deprecated socket option + impl $crate::sys::socket::GetSockOpt for $name { type Val = $ty; - fn get(&self, fd: RawFd) -> Result<$ty> { - unsafe { - let mut getter: $getter = Get::uninit(); - - let res = libc::getsockopt( - fd, - $level, - $flag, + fn get( + &self, + fd: &F, + ) -> $crate::Result<$ty> { + use std::os::fd::AsRawFd; + use $crate::sys::socket::sockopt::Get; + let mut getter: $getter = Get::uninit(); + let level = $level; + let flag = $flag; + let res = unsafe { + libc::getsockopt( + fd.as_fd().as_raw_fd(), + level, + flag, getter.ffi_ptr(), getter.ffi_len(), - ); - Errno::result(res)?; + ) + }; + $crate::errno::Errno::result(res)?; - match <$ty>::try_from(getter.assume_init()) { - Err(_) => Err(Errno::EINVAL), - Ok(r) => Ok(r) - } + // getter is definitely initialized now + let gotten = unsafe { getter.assume_init() }; + match <$ty>::try_from(gotten) { + // In most `getsockopt_impl!` implementations, `assume_init()` + // returns `$ty`, so calling `$ty`::try_from($ty) will always + // succeed. which makes the following `Err(_)` branch + // unreachable. + // + // However, there is indeed one exception, `sockopt::SockType`, + // `assume_init()` returns an `i32`, but `$ty` is `super::SockType`, + // this exception necessitates the use of that `try_from()`, + // and we have to allow the unreachable pattern wraning. + // + // For the reason why we are using `i32` as the underlying + // buffer type for this socket option, see issue: + // https://github.com/nix-rust/nix/issues/1819 + #[allow(unreachable_patterns)] + Err(_) => Err($crate::errno::Errno::EINVAL), + Ok(r) => Ok(r), } } } @@ -128,73 +162,90 @@ macro_rules! getsockopt_impl { /// # Arguments /// /// * `GetOnly`, `SetOnly` or `Both`: whether you want to implement only getter, only setter or -/// both of them. +/// both of them. /// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for. /// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets* -/// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), -/// and more. Please refer to your system manual for more options. Will be passed as the second -/// argument (`level`) to the `getsockopt`/`setsockopt` call. +/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), +/// and more. Please refer to your system manual for more options. Will be passed as the second +/// argument (`level`) to the `getsockopt`/`setsockopt` call. /// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`, -/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) -/// to the `setsockopt`/`getsockopt` call. +/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`) +/// to the `setsockopt`/`getsockopt` call. /// * `$ty:ty`: type of the value that will be get/set. /// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`. /// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`. // Some targets don't use all rules. -#[allow(unknown_lints)] #[allow(unused_macro_rules)] +#[macro_export] macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* - $name, GetOnly, $level, $flag, bool, GetBool); + $name, GetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool); }; ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => { - sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, GetU8); + sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, $crate::sys::socket::sockopt::GetU8); }; ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) => { sockopt_impl!($(#[$attr])* - $name, GetOnly, $level, $flag, usize, GetUsize); + $name, GetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize); + }; + + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, OwnedFd) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::GetOwnedFd); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* - $name, SetOnly, $level, $flag, bool, SetBool); + $name, SetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::SetBool); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => { - sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, SetU8); + sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, $crate::sys::socket::sockopt::SetU8); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) => { sockopt_impl!($(#[$attr])* - $name, SetOnly, $level, $flag, usize, SetUsize); + $name, SetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::SetUsize); + }; + + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, OwnedFd) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::SetOwnedFd); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, bool, GetBool, SetBool); + $name, Both, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool, $crate::sys::socket::sockopt::SetBool); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, u8, GetU8, SetU8); + $name, Both, $level, $flag, u8, $crate::sys::socket::sockopt::GetU8, $crate::sys::socket::sockopt::SetU8); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, usize, GetUsize, SetUsize); + $name, Both, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize, $crate::sys::socket::sockopt::SetUsize); + }; + + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, OwnedFd) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::GetOwnedFd, $crate::sys::socket::sockopt::SetOwnedFd); }; ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, OsString<$array:ty>) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, OsString, GetOsString<$array>, - SetOsString); + $name, Both, $level, $flag, std::ffi::OsString, $crate::sys::socket::sockopt::GetOsString<$array>, + $crate::sys::socket::sockopt::SetOsString); }; /* @@ -204,7 +255,7 @@ macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!($(#[$attr])* - $name, GetOnly, $level, $flag, $ty, GetStruct<$ty>); + $name, GetOnly, $level, $flag, $ty, $crate::sys::socket::sockopt::GetStruct<$ty>); }; ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty, @@ -220,7 +271,7 @@ macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!($(#[$attr])* - $name, SetOnly, $level, $flag, $ty, SetStruct<$ty>); + $name, SetOnly, $level, $flag, $ty, $crate::sys::socket::sockopt::SetStruct<$ty>); }; ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty, @@ -246,8 +297,8 @@ macro_rules! sockopt_impl { ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => { sockopt_impl!($(#[$attr])* - $name, Both, $level, $flag, $ty, GetStruct<$ty>, - SetStruct<$ty>); + $name, Both, $level, $flag, $ty, $crate::sys::socket::sockopt::GetStruct<$ty>, + $crate::sys::socket::sockopt::SetStruct<$ty>); }; } @@ -265,7 +316,7 @@ sockopt_impl!( libc::SO_REUSEADDR, bool ); -#[cfg(not(any(target_os = "illumos", target_os = "solaris")))] +#[cfg(not(any(solarish, target_os = "cygwin")))] sockopt_impl!( /// Permits multiple AF_INET or AF_INET6 sockets to be bound to an /// identical socket address. @@ -275,16 +326,54 @@ sockopt_impl!( libc::SO_REUSEPORT, bool ); +#[cfg(target_os = "freebsd")] +sockopt_impl!( + /// Enables incoming connections to be distributed among N sockets (up to 256) + /// via a Load-Balancing hash based algorithm. + ReusePortLb, + Both, + libc::SOL_SOCKET, + libc::SO_REUSEPORT_LB, + bool +); +#[cfg(target_os = "freebsd")] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Select or query the set of functions that TCP will use for this connection. This allows a + /// user to select an alternate TCP stack. + TcpFunctionBlk, + Both, + libc::IPPROTO_TCP, + libc::TCP_FUNCTION_BLK, + libc::tcp_function_set +); +#[cfg(target_os = "freebsd")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Query the alias name of the set of function of the socket's TCP stack. + /// Uses the same field for the main name when getting from TCP_FUNCTION_BLK. + /// Empty if no alias. + TcpFunctionAlias, + GetOnly, + libc::IPPROTO_TCP, + libc::TCP_FUNCTION_ALIAS, + libc::tcp_function_set +); +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Used to disable Nagle's algorithm. + /// + /// Nagle's algorithm: + /// /// Under most circumstances, TCP sends data when it is presented; when /// outstanding data has not yet been acknowledged, it gathers small amounts /// of output to be sent in a single packet once an acknowledgement is /// received. For a small number of clients, such as window systems that /// send a stream of mouse events which receive no replies, this - /// packetization may cause significant delays. The boolean option - /// TCP_NODELAY defeats this algorithm. + /// packetization may cause significant delays. The boolean option, when + /// enabled, defeats this algorithm. TcpNoDelay, Both, libc::IPPROTO_TCP, @@ -292,7 +381,7 @@ sockopt_impl!( bool ); sockopt_impl!( - /// When enabled, a close(2) or shutdown(2) will not return until all + /// 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, @@ -301,6 +390,15 @@ sockopt_impl!( libc::SO_LINGER, libc::linger ); +#[cfg(apple_targets)] +sockopt_impl!( + /// Same as `SO_LINGER`, but the duration is in seconds rather than kernel ticks. + LingerSec, + Both, + libc::SOL_SOCKET, + libc::SO_LINGER_SEC, + libc::linger +); #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -322,7 +420,7 @@ sockopt_impl!( super::IpMembershipRequest ); cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(linux_android)] { #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -333,14 +431,7 @@ cfg_if! { #[cfg_attr(docsrs, doc(cfg(feature = "net")))] /// Leave an IPv6 multicast group. Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, super::Ipv6MembershipRequest); - } else if #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris"))] { + } else if #[cfg(any(bsd, solarish))] { #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -367,6 +458,17 @@ sockopt_impl!( u8 ); #[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set or read the hop limit value of outgoing IPv6 multicast packets for + /// this socket. + Ipv6MulticastHops, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_MULTICAST_HOPS, + libc::c_int +); +#[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] /// Set or read a boolean integer argument that determines whether sent @@ -389,9 +491,10 @@ sockopt_impl!( libc::SO_PRIORITY, libc::c_int ); -#[cfg(target_os = "linux")] +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] sockopt_impl!( + #[deprecated(since = "0.30.0", note = "Use Ipv4Tos instead")] #[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 @@ -401,18 +504,52 @@ sockopt_impl!( libc::IP_TOS, libc::c_int ); -#[cfg(target_os = "linux")] +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] - /// Traffic class associated with outgoing packets + /// Set or receive the Type-Of-Service (TOS) field that is + /// sent with every IP packet originating from this socket + Ipv4Tos, + Both, + libc::IPPROTO_IP, + libc::IP_TOS, + libc::c_int +); +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// If enabled, the IP_TOS ancillary message is passed with incoming packets. + IpRecvTos, + Both, + libc::IPPROTO_IP, + libc::IP_RECVTOS, + bool +); +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Set the 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(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// If enabled, the IPV6_TCLASS ancillary message is passed with incoming packets. + Ipv6RecvTClass, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVTCLASS, + bool +); +#[cfg(any(linux_android, target_os = "fuchsia"))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -424,6 +561,20 @@ sockopt_impl!( libc::IP_FREEBIND, bool ); +#[cfg(linux_android)] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// If enabled, the kernel will not reserve an ephemeral port when binding + /// socket with a port number of 0. The port will later be automatically + /// chosen at connect time, in a way that allows sharing a source port as + /// long as the 4-tuple is unique. + IpBindAddressNoPort, + Both, + libc::IPPROTO_IP, + libc::IP_BIND_ADDRESS_NO_PORT, + bool +); sockopt_impl!( /// Specify the receiving timeout until reporting an error. ReceiveTimeout, @@ -481,12 +632,7 @@ sockopt_impl!( libc::SO_KEEPALIVE, bool ); -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "ios" -))] +#[cfg(freebsdlike)] sockopt_impl!( /// Get the credentials of the peer process of a connected unix domain /// socket. @@ -496,7 +642,36 @@ sockopt_impl!( libc::LOCAL_PEERCRED, super::XuCred ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(apple_targets)] +sockopt_impl!( + /// Get the credentials of the peer process of a connected unix domain + /// socket. + LocalPeerCred, + GetOnly, + libc::SOL_LOCAL, + libc::LOCAL_PEERCRED, + super::XuCred +); +#[cfg(apple_targets)] +sockopt_impl!( + /// Get the PID of the peer process of a connected unix domain socket. + LocalPeerPid, + GetOnly, + libc::SOL_LOCAL, + libc::LOCAL_PEERPID, + libc::c_int +); +#[cfg(apple_targets)] +sockopt_impl!( + /// Get the audit token of the peer process of a connected unix domain + /// socket. + LocalPeerToken, + GetOnly, + libc::SOL_LOCAL, + libc::LOCAL_PEERTOKEN, + super::audit_token_t +); +#[cfg(linux_android)] sockopt_impl!( /// Return the credentials of the foreign process connected to this socket. PeerCredentials, @@ -505,7 +680,27 @@ sockopt_impl!( libc::SO_PEERCRED, super::UnixCredentials ); -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(target_os = "linux")] +sockopt_impl!( + /// Return the pidfd of the foreign process connected to this socket. + PeerPidfd, + GetOnly, + libc::SOL_SOCKET, + libc::SO_PEERPIDFD, + OwnedFd +); +#[cfg(target_os = "freebsd")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Get backlog limit of the socket + ListenQLimit, + GetOnly, + libc::SOL_SOCKET, + libc::SO_LISTENQLIMIT, + u32 +); +#[cfg(apple_targets)] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -517,12 +712,7 @@ sockopt_impl!( libc::TCP_KEEPALIVE, u32 ); -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux" -))] +#[cfg(any(freebsdlike, linux_android))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -535,17 +725,21 @@ sockopt_impl!( u32 ); cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(any(linux_android, apple_targets))] { 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(any(target_os = "openbsd", target_os = "haiku")))] +#[cfg(not(any( + target_os = "openbsd", + target_os = "haiku", + target_os = "redox" +)))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -557,7 +751,7 @@ sockopt_impl!( libc::TCP_KEEPCNT, u32 ); -#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "fuchsia"))] sockopt_impl!( #[allow(missing_docs)] // Not documented by Linux! @@ -567,7 +761,11 @@ sockopt_impl!( libc::TCP_REPAIR, u32 ); -#[cfg(not(any(target_os = "openbsd", target_os = "haiku")))] +#[cfg(not(any( + target_os = "openbsd", + target_os = "haiku", + target_os = "redox" +)))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -591,6 +789,26 @@ sockopt_impl!( libc::TCP_USER_TIMEOUT, u32 ); +#[cfg(linux_android)] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Enables TCP Fast Open (RFC 7413) on a connecting socket. If a fast open + /// cookie is not available (first attempt to connect), `connect` syscall + /// will behave as usual, except for internally trying to solicit a cookie + /// from remote peer. When cookie is available, the next `connect` syscall + /// will immediately succeed without actually establishing TCP connection. + /// The connection establishment will be defered till the next `write` or + /// `sendmsg` syscalls on the socket, allowing TCP prtocol to establish + /// connection and send data in the same packets. Note: calling `read` right + /// after `connect` without `write` on the socket will cause the blocking + /// socket to be blocked forever. + TcpFastOpenConnect, + Both, + libc::IPPROTO_TCP, + libc::TCP_FASTOPEN_CONNECT, + bool +); sockopt_impl!( /// Sets or gets the maximum socket receive buffer in bytes. RcvBuf, @@ -607,7 +825,7 @@ sockopt_impl!( libc::SO_SNDBUF, usize ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] 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 @@ -618,7 +836,7 @@ sockopt_impl!( libc::SO_RCVBUFFORCE, usize ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] 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 @@ -647,7 +865,7 @@ sockopt_impl!( libc::SO_ACCEPTCONN, bool ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] sockopt_impl!( /// Bind this socket to a particular device like “eth0”. BindToDevice, @@ -656,7 +874,7 @@ sockopt_impl!( libc::SO_BINDTODEVICE, OsString<[u8; libc::IFNAMSIZ]> ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -668,7 +886,7 @@ sockopt_impl!( libc::SO_ORIGINAL_DST, libc::sockaddr_in ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] sockopt_impl!( #[allow(missing_docs)] // Not documented by Linux! @@ -678,7 +896,7 @@ sockopt_impl!( libc::IP6T_SO_ORIGINAL_DST, libc::sockaddr_in6 ); -#[cfg(target_os = "linux")] +#[cfg(linux_android)] sockopt_impl!( /// Specifies exact type of timestamping information collected by the kernel /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html) @@ -688,7 +906,13 @@ sockopt_impl!( libc::SO_TIMESTAMPING, super::TimestampingFlag ); -#[cfg(not(target_os = "haiku"))] +#[cfg(not(any( + target_os = "aix", + target_os = "haiku", + target_os = "hurd", + target_os = "redox", + target_os = "cygwin" +)))] sockopt_impl!( /// Enable or disable the receiving of the `SO_TIMESTAMP` control message. ReceiveTimestamp, @@ -697,7 +921,7 @@ sockopt_impl!( libc::SO_TIMESTAMP, bool ); -#[cfg(target_os = "linux")] +#[cfg(linux_android)] sockopt_impl!( /// Enable or disable the receiving of the `SO_TIMESTAMPNS` control message. ReceiveTimestampns, @@ -706,7 +930,17 @@ sockopt_impl!( libc::SO_TIMESTAMPNS, bool ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[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, + super::SocketTimestamp +); +#[cfg(linux_android)] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -741,6 +975,46 @@ sockopt_impl!( 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 @@ -751,7 +1025,7 @@ sockopt_impl!( libc::SO_MARK, u32 ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] sockopt_impl!( /// Enable or disable the receiving of the `SCM_CREDENTIALS` control /// message. @@ -773,13 +1047,7 @@ sockopt_impl!( libc::TCP_CONGESTION, OsString<[u8; TCP_CA_NAME_MAX]> ); -#[cfg(any( - target_os = "android", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", -))] +#[cfg(any(linux_android, apple_targets, target_os = "netbsd"))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -791,15 +1059,7 @@ sockopt_impl!( libc::IP_PKTINFO, bool ); -#[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(any(linux_android, bsd))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -811,13 +1071,20 @@ sockopt_impl!( libc::IPV6_RECVPKTINFO, bool ); -#[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] + +#[cfg(any(linux_android, bsd))] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// Pass an `IPV6_PKTINFO` ancillary message that contains a in6_pktinfo + /// structure that supplies some information about the incoming packet. + Ipv6PacketInfo, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_PKTINFO, + bool +); +#[cfg(bsd)] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -829,13 +1096,7 @@ sockopt_impl!( libc::IP_RECVIF, bool ); -#[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(bsd)] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -847,7 +1108,7 @@ sockopt_impl!( libc::IP_RECVDSTADDR, bool ); -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -893,7 +1154,7 @@ sockopt_impl!( libc::SO_TXTIME, libc::sock_txtime ); -#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "fuchsia"))] 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 @@ -914,7 +1175,7 @@ sockopt_impl!( libc::IPV6_V6ONLY, bool ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] sockopt_impl!( /// Enable extended reliable error message passing. Ipv4RecvErr, @@ -923,7 +1184,7 @@ sockopt_impl!( libc::IP_RECVERR, bool ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] sockopt_impl!( /// Control receiving of asynchronous error options. Ipv6RecvErr, @@ -932,7 +1193,7 @@ sockopt_impl!( libc::IPV6_RECVERR, bool ); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] sockopt_impl!( /// Fetch the current system-estimated Path MTU. IpMtu, @@ -941,7 +1202,7 @@ sockopt_impl!( libc::IP_MTU, libc::c_int ); -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] sockopt_impl!( /// Set or retrieve the current time-to-live field that is used in every /// packet sent from this socket. @@ -951,7 +1212,18 @@ sockopt_impl!( libc::IP_TTL, libc::c_int ); -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + /// Enables a receiving socket to retrieve the Time-to-Live (TTL) field + /// from incoming IPv4 packets. + Ipv4RecvTtl, + Both, + libc::IPPROTO_IP, + libc::IP_RECVTTL, + bool +); +#[cfg(any(apple_targets, linux_android, target_os = "freebsd"))] sockopt_impl!( /// Set the unicast hop limit for the socket. Ipv6Ttl, @@ -960,7 +1232,18 @@ sockopt_impl!( libc::IPV6_UNICAST_HOPS, libc::c_int ); -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +sockopt_impl!( + /// Enables a receiving socket to retrieve the Hop Limit field + /// (similar to TTL in IPv4) from incoming IPv6 packets. + Ipv6RecvHopLimit, + Both, + libc::IPPROTO_IPV6, + libc::IPV6_RECVHOPLIMIT, + bool +); +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -972,7 +1255,7 @@ sockopt_impl!( libc::IPV6_ORIGDSTADDR, bool ); -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(apple_targets)] sockopt_impl!( /// Set "don't fragment packet" flag on the IP packet. IpDontFrag, @@ -981,12 +1264,7 @@ sockopt_impl!( libc::IP_DONTFRAG, bool ); -#[cfg(any( - target_os = "android", - target_os = "ios", - target_os = "linux", - target_os = "macos", -))] +#[cfg(any(linux_android, apple_targets))] sockopt_impl!( /// Set "don't fragment packet" flag on the IPv6 packet. Ipv6DontFrag, @@ -995,23 +1273,58 @@ sockopt_impl!( libc::IPV6_DONTFRAG, bool ); +#[cfg(apple_targets)] +#[cfg(feature = "net")] +sockopt_impl!( + /// Get the utun interface name. + UtunIfname, + GetOnly, + libc::SYSPROTO_CONTROL, + libc::UTUN_OPT_IFNAME, + CString, + GetCString<[u8; libc::IFNAMSIZ]> +); + +#[cfg(solarish)] +sockopt_impl!( + /// Enable/disable exclusive binding. + /// Prevent multiple sockets to bind to the same + /// address:port, neutralizing `SO_REUSEADDR` effect. + ExclBind, + Both, + libc::SOL_SOCKET, + libc::SO_EXCLBIND, + bool +); +#[cfg(target_os = "linux")] +sockopt_impl!( + /// To be used with `ReusePort`, + /// we can then attach a BPF (classic) + /// to set how the packets are assigned + /// to the socket (e.g. cpu distribution). + AttachReusePortCbpf, + SetOnly, + libc::SOL_SOCKET, + libc::SO_ATTACH_REUSEPORT_CBPF, + libc::sock_fprog +); #[allow(missing_docs)] // Not documented by Linux! -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[derive(Copy, Clone, Debug)] pub struct AlgSetAeadAuthSize; // ALG_SET_AEAD_AUTH_SIZE read the length from passed `option_len` // See https://elixir.bootlin.com/linux/v4.4/source/crypto/af_alg.c#L222 -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] 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, + fd.as_fd().as_raw_fd(), libc::SOL_ALG, libc::ALG_SET_AEAD_AUTHSIZE, ::std::ptr::null(), @@ -1024,31 +1337,31 @@ impl SetSockOpt for AlgSetAeadAuthSize { #[allow(missing_docs)] // Not documented by Linux! -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[derive(Clone, Debug)] pub struct AlgSetKey(::std::marker::PhantomData); -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] impl Default for AlgSetKey { fn default() -> Self { AlgSetKey(Default::default()) } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] 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, + fd.as_fd().as_raw_fd(), libc::SOL_ALG, libc::ALG_SET_KEY, - val.as_ref().as_ptr() as *const _, + val.as_ref().as_ptr().cast(), val.as_ref().len() as libc::socklen_t, ); Errno::result(res).map(drop) @@ -1056,6 +1369,211 @@ where } } +/// Set the Upper Layer Protocol (ULP) on the TCP socket. +/// +/// For example, to enable the TLS ULP on a socket, the C function call would be: +/// +/// ```c +/// setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls")); +/// ``` +/// +/// ... and the `nix` equivalent is: +/// +/// ```ignore,rust +/// setsockopt(sock, TcpUlp::default(), b"tls"); +/// ``` +/// +/// Note that the ULP name does not need a trailing NUL terminator (`\0`). +#[cfg(linux_android)] +#[derive(Clone, Debug)] +pub struct TcpUlp(::std::marker::PhantomData); + +#[cfg(linux_android)] +impl Default for TcpUlp { + fn default() -> Self { + TcpUlp(Default::default()) + } +} + +#[cfg(linux_android)] +impl SetSockOpt for TcpUlp +where + T: AsRef<[u8]> + Clone, +{ + type Val = T; + + fn set(&self, fd: &F, val: &Self::Val) -> Result<()> { + unsafe { + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_TCP, + libc::TCP_ULP, + val.as_ref().as_ptr().cast(), + val.as_ref().len() as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +/// Value used with the [`TcpTlsTx`] and [`TcpTlsRx`] socket options. +#[cfg(target_os = "linux")] +#[derive(Copy, Clone, Debug)] +pub enum TlsCryptoInfo { + /// AES-128-GCM + Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128), + + /// AES-256-GCM + Aes256Gcm(libc::tls12_crypto_info_aes_gcm_256), + + /// CHACHA20-POLY1305 + Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305), +} + +/// Set the Kernel TLS write parameters on the TCP socket. +/// +/// For example, the C function call would be: +/// +/// ```c +/// setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info)); +/// ``` +/// +/// ... and the `nix` equivalent is: +/// +/// ```ignore,rust +/// setsockopt(sock, TcpTlsTx, &crypto_info); +/// ``` +#[cfg(target_os = "linux")] +#[derive(Copy, Clone, Debug)] +pub struct TcpTlsTx; + +#[cfg(target_os = "linux")] +impl SetSockOpt for TcpTlsTx { + type Val = TlsCryptoInfo; + + fn set(&self, fd: &F, val: &Self::Val) -> Result<()> { + let (ffi_ptr, ffi_len) = match val { + TlsCryptoInfo::Aes128Gcm(crypto_info) => { + (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info)) + } + TlsCryptoInfo::Aes256Gcm(crypto_info) => { + (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info)) + } + TlsCryptoInfo::Chacha20Poly1305(crypto_info) => { + (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info)) + } + }; + unsafe { + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_TLS, + libc::TLS_TX, + ffi_ptr, + ffi_len as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +/// Set the Kernel TLS read parameters on the TCP socket. +/// +/// For example, the C function call would be: +/// +/// ```c +/// setsockopt(sock, SOL_TLS, TLS_RX, &crypto_info, sizeof(crypto_info)); +/// ``` +/// +/// ... and the `nix` equivalent is: +/// +/// ```ignore,rust +/// setsockopt(sock, TcpTlsRx, &crypto_info); +/// ``` +#[cfg(target_os = "linux")] +#[derive(Copy, Clone, Debug)] +pub struct TcpTlsRx; + +#[cfg(target_os = "linux")] +impl SetSockOpt for TcpTlsRx { + type Val = TlsCryptoInfo; + + fn set(&self, fd: &F, val: &Self::Val) -> Result<()> { + let (ffi_ptr, ffi_len) = match val { + TlsCryptoInfo::Aes128Gcm(crypto_info) => { + (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info)) + } + TlsCryptoInfo::Aes256Gcm(crypto_info) => { + (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info)) + } + TlsCryptoInfo::Chacha20Poly1305(crypto_info) => { + (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info)) + } + }; + unsafe { + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_TLS, + libc::TLS_RX, + ffi_ptr, + ffi_len as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +#[cfg(target_os = "illumos")] +#[derive(Copy, Clone, Debug)] +/// Attach a named filter to this socket to be able to +/// defer when anough byte had been buffered by the kernel +pub struct FilterAttach; + +#[cfg(target_os = "illumos")] +impl SetSockOpt for FilterAttach { + type Val = OsStr; + + fn set(&self, fd: &F, val: &Self::Val) -> Result<()> { + if val.len() > libc::FILNAME_MAX as usize { + return Err(Errno::EINVAL); + } + unsafe { + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_FILTER, + libc::FIL_ATTACH, + val.as_bytes().as_ptr().cast(), + val.len() as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} + +#[cfg(target_os = "illumos")] +#[derive(Copy, Clone, Debug)] +/// Detach a socket filter previously attached with FIL_ATTACH +pub struct FilterDetach; + +#[cfg(target_os = "illumos")] +impl SetSockOpt for FilterDetach { + type Val = OsStr; + + fn set(&self, fd: &F, val: &Self::Val) -> Result<()> { + if val.len() > libc::FILNAME_MAX as usize { + return Err(Errno::EINVAL); + } + unsafe { + let res = libc::setsockopt( + fd.as_fd().as_raw_fd(), + libc::SOL_FILTER, + libc::FIL_DETACH, + val.as_bytes().as_ptr().cast(), + val.len() as libc::socklen_t, + ); + Errno::result(res).map(drop) + } + } +} /* * * ===== Accessor helpers ===== @@ -1063,7 +1581,9 @@ where */ /// Helper trait that describes what is expected from a `GetSockOpt` getter. -trait Get { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +pub trait Get { /// Returns an uninitialized value. fn uninit() -> Self; /// Returns a pointer to the stored value. This pointer will be passed to the system's @@ -1077,7 +1597,9 @@ trait Get { } /// Helper trait that describes what is expected from a `SetSockOpt` setter. -trait Set<'a, T> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +pub trait Set<'a, T> { /// Initialize the setter with a given value. fn new(val: &'a T) -> Self; /// Returns a pointer to the stored value. This pointer will be passed to the system's @@ -1089,7 +1611,10 @@ trait Set<'a, T> { } /// Getter for an arbitrary `struct`. -struct GetStruct { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct GetStruct { len: socklen_t, val: MaybeUninit, } @@ -1103,7 +1628,7 @@ impl Get for GetStruct { } fn ffi_ptr(&mut self) -> *mut c_void { - self.val.as_mut_ptr() as *mut c_void + self.val.as_mut_ptr().cast() } fn ffi_len(&mut self) -> *mut socklen_t { @@ -1116,12 +1641,15 @@ impl Get for GetStruct { mem::size_of::(), "invalid getsockopt implementation" ); - self.val.assume_init() + unsafe { self.val.assume_init() } } } /// Setter for an arbitrary `struct`. -struct SetStruct<'a, T: 'static> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct SetStruct<'a, T: 'static> { ptr: &'a T, } @@ -1140,7 +1668,10 @@ impl<'a, T> Set<'a, T> for SetStruct<'a, T> { } /// Getter for a boolean value. -struct GetBool { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetBool { len: socklen_t, val: MaybeUninit, } @@ -1154,7 +1685,7 @@ impl Get for GetBool { } fn ffi_ptr(&mut self) -> *mut c_void { - self.val.as_mut_ptr() as *mut c_void + self.val.as_mut_ptr().cast() } fn ffi_len(&mut self) -> *mut socklen_t { @@ -1167,12 +1698,15 @@ impl Get for GetBool { mem::size_of::(), "invalid getsockopt implementation" ); - self.val.assume_init() != 0 + unsafe { self.val.assume_init() != 0 } } } /// Setter for a boolean value. -struct SetBool { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetBool { val: c_int, } @@ -1188,16 +1722,21 @@ impl<'a> Set<'a, bool> for SetBool { } fn ffi_len(&self) -> socklen_t { - mem::size_of::() as socklen_t + mem::size_of_val(&self.val) as socklen_t } } /// Getter for an `u8` value. -struct GetU8 { +#[cfg(feature = "net")] +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetU8 { len: socklen_t, val: MaybeUninit, } +#[cfg(feature = "net")] impl Get for GetU8 { fn uninit() -> Self { GetU8 { @@ -1207,7 +1746,7 @@ impl Get for GetU8 { } fn ffi_ptr(&mut self) -> *mut c_void { - self.val.as_mut_ptr() as *mut c_void + self.val.as_mut_ptr().cast() } fn ffi_len(&mut self) -> *mut socklen_t { @@ -1220,15 +1759,19 @@ impl Get for GetU8 { mem::size_of::(), "invalid getsockopt implementation" ); - self.val.assume_init() + unsafe { self.val.assume_init() } } } /// Setter for an `u8` value. -struct SetU8 { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetU8 { val: u8, } +#[cfg(feature = "net")] impl<'a> Set<'a, u8> for SetU8 { fn new(val: &'a u8) -> SetU8 { SetU8 { val: *val } @@ -1239,12 +1782,15 @@ impl<'a> Set<'a, u8> for SetU8 { } fn ffi_len(&self) -> socklen_t { - mem::size_of::() as socklen_t + mem::size_of_val(&self.val) as socklen_t } } /// Getter for an `usize` value. -struct GetUsize { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetUsize { len: socklen_t, val: MaybeUninit, } @@ -1258,7 +1804,7 @@ impl Get for GetUsize { } fn ffi_ptr(&mut self) -> *mut c_void { - self.val.as_mut_ptr() as *mut c_void + self.val.as_mut_ptr().cast() } fn ffi_len(&mut self) -> *mut socklen_t { @@ -1271,12 +1817,15 @@ impl Get for GetUsize { mem::size_of::(), "invalid getsockopt implementation" ); - self.val.assume_init() as usize + unsafe { self.val.assume_init() as usize } } } /// Setter for an `usize` value. -struct SetUsize { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetUsize { val: c_int, } @@ -1290,12 +1839,77 @@ impl<'a> Set<'a, usize> for SetUsize { } fn ffi_len(&self) -> socklen_t { - mem::size_of::() as socklen_t + mem::size_of_val(&self.val) as socklen_t + } +} + + +/// Getter for a `OwnedFd` value. +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct GetOwnedFd { + len: socklen_t, + val: MaybeUninit, +} + +impl Get for GetOwnedFd { + fn uninit() -> Self { + GetOwnedFd { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr().cast() + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> OwnedFd { + use std::os::fd::{FromRawFd, RawFd}; + + assert_eq!( + self.len as usize, + mem::size_of::(), + "invalid getsockopt implementation" + ); + unsafe { OwnedFd::from_raw_fd(self.val.assume_init() as RawFd) } + } +} + +/// Setter for an `OwnedFd` value. +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetOwnedFd { + val: c_int, +} + +impl<'a> Set<'a, OwnedFd> for SetOwnedFd { + fn new(val: &'a OwnedFd) -> SetOwnedFd { + use std::os::fd::AsRawFd; + + SetOwnedFd { val: val.as_raw_fd() as c_int } + } + + fn ffi_ptr(&self) -> *const c_void { + &self.val as *const c_int as *const c_void + } + + fn ffi_len(&self) -> socklen_t { + mem::size_of_val(&self.val) as socklen_t } } /// Getter for a `OsString` value. -struct GetOsString> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Debug)] +pub struct GetOsString> { len: socklen_t, val: MaybeUninit, } @@ -1309,7 +1923,7 @@ impl> Get for GetOsString { } fn ffi_ptr(&mut self) -> *mut c_void { - self.val.as_mut_ptr() as *mut c_void + self.val.as_mut_ptr().cast() } fn ffi_len(&mut self) -> *mut socklen_t { @@ -1318,25 +1932,37 @@ impl> Get for GetOsString { unsafe fn assume_init(self) -> OsString { let len = self.len as usize; - let mut v = self.val.assume_init(); - OsStr::from_bytes(&v.as_mut()[0..len]).to_owned() + let mut v = unsafe { self.val.assume_init() }; + if let Ok(cs) = CStr::from_bytes_until_nul(&v.as_mut()[0..len]) { + // It's legal for the kernel to return any number of NULs at the + // end of the string. C applications don't care, after all. + OsStr::from_bytes(cs.to_bytes()) + } else { + // Even zero NULs is possible. + OsStr::from_bytes(&v.as_mut()[0..len]) + } + .to_owned() } } /// Setter for a `OsString` value. -struct SetOsString<'a> { +// Hide the docs, because it's an implementation detail of `sockopt_impl!` +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SetOsString<'a> { val: &'a OsStr, } +#[cfg(any(target_os = "freebsd", linux_android, target_os = "illumos"))] impl<'a> Set<'a, OsString> for SetOsString<'a> { - fn new(val: &'a OsString) -> SetOsString { + fn new(val: &OsString) -> SetOsString { SetOsString { val: val.as_os_str(), } } fn ffi_ptr(&self) -> *const c_void { - self.val.as_bytes().as_ptr() as *const c_void + self.val.as_bytes().as_ptr().cast() } fn ffi_len(&self) -> socklen_t { @@ -1344,79 +1970,34 @@ impl<'a> Set<'a, OsString> for SetOsString<'a> { } } -#[cfg(test)] -mod test { - #[cfg(any(target_os = "android", target_os = "linux"))] - #[test] - fn can_get_peercred_on_unix_socket() { - use super::super::*; +/// Getter for a `CString` value. +#[cfg(apple_targets)] +struct GetCString> { + len: socklen_t, + val: MaybeUninit, +} - let (a, b) = socketpair( - AddressFamily::Unix, - SockType::Stream, - None, - SockFlag::empty(), - ) - .unwrap(); - let a_cred = getsockopt(a, super::PeerCredentials).unwrap(); - let b_cred = getsockopt(b, super::PeerCredentials).unwrap(); - assert_eq!(a_cred, b_cred); - assert_ne!(a_cred.pid(), 0); +#[cfg(apple_targets)] +impl> Get for GetCString { + fn uninit() -> Self { + GetCString { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } } - #[test] - fn is_socket_type_unix() { - use super::super::*; - use crate::unistd::close; - - let (a, b) = socketpair( - AddressFamily::Unix, - SockType::Stream, - None, - SockFlag::empty(), - ) - .unwrap(); - let a_type = getsockopt(a, super::SockType).unwrap(); - assert_eq!(a_type, SockType::Stream); - close(a).unwrap(); - close(b).unwrap(); + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr().cast() } - #[test] - fn is_socket_type_dgram() { - use super::super::*; - use crate::unistd::close; - - let s = socket( - AddressFamily::Inet, - SockType::Datagram, - SockFlag::empty(), - None, - ) - .unwrap(); - let s_type = getsockopt(s, super::SockType).unwrap(); - assert_eq!(s_type, SockType::Datagram); - close(s).unwrap(); + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len } - #[cfg(any(target_os = "freebsd", target_os = "linux"))] - #[test] - fn can_get_listen_on_tcp_socket() { - use super::super::*; - use crate::unistd::close; - - let s = socket( - AddressFamily::Inet, - SockType::Stream, - SockFlag::empty(), - None, - ) - .unwrap(); - let s_listening = getsockopt(s, super::AcceptConn).unwrap(); - assert!(!s_listening); - listen(s, 10).unwrap(); - let s_listening2 = getsockopt(s, super::AcceptConn).unwrap(); - assert!(s_listening2); - close(s).unwrap(); + unsafe fn assume_init(self) -> CString { + let mut v = unsafe { self.val.assume_init() }; + CStr::from_bytes_until_nul(v.as_mut()) + .expect("string should be null-terminated") + .to_owned() } } diff --git a/src/sys/stat.rs b/src/sys/stat.rs index 78203bfb..0116130a 100644 --- a/src/sys/stat.rs +++ b/src/sys/stat.rs @@ -1,20 +1,15 @@ -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +#[cfg(any(apple_targets, target_os = "openbsd"))] pub use libc::c_uint; -#[cfg(any( - target_os = "netbsd", - target_os = "freebsd", - target_os = "dragonfly" -))] +#[cfg(any(target_os = "netbsd", freebsdlike))] pub use libc::c_ulong; pub use libc::stat as FileStat; pub use libc::{dev_t, mode_t}; #[cfg(not(target_os = "redox"))] -use crate::fcntl::{at_rawfd, AtFlags}; +use crate::fcntl::AtFlags; use crate::sys::time::{TimeSpec, TimeVal}; use crate::{errno::Errno, NixPath, Result}; use std::mem; -use std::os::unix::io::RawFd; libc_bitflags!( /// "File type" flags for `mknod` and related functions. @@ -43,7 +38,7 @@ libc_bitflags! { S_IXUSR; /// Read write and execute for group. S_IRWXG; - /// Read fr group. + /// Read for group. S_IRGRP; /// Write for group. S_IWGRP; @@ -65,26 +60,14 @@ libc_bitflags! { } } -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +#[cfg(any(apple_targets, target_os = "openbsd"))] pub type type_of_file_flag = c_uint; -#[cfg(any( - target_os = "netbsd", - target_os = "freebsd", - target_os = "dragonfly" -))] +#[cfg(any(freebsdlike, target_os = "netbsd"))] 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" -))] +#[cfg(bsd)] 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; @@ -101,7 +84,7 @@ libc_bitflags! { #[cfg(any(target_os = "dragonfly"))] SF_NOHISTORY; /// The file may not be renamed or deleted. - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] SF_NOUNLINK; /// Mask of superuser changeable flags SF_SETTABLE; @@ -121,14 +104,13 @@ libc_bitflags! { #[cfg(any(target_os = "dragonfly"))] UF_CACHE; /// File is compressed at the file system level. - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] 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", + apple_targets, ))] UF_HIDDEN; /// The file may not be changed. @@ -138,7 +120,7 @@ libc_bitflags! { #[cfg(any(target_os = "dragonfly"))] UF_NOHISTORY; /// The file may not be renamed or deleted. - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] UF_NOUNLINK; /// The file is offline, or has the Windows and CIFS /// `FILE_ATTRIBUTE_OFFLINE` attribute. @@ -162,7 +144,7 @@ libc_bitflags! { #[cfg(any(target_os = "freebsd"))] UF_SYSTEM; /// File renames and deletes are tracked. - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(apple_targets)] UF_TRACKED; #[cfg(any(target_os = "dragonfly"))] UF_XLINK; @@ -177,32 +159,28 @@ pub fn mknod( 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", - target_os = "haiku" -)))] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn mknodat( - dirfd: RawFd, +#[cfg(not(any(apple_targets, target_os = "redox", target_os = "haiku")))] +pub fn mknodat( + dirfd: Fd, path: &P, kind: SFlag, perm: Mode, dev: dev_t, ) -> Result<()> { + use std::os::fd::AsRawFd; + let res = path.with_nix_path(|cstr| unsafe { libc::mknodat( - dirfd, + dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), - kind.bits | perm.bits() as mode_t, + kind.bits() | perm.bits() as mode_t, dev, ) })?; @@ -211,19 +189,16 @@ pub fn mknodat( } #[cfg(target_os = "linux")] -#[cfg_attr(docsrs, doc(cfg(all())))] pub const fn major(dev: dev_t) -> u64 { ((dev >> 32) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff) } #[cfg(target_os = "linux")] -#[cfg_attr(docsrs, doc(cfg(all())))] pub const fn minor(dev: dev_t) -> u64 { ((dev >> 12) & 0xffff_ff00) | ((dev) & 0x0000_00ff) } #[cfg(target_os = "linux")] -#[cfg_attr(docsrs, doc(cfg(all())))] pub const fn makedev(major: u64, minor: u64) -> dev_t { ((major & 0xffff_f000) << 32) | ((major & 0x0000_0fff) << 8) @@ -258,9 +233,11 @@ pub fn lstat(path: &P) -> Result { Ok(unsafe { dst.assume_init() }) } -pub fn fstat(fd: RawFd) -> Result { +pub fn fstat(fd: Fd) -> Result { + use std::os::fd::AsRawFd; + let mut dst = mem::MaybeUninit::uninit(); - let res = unsafe { libc::fstat(fd, dst.as_mut_ptr()) }; + let res = unsafe { libc::fstat(fd.as_fd().as_raw_fd(), dst.as_mut_ptr()) }; Errno::result(res)?; @@ -268,16 +245,17 @@ pub fn fstat(fd: RawFd) -> Result { } #[cfg(not(target_os = "redox"))] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn fstatat( - dirfd: RawFd, +pub fn fstatat( + dirfd: Fd, pathname: &P, f: AtFlags, ) -> Result { + use std::os::fd::AsRawFd; + let mut dst = mem::MaybeUninit::uninit(); let res = pathname.with_nix_path(|cstr| unsafe { libc::fstatat( - dirfd, + dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), dst.as_mut_ptr(), f.bits() as libc::c_int, @@ -294,8 +272,11 @@ pub fn fstatat( /// # References /// /// [fchmod(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html). -pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> { - let res = unsafe { libc::fchmod(fd, mode.bits() as mode_t) }; +pub fn fchmod(fd: Fd, mode: Mode) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = + unsafe { libc::fchmod(fd.as_fd().as_raw_fd(), mode.bits() as mode_t) }; Errno::result(res).map(drop) } @@ -311,12 +292,12 @@ pub enum FchmodatFlags { /// /// The file to be changed is determined relative to the directory associated /// with the file descriptor `dirfd` or the current working directory -/// if `dirfd` is `None`. +/// if `dirfd` is [`AT_FDCWD`](crate::fcntl::AT_FDCWD). /// /// If `flag` is `FchmodatFlags::NoFollowSymlink` and `path` names a symbolic link, /// then the mode of the symbolic link is changed. /// -/// `fchmodat(None, path, mode, FchmodatFlags::FollowSymlink)` is identical to +/// `fchmodat(AT_FDCWD, path, mode, FchmodatFlags::FollowSymlink)` is identical to /// a call `libc::chmod(path, mode)`. That's why `chmod` is unimplemented /// in the `nix` crate. /// @@ -324,20 +305,21 @@ 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, +pub fn fchmodat( + dirfd: Fd, path: &P, mode: Mode, flag: FchmodatFlags, ) -> Result<()> { + use std::os::fd::AsRawFd; + 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), + dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), mode.bits() as mode_t, atflag.bits() as libc::c_int, @@ -383,12 +365,10 @@ pub fn utimes( #[cfg(any( target_os = "linux", target_os = "haiku", - target_os = "ios", - target_os = "macos", + apple_targets, target_os = "freebsd", target_os = "netbsd" ))] -#[cfg_attr(docsrs, doc(cfg(all())))] pub fn lutimes( path: &P, atime: &TimeVal, @@ -404,13 +384,22 @@ pub fn lutimes( /// Change the access and modification times of the file specified by a file descriptor. /// +/// If you want to set the timestamp to now, use `TimeSpec::UTIME_NOW`. Use +/// `TimeSpec::UTIME_OMIT` if you don't want to change it. +/// /// # References /// /// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html). #[inline] -pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> { +pub fn futimens( + fd: Fd, + atime: &TimeSpec, + mtime: &TimeSpec, +) -> Result<()> { + use std::os::fd::AsRawFd; + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; - let res = unsafe { libc::futimens(fd, ×[0]) }; + let res = unsafe { libc::futimens(fd.as_fd().as_raw_fd(), ×[0]) }; Errno::result(res).map(drop) } @@ -427,27 +416,31 @@ pub enum UtimensatFlags { /// /// The file to be changed is determined relative to the directory associated /// with the file descriptor `dirfd` or the current working directory -/// if `dirfd` is `None`. +/// if `dirfd` is [`AT_FDCWD`](crate::fcntl::AT_FDCWD). /// /// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link, /// then the mode of the symbolic link is changed. /// -/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)` is identical to +/// `utimensat(AT_FDCWD, path, times, UtimensatFlags::FollowSymlink)` is identical to /// `utimes(path, times)`. The latter is a deprecated API so prefer using the /// former if the platforms you care about support it. /// +/// If you want to set the timestamp to now, use `TimeSpec::UTIME_NOW`. Use +/// `TimeSpec::UTIME_OMIT` if you don't want to change it. +/// /// # References /// /// [utimensat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html). #[cfg(not(target_os = "redox"))] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn utimensat( - dirfd: Option, +pub fn utimensat( + dirfd: Fd, path: &P, atime: &TimeSpec, mtime: &TimeSpec, flag: UtimensatFlags, ) -> Result<()> { + use std::os::fd::AsRawFd; + let atflag = match flag { UtimensatFlags::FollowSymlink => AtFlags::empty(), UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, @@ -455,7 +448,7 @@ pub fn utimensat( let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; let res = path.with_nix_path(|cstr| unsafe { libc::utimensat( - at_rawfd(dirfd), + dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), ×[0], atflag.bits() as libc::c_int, @@ -465,15 +458,28 @@ pub fn utimensat( Errno::result(res).map(drop) } +/// Create a directory at the path specified by `dirfd` and `path`. +/// +/// If `path` is a relative path, then it is interpreted relative to the directory +/// referred to by the file descriptor `dirfd`. (One can use [`AT_FDCWD`][link] to +/// specify the current working directory in `dirfd`). If `path` is absolute, +/// then `dirfd` is ignored. +/// +/// [link]: crate::fcntl::AT_FDCWD #[cfg(not(target_os = "redox"))] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn mkdirat( - fd: RawFd, +pub fn mkdirat( + dirfd: Fd, path: &P, mode: Mode, ) -> Result<()> { + use std::os::fd::AsRawFd; + let res = path.with_nix_path(|cstr| unsafe { - libc::mkdirat(fd, cstr.as_ptr(), mode.bits() as mode_t) + libc::mkdirat( + dirfd.as_fd().as_raw_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 2f0a9140..c0a9f6c9 100644 --- a/src/sys/statfs.rs +++ b/src/sys/statfs.rs @@ -1,24 +1,15 @@ //! Get filesystem statistics, non-portably //! //! See [`statvfs`](crate::sys::statvfs) for a portable alternative. -#[cfg(not(any(target_os = "linux", target_os = "android")))] +#[cfg(not(any(linux_android, target_os = "cygwin")))] use std::ffi::CStr; use std::fmt::{self, Debug}; use std::mem; -use std::os::unix::io::AsRawFd; +use std::os::unix::io::{AsFd, AsRawFd}; use cfg_if::cfg_if; -#[cfg(all( - feature = "mount", - any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" - ) -))] +#[cfg(all(feature = "mount", bsd))] use crate::mount::MntFlags; #[cfg(target_os = "linux")] use crate::sys::statvfs::FsFlags; @@ -26,28 +17,29 @@ 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())))] +#[cfg(not(any(target_os = "android", target_os = "cygwin")))] pub type fsid_t = libc::fsid_t; +/// Identifies a mounted file system +#[cfg(target_os = "cygwin")] +pub type fsid_t = libc::c_long; cfg_if! { - if #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] { + if #[cfg(any(linux_android, target_os = "fuchsia"))] { type type_of_statfs = libc::statfs64; - const LIBC_FSTATFS: unsafe extern fn + const LIBC_FSTATFS: unsafe extern "C" fn (fd: libc::c_int, buf: *mut type_of_statfs) -> libc::c_int = libc::fstatfs64; - const LIBC_STATFS: unsafe extern fn + const LIBC_STATFS: unsafe extern "C" 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 + const LIBC_FSTATFS: unsafe extern "C" fn (fd: libc::c_int, buf: *mut type_of_statfs) -> libc::c_int = libc::fstatfs; - const LIBC_STATFS: unsafe extern fn + const LIBC_STATFS: unsafe extern "C" fn (path: *const libc::c_char, buf: *mut type_of_statfs) -> libc::c_int = libc::statfs; } @@ -62,7 +54,11 @@ pub struct Statfs(type_of_statfs); type fs_type_t = u32; #[cfg(target_os = "android")] type fs_type_t = libc::c_ulong; -#[cfg(all(target_os = "linux", target_arch = "s390x"))] +#[cfg(all( + target_os = "linux", + target_arch = "s390x", + not(target_env = "musl") +))] type fs_type_t = libc::c_uint; #[cfg(all(target_os = "linux", any(target_env = "musl", target_env = "ohos")))] type fs_type_t = libc::c_ulong; @@ -78,223 +74,224 @@ type fs_type_t = libc::c_int; )) ))] type fs_type_t = libc::__fsword_t; +#[cfg(target_os = "cygwin")] +type fs_type_t = libc::c_long; /// Describes the file system type as known by the operating system. #[cfg(any( target_os = "freebsd", target_os = "android", all(target_os = "linux", target_arch = "s390x"), - all(target_os = "linux", any(target_env = "musl", target_env = "ohos")), + all(target_os = "linux", target_env = "musl"), + all(target_os = "linux", target_env = "ohos"), all( target_os = "linux", - not(any(target_arch = "s390x", target_env = "musl", target_env = "ohos")) + not(any(target_arch = "s390x", target_env = "musl")) ), + target_os = "cygwin", ))] #[derive(Eq, Copy, Clone, PartialEq, Debug)] pub struct FsType(pub fs_type_t); // These constants are defined without documentation in the Linux headers, so we // can't very well document them here. -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const BPF_FS_MAGIC: FsType = FsType(libc::BPF_FS_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const BTRFS_SUPER_MAGIC: FsType = FsType(libc::BTRFS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const CGROUP2_SUPER_MAGIC: FsType = FsType(libc::CGROUP2_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const CRAMFS_MAGIC: FsType = FsType(libc::CRAMFS_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const EFS_SUPER_MAGIC: FsType = FsType(libc::EFS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const F2FS_SUPER_MAGIC: FsType = FsType(libc::F2FS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const FUSE_SUPER_MAGIC: FsType = FsType(libc::FUSE_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const FUTEXFS_SUPER_MAGIC: FsType = FsType(libc::FUTEXFS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const HOSTFS_SUPER_MAGIC: FsType = FsType(libc::HOSTFS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const HUGETLBFS_MAGIC: FsType = FsType(libc::HUGETLBFS_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const JFFS2_SUPER_MAGIC: FsType = FsType(libc::JFFS2_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const MINIX2_SUPER_MAGIC2: FsType = FsType(libc::MINIX2_SUPER_MAGIC2 as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const MINIX2_SUPER_MAGIC: FsType = FsType(libc::MINIX2_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const MINIX3_SUPER_MAGIC: FsType = FsType(libc::MINIX3_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const MINIX_SUPER_MAGIC2: FsType = FsType(libc::MINIX_SUPER_MAGIC2 as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const MINIX_SUPER_MAGIC: FsType = FsType(libc::MINIX_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const MSDOS_SUPER_MAGIC: FsType = FsType(libc::MSDOS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const NCP_SUPER_MAGIC: FsType = FsType(libc::NCP_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const NFS_SUPER_MAGIC: FsType = FsType(libc::NFS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const QNX6_SUPER_MAGIC: FsType = FsType(libc::QNX6_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const RDTGROUP_SUPER_MAGIC: FsType = FsType(libc::RDTGROUP_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const REISERFS_SUPER_MAGIC: FsType = FsType(libc::REISERFS_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const SECURITYFS_MAGIC: FsType = FsType(libc::SECURITYFS_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const SELINUX_MAGIC: FsType = FsType(libc::SELINUX_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const SMB_SUPER_MAGIC: FsType = FsType(libc::SMB_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const TMPFS_MAGIC: FsType = FsType(libc::TMPFS_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const TRACEFS_MAGIC: FsType = FsType(libc::TRACEFS_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const UDF_SUPER_MAGIC: FsType = FsType(libc::UDF_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[allow(missing_docs)] pub const USBDEVICE_SUPER_MAGIC: FsType = FsType(libc::USBDEVICE_SUPER_MAGIC as fs_type_t); -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_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"))] +#[cfg(linux_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(any(target_env = "musl", target_env = "ohos")) -))] +#[cfg(all(linux_android, not(target_env = "musl"), not(target_env = "ohos")))] #[allow(missing_docs)] pub const XFS_SUPER_MAGIC: FsType = FsType(libc::XFS_SUPER_MAGIC as fs_type_t); @@ -303,39 +300,37 @@ impl Statfs { #[cfg(not(any( target_os = "openbsd", target_os = "dragonfly", - target_os = "ios", - target_os = "macos" + apple_targets, )))] - #[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())))] + #[cfg(not(any(linux_android, target_os = "cygwin")))] pub fn filesystem_type_name(&self) -> &str { let c_str = unsafe { CStr::from_ptr(self.0.f_fstypename.as_ptr()) }; c_str.to_str().unwrap() } /// Optimal transfer block size - #[cfg(any(target_os = "ios", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(apple_targets)] 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())))] + #[cfg(all( + target_os = "linux", + target_arch = "s390x", + not(target_env = "musl") + ))] pub fn optimal_transfer_size(&self) -> u32 { self.0.f_bsize } @@ -343,9 +338,9 @@ impl Statfs { /// Optimal transfer block size #[cfg(any( target_os = "android", - all(target_os = "linux", any(target_env = "musl", target_env = "ohos")) + all(target_os = "linux", target_env = "musl"), + all(target_os = "linux", target_env = "ohos") ))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn optimal_transfer_size(&self) -> libc::c_ulong { self.0.f_bsize } @@ -360,51 +355,55 @@ impl Statfs { 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())))] + #[cfg(any(apple_targets, target_os = "openbsd"))] pub fn block_size(&self) -> u32 { self.0.f_bsize } /// Size of a block // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 - #[cfg(all(target_os = "linux", target_arch = "s390x"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(all( + target_os = "linux", + target_arch = "s390x", + not(target_env = "musl") + ))] pub fn block_size(&self) -> u32 { self.0.f_bsize } /// Size of a block // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 - #[cfg(all(target_os = "linux", any(target_env = "musl", target_env = "ohos")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(all(target_os = "linux", target_env = "musl"))] + pub fn block_size(&self) -> libc::c_ulong { + self.0.f_bsize + } + + /// Size of a block + // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 + #[cfg(all(target_os = "linux", target_env = "ohos"))] pub fn block_size(&self) -> libc::c_ulong { self.0.f_bsize } @@ -412,7 +411,6 @@ 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 = "uclibc"))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn block_size(&self) -> libc::c_int { self.0.f_bsize } @@ -428,44 +426,30 @@ impl Statfs { 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())))] + #[cfg(any(target_os = "dragonfly", target_os = "cygwin"))] 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())))] + #[cfg(all(feature = "mount", bsd))] #[allow(clippy::unnecessary_cast)] // Not unnecessary on all arches pub fn flags(&self) -> MntFlags { MntFlags::from_bits_truncate(self.0.f_flags as i32) @@ -475,35 +459,34 @@ impl Statfs { // 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())))] + #[cfg(all( + target_os = "linux", + target_arch = "s390x", + not(target_env = "musl") + ))] pub fn maximum_name_length(&self) -> u32 { self.0.f_namelen } /// Maximum length of filenames - #[cfg(all(target_os = "linux", any(target_env = "musl", target_env = "ohos")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(all(target_os = "linux", target_env = "musl"))] pub fn maximum_name_length(&self) -> libc::c_ulong { self.0.f_namelen } /// Maximum length of filenames #[cfg(all(target_os = "linux", target_env = "uclibc"))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn maximum_name_length(&self) -> libc::c_int { self.0.f_namelen } @@ -518,170 +501,137 @@ impl Statfs { target_env = "uclibc" )) ))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn maximum_name_length(&self) -> libc::__fsword_t { self.0.f_namelen } /// Maximum length of filenames #[cfg(target_os = "android")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn maximum_name_length(&self) -> libc::c_ulong { self.0.f_namelen } /// Total data blocks in filesystem #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "android", + apple_targets, + linux_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())))] + #[cfg(any(target_os = "dragonfly", target_os = "cygwin"))] pub fn blocks(&self) -> libc::c_long { self.0.f_blocks } /// Total data blocks in filesystem #[cfg(target_os = "emscripten")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn blocks(&self) -> u32 { self.0.f_blocks } /// Free blocks in filesystem #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "android", + apple_targets, + linux_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())))] + #[cfg(any(target_os = "dragonfly", target_os = "cygwin"))] pub fn blocks_free(&self) -> libc::c_long { self.0.f_bfree } /// Free blocks in filesystem #[cfg(target_os = "emscripten")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn blocks_free(&self) -> u32 { self.0.f_bfree } /// Free blocks available to unprivileged user - #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "android", - target_os = "fuchsia", - target_os = "linux", - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(apple_targets, linux_android, target_os = "fuchsia"))] 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())))] + #[cfg(any(target_os = "dragonfly", target_os = "cygwin"))] pub fn blocks_available(&self) -> libc::c_long { self.0.f_bavail } /// Free blocks available to unprivileged user #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn blocks_available(&self) -> i64 { self.0.f_bavail } /// Free blocks available to unprivileged user #[cfg(target_os = "emscripten")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn blocks_available(&self) -> u32 { self.0.f_bavail } /// Total file nodes in filesystem #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "android", + apple_targets, + linux_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())))] + #[cfg(any(target_os = "dragonfly", target_os = "cygwin"))] pub fn files(&self) -> libc::c_long { self.0.f_files } /// Total file nodes in filesystem #[cfg(target_os = "emscripten")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn files(&self) -> u32 { self.0.f_files } /// Free file nodes in filesystem #[cfg(any( - target_os = "ios", - target_os = "macos", - target_os = "android", + apple_targets, + linux_android, target_os = "fuchsia", target_os = "openbsd", - target_os = "linux", ))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn files_free(&self) -> u64 { self.0.f_ffree } /// Free file nodes in filesystem - #[cfg(target_os = "dragonfly")] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(target_os = "dragonfly", target_os = "cygwin"))] pub fn files_free(&self) -> libc::c_long { self.0.f_ffree } /// Free file nodes in filesystem #[cfg(target_os = "freebsd")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn files_free(&self) -> i64 { self.0.f_ffree } /// Free file nodes in filesystem #[cfg(target_os = "emscripten")] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn files_free(&self) -> u32 { self.0.f_ffree } @@ -695,6 +645,7 @@ impl Statfs { impl Debug for Statfs { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut ds = f.debug_struct("Statfs"); + #[cfg(not(target_os = "cygwin"))] ds.field("optimal_transfer_size", &self.optimal_transfer_size()); ds.field("block_size", &self.block_size()); ds.field("blocks", &self.blocks()); @@ -703,16 +654,7 @@ impl Debug for Statfs { 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" - ) - ))] + #[cfg(all(feature = "mount", bsd))] ds.field("flags", &self.flags()); ds.finish() } @@ -744,114 +686,10 @@ pub fn statfs(path: &P) -> Result { /// # 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())) + Errno::result(LIBC_FSTATFS(fd.as_fd().as_raw_fd(), stat.as_mut_ptr())) .map(|_| Statfs(stat.assume_init())) } } - -#[cfg(test)] -mod test { - use std::fs::File; - - use crate::sys::statfs::*; - use crate::sys::statvfs::*; - use std::path::Path; - - #[test] - fn statfs_call() { - check_statfs("/tmp"); - check_statfs("/dev"); - check_statfs("/run"); - check_statfs("/"); - } - - #[test] - fn fstatfs_call() { - check_fstatfs("/tmp"); - check_fstatfs("/dev"); - check_fstatfs("/run"); - check_fstatfs("/"); - } - - fn check_fstatfs(path: &str) { - if !Path::new(path).exists() { - return; - } - let vfs = statvfs(path.as_bytes()).unwrap(); - let file = File::open(path).unwrap(); - let fs = fstatfs(&file).unwrap(); - assert_fs_equals(fs, vfs); - } - - fn check_statfs(path: &str) { - if !Path::new(path).exists() { - return; - } - let vfs = statvfs(path.as_bytes()).unwrap(); - let fs = statfs(path.as_bytes()).unwrap(); - assert_fs_equals(fs, vfs); - } - - // The cast is not unnecessary on all platforms. - #[allow(clippy::unnecessary_cast)] - fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { - assert_eq!(fs.files() as u64, vfs.files() as u64); - assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); - assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); - } - - // This test is ignored because files_free/blocks_free can change after statvfs call and before - // statfs call. - #[test] - #[ignore] - fn statfs_call_strict() { - check_statfs_strict("/tmp"); - check_statfs_strict("/dev"); - check_statfs_strict("/run"); - check_statfs_strict("/"); - } - - // This test is ignored because files_free/blocks_free can change after statvfs call and before - // fstatfs call. - #[test] - #[ignore] - fn fstatfs_call_strict() { - check_fstatfs_strict("/tmp"); - check_fstatfs_strict("/dev"); - check_fstatfs_strict("/run"); - check_fstatfs_strict("/"); - } - - fn check_fstatfs_strict(path: &str) { - if !Path::new(path).exists() { - return; - } - let vfs = statvfs(path.as_bytes()); - let file = File::open(path).unwrap(); - let fs = fstatfs(&file); - assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) - } - - fn check_statfs_strict(path: &str) { - if !Path::new(path).exists() { - return; - } - let vfs = statvfs(path.as_bytes()); - let fs = statfs(path.as_bytes()); - assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) - } - - // The cast is not unnecessary on all platforms. - #[allow(clippy::unnecessary_cast)] - fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) { - assert_eq!(fs.files_free() as u64, vfs.files_free() as u64); - assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64); - assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64); - assert_eq!(fs.files() as u64, vfs.files() as u64); - assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); - assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); - } -} diff --git a/src/sys/statvfs.rs b/src/sys/statvfs.rs index 892937f5..db1abdd4 100644 --- a/src/sys/statvfs.rs +++ b/src/sys/statvfs.rs @@ -3,7 +3,7 @@ //! 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}; @@ -12,7 +12,6 @@ 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 @@ -22,44 +21,34 @@ libc_bitflags!( #[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())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] ST_NOEXEC; /// All IO should be done synchronously - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] ST_SYNCHRONOUS; /// Allow mandatory locks on the filesystem - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] ST_NOATIME; /// Do not update access times on files - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] ST_NODIRATIME; /// Update access time relative to modify/change time - #[cfg(any(target_os = "android", all(target_os = "linux", not(any(target_env = "musl", target_env = "ohos")))))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(target_os = "android", all(target_os = "linux", not(target_env = "musl"), not(target_env = "ohos"))))] ST_RELATIME; } ); @@ -115,13 +104,18 @@ impl Statvfs { } /// Get the file system id + #[cfg(not(target_os = "hurd"))] pub fn filesystem_id(&self) -> c_ulong { self.0.f_fsid } + /// Get the file system id + #[cfg(target_os = "hurd")] + pub fn filesystem_id(&self) -> u64 { + self.0.f_fsid + } /// Get the mount flags #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn flags(&self) -> FsFlags { FsFlags::from_bits_truncate(self.0.f_flag) } @@ -146,28 +140,11 @@ pub fn statvfs(path: &P) -> Result { } /// 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 crate::sys::statvfs::*; - use std::fs::File; - - #[test] - fn statvfs_call() { - statvfs(&b"/"[..]).unwrap(); - } - - #[test] - fn fstatvfs_call() { - let root = File::open("/").unwrap(); - fstatvfs(&root).unwrap(); - } -} diff --git a/src/sys/sysinfo.rs b/src/sys/sysinfo.rs index e8aa00b0..a2bc0936 100644 --- a/src/sys/sysinfo.rs +++ b/src/sys/sysinfo.rs @@ -1,4 +1,4 @@ -use libc::{self, SI_LOAD_SHIFT}; +use libc::SI_LOAD_SHIFT; use std::time::Duration; use std::{cmp, mem}; diff --git a/src/sys/termios.rs b/src/sys/termios.rs index fba2cd82..b2415c0a 100644 --- a/src/sys/termios.rs +++ b/src/sys/termios.rs @@ -85,28 +85,8 @@ //! //! 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(bsd, doc = " ```rust,ignore")] +#![cfg_attr(not(bsd), doc = " ```rust")] //! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; //! # fn main() { //! # let mut t: Termios = unsafe { std::mem::zeroed() }; @@ -118,28 +98,8 @@ //! //! 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(bsd, doc = " ```rust")] +#![cfg_attr(not(bsd), doc = " ```rust,ignore")] //! # use nix::sys::termios::{BaudRate, cfgetispeed, cfgetospeed, cfsetspeed, Termios}; //! # fn main() { //! # let mut t: Termios = unsafe { std::mem::zeroed() }; @@ -151,28 +111,8 @@ //! //! 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(bsd, doc = " ```rust")] +#![cfg_attr(not(bsd), doc = " ```rust,ignore")] //! # use nix::sys::termios::{BaudRate, cfgetispeed, cfsetspeed, Termios}; //! # fn main() { //! # let mut t: Termios = unsafe { std::mem::zeroed() }; @@ -185,28 +125,8 @@ //! 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(bsd, doc = " ```rust")] +#![cfg_attr(not(bsd), doc = " ```rust,ignore")] //! # use nix::sys::termios::{cfsetispeed, cfsetospeed, cfsetspeed, Termios}; //! # fn main() { //! # let mut t: Termios = unsafe { std::mem::zeroed() }; @@ -222,7 +142,7 @@ 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; @@ -246,7 +166,7 @@ pub struct Termios { /// 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",))] + #[cfg(linux_android)] pub line_discipline: libc::cc_t, /// Line discipline (see `termios.c_line` documentation) #[cfg(target_os = "haiku")] @@ -266,11 +186,7 @@ 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", - ))] + #[cfg(any(linux_android, target_os = "haiku"))] { termios.c_line = self.line_discipline; } @@ -292,11 +208,7 @@ 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", - ))] + #[cfg(any(linux_android, target_os = "haiku"))] { termios.c_line = self.line_discipline; } @@ -309,14 +221,10 @@ 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", - ))] + #[cfg(any(linux_android, target_os = "haiku"))] { self.line_discipline = termios.c_line; } @@ -332,11 +240,7 @@ 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", - ))] + #[cfg(any(linux_android, target_os = "haiku"))] line_discipline: termios.c_line, } } @@ -355,9 +259,14 @@ libc_enum! { /// enum. /// /// B0 is special and will disable the port. - #[cfg_attr(all(any(target_os = "haiku"), target_pointer_width = "64"), repr(u8))] - #[cfg_attr(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64"), repr(u64))] - #[cfg_attr(not(all(any(target_os = "ios", target_os = "macos", target_os = "haiku"), target_pointer_width = "64")), repr(u32))] + #[cfg_attr(target_os = "haiku", repr(u8))] + #[cfg_attr(target_os = "hurd", repr(i32))] + #[cfg_attr(all(apple_targets, target_pointer_width = "64"), repr(u64))] + #[cfg_attr(all( + not(all(apple_targets, target_pointer_width = "64")), + not(target_os = "haiku"), + not(target_os = "hurd") + ), repr(u32))] #[non_exhaustive] pub enum BaudRate { B0, @@ -373,104 +282,62 @@ libc_enum! { B1800, B2400, B4800, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] B7200, B9600, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] B14400, B19200, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] B28800, B38400, + #[cfg(not(target_os = "aix"))] B57600, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] B76800, + #[cfg(not(target_os = "aix"))] B115200, - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(solarish)] B153600, + #[cfg(not(target_os = "aix"))] B230400, - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(solarish)] B307200, - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, + solarish, target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + target_os = "netbsd"))] B460800, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] B500000, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] B576000, - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, + solarish, target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + target_os = "netbsd"))] B921600, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] B1000000, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] B1152000, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] B1500000, - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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(bsd)] impl From for u32 { fn from(b: BaudRate) -> u32 { b as u32 @@ -536,74 +403,57 @@ 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(any(target_os = "aix", target_os = "haiku")))] VDISCARD, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, + solarish, + target_os = "aix"))] VDSUSP, VEOF, VEOL, VEOL2, VERASE, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(freebsdlike, target_os = "illumos"))] VERASE2, VINTR, VKILL, + #[cfg(not(target_os = "haiku"))] VLNEXT, #[cfg(not(any(all(target_os = "linux", target_arch = "sparc64"), - target_os = "illumos", target_os = "solaris")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + solarish, target_os = "aix", target_os = "haiku")))] VMIN, VQUIT, + #[cfg(not(target_os = "haiku"))] VREPRINT, VSTART, - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "illumos"))] 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())))] + #[cfg(any(solarish, target_os = "haiku"))] VSWTCH, #[cfg(not(any(all(target_os = "linux", target_arch = "sparc64"), - target_os = "illumos", target_os = "solaris")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + solarish, target_os = "aix", target_os = "haiku")))] VTIME, + #[cfg(not(any(target_os = "aix", target_os = "haiku")))] 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" + solarish, + target_os = "aix", + target_os = "haiku", ))] impl SpecialCharacterIndices { pub const VMIN: SpecialCharacterIndices = SpecialCharacterIndices::VEOF; @@ -611,16 +461,7 @@ impl SpecialCharacterIndices { } pub use libc::NCCS; -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" -))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(any(bsd, linux_android, target_os = "aix", target_os = "solaris"))] pub use libc::_POSIX_VDISABLE; libc_bitflags! { @@ -638,13 +479,10 @@ libc_bitflags! { IXON; IXOFF; #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] IXANY; #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] IMAXBEL; - #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, apple_targets))] IUTF8; } } @@ -653,209 +491,119 @@ libc_bitflags! { /// Flags for configuring the output mode of a terminal pub struct OutputFlags: tcflag_t { OPOST; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "linux", target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] OLCUC; ONLCR; OCRNL as tcflag_t; ONOCR as tcflag_t; ONLRET as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] - OFILL as tcflag_t; - #[cfg(any(target_os = "android", - target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] OFDEL as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] NL0 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] NL1 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] CR0 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] CR1 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] CR2 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] CR3 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "freebsd", target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] TAB0 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] TAB1 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] TAB2 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "freebsd", target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] TAB3 as tcflag_t; - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] XTABS; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] BS0 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] BS1 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] VT0 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] VT1 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] FF0 as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] FF1 as tcflag_t; - #[cfg(any(target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] OXTABS; - #[cfg(any(target_os = "freebsd", - target_os = "dragonfly", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] ONOEOT as tcflag_t; // Bitmasks for use with OutputFlags to select specific settings // These should be moved to be a mask once https://github.com/rust-lang-nursery/bitflags/issues/110 // is resolved. - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] NLDLY as tcflag_t; // FIXME: Datatype needs to be corrected in libc for mac - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] CRDLY as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "freebsd", target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] TABDLY as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] BSDLY as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] VTDLY as tcflag_t; - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, target_os = "haiku", - target_os = "ios", - target_os = "linux", - target_os = "macos"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + apple_targets))] FFDLY as tcflag_t; } } @@ -863,13 +611,7 @@ libc_bitflags! { libc_bitflags! { /// Flags for setting the control mode of a terminal pub struct ControlFlags: tcflag_t { - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] CIGNORE; CS5; CS6; @@ -881,55 +623,31 @@ libc_bitflags! { PARODD; HUPCL; CLOCAL; - #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_os = "redox", target_os = "aix")))] CRTSCTS; - #[cfg(any(target_os = "android", target_os = "linux"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] 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())))] + #[cfg(linux_android)] CBAUDEX; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] MDMBUF; - #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(netbsdlike)] CHWFLOW; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(freebsdlike, netbsdlike))] CCTS_OFLOW; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(freebsdlike, netbsdlike))] CRTS_IFLOW; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] CDTR_IFLOW; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] CDSR_OFLOW; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] CCAR_OFLOW; // Bitmasks for use with ControlFlags to select specific settings @@ -944,58 +662,35 @@ 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())))] + #[cfg(not(any(target_os = "redox", target_os = "cygwin")))] ECHOPRT; #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] ECHOCTL; ISIG; ICANON; - #[cfg(any(target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] ALTWERASE; IEXTEN; - #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_os = "redox", target_os = "haiku", target_os = "aix", target_os = "cygwin")))] EXTPROC; TOSTOP; #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] FLUSHO; - #[cfg(any(target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(bsd)] NOKERNINFO; - #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any(target_os = "redox", target_os = "cygwin")))] PENDIN; NOFLSH; } } cfg_if! { - if #[cfg(any(target_os = "freebsd", - target_os = "dragonfly", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] { + if #[cfg(bsd)] { /// Get input baud rate (see /// [cfgetispeed(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html)). /// @@ -1128,7 +823,6 @@ 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 { @@ -1143,10 +837,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)?; @@ -1159,18 +855,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) + 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 @@ -1178,8 +882,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 @@ -1187,8 +894,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 @@ -1196,32 +906,20 @@ 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 { - use super::*; - use std::convert::TryFrom; - - #[test] - fn try_from() { - assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0)); - #[cfg(not(target_os = "haiku"))] - BaudRate::try_from(999999999).expect_err("assertion failed"); - #[cfg(target_os = "haiku")] - BaudRate::try_from(99).expect_err("assertion failed"); - } -} diff --git a/src/sys/time.rs b/src/sys/time.rs index 0fff22c2..7e89c721 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -1,8 +1,10 @@ -#[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] +#[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) +)] // https://github.com/rust-lang/libc/issues/1848 pub use libc::{suseconds_t, time_t}; use libc::{timespec, timeval}; -use std::convert::From; use std::time::Duration; use std::{cmp, fmt, ops}; @@ -18,7 +20,7 @@ const fn zero_init_timespec() -> timespec { all( any( target_os = "freebsd", - target_os = "illumos", + solarish, target_os = "linux", target_os = "netbsd" ), @@ -88,21 +90,19 @@ pub(crate) mod timer { Interval(TimeSpec), } - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] 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" - ))] + #[cfg(any(freebsdlike, target_os = "netbsd", solarish))] 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; } @@ -256,13 +256,15 @@ impl PartialOrd for TimeSpec { impl TimeValLike for TimeSpec { #[inline] - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 fn seconds(seconds: i64) -> TimeSpec { assert!( (TS_MIN_SECONDS..=TS_MAX_SECONDS).contains(&seconds), - "TimeSpec out of bounds; seconds={}", - seconds + "TimeSpec out of bounds; seconds={seconds}", ); let mut ts = zero_init_timespec(); ts.tv_sec = seconds as time_t; @@ -290,7 +292,10 @@ impl TimeValLike for TimeSpec { /// Makes a new `TimeSpec` with given number of nanoseconds. #[inline] - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + 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); @@ -332,8 +337,22 @@ impl TimeValLike for TimeSpec { } impl TimeSpec { + /// Leave the timestamp unchanged. + #[cfg(not(target_os = "redox"))] + // At the time of writing this PR, redox does not support this feature + pub const UTIME_OMIT: TimeSpec = + TimeSpec::new(0, libc::UTIME_OMIT as timespec_tv_nsec_t); + /// Update the timestamp to `Now` + // At the time of writing this PR, redox does not support this feature + #[cfg(not(target_os = "redox"))] + pub const UTIME_NOW: TimeSpec = + TimeSpec::new(0, libc::UTIME_NOW as timespec_tv_nsec_t); + /// Construct a new `TimeSpec` from its components - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + 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; @@ -349,7 +368,10 @@ impl TimeSpec { } } - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 pub const fn tv_sec(&self) -> time_t { self.0.tv_sec } @@ -358,7 +380,10 @@ impl TimeSpec { self.0.tv_nsec } - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 pub const fn from_duration(duration: Duration) -> Self { let mut ts = zero_init_timespec(); @@ -428,20 +453,20 @@ 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(()) @@ -497,10 +522,12 @@ impl TimeValLike for TimeVal { fn seconds(seconds: i64) -> TimeVal { assert!( (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&seconds), - "TimeVal out of bounds; seconds={}", - seconds + "TimeVal out of bounds; seconds={seconds}" ); - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 TimeVal(timeval { tv_sec: seconds as time_t, @@ -525,7 +552,10 @@ impl TimeValLike for TimeVal { (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&secs), "TimeVal out of bounds" ); - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 TimeVal(timeval { tv_sec: secs as time_t, @@ -543,7 +573,10 @@ impl TimeValLike for TimeVal { (TV_MIN_SECONDS..=TV_MAX_SECONDS).contains(&secs), "TimeVal out of bounds" ); - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 TimeVal(timeval { tv_sec: secs as time_t, @@ -580,7 +613,10 @@ impl TimeValLike for TimeVal { impl TimeVal { /// Construct a new `TimeVal` from its components - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + 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, @@ -596,7 +632,10 @@ impl TimeVal { } } - #[cfg_attr(any(target_env = "musl", target_env = "ohos"), allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 + #[cfg_attr( + any(target_env = "musl", target_env = "ohos"), + allow(deprecated) + )] // https://github.com/rust-lang/libc/issues/1848 pub const fn tv_sec(&self) -> time_t { self.0.tv_sec } @@ -662,18 +701,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(()) @@ -711,101 +750,3 @@ fn mod_floor_64(this: i64, other: i64) -> i64 { fn div_rem_64(this: i64, other: i64) -> (i64, i64) { (this / other, this % other) } - -#[cfg(test)] -mod test { - use super::{TimeSpec, TimeVal, TimeValLike}; - use std::time::Duration; - - #[test] - pub fn test_timespec() { - assert_ne!(TimeSpec::seconds(1), TimeSpec::zero()); - assert_eq!( - TimeSpec::seconds(1) + TimeSpec::seconds(2), - TimeSpec::seconds(3) - ); - assert_eq!( - TimeSpec::minutes(3) + TimeSpec::seconds(2), - TimeSpec::seconds(182) - ); - } - - #[test] - pub fn test_timespec_from() { - let duration = Duration::new(123, 123_456_789); - let timespec = TimeSpec::nanoseconds(123_123_456_789); - - assert_eq!(TimeSpec::from(duration), timespec); - assert_eq!(Duration::from(timespec), duration); - } - - #[test] - pub fn test_timespec_neg() { - let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); - let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); - - assert_eq!(a, -b); - } - - #[test] - pub fn test_timespec_ord() { - assert_eq!(TimeSpec::seconds(1), TimeSpec::nanoseconds(1_000_000_000)); - assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001)); - assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999)); - assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999)); - assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001)); - } - - #[test] - pub fn test_timespec_fmt() { - assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); - assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); - assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); - assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); - assert_eq!( - TimeSpec::nanoseconds(42).to_string(), - "0.000000042 seconds" - ); - assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); - } - - #[test] - pub fn test_timeval() { - assert_ne!(TimeVal::seconds(1), TimeVal::zero()); - assert_eq!( - TimeVal::seconds(1) + TimeVal::seconds(2), - TimeVal::seconds(3) - ); - assert_eq!( - TimeVal::minutes(3) + TimeVal::seconds(2), - TimeVal::seconds(182) - ); - } - - #[test] - pub fn test_timeval_ord() { - assert_eq!(TimeVal::seconds(1), TimeVal::microseconds(1_000_000)); - assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001)); - assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999)); - assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999)); - assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001)); - } - - #[test] - pub fn test_timeval_neg() { - let a = TimeVal::seconds(1) + TimeVal::microseconds(123); - let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); - - assert_eq!(a, -b); - } - - #[test] - pub fn test_timeval_fmt() { - assert_eq!(TimeVal::zero().to_string(), "0 seconds"); - assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); - assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); - assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); - assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); - assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); - } -} diff --git a/src/sys/timer.rs b/src/sys/timer.rs index e1a34051..8876f569 100644 --- a/src/sys/timer.rs +++ b/src/sys/timer.rs @@ -95,7 +95,7 @@ impl Timer { /// There are 3 types of alarms you can set: /// /// - one shot: the alarm will trigger once after the specified amount of - /// time. + /// 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. diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs index a35fc927..92ebc29d 100644 --- a/src/sys/timerfd.rs +++ b/src/sys/timerfd.rs @@ -33,24 +33,34 @@ pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; use crate::unistd::read; use crate::{errno::Errno, Result}; use libc::c_int; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +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: unsafe { OwnedFd::from_raw_fd(fd) }, + } + } +} + +impl From for OwnedFd { + fn from(value: TimerFd) -> Self { + value.fd } } @@ -97,7 +107,9 @@ impl TimerFd { Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) }) - .map(|fd| Self { fd }) + .map(|fd| Self { + fd: unsafe { OwnedFd::from_raw_fd(fd) }, + }) } /// Sets a new alarm on the timer. @@ -107,7 +119,7 @@ impl TimerFd { /// There are 3 types of alarms you can set: /// /// - one shot: the alarm will trigger once after the specified amount of - /// time. + /// 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. @@ -129,6 +141,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 @@ -145,7 +164,7 @@ impl TimerFd { 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(), @@ -159,7 +178,10 @@ impl TimerFd { pub fn get(&self) -> Result> { let mut timerspec = TimerSpec::none(); Errno::result(unsafe { - libc::timerfd_gettime(self.fd, timerspec.as_mut()) + libc::timerfd_gettime( + self.fd.as_fd().as_raw_fd(), + timerspec.as_mut(), + ) }) .map(|_| { if timerspec.as_ref().it_interval.tv_sec == 0 @@ -179,7 +201,7 @@ impl TimerFd { 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(), @@ -192,7 +214,10 @@ 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, &mut [0u8; 8]) { + if e == Errno::ECANCELED { + break; + } if e != Errno::EINTR { return Err(e); } @@ -200,15 +225,16 @@ impl TimerFd { 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"); - } + + /// Constructs a `TimerFd` wrapping an existing `OwnedFd`. + /// + /// # Safety + /// + /// `OwnedFd` is a valid `TimerFd`. + pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { + Self { + fd } } } diff --git a/src/sys/uio.rs b/src/sys/uio.rs index c96ade37..f47083cb 100644 --- a/src/sys/uio.rs +++ b/src/sys/uio.rs @@ -2,15 +2,14 @@ use crate::errno::Errno; use crate::Result; -use libc::{self, c_int, c_void, off_t, size_t}; +use libc::{self, c_int, off_t, size_t}; use std::io::{IoSlice, IoSliceMut}; -use std::marker::PhantomData; -use std::os::unix::io::RawFd; +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: &[IoSlice<'_>]) -> Result { +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 @@ -19,7 +18,11 @@ pub fn writev(fd: RawFd, iov: &[IoSlice<'_>]) -> Result { // // Because it is ABI compatible, a pointer cast here is valid let res = unsafe { - libc::writev(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + libc::writev( + fd.as_fd().as_raw_fd(), + iov.as_ptr().cast(), + iov.len() as c_int, + ) }; Errno::result(res).map(|r| r as usize) @@ -31,10 +34,14 @@ pub fn writev(fd: RawFd, iov: &[IoSlice<'_>]) -> Result { // 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: RawFd, iov: &mut [IoSliceMut<'_>]) -> Result { +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, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) + libc::readv( + fd.as_fd().as_raw_fd(), + iov.as_ptr().cast(), + iov.len() as c_int, + ) }; Errno::result(res).map(|r| r as usize) @@ -46,17 +53,20 @@ pub fn readv(fd: RawFd, iov: &mut [IoSliceMut<'_>]) -> Result { /// or an error occurs. The file offset is not changed. /// /// See also: [`writev`](fn.writev.html) and [`pwrite`](fn.pwrite.html) -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -#[cfg_attr(docsrs, doc(cfg(all())))] -pub fn pwritev(fd: RawFd, iov: &[IoSlice<'_>], offset: off_t) -> Result { +#[cfg(not(any(target_os = "redox", target_os = "haiku", target_os = "solaris", target_os = "cygwin")))] +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, + fd.as_fd().as_raw_fd(), + iov.as_ptr().cast(), iov.len() as c_int, offset, ) @@ -72,13 +82,12 @@ pub fn pwritev(fd: RawFd, iov: &[IoSlice<'_>], offset: off_t) -> Result { /// changed. /// /// See also: [`readv`](fn.readv.html) and [`pread`](fn.pread.html) -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(not(any(target_os = "redox", target_os = "haiku", target_os = "solaris", target_os = "cygwin")))] // 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: RawFd, +pub fn preadv( + fd: Fd, iov: &mut [IoSliceMut<'_>], offset: off_t, ) -> Result { @@ -88,8 +97,8 @@ pub fn preadv( // SAFETY: same as in readv() let res = unsafe { libc::preadv( - fd, - iov.as_ptr() as *const libc::iovec, + fd.as_fd().as_raw_fd(), + iov.as_ptr().cast(), iov.len() as c_int, offset, ) @@ -102,11 +111,11 @@ pub fn preadv( /// /// 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, + fd.as_fd().as_raw_fd(), + buf.as_ptr().cast(), buf.len() as size_t, offset, ) @@ -119,11 +128,11 @@ pub fn pwrite(fd: RawFd, buf: &[u8], offset: off_t) -> Result { /// /// 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, + fd.as_fd().as_raw_fd(), + buf.as_mut_ptr().cast(), buf.len() as size_t, offset, ) @@ -140,8 +149,7 @@ pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result { /// therefore not represented in Rust by an actual slice as `IoSlice` is. It /// is used with [`process_vm_readv`](fn.process_vm_readv.html) /// and [`process_vm_writev`](fn.process_vm_writev.html). -#[cfg(any(target_os = "linux", target_os = "android"))] -#[cfg_attr(docsrs, doc(cfg(all())))] +#[cfg(linux_android)] #[repr(C)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct RemoteIoVec { @@ -151,77 +159,6 @@ pub struct RemoteIoVec { pub len: usize, } -/// A vector of buffers. -/// -/// Vectored I/O methods like [`writev`] and [`readv`] use this structure for -/// both reading and writing. Each `IoVec` specifies the base address and -/// length of an area in memory. -#[deprecated( - since = "0.24.0", - note = "`IoVec` is no longer used in the public interface, use `IoSlice` or `IoSliceMut` instead" -)] -#[repr(transparent)] -#[allow(renamed_and_removed_lints)] -#[allow(clippy::unknown_clippy_lints)] -// Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/8867 -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct IoVec(pub(crate) libc::iovec, PhantomData); - -#[allow(deprecated)] -impl IoVec { - /// View the `IoVec` as a Rust slice. - #[deprecated( - since = "0.24.0", - note = "Use the `Deref` impl of `IoSlice` or `IoSliceMut` instead" - )] - #[inline] - pub fn as_slice(&self) -> &[u8] { - use std::slice; - - unsafe { - slice::from_raw_parts(self.0.iov_base as *const u8, self.0.iov_len) - } - } -} - -#[allow(deprecated)] -impl<'a> IoVec<&'a [u8]> { - /// Create an `IoVec` from a Rust slice. - #[deprecated(since = "0.24.0", note = "Use `IoSlice::new` instead")] - pub fn from_slice(buf: &'a [u8]) -> IoVec<&'a [u8]> { - IoVec( - libc::iovec { - iov_base: buf.as_ptr() as *mut c_void, - iov_len: buf.len() as size_t, - }, - PhantomData, - ) - } -} - -#[allow(deprecated)] -impl<'a> IoVec<&'a mut [u8]> { - /// Create an `IoVec` from a mutable Rust slice. - #[deprecated(since = "0.24.0", note = "Use `IoSliceMut::new` instead")] - pub fn from_mut_slice(buf: &'a mut [u8]) -> IoVec<&'a mut [u8]> { - IoVec( - libc::iovec { - iov_base: buf.as_mut_ptr() as *mut c_void, - iov_len: buf.len() as size_t, - }, - PhantomData, - ) - } -} - -// The only reason IoVec isn't automatically Send+Sync is because libc::iovec -// contains raw pointers. -#[allow(deprecated)] -unsafe impl Send for IoVec where T: Send {} -#[allow(deprecated)] -unsafe impl Sync for IoVec where T: Sync {} - feature! { #![feature = "process"] @@ -245,7 +182,7 @@ feature! { /// [ptrace]: ../ptrace/index.html /// [`IoSlice`]: https://doc.rust-lang.org/std/io/struct.IoSlice.html /// [`RemoteIoVec`]: struct.RemoteIoVec.html -#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +#[cfg(all(linux_android, not(target_env = "uclibc")))] pub fn process_vm_writev( pid: crate::unistd::Pid, local_iov: &[IoSlice<'_>], @@ -253,8 +190,8 @@ pub fn process_vm_writev( { let res = unsafe { libc::process_vm_writev(pid.into(), - local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, - remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + local_iov.as_ptr().cast(), local_iov.len() as libc::c_ulong, + remote_iov.as_ptr().cast(), remote_iov.len() as libc::c_ulong, 0) }; Errno::result(res).map(|r| r as usize) @@ -280,7 +217,7 @@ pub fn process_vm_writev( /// [`ptrace`]: ../ptrace/index.html /// [`IoSliceMut`]: https://doc.rust-lang.org/std/io/struct.IoSliceMut.html /// [`RemoteIoVec`]: struct.RemoteIoVec.html -#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +#[cfg(all(linux_android, not(target_env = "uclibc")))] pub fn process_vm_readv( pid: crate::unistd::Pid, local_iov: &mut [IoSliceMut<'_>], @@ -288,8 +225,8 @@ pub fn process_vm_readv( { let res = unsafe { libc::process_vm_readv(pid.into(), - local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong, - remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0) + local_iov.as_ptr().cast(), local_iov.len() as libc::c_ulong, + remote_iov.as_ptr().cast(), remote_iov.len() as libc::c_ulong, 0) }; Errno::result(res).map(|r| r as usize) diff --git a/src/sys/utsname.rs b/src/sys/utsname.rs index b48ed9f4..cf4e6cc7 100644 --- a/src/sys/utsname.rs +++ b/src/sys/utsname.rs @@ -37,7 +37,7 @@ impl UtsName { } /// NIS or YP domain name of this machine. - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] pub fn domainname(&self) -> &OsStr { cast_and_trim(&self.0.domainname) } @@ -62,24 +62,3 @@ fn cast_and_trim(slice: &[c_char]) -> &OsStr { OsStr::from_bytes(bytes) } - -#[cfg(test)] -mod test { - #[cfg(target_os = "linux")] - #[test] - pub fn test_uname_linux() { - assert_eq!(super::uname().unwrap().sysname(), "Linux"); - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - #[test] - pub fn test_uname_darwin() { - assert_eq!(super::uname().unwrap().sysname(), "Darwin"); - } - - #[cfg(target_os = "freebsd")] - #[test] - pub fn test_uname_freebsd() { - assert_eq!(super::uname().unwrap().sysname(), "FreeBSD"); - } -} diff --git a/src/sys/wait.rs b/src/sys/wait.rs index b6524e86..844e165c 100644 --- a/src/sys/wait.rs +++ b/src/sys/wait.rs @@ -10,7 +10,7 @@ use std::convert::TryFrom; target_os = "android", all(target_os = "linux", not(target_env = "uclibc")), ))] -use std::os::unix::io::RawFd; +use std::os::unix::io::{AsRawFd, BorrowedFd}; libc_bitflags!( /// Controls the behavior of [`waitpid`]. @@ -24,53 +24,41 @@ libc_bitflags!( /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal. WUNTRACED; /// Report the status of selected processes which have terminated. - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, + apple_targets, target_os = "freebsd", target_os = "haiku", - target_os = "ios", - target_os = "linux", target_os = "redox", - target_os = "macos", target_os = "netbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] WEXITED; /// Report the status of selected processes that have continued from a /// job control stop by receiving a /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal. WCONTINUED; /// An alias for WUNTRACED. - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, + apple_targets, target_os = "freebsd", target_os = "haiku", - target_os = "ios", - target_os = "linux", target_os = "redox", - target_os = "macos", target_os = "netbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] WSTOPPED; /// Don't reap, just poll status. - #[cfg(any(target_os = "android", + #[cfg(any(linux_android, + apple_targets, target_os = "freebsd", target_os = "haiku", - target_os = "ios", - target_os = "linux", target_os = "redox", - target_os = "macos", target_os = "netbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] WNOWAIT; /// Don't wait on children of other threads in this group - #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, target_os = "redox"))] __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())))] + #[cfg(any(linux_android, target_os = "redox"))] __WALL; /// Wait for "clone" children only. - #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, target_os = "redox"))] __WCLONE; } ); @@ -107,16 +95,14 @@ 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())))] + #[cfg(linux_android)] PtraceEvent(Pid, Signal, c_int), /// The traced process was stopped by execution of a system call, /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for /// more information. /// /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html - #[cfg(any(target_os = "linux", target_os = "android"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(linux_android)] PtraceSyscall(Pid), /// The process was previously stopped but has resumed execution /// after receiving a `SIGCONT` signal. This is only reported if @@ -139,7 +125,7 @@ impl WaitStatus { Some(p) } StillAlive => None, - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p), } } @@ -173,7 +159,7 @@ fn stop_signal(status: i32) -> Result { Signal::try_from(libc::WSTOPSIG(status)) } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn syscall_stop(status: i32) -> bool { // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect // of delivering SIGTRAP | 0x80 as the signal number for syscall @@ -182,7 +168,7 @@ fn syscall_stop(status: i32) -> bool { libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn stop_additional(status: i32) -> c_int { (status >> 16) as c_int } @@ -216,7 +202,7 @@ impl WaitStatus { WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status)) } else if stopped(status) { cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(linux_android)] { fn decode_stopped(pid: Pid, status: i32) -> Result { let status_additional = stop_additional(status); Ok(if syscall_stop(status) { @@ -259,7 +245,7 @@ impl WaitStatus { all(target_os = "linux", not(target_env = "uclibc")), ))] unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result { - let si_pid = siginfo.si_pid(); + let si_pid = unsafe { siginfo.si_pid() }; if si_pid == 0 { return Ok(WaitStatus::StillAlive); } @@ -267,7 +253,7 @@ impl WaitStatus { assert_eq!(siginfo.si_signo, libc::SIGCHLD); let pid = Pid::from_raw(si_pid); - let si_status = siginfo.si_status(); + let si_status = unsafe { siginfo.si_status() }; let status = match siginfo.si_code { libc::CLD_EXITED => WaitStatus::Exited(pid, si_status), @@ -280,7 +266,7 @@ impl WaitStatus { WaitStatus::Stopped(pid, Signal::try_from(si_status)?) } libc::CLD_CONTINUED => WaitStatus::Continued(pid), - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] libc::CLD_TRAPPED => { if si_status == libc::SIGTRAP | 0x80 { WaitStatus::PtraceSyscall(pid) @@ -343,8 +329,8 @@ pub fn wait() -> Result { target_os = "haiku", all(target_os = "linux", not(target_env = "uclibc")), ))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Id { +#[derive(Debug)] +pub enum Id<'fd> { /// Wait for any child All, /// Wait for the child whose process ID matches the given PID @@ -354,8 +340,12 @@ pub enum Id { /// If the PID is zero, the caller's process group is used since Linux 5.4. PGid(Pid), /// Wait for the child referred to by the given PID file descriptor - #[cfg(any(target_os = "android", target_os = "linux"))] - PIDFd(RawFd), + #[cfg(linux_android)] + 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 @@ -372,8 +362,11 @@ pub fn waitid(id: Id, flags: WaitPidFlag) -> Result { Id::All => (libc::P_ALL, 0), Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t), Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), - #[cfg(any(target_os = "android", target_os = "linux"))] - Id::PIDFd(fd) => (libc::P_PIDFD, fd as libc::id_t), + #[cfg(linux_android)] + 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 { diff --git a/src/syslog.rs b/src/syslog.rs new file mode 100644 index 00000000..78949780 --- /dev/null +++ b/src/syslog.rs @@ -0,0 +1,293 @@ +//! Interfaces for controlling system log. + +use crate::{NixPath, Result}; +use std::ffi::OsStr; +use std::ptr; + +/// Logging options of subsequent [`syslog`] calls can be set by calling [`openlog`]. +/// +/// The parameter `ident` is a string that will be prepended to every message. The `logopt` +/// argument specifies logging options. The `facility` parameter encodes a default facility to be +/// assigned to all messages that do not have an explicit facility encoded. +// +// On Linux, the `ident` argument needs to have static lifetime according to the +// man page: +// +// The argument ident in the call of openlog() is probably stored as-is. Thus, +// if the string it points to is changed, syslog() may start prepending the changed +// string, and if the string it points to ceases to exist, the results are +// undefined. Most portable is to use a string constant. +#[cfg(target_os = "linux")] +pub fn openlog( + ident: Option<&'static std::ffi::CStr>, + logopt: LogFlags, + facility: Facility, +) -> Result<()> { + let logopt = logopt.bits(); + let facility = facility as libc::c_int; + match ident { + None => unsafe { + libc::openlog(ptr::null(), logopt, facility); + }, + Some(ident) => ident.with_nix_path(|ident| unsafe { + libc::openlog(ident.as_ptr(), logopt, facility); + })?, + } + + Ok(()) +} + +/// Logging options of subsequent [`syslog`] calls can be set by calling [`openlog`]. +/// +/// The parameter `ident` is a string that will be prepended to every message. The `logopt` +/// argument specifies logging options. The `facility` parameter encodes a default facility to be +/// assigned to all messages that do not have an explicit facility encoded. +#[cfg(not(target_os = "linux"))] +pub fn openlog + ?Sized>( + ident: Option<&S>, + logopt: LogFlags, + facility: Facility, +) -> Result<()> { + let logopt = logopt.bits(); + let facility = facility as libc::c_int; + match ident.map(OsStr::new) { + None => unsafe { + libc::openlog(ptr::null(), logopt, facility); + }, + Some(ident) => ident.with_nix_path(|ident| unsafe { + libc::openlog(ident.as_ptr(), logopt, facility); + })?, + } + + Ok(()) +} + +/// Writes message to the system message logger. +/// +/// The message is then written to the system console, log files, logged-in users, or forwarded +/// to other machines as appropriate. +/// +/// # Examples +/// +/// ```rust +/// use nix::syslog::{openlog, syslog, Facility, LogFlags, Severity, Priority}; +/// +/// let priority = Priority::new(Severity::LOG_EMERG, Facility::LOG_USER); +/// syslog(priority, "Hello, nix!").unwrap(); +/// +/// // use `format!` to format the message +/// let name = "syslog"; +/// syslog(priority, &format!("Hello, {name}!")).unwrap(); +/// ``` +pub fn syslog(priority: P, message: &S) -> Result<()> +where + P: Into, + S: AsRef + ?Sized, +{ + let priority = priority.into(); + let formatter = OsStr::new("%s"); + let message = OsStr::new(message); + formatter.with_nix_path(|formatter| { + message.with_nix_path(|message| unsafe { + libc::syslog(priority.0, formatter.as_ptr(), message.as_ptr()) + }) + })??; + Ok(()) +} + +/// Set the process-wide priority mask to `mask` and return the previous mask +/// value. +/// +/// Calls to `syslog()` with a priority level not set in `mask` are ignored. The +/// default is to log all priorities. +/// +/// If the `mask` argument is `None`, the current logmask is not modified, this +/// can be used to query the current log mask. +pub fn setlogmask(mask: Option) -> LogMask { + let mask = match mask { + Some(mask) => mask.0, + None => 0, + }; + let prev_mask = unsafe { libc::setlogmask(mask) }; + LogMask(prev_mask) +} + +/// Closes the log file. +pub fn closelog() { + unsafe { libc::closelog() } +} + +/// System log priority mask. +#[derive(Debug, Clone, Copy)] +pub struct LogMask(libc::c_int); + +impl LogMask { + /// Creates a mask of all priorities up to and including `priority`. + #[doc(alias("LOG_UPTO"))] + pub fn up_to(priority: Severity) -> Self { + let pri = priority as libc::c_int; + Self((1 << (pri + 1)) - 1) + } + + /// Creates a mask for the specified priority. + #[doc(alias("LOG_MASK"))] + pub fn of_priority(priority: Severity) -> Self { + let pri = priority as libc::c_int; + Self(1 << pri) + } + + /// Returns if the mask for the specified `priority` is set. + pub fn contains(&self, priority: Severity) -> bool { + let priority = Self::of_priority(priority); + let and_result = *self & priority; + and_result.0 != 0 + } +} + +impl std::ops::BitOr for LogMask { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl std::ops::BitAnd for LogMask { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } +} + +impl std::ops::BitOrAssign for LogMask { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +impl std::ops::BitAndAssign for LogMask { + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= rhs.0; + } +} + +impl std::ops::Not for LogMask { + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } +} + +/// The priority for a log message. +#[derive(Debug, Clone, Copy)] +pub struct Priority(libc::c_int); + +impl Priority { + /// Create a new priority from a facility and severity level. + pub fn new(severity: Severity, facility: Facility) -> Self { + let priority = (facility as libc::c_int) | (severity as libc::c_int); + Priority(priority) + } +} + +impl From for Priority { + fn from(severity: Severity) -> Self { + let priority = severity as libc::c_int; + Priority(priority) + } +} + +libc_bitflags! { + /// Options for system logging. + pub struct LogFlags: libc::c_int { + /// Log the process id with each message: useful for identifying instantiations of + /// daemons. + LOG_PID; + /// If syslog() cannot pass the message to syslogd(8) it will attempt to write the + /// message to the console ("/dev/console"). + LOG_CONS; + /// The converse of [`LOG_NDELAY`][LogFlags::LOG_NDELAY]; opening of the connection is + /// delayed until `syslog` is called. + /// + /// This is the default, and need not be specified. + LOG_ODELAY; + /// Open the connection to syslogd(8) immediately. Normally the open is delayed until + /// the first message is logged. Useful for programs that need to manage the order in + /// which file descriptors are allocated. + LOG_NDELAY; + /// Write the message to standard error output as well to the system log. + #[cfg(not(any(solarish, target_os = "redox", target_os = "cygwin")))] + LOG_PERROR; + } +} + +libc_enum! { + /// Severity levels for log messages. + #[repr(i32)] + #[non_exhaustive] + pub enum Severity { + /// A panic condition. + /// + /// This is normally broadcast to all users. + LOG_EMERG, + /// A condition that should be corrected immediately, such as a corrupted system database. + LOG_ALERT, + /// Critical conditions, e.g., hard device errors. + LOG_CRIT, + /// Errors. + LOG_ERR, + /// Warning messages. + LOG_WARNING, + /// Conditions that are not error conditions, but should possibly be handled specially. + LOG_NOTICE, + /// Informational messages. + LOG_INFO, + /// Messages that contain information normally of use only when debugging a program. + LOG_DEBUG, + } +} + +libc_enum! { + /// Facilities for log messages. + #[repr(i32)] + #[non_exhaustive] + pub enum Facility { + /// Messages generated by the kernel. + /// + /// These cannot be generated by any user processes. + LOG_KERN, + /// Messages generated by random user processes. + /// + /// This is the default facility identifier if none is specified. + LOG_USER, + /// The mail system. + LOG_MAIL, + /// System daemons, such as routed(8), that are not provided for explicitly by other facilities. + LOG_DAEMON, + /// The authorization system: login(1), su(1), getty(8), etc. + LOG_AUTH, + /// Messages generated internally by syslogd(8). + LOG_SYSLOG, + /// The line printer spooling system: cups-lpd(8), cupsd(8), etc. + LOG_LPR, + /// The network news system. + LOG_NEWS, + /// The uucp system. + LOG_UUCP, + /// Reserved for local use. + LOG_LOCAL0, + /// Reserved for local use. + LOG_LOCAL1, + /// Reserved for local use. + LOG_LOCAL2, + /// Reserved for local use. + LOG_LOCAL3, + /// Reserved for local use. + LOG_LOCAL4, + /// Reserved for local use. + LOG_LOCAL5, + /// Reserved for local use. + LOG_LOCAL6, + /// Reserved for local use. + LOG_LOCAL7, + } +} diff --git a/src/time.rs b/src/time.rs index 8a040f63..e5287362 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,11 +1,6 @@ +//! Sleep, query system clocks, and set system clock use crate::sys::time::TimeSpec; -#[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux", - target_os = "android", - target_os = "emscripten", -))] +#[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] #[cfg(feature = "process")] use crate::unistd::Pid; use crate::{Errno, Result}; @@ -14,8 +9,7 @@ use std::mem::MaybeUninit; /// Clock identifier /// -/// Newtype pattern around `clockid_t` (which is just alias). It prevents bugs caused by -/// accidentally passing wrong value. +/// Newtype pattern around [`libc::clockid_t`]. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct ClockId(clockid_t); @@ -28,14 +22,7 @@ impl ClockId { feature! { #![feature = "process"] /// Returns `ClockId` of a `pid` CPU-time clock - #[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux", - target_os = "android", - target_os = "emscripten", - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] pub fn pid_cpu_clock_id(pid: Pid) -> Result { clock_getcpuclockid(pid) } @@ -43,7 +30,6 @@ impl ClockId { /// 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) } @@ -55,12 +41,12 @@ impl ClockId { /// Sets time to `timespec` on the clock id #[cfg(not(any( - target_os = "macos", target_os = "ios", + target_os = "tvos", + target_os = "watchos", target_os = "redox", - target_os = "hermit", + target_os = "hermit" )))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub fn set_time(self, timespec: TimeSpec) -> Result<()> { clock_settime(self, timespec) } @@ -70,135 +56,106 @@ impl ClockId { self.0 } - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] + /// Starts at zero when the kernel boots and increments monotonically in SI seconds while the + /// machine is running. pub const CLOCK_BOOTTIME: ClockId = ClockId(libc::CLOCK_BOOTTIME); - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Like [`CLOCK_BOOTTIME`](ClockId::CLOCK_BOOTTIME), but will wake the system if it is + /// suspended.. + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] pub const CLOCK_BOOTTIME_ALARM: ClockId = ClockId(libc::CLOCK_BOOTTIME_ALARM); + /// Increments in SI seconds. pub const CLOCK_MONOTONIC: ClockId = ClockId(libc::CLOCK_MONOTONIC); - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Like [`CLOCK_MONOTONIC`](ClockId::CLOCK_MONOTONIC), but optimized for execution time at the expense of accuracy. + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] pub const CLOCK_MONOTONIC_COARSE: ClockId = ClockId(libc::CLOCK_MONOTONIC_COARSE); - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] + /// Like [`CLOCK_MONOTONIC`](ClockId::CLOCK_MONOTONIC), but optimized for execution time at the expense of accuracy. pub const CLOCK_MONOTONIC_FAST: ClockId = ClockId(libc::CLOCK_MONOTONIC_FAST); - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] + /// Like [`CLOCK_MONOTONIC`](ClockId::CLOCK_MONOTONIC), but optimized for accuracy at the expense of execution time. pub const CLOCK_MONOTONIC_PRECISE: ClockId = ClockId(libc::CLOCK_MONOTONIC_PRECISE); - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Similar to [`CLOCK_MONOTONIC`](ClockId::CLOCK_MONOTONIC), but provides access to a raw + /// hardware-based time that is not subject to NTP adjustments. + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] pub const CLOCK_MONOTONIC_RAW: ClockId = ClockId(libc::CLOCK_MONOTONIC_RAW); #[cfg(any( - target_os = "android", + linux_android, + apple_targets, + freebsdlike, target_os = "emscripten", target_os = "fuchsia", - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "dragonfly", target_os = "redox", - target_os = "linux" ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Returns the execution time of the calling process. 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())))] + #[cfg(freebsdlike)] + /// Increments when the CPU is running in user or kernel mode pub const CLOCK_PROF: ClockId = ClockId(libc::CLOCK_PROF); + /// Increments as a wall clock should. pub const CLOCK_REALTIME: ClockId = ClockId(libc::CLOCK_REALTIME); - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Like [`CLOCK_REALTIME`](ClockId::CLOCK_REALTIME), but not settable. + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] pub const CLOCK_REALTIME_ALARM: ClockId = ClockId(libc::CLOCK_REALTIME_ALARM); - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Like [`CLOCK_REALTIME`](ClockId::CLOCK_REALTIME), but optimized for execution time at the expense of accuracy. + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] 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())))] + #[cfg(freebsdlike)] + /// Like [`CLOCK_REALTIME`](ClockId::CLOCK_REALTIME), but optimized for execution time at the expense of accuracy. pub const CLOCK_REALTIME_FAST: ClockId = ClockId(libc::CLOCK_REALTIME_FAST); - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] + /// Like [`CLOCK_REALTIME`](ClockId::CLOCK_REALTIME), but optimized for accuracy at the expense of execution time. 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())))] + #[cfg(freebsdlike)] + /// Returns the current second without performing a full time counter query, using an in-kernel + /// cached value of the current second. pub const CLOCK_SECOND: ClockId = ClockId(libc::CLOCK_SECOND); + #[allow(missing_docs)] // Undocumented on Linux! #[cfg(any( target_os = "emscripten", target_os = "fuchsia", - all(target_os = "linux", any(target_env = "musl", target_env = "ohos")) + all( + target_os = "linux", + any(target_env = "musl", target_env = "ohos") + ) ))] - #[cfg_attr(docsrs, doc(cfg(all())))] pub const CLOCK_SGI_CYCLE: ClockId = ClockId(libc::CLOCK_SGI_CYCLE); - #[cfg(any( - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "linux" - ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// International Atomic Time. + /// + /// A nonsettable system-wide clock derived from wall-clock time but ignoring leap seconds. + #[cfg(any(linux_android, target_os = "emscripten", target_os = "fuchsia"))] pub const CLOCK_TAI: ClockId = ClockId(libc::CLOCK_TAI); #[cfg(any( - target_os = "android", + linux_android, + apple_targets, + freebsdlike, target_os = "emscripten", target_os = "fuchsia", - target_os = "ios", - target_os = "macos", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux" ))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Returns the execution time of the calling thread. 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())))] + #[cfg(freebsdlike)] + /// Starts at zero when the kernel boots and increments monotonically in SI seconds while the + /// machine is running. pub const CLOCK_UPTIME: ClockId = ClockId(libc::CLOCK_UPTIME); - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] + /// Like [`CLOCK_UPTIME`](ClockId::CLOCK_UPTIME), but optimized for execution time at the expense of accuracy. pub const CLOCK_UPTIME_FAST: ClockId = ClockId(libc::CLOCK_UPTIME_FAST); - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(freebsdlike)] + /// Like [`CLOCK_UPTIME`](ClockId::CLOCK_UPTIME), but optimized for accuracy at the expense of execution time. 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())))] + #[cfg(freebsdlike)] + /// Increments only when the CPU is running in user mode on behalf of the calling process. pub const CLOCK_VIRTUAL: ClockId = ClockId(libc::CLOCK_VIRTUAL); } @@ -223,7 +180,6 @@ 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 = @@ -247,12 +203,12 @@ pub fn clock_gettime(clock_id: ClockId) -> Result { /// Set the time of the specified clock, (see /// [clock_settime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_settime.html)). #[cfg(not(any( - target_os = "macos", target_os = "ios", + target_os = "tvos", + target_os = "watchos", target_os = "redox", - target_os = "hermit", + 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()) }; @@ -261,13 +217,7 @@ pub fn clock_settime(clock_id: ClockId, timespec: TimeSpec) -> Result<()> { /// Get the clock id of the specified process id, (see /// [clock_getcpuclockid(3)](https://pubs.opengroup.org/onlinepubs/009695399/functions/clock_getcpuclockid.html)). -#[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux", - target_os = "android", - target_os = "emscripten", -))] +#[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] #[cfg(feature = "process")] #[cfg_attr(docsrs, doc(cfg(feature = "process")))] pub fn clock_getcpuclockid(pid: Pid) -> Result { @@ -278,6 +228,61 @@ pub fn clock_getcpuclockid(pid: Pid) -> Result { let res = unsafe { clk_id.assume_init() }; Ok(ClockId::from(res)) } else { - Err(Errno::from_i32(ret)) + Err(Errno::from_raw(ret)) + } +} + +#[cfg(any( + linux_android, + solarish, + freebsdlike, + target_os = "netbsd", + target_os = "hurd", + target_os = "aix" +))] +libc_bitflags! { + /// Flags that are used for arming the timer. + pub struct ClockNanosleepFlags: libc::c_int { + /// Indicates that a requested time value should be treated as absolute instead of + /// relative. + TIMER_ABSTIME; + } +} + +/// Suspend execution of this thread for the amount of time specified by `request` +/// and measured against the clock speficied by `clock_id`. +/// +/// If `flags` is [`TIMER_ABSTIME`](ClockNanosleepFlags::TIMER_ABSTIME), this function will suspend +/// execution until the time value of clock_id reaches the absolute time specified by `request`. If +/// a signal is caught by a signal-catching function, or a signal causes the process to terminate, +/// this sleep is interrrupted. +/// +/// see also [man 3 clock_nanosleep](https://pubs.opengroup.org/onlinepubs/009695399/functions/clock_nanosleep.html) +#[cfg(any( + linux_android, + solarish, + freebsdlike, + target_os = "netbsd", + target_os = "hurd", + target_os = "aix" +))] +pub fn clock_nanosleep( + clock_id: ClockId, + flags: ClockNanosleepFlags, + request: &TimeSpec, +) -> Result { + let mut remain = TimeSpec::new(0, 0); + let ret = unsafe { + libc::clock_nanosleep( + clock_id.as_raw(), + flags.bits(), + request.as_ref() as *const _, + remain.as_mut() as *mut _, + ) + }; + if ret == 0 { + Ok(remain) + } else { + Err(Errno::from_raw(ret)) } } diff --git a/src/unistd.rs b/src/unistd.rs index ca07b34a..8995fbe0 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1,65 +1,48 @@ //! Safe wrappers around functions found in libc "unistd.h" header -use crate::errno::{self, Errno}; +use crate::errno::Errno; + #[cfg(not(target_os = "redox"))] #[cfg(feature = "fs")] -use crate::fcntl::{at_rawfd, AtFlags}; +use crate::fcntl::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" - ) +#[cfg(any( + linux_android, + freebsdlike, + solarish, + netbsdlike, + target_os = "emscripten", + target_os = "fuchsia", + target_os = "hurd", + target_os = "redox", + target_os = "cygwin", ))] +use crate::fcntl::OFlag; +#[cfg(all(feature = "fs", bsd))] 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 libc::{c_char, c_int, c_long, c_uint, gid_t, off_t, pid_t, size_t, uid_t}; use std::convert::Infallible; -use std::ffi::{CStr, OsString}; #[cfg(not(target_os = "redox"))] -use std::ffi::{CString, OsStr}; -#[cfg(not(target_os = "redox"))] -use std::os::unix::ffi::OsStrExt; -use std::os::unix::ffi::OsStringExt; -use std::os::unix::io::RawFd; +use std::ffi::CString; +use std::ffi::{CStr, OsStr, OsString}; +use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::path::PathBuf; use std::{fmt, mem, ptr}; feature! { #![feature = "fs"] - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] pub use self::pivot_root::*; } -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +#[cfg(any(freebsdlike, linux_android, target_os = "openbsd"))] pub use self::setres::*; -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +#[cfg(any(freebsdlike, linux_android, target_os = "openbsd"))] pub use self::getres::*; feature! { @@ -217,7 +200,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 @@ -225,12 +207,16 @@ impl fmt::Display for Pid { /// you are now executing in the parent process or in the child. #[derive(Clone, Copy, Debug)] pub enum ForkResult { - Parent { child: Pid }, + /// This is the parent process of the fork. + Parent { + /// The PID of the fork's child process + child: Pid + }, + /// This is the child process of the fork. Child, } impl ForkResult { - /// Return `true` if this is the child process of the `fork()` #[inline] pub fn is_child(self) -> bool { @@ -261,7 +247,7 @@ impl ForkResult { /// } /// Ok(ForkResult::Child) => { /// // Unsafe to use `println!` (or `unwrap`) here. See Safety. -/// write(libc::STDOUT_FILENO, "I'm a new child process\n".as_bytes()).ok(); +/// write(std::io::stdout(), "I'm a new child process\n".as_bytes()).ok(); /// unsafe { libc::_exit(0) }; /// } /// Err(_) => println!("Fork failed"), @@ -280,9 +266,9 @@ impl ForkResult { /// # Safety /// /// In a multithreaded program, only [async-signal-safe] functions like `pause` -/// and `_exit` may be called by the child (the parent isn't restricted). Note -/// that memory allocation may **not** be async-signal-safe and thus must be -/// prevented. +/// and `_exit` may be called by the child (the parent isn't restricted) until +/// a call of `execve(2)`. Note that memory allocation may **not** be +/// async-signal-safe and thus must be prevented. /// /// Those functions are only a small subset of your operating system's API, so /// special care must be taken to only invoke code you can control and audit. @@ -291,7 +277,7 @@ impl ForkResult { #[inline] pub unsafe fn fork() -> Result { use self::ForkResult::*; - let res = libc::fork(); + let res = unsafe { libc::fork() }; Errno::result(res).map(|res| match res { 0 => Child, @@ -333,6 +319,9 @@ pub fn setpgid(pid: Pid, pgid: Pid) -> Result<()> { let res = unsafe { libc::setpgid(pid.into(), pgid.into()) }; Errno::result(res).map(drop) } +/// Get process group +/// +/// See Also [`getpgid`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgid.html) #[inline] pub fn getpgid(pid: Option) -> Result { let res = unsafe { libc::getpgid(pid.unwrap_or(Pid(0)).into()) }; @@ -367,8 +356,10 @@ feature! { /// Get the group process id (GPID) of the foreground process group on the /// terminal associated to file descriptor (FD). #[inline] -pub fn tcgetpgrp(fd: c_int) -> Result { - let res = unsafe { libc::tcgetpgrp(fd) }; +pub fn tcgetpgrp(fd: F) -> Result { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::tcgetpgrp(fd.as_fd().as_raw_fd()) }; Errno::result(res).map(Pid) } /// Set the terminal foreground process group (see @@ -377,8 +368,10 @@ pub fn tcgetpgrp(fd: c_int) -> Result { /// Get the group process id (PGID) to the foreground process group on the /// terminal associated to file descriptor (FD). #[inline] -pub fn tcsetpgrp(fd: c_int, pgrp: Pid) -> Result<()> { - let res = unsafe { libc::tcsetpgrp(fd, pgrp.into()) }; +pub fn tcsetpgrp(fd: F, pgrp: Pid) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::tcsetpgrp(fd.as_fd().as_raw_fd(), pgrp.into()) }; Errno::result(res).map(drop) } } @@ -405,7 +398,7 @@ pub fn getpgrp() -> Pid { /// /// No error handling is required as a thread id should always exist for any /// process, even if threads are not being used. -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[inline] pub fn gettid() -> Pid { Pid(unsafe { libc::syscall(libc::SYS_gettid) as pid_t }) @@ -414,8 +407,7 @@ pub fn gettid() -> Pid { feature! { #![feature = "fs"] -/// Create a copy of the specified file descriptor (see -/// [dup(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html)). +/// Create a copy of the specified file descriptor. /// /// 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 @@ -424,51 +416,298 @@ feature! { /// for the file descriptor will be the lowest fd index that is available. /// /// The two file descriptors do not share file descriptor flags (e.g. `OFlag::FD_CLOEXEC`). +/// +/// # Reference +/// +/// * [POSIX manual](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html) +/// +/// # See also +/// +/// * [`dup2()`] +/// * [`dup2_raw()`] +/// * `dup3()` +/// * `dup3_raw()` #[inline] -pub fn dup(oldfd: RawFd) -> Result { - let res = unsafe { libc::dup(oldfd) }; +pub fn dup(oldfd: Fd) -> Result { + use std::os::fd::AsRawFd; + use std::os::fd::OwnedFd; + use std::os::fd::FromRawFd; - Errno::result(res) + let res = unsafe { libc::dup(oldfd.as_fd().as_raw_fd()) }; + Errno::result(res)?; + // SAFETY: + // + // `dup(2)` would return a valid owned file descriptor on success + Ok( unsafe { OwnedFd::from_raw_fd(res) }) } -/// Create a copy of the specified file descriptor using the specified fd (see -/// [dup(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html)). +/// Duplicate `fd` with Stdin, i.e., Stdin redirection. +#[inline] +pub fn dup2_stdin(fd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + use libc::STDIN_FILENO; + + let res = unsafe { libc::dup2(fd.as_fd().as_raw_fd(), STDIN_FILENO) }; + Errno::result(res).map(drop) +} + +/// Duplicate `fd` with Stdout, i.e., Stdout redirection. +/// +/// # Examples +/// +/// Redirect the Stdout to file foo and restore it: +/// +/// ```no_run +/// use nix::fcntl::open; +/// use nix::fcntl::OFlag; +/// use nix::sys::stat::Mode; +/// use nix::unistd::dup; +/// use nix::unistd::dup2_stdout; +/// use std::io::{stdout, Write}; +/// +/// let mut stdout = stdout(); +/// +/// // Save the previous Stdout so that we can restore it +/// let saved_stdout = dup(&stdout).unwrap(); +/// let foo = open( +/// "foo", +/// OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_CREAT | OFlag::O_EXCL, +/// Mode::S_IRWXU, +/// ) +/// .unwrap(); +/// // Now our Stdout has been redirected to file foo +/// dup2_stdout(foo).unwrap(); +/// // Let's say hi to foo +/// // NOTE: add a newline here to flush the buffer +/// stdout.write(b"Hi, foo!\n").unwrap(); +/// +/// // Restore the Stdout +/// dup2_stdout(saved_stdout).unwrap(); +/// +/// // Let's say hi to Stdout +/// // NOTE: add a newline here to flush the buffer +/// stdout.write(b"Hi, Stdout!\n").unwrap(); +/// ``` +#[inline] +pub fn dup2_stdout(fd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + use libc::STDOUT_FILENO; + + let res = unsafe { libc::dup2(fd.as_fd().as_raw_fd(), STDOUT_FILENO) }; + Errno::result(res).map(drop) +} + +/// Duplicate `fd` with Stderr, i.e., Stderr redirection. +/// +/// # Examples +/// +/// See the example of [`dup2_stdout()`](fn.dup2_stdout.html#examples) +#[inline] +pub fn dup2_stderr(fd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + use libc::STDERR_FILENO; + + let res = unsafe { libc::dup2(fd.as_fd().as_raw_fd(), STDERR_FILENO) }; + Errno::result(res).map(drop) +} + +/// Create a copy of `oldfd` using `newfd`. /// /// This function behaves similar to `dup()` except that it will try to use the -/// specified fd instead of allocating a new one. See the man pages for more -/// detail on the exact behavior of this function. +/// specified fd `newfd` instead of allocating a new one. See the man pages for +/// more detail on the exact behavior of this function. +/// +/// This function does not allow you to duplicate `oldfd` with any file descriptor +/// you want, to do that, use [`dup2_raw()`]. +/// +/// # Stdin/Stdout/Stderr redirection +/// +/// To duplicate a fd with Stdin/Stdout/Stderr, see: +/// +/// * [`dup2_stdin()`] +/// * [`dup2_stdout()`] +/// * [`dup2_stderr()`] +/// +/// # Reference +/// +/// [dup(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html) #[inline] -pub fn dup2(oldfd: RawFd, newfd: RawFd) -> Result { - let res = unsafe { libc::dup2(oldfd, newfd) }; +pub fn dup2(oldfd: Fd, newfd: &mut std::os::fd::OwnedFd) -> Result<()> { + use std::os::fd::AsRawFd; - Errno::result(res) + let res = unsafe { libc::dup2(oldfd.as_fd().as_raw_fd(), newfd.as_raw_fd()) }; + + Errno::result(res).map(drop) +} + +/// Create a copy of `oldfd` with any fd value you want. +/// +/// # Safety +/// +/// Since this function returns an `OwnedFd`, you have to ensure that the returned +/// `OwnedFd` is the ONLY owner of the file descriptor specified `newfd`. Otherwise, +/// double close could happen. +/// +/// ```no_run +/// # use nix::{ +/// # fcntl::{open, OFlag}, +/// # sys::stat::Mode, +/// # unistd::dup2_raw, +/// # }; +/// # use std::os::fd::OwnedFd; +/// # use std::os::fd::AsRawFd; +/// let oldfd: OwnedFd = open("foo", OFlag::O_RDONLY, Mode::empty()).unwrap(); +/// let newfd: OwnedFd = open("bar", OFlag::O_RDONLY, Mode::empty()).unwrap(); +/// +/// // SAFETY: +/// // it is NOT safe. +/// // NOTE that we are passing a RawFd to `newfd` +/// let duplicated_fd: OwnedFd = unsafe { dup2_raw(&oldfd, newfd.as_raw_fd()) }.unwrap(); +/// +/// // `newfd` and `duplicated_fd` refer to the same file descriptor, and +/// // they are both owned, double close will happen here. +/// ``` +/// +/// # Examples +/// +/// Duplicate a file descriptor with a descriptor that is still not open: +/// +/// ```no_run +/// # use nix::{ +/// # fcntl::{open, OFlag}, +/// # sys::stat::Mode, +/// # unistd::dup2_raw, +/// # }; +/// let oldfd = open("foo", OFlag::O_RDONLY, Mode::empty()).unwrap(); +/// +/// // SAFETY: +/// // It is safe given that we are sure that fd 100 is not open, and the returned +/// // OwnedFd will be its only owner. +/// let duplicated_fd = unsafe { dup2_raw(&oldfd, 100) }.unwrap(); +/// +/// // do something with `duplicated_fd` +/// ``` +/// +/// The code demonstrating double close can be fixed by passing `newfd` by value: +/// +/// ```no_run +/// # use nix::{ +/// # fcntl::{open, OFlag}, +/// # sys::stat::Mode, +/// # unistd::dup2_raw, +/// # }; +/// # use std::os::fd::OwnedFd; +/// let oldfd: OwnedFd = open("foo", OFlag::O_RDONLY, Mode::empty()).unwrap(); +/// let newfd: OwnedFd = open("bar", OFlag::O_RDONLY, Mode::empty()).unwrap(); +/// +/// // SAFETY: +/// // it is safe since `duplicated_fd` is the only owner of the fd it refers to. +/// // NOTE that we are passing `newfd` by value, i.e., transfer the ownership +/// let duplicated_fd: OwnedFd = unsafe { dup2_raw(&oldfd, newfd) }.unwrap(); +/// ``` +/// +/// # Reference +/// +/// * [POSIX manual](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html) +/// +/// # See also +/// +/// * [`dup2()`] +#[inline] +pub unsafe fn dup2_raw(oldfd: Fd1, newfd: Fd2) -> Result { + use std::os::fd::AsRawFd; + use std::os::fd::FromRawFd; + use std::os::fd::OwnedFd; + + let duplicated_fd = unsafe { + libc::dup2(oldfd.as_fd().as_raw_fd(), newfd.into_raw_fd()) + }; + // SAFETY: + // + // This is unsafe if `newfd` is not a file descriptor that can be consumed + Ok(unsafe { + OwnedFd::from_raw_fd(duplicated_fd) + }) } /// Create a new copy of the specified file descriptor using the specified fd -/// and flags (see [dup(2)](https://man7.org/linux/man-pages/man2/dup.2.html)). +/// and flags. /// -/// This function behaves similar to `dup2()` but allows for flags to be -/// specified. -pub fn dup3(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result { - dup3_polyfill(oldfd, newfd, flags) +/// This function behaves similar to [`dup2()`] but allows flags to be specified +/// for the new file descriptor. Currently, the only flag that is allowed is +/// [`OFlag::O_CLOEXEC`], setting other flags will return `EINVAL`. Also, if +/// `oldfd` and `newfd` have the same fd value, `EINVAL` will also be returned. +/// +/// This function does not allow you to duplicate `oldfd` with any file descriptor +/// you want, to do that, use [`dup3_raw()`]. +/// +/// # References +/// +/// * [FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=dup3&sektion=3) +/// * [Linux](https://man7.org/linux/man-pages/man2/dup.2.html) +/// * [NetBSD](https://man.netbsd.org/dup3.2) +/// * [OpenBSD](https://man.openbsd.org/dup3.2) +#[cfg(any( + netbsdlike, + solarish, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "hurd", + target_os = "linux" +))] +pub fn dup3(oldfd: Fd, newfd: &mut std::os::fd::OwnedFd, flags: OFlag) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::dup3(oldfd.as_fd().as_raw_fd(), newfd.as_raw_fd(), flags.bits()) }; + Errno::result(res).map(drop) } -#[inline] -fn dup3_polyfill(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result { - if oldfd == newfd { - return Err(Errno::EINVAL); - } +/// Create a new copy of the specified file descriptor using the specified fd +/// and flags. +/// +/// This function behaves similar to [`dup3()`] except for it allows you to specify +/// arbitrary fd values. +/// +/// # Safety +/// +/// Since this function returns an `OwnedFd`, you have to ensure that the returned +/// `OwnedFd` is the ONLY owner of the file descriptor specified `newfd`. Otherwise, +/// double close could happen. +/// +/// For more information, see the documentation of [`dup2_raw()`]. +/// +/// # References +/// +/// * [FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=dup3&sektion=3) +/// * [Linux](https://man7.org/linux/man-pages/man2/dup.2.html) +/// * [NetBSD](https://man.netbsd.org/dup3.2) +/// * [OpenBSD](https://man.openbsd.org/dup3.2) +/// +/// # See also +/// +/// * [`dup3()`] +#[cfg(any( + netbsdlike, + solarish, + target_os = "freebsd", + target_os = "fuchsia", + target_os = "hurd", + target_os = "linux" +))] +pub unsafe fn dup3_raw(oldfd: Fd1, newfd: Fd2, flags: OFlag) -> Result { + use std::os::fd::AsRawFd; + use std::os::fd::OwnedFd; + use std::os::fd::FromRawFd; - let fd = dup2(oldfd, newfd)?; + let res = unsafe { libc::dup3(oldfd.as_fd().as_raw_fd(), newfd.into_raw_fd(), flags.bits()) }; + Errno::result(res)?; - if flags.contains(OFlag::O_CLOEXEC) { - if let Err(e) = fcntl(fd, F_SETFD(FdFlag::FD_CLOEXEC)) { - let _ = close(fd); - return Err(e); - } - } - - Ok(fd) + // SAFETY: + // + // This is unsafe if `newfd` is not a file descriptor that can be consumed + Ok(unsafe { + OwnedFd::from_raw_fd(res) + }) } /// Change the current working directory of the calling process (see @@ -478,9 +717,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) } @@ -493,8 +731,10 @@ pub fn chdir(path: &P) -> Result<()> { /// pages for additional details on possible failure cases. #[inline] #[cfg(not(target_os = "fuchsia"))] -pub fn fchdir(dirfd: RawFd) -> Result<()> { - let res = unsafe { libc::fchdir(dirfd) }; +pub fn fchdir(dirfd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::fchdir(dirfd.as_fd().as_raw_fd()) }; Errno::result(res).map(drop) } @@ -526,15 +766,15 @@ 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) } +pub fn mkdir(path: &P, mode: crate::sys::stat::Mode) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mkdir(cstr.as_ptr(), mode.bits() as libc::mode_t) })?; Errno::result(res).map(drop) } -/// Creates new fifo special file (named pipe) with path `path` and access rights `mode`. +/// Creates new FIFO special file (named pipe) with path `path` and access rights `mode`. /// /// # Errors /// @@ -565,19 +805,28 @@ 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) } +pub fn mkfifo(path: &P, mode: crate::sys::stat::Mode) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::mkfifo(cstr.as_ptr(), mode.bits() as libc::mode_t) })?; Errno::result(res).map(drop) } -/// Creates new fifo special file (named pipe) with path `path` and access rights `mode`. +/// Creates new FIFO special file (named pipe) with access rights set to `mode` +/// in the path specified by `dirfd` and `path`. /// -/// If `dirfd` has a value, then `path` is relative to directory associated with the file descriptor. +/// # Examples /// -/// If `dirfd` is `None`, then `path` is relative to the current working directory. +/// Create a FIFO in the current working directory: +/// +/// ```no_run +/// use nix::fcntl::AT_FDCWD; +/// use nix::unistd::mkfifoat; +/// use nix::sys::stat::Mode; +/// +/// mkfifoat(AT_FDCWD, "fifo", Mode::S_IRWXU).unwrap(); +/// ``` /// /// # References /// @@ -585,42 +834,60 @@ 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 = "haiku", - target_os = "android", target_os = "redox")))] -pub fn mkfifoat(dirfd: Option, path: &P, mode: Mode) -> Result<()> { + apple_targets, + target_os = "haiku", + target_os = "android", + target_os = "redox" +)))] +pub fn mkfifoat( + dirfd: Fd, + path: &P, + mode: crate::sys::stat::Mode, +) -> Result<()> { + use std::os::fd::AsRawFd; + let res = path.with_nix_path(|cstr| unsafe { - libc::mkfifoat(at_rawfd(dirfd), cstr.as_ptr(), mode.bits() as mode_t) + libc::mkfifoat(dirfd.as_fd().as_raw_fd(), cstr.as_ptr(), mode.bits() as libc::mode_t) })?; Errno::result(res).map(drop) } -/// Creates a symbolic link at `path2` which points to `path1`. +/// Creates a symbolic link to `path1` in the path specified by `dirfd` and +/// `path2`. /// -/// If `dirfd` has a value, then `path2` is relative to directory associated -/// with the file descriptor. +/// # Examples /// -/// If `dirfd` is `None`, then `path2` is relative to the current working -/// directory. This is identical to `libc::symlink(path1, path2)`. +/// Assume file `foo` exists in the current working directory, create a symlink +/// to it: /// -/// See also [symlinkat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html). +/// ```no_run +/// use nix::fcntl::AT_FDCWD; +/// use nix::unistd::symlinkat; +/// +/// symlinkat("foo", AT_FDCWD, "link_to_foo").unwrap(); +/// ``` +/// +/// # References +/// +/// [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html) #[cfg(not(target_os = "redox"))] -pub fn symlinkat( +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() - ) - } - }) - })??; + dirfd: Fd, + path2: &P2, +) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = path1.with_nix_path(|path1| { + path2.with_nix_path(|path2| unsafe { + libc::symlinkat( + path1.as_ptr(), + dirfd.as_fd().as_raw_fd(), + path2.as_ptr(), + ) + }) + })??; Errno::result(res).map(drop) } } @@ -660,17 +927,19 @@ feature! { /// ``` #[inline] pub fn getcwd() -> Result { - let mut buf = Vec::with_capacity(512); + let mut buf = Vec::::with_capacity(512); loop { unsafe { - let ptr = buf.as_mut_ptr() as *mut c_char; + let ptr = buf.as_mut_ptr().cast(); // The buffer must be large enough to store the absolute pathname plus // a terminating null byte, or else null is returned. // To safely handle this we start with a reasonable size (512 bytes) // and double the buffer size upon every error if !libc::getcwd(ptr, buf.capacity()).is_null() { - let len = CStr::from_ptr(buf.as_ptr() as *const c_char).to_bytes().len(); + let len = CStr::from_ptr(buf.as_ptr().cast()) + .to_bytes() + .len(); buf.set_len(len); buf.shrink_to_fit(); return Ok(PathBuf::from(OsString::from_vec(buf))); @@ -680,10 +949,15 @@ pub fn getcwd() -> Result { if error != Errno::ERANGE { return Err(error); } - } + } + + #[cfg(not(target_os = "hurd"))] + const PATH_MAX: usize = libc::PATH_MAX as usize; + #[cfg(target_os = "hurd")] + const PATH_MAX: usize = 1024; // Hurd does not define a hard limit, so try a guess first // Trigger the internal buffer resizing logic. - reserve_double_buffer_size(&mut buf, PATH_MAX as usize)?; + reserve_double_buffer_size(&mut buf, PATH_MAX)?; } } } @@ -695,13 +969,15 @@ feature! { /// 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) } @@ -714,7 +990,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) } @@ -723,25 +1003,36 @@ pub fn chown(path: &P, owner: Option, group: Option, group: Option) -> Result<()> { +pub fn fchown(fd: Fd, owner: Option, group: Option) -> Result<()> { + use std::os::fd::AsRawFd; + let (uid, gid) = chown_raw_ids(owner, group); - let res = unsafe { libc::fchown(fd, uid, gid) }; + let res = unsafe { libc::fchown(fd.as_fd().as_raw_fd(), uid, gid) }; Errno::result(res).map(drop) } -/// Flags for `fchownat` function. -#[derive(Clone, Copy, Debug)] -pub enum FchownatFlags { - FollowSymlink, - NoFollowSymlink, +// Just a wrapper around `AtFlags` so that we can help our users migrate. +#[allow(missing_docs)] +#[cfg(not(target_os = "redox"))] +pub type FchownatFlags = AtFlags; +#[allow(missing_docs)] +#[cfg(not(target_os = "redox"))] +impl FchownatFlags { + #[deprecated(since = "0.28.0", note = "The variant is deprecated, please use `AtFlags` instead")] + #[allow(non_upper_case_globals)] + pub const FollowSymlink: FchownatFlags = FchownatFlags::empty(); + #[deprecated(since = "0.28.0", note = "The variant is deprecated, please use `AtFlags` instead")] + #[allow(non_upper_case_globals)] + pub const NoFollowSymlink: FchownatFlags = FchownatFlags::AT_SYMLINK_NOFOLLOW; } /// Change the ownership of the file at `path` to be owned by the specified @@ -751,14 +1042,10 @@ pub enum FchownatFlags { /// provided for that argument. Ownership change will be attempted for the path /// only if `Some` owner/group is provided. /// -/// The file to be changed is determined relative to the directory associated -/// with the file descriptor `dirfd` or the current working directory -/// if `dirfd` is `None`. -/// -/// If `flag` is `FchownatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// If `flag` is `AtFlags::AT_SYMLINK_NOFOLLOW` and `path` names a symbolic link, /// then the mode of the symbolic link is changed. /// -/// `fchownat(None, path, owner, group, FchownatFlags::NoFollowSymlink)` is identical to +/// `fchownat(AT_FDCWD, path, owner, group, AtFlags::AT_SYMLINK_NOFOLLOW)` is identical to /// a call `libc::lchown(path, owner, group)`. That's why `lchown` is unimplemented in /// the `nix` crate. /// @@ -766,22 +1053,24 @@ pub enum FchownatFlags { /// /// [fchownat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html). #[cfg(not(target_os = "redox"))] -pub fn fchownat( - dirfd: Option, +pub fn fchownat( + dirfd: Fd, path: &P, owner: Option, group: Option, - flag: FchownatFlags, + flag: AtFlags, ) -> Result<()> { - let atflag = - match flag { - FchownatFlags::FollowSymlink => AtFlags::empty(), - FchownatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, - }; + use std::os::fd::AsRawFd; + 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( + dirfd.as_fd().as_raw_fd(), + cstr.as_ptr(), + uid, + gid, + flag.bits() + ) })?; Errno::result(res).map(drop) @@ -808,14 +1097,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)). /// @@ -829,13 +1115,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()) } @@ -850,12 +1138,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()) } @@ -867,10 +1156,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 = "hurd", 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); @@ -891,18 +1182,19 @@ 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. -#[cfg(any(target_os = "android", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd"))] +#[cfg(any(linux_android, freebsdlike, target_os = "hurd"))] #[inline] -pub fn fexecve ,SE: AsRef>(fd: RawFd, args: &[SA], env: &[SE]) -> Result { +pub fn fexecve, SE: AsRef>( + fd: Fd, + args: &[SA], + env: &[SE], +) -> Result { + use std::os::fd::AsRawFd; + 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.as_fd().as_raw_fd(), args_p.as_ptr(), env_p.as_ptr()) }; Err(Errno::last()) } @@ -917,16 +1209,29 @@ pub fn fexecve ,SE: AsRef>(fd: RawFd, args: &[SA], env: &[ /// /// This function is similar to `execve`, except that the program to be executed /// is referenced as a file descriptor to the base directory plus a path. -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[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: Fd, + pathname: &CStr, + args: &[SA], + env: &[SE], + flags: super::fcntl::AtFlags, +) -> Result { + use std::os::fd::AsRawFd; + 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.as_fd().as_raw_fd(), + pathname.as_ptr(), + args_p.as_ptr(), + env_p.as_ptr(), + flags, + ); }; Err(Errno::last()) @@ -957,14 +1262,12 @@ 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( + linux_android, + freebsdlike, + solarish, + netbsdlike +))] 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) @@ -985,18 +1288,16 @@ feature! { pub fn sethostname>(name: S) -> Result<()> { // Handle some differences in type of the len arg across platforms. cfg_if! { - if #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "solaris", ))] { + if #[cfg(any(freebsdlike, + solarish, + apple_targets, + target_os = "aix"))] { type sethostname_len_t = c_int; } else { type sethostname_len_t = size_t; } } - let ptr = name.as_ref().as_bytes().as_ptr() as *const c_char; + let ptr = name.as_ref().as_bytes().as_ptr().cast(); let len = name.as_ref().len() as sethostname_len_t; let res = unsafe { libc::sethostname(ptr, len) }; @@ -1004,12 +1305,13 @@ pub fn sethostname>(name: S) -> Result<()> { } /// 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)). +/// `OsString` on success. /// /// This function call attempts to get the host name for the running system and /// store it in an internal buffer, returning it as an `OsString` if successful. /// +/// # Examples +/// /// ```no_run /// use nix::unistd; /// @@ -1017,17 +1319,19 @@ pub fn sethostname>(name: S) -> Result<()> { /// let hostname = hostname.into_string().expect("Hostname wasn't valid UTF-8"); /// println!("Hostname: {}", hostname); /// ``` +/// +/// See also [gethostname(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html). 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 ptr = buffer.as_mut_ptr().cast(); let len = buffer.capacity() as size_t; let res = unsafe { libc::gethostname(ptr, len) }; Errno::result(res).map(|_| { unsafe { buffer.as_mut_ptr().wrapping_add(len - 1).write(0); // ensure always null-terminated - let len = CStr::from_ptr(buffer.as_ptr() as *const c_char).len(); + let len = CStr::from_ptr(buffer.as_ptr().cast()).len(); buffer.set_len(len); } OsString::from_vec(buffer) @@ -1035,42 +1339,27 @@ pub fn gethostname() -> Result { } } -/// Close a raw file descriptor +/// Close a file descriptor. /// -/// Be aware that many Rust types implicitly close-on-drop, including -/// `std::fs::File`. Explicitly closing them with this method too can result in -/// a double-close condition, which can cause confusing `EBADF` errors in -/// seemingly unrelated code. Caveat programmer. See also -/// [close(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html). -/// -/// # Examples -/// -/// ```no_run -/// use std::os::unix::io::AsRawFd; -/// use nix::unistd::close; -/// -/// let f = tempfile::tempfile().unwrap(); -/// close(f.as_raw_fd()).unwrap(); // Bad! f will also close on drop! -/// ``` -/// -/// ```rust -/// use std::os::unix::io::IntoRawFd; -/// use nix::unistd::close; -/// -/// let f = tempfile::tempfile().unwrap(); -/// close(f.into_raw_fd()).unwrap(); // Good. into_raw_fd consumes f -/// ``` -pub fn close(fd: RawFd) -> Result<()> { - let res = unsafe { libc::close(fd) }; +/// If `fd` is an owned file descriptor, it is generally preferred to call +/// `drop(fd)` rather than `close(fd)`. +pub fn close(fd: Fd) -> Result<()> { + let res = unsafe { libc::close(fd.into_raw_fd()) }; Errno::result(res).map(drop) } /// Read from a raw file descriptor. /// /// See also [read(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html) -pub fn read(fd: RawFd, buf: &mut [u8]) -> Result { +pub fn read(fd: Fd, buf: &mut [u8]) -> Result { + use std::os::fd::AsRawFd; + let res = unsafe { - libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len() as size_t) + libc::read( + fd.as_fd().as_raw_fd(), + buf.as_mut_ptr().cast(), + buf.len() as size_t, + ) }; Errno::result(res).map(|r| r as usize) @@ -1079,9 +1368,15 @@ pub fn read(fd: RawFd, buf: &mut [u8]) -> Result { /// Write to a raw file descriptor. /// /// See also [write(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html) -pub fn write(fd: RawFd, buf: &[u8]) -> Result { +pub fn write(fd: Fd, buf: &[u8]) -> Result { + use std::os::fd::AsRawFd; + let res = unsafe { - libc::write(fd, buf.as_ptr() as *const c_void, buf.len() as size_t) + libc::write( + fd.as_fd().as_raw_fd(), + buf.as_ptr().cast(), + buf.len() as size_t, + ) }; Errno::result(res).map(|r| r as usize) @@ -1106,37 +1401,53 @@ 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( + apple_targets, + freebsdlike, + solarish, + target_os = "hurd", + target_os = "linux", + ))] 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( + apple_targets, + freebsdlike, + solarish, + target_os = "hurd", + target_os = "linux", + ))] + SeekHole = libc::SEEK_HOLE, } /// Move the read/write file offset. /// /// See also [lseek(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html) -pub fn lseek(fd: RawFd, offset: off_t, whence: Whence) -> Result { - let res = unsafe { libc::lseek(fd, offset, whence as i32) }; +pub fn lseek(fd: Fd, offset: off_t, whence: Whence) -> Result { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::lseek(fd.as_fd().as_raw_fd(), offset, whence as i32) }; Errno::result(res).map(|r| r as off_t) } -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn lseek64(fd: RawFd, offset: libc::off64_t, whence: Whence) -> Result { - let res = unsafe { libc::lseek64(fd, offset, whence as i32) }; +/// Move the read/write file offset. +/// +/// Unlike [`lseek`], it takes a 64-bit argument even on platforms where [`libc::off_t`] is +/// 32 bits. +#[cfg(linux_android)] +pub fn lseek64( + fd: Fd, + offset: libc::off64_t, + whence: Whence, +) -> Result { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::lseek64(fd.as_fd().as_raw_fd(), offset, whence as i32) }; Errno::result(res).map(|r| r as libc::off64_t) } @@ -1145,14 +1456,16 @@ pub fn lseek64(fd: RawFd, offset: libc::off64_t, whence: Whence) -> Result std::result::Result<(RawFd, RawFd), Error> { - let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); +pub fn pipe( +) -> std::result::Result<(std::os::fd::OwnedFd, std::os::fd::OwnedFd), Error> { + let mut fds = mem::MaybeUninit::<[std::os::fd::OwnedFd; 2]>::uninit(); - let res = unsafe { libc::pipe(fds.as_mut_ptr() as *mut c_int) }; + let res = unsafe { libc::pipe(fds.as_mut_ptr().cast()) }; Error::result(res)?; - unsafe { Ok((fds.assume_init()[0], fds.assume_init()[1])) } + let [read, write] = unsafe { fds.assume_init() }; + Ok((read, write)) } feature! { @@ -1163,31 +1476,37 @@ feature! { /// 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"))] -pub fn pipe2(flags: OFlag) -> Result<(RawFd, RawFd)> { - let mut fds = mem::MaybeUninit::<[c_int; 2]>::uninit(); +#[cfg(any( + linux_android, + freebsdlike, + solarish, + target_os = "emscripten", + target_os = "hurd", + target_os = "redox", + netbsdlike, + target_os = "cygwin", +))] +pub fn pipe2(flags: OFlag) -> Result<(std::os::fd::OwnedFd, std::os::fd::OwnedFd)> { + let mut fds = mem::MaybeUninit::<[std::os::fd::OwnedFd; 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().cast(), flags.bits()) }; Errno::result(res)?; - unsafe { Ok((fds.assume_init()[0], fds.assume_init()[1])) } + let [read, write] = unsafe { fds.assume_init() }; + Ok((read, write)) } /// Truncate a file to a specified length @@ -1196,11 +1515,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) } @@ -1209,133 +1525,141 @@ 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<()> { + use std::os::fd::AsRawFd; + + Errno::result(unsafe { libc::ftruncate(fd.as_fd().as_raw_fd(), len) }).map(drop) } -pub fn isatty(fd: RawFd) -> Result { +/// Determines if the file descriptor refers to a valid terminal type device. +pub fn isatty(fd: Fd) -> Result { + use std::os::fd::AsRawFd; + unsafe { // ENOTTY means `fd` is a valid file descriptor, but not a TTY, so // we return `Ok(false)` - if libc::isatty(fd) == 1 { + if libc::isatty(fd.as_fd().as_raw_fd()) == 1 { Ok(true) } else { match Errno::last() { Errno::ENOTTY => Ok(false), err => Err(err), } - } + } } } -/// Flags for `linkat` function. -#[derive(Clone, Copy, Debug)] -pub enum LinkatFlags { - SymlinkFollow, - NoSymlinkFollow, +#[allow(missing_docs)] +#[cfg(not(target_os = "redox"))] +pub type LinkatFlags = AtFlags; +#[allow(missing_docs)] +#[cfg(not(target_os = "redox"))] +impl LinkatFlags { + #[deprecated(since = "0.28.0", note = "The variant is deprecated, please use `AtFlags` instead")] + #[allow(non_upper_case_globals)] + pub const SymlinkFollow: LinkatFlags = LinkatFlags::AT_SYMLINK_FOLLOW; + #[deprecated(since = "0.28.0", note = "The variant is deprecated, please use `AtFlags` instead")] + #[allow(non_upper_case_globals)] + pub const NoSymlinkFollow: LinkatFlags = LinkatFlags::empty(); } /// Link one file to another file /// -/// Creates a new link (directory entry) at `newpath` for the existing file at `oldpath`. In the -/// case of a relative `oldpath`, the path is interpreted relative to the directory associated -/// with file descriptor `olddirfd` instead of the current working directory and similiarly for -/// `newpath` and file descriptor `newdirfd`. In case `flag` is LinkatFlags::SymlinkFollow and -/// `oldpath` names a symoblic link, a new link for the target of the symbolic link is created. -/// If either `olddirfd` or `newdirfd` is `None`, `AT_FDCWD` is used respectively where `oldpath` -/// and/or `newpath` is then interpreted relative to the current working directory of the calling -/// process. If either `oldpath` or `newpath` is absolute, then `dirfd` is ignored. +/// Creates a new hard link (directory entry) at `newpath` for the existing file +/// at `oldpath`. In the case of a relative `oldpath`, the path is interpreted +/// relative to the directory associated with file descriptor `olddirfd` instead +/// of the current working directory, use [`AT_FDCWD`](crate::fcntl::AT_FDCWD) +/// if you want to make it relative to the current working directory. Similarly +/// for `newpath` and file descriptor `newdirfd`. If either `oldpath` or `newpath` +/// is absolute, then `dirfd` is ignored. +/// +/// In case `flag` is `AtFlags::AT_SYMLINK_FOLLOW` and `oldpath` names a symoblic +/// link, a new link for the target of the symbolic link is created. /// /// # References /// See also [linkat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html) -#[cfg(not(target_os = "redox"))] // RedoxFS does not support symlinks yet -pub fn linkat( - olddirfd: Option, - oldpath: &P, - newdirfd: Option, - newpath: &P, - flag: LinkatFlags, +#[cfg(not(target_os = "redox"))] // Redox does not have this yet +pub fn linkat( + olddirfd: Fd1, + oldpath: &P1, + newdirfd: Fd2, + newpath: &P2, + flag: AtFlags, ) -> Result<()> { + use std::os::fd::AsRawFd; - 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( + olddirfd.as_fd().as_raw_fd(), + oldcstr.as_ptr(), + newdirfd.as_fd().as_raw_fd(), + newcstr.as_ptr(), + flag.bits(), + ) + }) + })??; 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) } /// Flags for `unlinkat` function. #[derive(Clone, Copy, Debug)] pub enum UnlinkatFlags { + /// Remove the directory entry as a directory, not a normal file RemoveDir, + /// Remove the directory entry as a normal file, not a directory NoRemoveDir, } /// Remove a directory entry /// -/// In the case of a relative path, the directory entry to be removed is determined relative to -/// the directory associated with the file descriptor `dirfd` or the current working directory -/// if `dirfd` is `None`. In the case of an absolute `path` `dirfd` is ignored. If `flag` is -/// `UnlinkatFlags::RemoveDir` then removal of the directory entry specified by `dirfd` and `path` -/// is performed. +/// In the case of a relative path, the directory entry to be removed is determined +/// relative to the directory associated with the file descriptor `dirfd` (Use +/// [`AT_FDCWD`](crate::fcntl::AT_FDCWD) if you want to specify the current working +/// directory in `dirfd`). In the case of an absolute path, `dirfd` is ignored. +/// +/// If `flag` is `UnlinkatFlags::RemoveDir` then removal of the directory entry +/// specified by `dirfd` and `path` is performed. /// /// # References /// See also [unlinkat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html) #[cfg(not(target_os = "redox"))] -pub fn unlinkat( - dirfd: Option, +pub fn unlinkat( + dirfd: Fd, 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) - } + use std::os::fd::AsRawFd; + + let atflag = match flag { + UnlinkatFlags::RemoveDir => AtFlags::AT_REMOVEDIR, + UnlinkatFlags::NoRemoveDir => AtFlags::empty(), + }; + let res = path.with_nix_path(|cstr| unsafe { + libc::unlinkat( + dirfd.as_fd().as_raw_fd(), + cstr.as_ptr(), + atflag.bits() as libc::c_int, + ) })?; Errno::result(res).map(drop) } - +/// Change a process's root directory #[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) } @@ -1343,13 +1667,7 @@ pub fn chroot(path: &P) -> Result<()> { /// Commit filesystem caches to disk /// /// See also [sync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html) -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd" -))] +#[cfg(any(bsd, linux_android, solarish, target_os = "haiku", target_os = "aix", target_os = "hurd"))] pub fn sync() { unsafe { libc::sync() }; } @@ -1358,9 +1676,11 @@ pub fn sync() { /// 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) }; +#[cfg(any(linux_android, target_os = "hurd"))] +pub fn syncfs(fd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::syncfs(fd.as_fd().as_raw_fd()) }; Errno::result(res).map(drop) } @@ -1369,8 +1689,10 @@ pub fn syncfs(fd: RawFd) -> Result<()> { /// /// See also [fsync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html) #[inline] -pub fn fsync(fd: RawFd) -> Result<()> { - let res = unsafe { libc::fsync(fd) }; +pub fn fsync(fd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = unsafe { libc::fsync(fd.as_fd().as_raw_fd()) }; Errno::result(res).map(drop) } @@ -1379,18 +1701,33 @@ pub fn fsync(fd: RawFd) -> Result<()> { /// /// See also /// [fdatasync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html) -#[cfg(any(target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "freebsd", - target_os = "fuchsia", - target_os = "netbsd", - target_os = "openbsd", - target_os = "illumos", - target_os = "solaris"))] +#[cfg(any( + linux_android, + solarish, + netbsdlike, + apple_targets, + target_os = "freebsd", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "aix", + target_os = "hurd", +))] #[inline] -pub fn fdatasync(fd: RawFd) -> Result<()> { - let res = unsafe { libc::fdatasync(fd) }; +pub fn fdatasync(fd: Fd) -> Result<()> { + use std::os::fd::AsRawFd; + + cfg_if! { + // apple libc supports fdatasync too, albeit not being present in its headers + // [fdatasync](https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/vfs/vfs_syscalls.c#L7728) + if #[cfg(apple_targets)] { + extern "C" { + fn fdatasync(fd: libc::c_int) -> libc::c_int; + } + } else { + use libc::fdatasync as fdatasync; + } + } + let res = unsafe { fdatasync(fd.as_fd().as_raw_fd()) }; Errno::result(res).map(drop) } @@ -1487,7 +1824,7 @@ feature! { /// ID of the caller. /// /// See also [setfsuid(2)](https://man7.org/linux/man-pages/man2/setfsuid.2.html) -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] pub fn setfsuid(uid: Uid) -> Uid { let prev_fsuid = unsafe { libc::setfsuid(uid.into()) }; Uid::from_raw(prev_fsuid as uid_t) @@ -1498,7 +1835,7 @@ pub fn setfsuid(uid: Uid) -> Uid { /// ID of the caller. /// /// See also [setfsgid(2)](https://man7.org/linux/man-pages/man2/setfsgid.2.html) -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] pub fn setfsgid(gid: Gid) -> Gid { let prev_fsgid = unsafe { libc::setfsgid(gid.into()) }; Gid::from_raw(prev_fsgid as gid_t) @@ -1515,14 +1852,14 @@ feature! { /// **Note:** This function is not available for Apple platforms. On those /// platforms, checking group membership should be achieved via communication /// with the `opendirectoryd` service. -#[cfg(not(any(target_os = "ios", target_os = "macos")))] +#[cfg(not(apple_targets))] pub fn getgroups() -> Result> { // First get the maximum number of groups. The value returned // shall always be greater than or equal to one and less than or // equal to the value of {NGROUPS_MAX} + 1. let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { Ok(Some(n)) => (n + 1) as usize, - Ok(None) | Err(_) => ::max_value(), + Ok(None) | Err(_) => usize::MAX, }; // Next, get the number of groups so we can size our Vec @@ -1539,27 +1876,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().cast(), + ) }; 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), } } } @@ -1595,17 +1936,17 @@ pub fn getgroups() -> Result> { /// # /// # try_main().unwrap(); /// ``` -#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox", target_os = "haiku")))] +#[cfg(not(any( + apple_targets, + target_os = "redox", + target_os = "haiku" +)))] pub fn setgroups(groups: &[Gid]) -> Result<()> { cfg_if! { - if #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "illumos", - target_os = "openbsd"))] { + if #[cfg(any(bsd, + solarish, + target_os = "aix", + target_os = "cygwin"))] { type setgroups_ngroups_t = c_int; } else { type setgroups_ngroups_t = size_t; @@ -1615,7 +1956,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().cast(), + ) }; Errno::result(res).map(drop) @@ -1641,19 +1985,22 @@ 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", + solarish, + apple_targets, + target_os = "redox", + target_os = "emscripten", +)))] pub fn getgrouplist(user: &CStr, group: Gid) -> Result> { let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { Ok(Some(n)) => n as c_int, - Ok(None) | Err(_) => ::max_value(), + Ok(None) | Err(_) => c_int::MAX, }; use std::cmp::min; let mut groups = Vec::::with_capacity(min(ngroups_max, 8) as usize); cfg_if! { - if #[cfg(any(target_os = "ios", target_os = "macos"))] { + if #[cfg(apple_targets)] { type getgrouplist_group_t = c_int; } else { type getgrouplist_group_t = gid_t; @@ -1663,10 +2010,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().cast(), + &mut ngroups, + ) }; // BSD systems only return 0 or -1, Linux returns ngroups on success. @@ -1722,17 +2071,23 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result> { /// # /// # try_main().unwrap(); /// ``` -#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox", target_os = "haiku")))] +#[cfg(not(any( + apple_targets, + target_os = "redox", + target_os = "haiku", + target_os = "emscripten", +)))] pub fn initgroups(user: &CStr, group: Gid) -> Result<()> { cfg_if! { - if #[cfg(any(target_os = "ios", target_os = "macos"))] { + if #[cfg(apple_targets)] { type initgroups_group_t = c_int; } else { type initgroups_group_t = gid_t; } } let gid: gid_t = group.into(); - let res = unsafe { libc::initgroups(user.as_ptr(), gid as initgroups_group_t) }; + let res = + unsafe { libc::initgroups(user.as_ptr(), gid as initgroups_group_t) }; Errno::result(res).map(drop) } @@ -1776,8 +2131,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}; @@ -1805,7 +2160,9 @@ pub mod alarm { //! sigset.add(Signal::SIGALRM); //! sigset.wait(); //! - //! assert!(start.elapsed() >= Duration::from_secs(1)); + //! // On Solaris, the signal can arrive before the full second. + //! const TOLERANCE: Duration = Duration::from_millis(10); + //! assert!(start.elapsed() + TOLERANCE >= Duration::from_secs(1)); //! ``` //! //! # References @@ -1851,19 +2208,19 @@ pub fn sleep(seconds: c_uint) -> c_uint { feature! { #![feature = "acct"] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +/// Process accounting +#[cfg(not(any(target_os = "redox", target_os = "haiku", target_os = "cygwin")))] 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) } @@ -1904,14 +2261,22 @@ feature! { /// // do something with fd /// ``` #[inline] -pub fn mkstemp(template: &P) -> Result<(RawFd, PathBuf)> { - let mut path = template.with_nix_path(|path| {path.to_bytes_with_nul().to_owned()})?; - let p = path.as_mut_ptr() as *mut _; +pub fn mkstemp(template: &P) -> Result<(std::os::fd::OwnedFd, PathBuf)> { + use std::os::fd::OwnedFd; + use std::os::fd::FromRawFd; + + let mut path = + template.with_nix_path(|path| path.to_bytes_with_nul().to_owned())?; + let p = path.as_mut_ptr().cast(); let fd = unsafe { libc::mkstemp(p) }; let last = path.pop(); // drop the trailing nul debug_assert!(last == Some(b'\0')); let pathname = OsString::from_vec(path); Errno::result(fd)?; + // SAFETY: + // + // `mkstemp(3)` should return a valid owned file descriptor on success. + let fd = unsafe { OwnedFd::from_raw_fd(fd) }; Ok((fd, PathBuf::from(pathname))) } } @@ -1919,6 +2284,38 @@ pub fn mkstemp(template: &P) -> Result<(RawFd, PathBuf)> { feature! { #![all(feature = "fs", feature = "feature")] +/// Creates a directory which persists even after process termination +/// +/// * `template`: a path whose rightmost characters contain some number of X, e.g. `/tmp/tmpdir_XXXXXX` +/// * returns: filename +/// +/// Err is returned either if no temporary filename could be created or the template had insufficient X +/// +/// See also [mkstemp(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html) +/// +/// ``` +/// use nix::unistd; +/// +/// match unistd::mkdtemp("/tmp/tempdir_XXXXXX") { +/// Ok(_path) => { +/// // do something with directory +/// } +/// Err(e) => panic!("mkdtemp failed: {}", e) +/// }; +/// ``` +pub fn mkdtemp(template: &P) -> Result { + let mut path = template.with_nix_path(|path| {path.to_bytes_with_nul().to_owned()})?; + let p = path.as_mut_ptr() as *mut _; + let p = unsafe { libc::mkdtemp(p) }; + if p.is_null() { + return Err(Errno::last()); + } + let last = path.pop(); // drop the trailing nul + debug_assert!(last == Some(b'\0')); + let pathname = OsString::from_vec(path); + Ok(PathBuf::from(pathname)) +} + /// Variable names for `pathconf` /// /// Nix uses the same naming convention for these variables as the @@ -1939,11 +2336,14 @@ feature! { #[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( + freebsdlike, + netbsdlike, + target_os = "linux", + 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, @@ -1953,6 +2353,19 @@ pub enum PathconfVar { /// queue; therefore, the maximum number of bytes a conforming application /// may require to be typed as input before reading them. MAX_INPUT = libc::_PC_MAX_INPUT, + #[cfg(any( + apple_targets, + solarish, + freebsdlike, + target_os = "netbsd", + ))] + /// If a file system supports the reporting of holes (see lseek(2)), + /// pathconf() and fpathconf() return a positive number that represents the + /// minimum hole size returned in bytes. The offsets of holes returned will + /// be aligned to this same value. A special value of 1 is returned if the + /// file system does not specify the minimum hole size but still reports + /// holes. + MIN_HOLE_SIZE = libc::_PC_MIN_HOLE_SIZE, /// Maximum number of bytes in a filename (not including the terminating /// null of a filename string). NAME_MAX = libc::_PC_NAME_MAX, @@ -1964,43 +2377,63 @@ 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_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + solarish, + netbsdlike, + target_os = "dragonfly", + target_os = "redox", + ))] /// Symbolic links can be created. POSIX2_SYMLINKS = libc::_PC_2_SYMLINKS, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "linux", target_os = "openbsd", target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + target_os = "openbsd", + target_os = "redox" + ))] /// Minimum number of bytes of storage actually allocated for any portion of /// a file. POSIX_ALLOC_SIZE_MIN = libc::_PC_ALLOC_SIZE_MIN, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "linux", target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + linux_android, + target_os = "openbsd" + ))] /// Recommended increment for file transfer sizes between the /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values. POSIX_REC_INCR_XFER_SIZE = libc::_PC_REC_INCR_XFER_SIZE, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "linux", target_os = "openbsd", target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + target_os = "openbsd", + target_os = "redox" + ))] /// Maximum recommended file transfer size. POSIX_REC_MAX_XFER_SIZE = libc::_PC_REC_MAX_XFER_SIZE, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "linux", target_os = "openbsd", target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + target_os = "openbsd", + target_os = "redox" + ))] /// Minimum recommended file transfer size. POSIX_REC_MIN_XFER_SIZE = libc::_PC_REC_MIN_XFER_SIZE, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "linux", target_os = "openbsd", target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + target_os = "openbsd", + target_os = "redox" + ))] /// Recommended file transfer buffer alignment. POSIX_REC_XFER_ALIGN = libc::_PC_REC_XFER_ALIGN, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "illumos", target_os = "linux", target_os = "netbsd", - target_os = "openbsd", target_os = "redox", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + solarish, + netbsdlike, + target_os = "redox", + ))] /// 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 @@ -2013,31 +2446,39 @@ 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_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + solarish, + target_os = "openbsd", + target_os = "redox", + ))] /// Asynchronous input or output operations may be performed for the /// associated file. _POSIX_ASYNC_IO = libc::_PC_ASYNC_IO, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "illumos", target_os = "linux", target_os = "openbsd", - target_os = "redox", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + solarish, + target_os = "openbsd", + target_os = "redox", + ))] /// Prioritized input or output operations may be performed for the /// associated file. _POSIX_PRIO_IO = libc::_PC_PRIO_IO, - #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", - target_os = "illumos", target_os = "linux", target_os = "netbsd", - target_os = "openbsd", target_os = "redox", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + solarish, + netbsdlike, + target_os = "redox", + ))] /// 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 @@ -2056,13 +2497,15 @@ pub enum PathconfVar { /// - `Ok(None)`: the variable has no limit (for limit variables) or is /// unsupported (for option variables) /// - `Err(x)`: an error occurred -pub fn fpathconf(fd: RawFd, var: PathconfVar) -> Result> { +pub fn fpathconf(fd: F, var: PathconfVar) -> Result> { + use std::os::fd::AsRawFd; + let raw = unsafe { Errno::clear(); - libc::fpathconf(fd, var as c_int) + libc::fpathconf(fd.as_fd().as_raw_fd(), var as c_int) }; if raw == -1 { - if errno::errno() == 0 { + if Errno::last_raw() == 0 { Ok(None) } else { Err(Errno::last()) @@ -2093,15 +2536,16 @@ 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 { + if Errno::last_raw() == 0 { Ok(None) } else { Err(Errno::last()) @@ -2138,17 +2582,17 @@ pub enum SysconfVar { /// Maximum number of I/O operations in a single list I/O call supported by /// the implementation. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] AIO_LISTIO_MAX = libc::_SC_AIO_LISTIO_MAX, /// Maximum number of outstanding asynchronous I/O operations supported by /// the implementation. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] AIO_MAX = libc::_SC_AIO_MAX, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The maximum amount by which a process can decrease its asynchronous I/O /// priority level from its own scheduling priority. AIO_PRIO_DELTA_MAX = libc::_SC_AIO_PRIO_DELTA_MAX, @@ -2156,60 +2600,47 @@ pub enum SysconfVar { ARG_MAX = libc::_SC_ARG_MAX, /// Maximum number of functions that may be registered with `atexit`. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] ATEXIT_MAX = libc::_SC_ATEXIT_MAX, /// Maximum obase values allowed by the bc utility. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] BC_BASE_MAX = libc::_SC_BC_BASE_MAX, /// Maximum number of elements permitted in an array by the bc utility. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] BC_DIM_MAX = libc::_SC_BC_DIM_MAX, /// Maximum scale value allowed by the bc utility. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] BC_SCALE_MAX = libc::_SC_BC_SCALE_MAX, /// Maximum length of a string constant accepted by the bc utility. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] BC_STRING_MAX = libc::_SC_BC_STRING_MAX, /// Maximum number of simultaneous processes per real user ID. CHILD_MAX = libc::_SC_CHILD_MAX, - // The number of clock ticks per second. + /// The frequency of the statistics clock in ticks per second. CLK_TCK = libc::_SC_CLK_TCK, /// Maximum number of weights that can be assigned to an entry of the /// LC_COLLATE order keyword in the locale definition file #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] COLL_WEIGHTS_MAX = libc::_SC_COLL_WEIGHTS_MAX, /// Maximum number of timer expiration overruns. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] DELAYTIMER_MAX = libc::_SC_DELAYTIMER_MAX, /// Maximum number of expressions that can be nested within parentheses by /// the expr utility. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] EXPR_NEST_MAX = libc::_SC_EXPR_NEST_MAX, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, solarish, target_os = "linux"))] /// Maximum length of a host name (not including the terminating null) as /// returned from the `gethostname` function HOST_NAME_MAX = libc::_SC_HOST_NAME_MAX, /// Maximum number of iovec structures that one process has available for /// use with `readv` or `writev`. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] IOV_MAX = libc::_SC_IOV_MAX, /// Unless otherwise noted, the maximum length, in bytes, of a utility's /// input line (either standard input or another file), when the utility is /// described as processing text files. The length includes room for the /// trailing newline. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] LINE_MAX = libc::_SC_LINE_MAX, /// Maximum length of a login name. #[cfg(not(target_os = "haiku"))] @@ -2218,491 +2649,504 @@ pub enum SysconfVar { NGROUPS_MAX = libc::_SC_NGROUPS_MAX, /// Initial size of `getgrgid_r` and `getgrnam_r` data buffers #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] GETGR_R_SIZE_MAX = libc::_SC_GETGR_R_SIZE_MAX, /// Initial size of `getpwuid_r` and `getpwnam_r` data buffers #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] GETPW_R_SIZE_MAX = libc::_SC_GETPW_R_SIZE_MAX, /// The maximum number of open message queue descriptors a process may hold. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] MQ_OPEN_MAX = libc::_SC_MQ_OPEN_MAX, /// The maximum number of message priorities supported by the implementation. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] MQ_PRIO_MAX = libc::_SC_MQ_PRIO_MAX, /// A value one greater than the maximum value that the system may assign to /// a newly-created file descriptor. OPEN_MAX = libc::_SC_OPEN_MAX, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Advisory Information option. _POSIX_ADVISORY_INFO = libc::_SC_ADVISORY_INFO, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, solarish, target_os = "linux"))] /// The implementation supports barriers. _POSIX_BARRIERS = libc::_SC_BARRIERS, /// The implementation supports asynchronous input and output. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_ASYNCHRONOUS_IO = libc::_SC_ASYNCHRONOUS_IO, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, solarish, target_os = "linux"))] /// The implementation supports clock selection. _POSIX_CLOCK_SELECTION = libc::_SC_CLOCK_SELECTION, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, solarish, target_os = "linux"))] /// The implementation supports the Process CPU-Time Clocks option. _POSIX_CPUTIME = libc::_SC_CPUTIME, /// The implementation supports the File Synchronization option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_FSYNC = libc::_SC_FSYNC, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + solarish, + target_os = "linux", + target_os = "openbsd", + ))] /// The implementation supports the IPv6 option. _POSIX_IPV6 = libc::_SC_IPV6, /// The implementation supports job control. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_JOB_CONTROL = libc::_SC_JOB_CONTROL, /// The implementation supports memory mapped Files. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_MAPPED_FILES = libc::_SC_MAPPED_FILES, /// The implementation supports the Process Memory Locking option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_MEMLOCK = libc::_SC_MEMLOCK, /// The implementation supports the Range Memory Locking option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_MEMLOCK_RANGE = libc::_SC_MEMLOCK_RANGE, /// The implementation supports memory protection. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_MEMORY_PROTECTION = libc::_SC_MEMORY_PROTECTION, /// The implementation supports the Message Passing option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_MESSAGE_PASSING = libc::_SC_MESSAGE_PASSING, /// The implementation supports the Monotonic Clock option. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_MONOTONIC_CLOCK = libc::_SC_MONOTONIC_CLOCK, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "illumos", target_os = "ios", target_os="linux", - target_os = "macos", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + solarish, + apple_targets, + target_os = "openbsd", + ))] /// The implementation supports the Prioritized Input and Output option. _POSIX_PRIORITIZED_IO = libc::_SC_PRIORITIZED_IO, /// The implementation supports the Process Scheduling option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_PRIORITY_SCHEDULING = libc::_SC_PRIORITY_SCHEDULING, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + solarish, + apple_targets, + target_os = "linux", + target_os = "openbsd", + ))] /// The implementation supports the Raw Sockets option. _POSIX_RAW_SOCKETS = libc::_SC_RAW_SOCKETS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + bsd, + solarish, + target_os = "linux", + ))] /// The implementation supports read-write locks. _POSIX_READER_WRITER_LOCKS = libc::_SC_READER_WRITER_LOCKS, - #[cfg(any(target_os = "android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The implementation supports realtime signals. _POSIX_REALTIME_SIGNALS = libc::_SC_REALTIME_SIGNALS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "illumos", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd", target_os = "solaris"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + bsd, + solarish, + target_os = "linux", + ))] /// The implementation supports the Regular Expression Handling option. _POSIX_REGEXP = libc::_SC_REGEXP, /// Each process has a saved set-user-ID and a saved set-group-ID. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_SAVED_IDS = libc::_SC_SAVED_IDS, /// The implementation supports semaphores. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_SEMAPHORES = libc::_SC_SEMAPHORES, /// The implementation supports the Shared Memory Objects option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_SHARED_MEMORY_OBJECTS = libc::_SC_SHARED_MEMORY_OBJECTS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux",))] /// The implementation supports the POSIX shell. _POSIX_SHELL = libc::_SC_SHELL, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux",))] /// The implementation supports the Spawn option. _POSIX_SPAWN = libc::_SC_SPAWN, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux",))] /// The implementation supports spin locks. _POSIX_SPIN_LOCKS = libc::_SC_SPIN_LOCKS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Process Sporadic Server option. _POSIX_SPORADIC_SERVER = libc::_SC_SPORADIC_SERVER, - #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// The number of replenishment operations that can be simultaneously pending for a particular + /// sporadic server scheduler. + #[cfg(any( + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] _POSIX_SS_REPL_MAX = libc::_SC_SS_REPL_MAX, /// The implementation supports the Synchronized Input and Output option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_SYNCHRONIZED_IO = libc::_SC_SYNCHRONIZED_IO, /// The implementation supports the Thread Stack Address Attribute option. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREAD_ATTR_STACKADDR = libc::_SC_THREAD_ATTR_STACKADDR, /// The implementation supports the Thread Stack Size Attribute option. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREAD_ATTR_STACKSIZE = libc::_SC_THREAD_ATTR_STACKSIZE, - #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", - target_os="netbsd", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + apple_targets, + target_os = "linux", + netbsdlike, + ))] /// The implementation supports the Thread CPU-Time Clocks option. _POSIX_THREAD_CPUTIME = libc::_SC_THREAD_CPUTIME, /// The implementation supports the Non-Robust Mutex Priority Inheritance /// option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREAD_PRIO_INHERIT = libc::_SC_THREAD_PRIO_INHERIT, /// The implementation supports the Non-Robust Mutex Priority Protection option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREAD_PRIO_PROTECT = libc::_SC_THREAD_PRIO_PROTECT, /// The implementation supports the Thread Execution Scheduling option. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREAD_PRIORITY_SCHEDULING = libc::_SC_THREAD_PRIORITY_SCHEDULING, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Thread Process-Shared Synchronization /// option. _POSIX_THREAD_PROCESS_SHARED = libc::_SC_THREAD_PROCESS_SHARED, - #[cfg(any(target_os="dragonfly", target_os="linux", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + target_os = "dragonfly", + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Robust Mutex Priority Inheritance option. _POSIX_THREAD_ROBUST_PRIO_INHERIT = libc::_SC_THREAD_ROBUST_PRIO_INHERIT, - #[cfg(any(target_os="dragonfly", target_os="linux", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + target_os = "dragonfly", + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Robust Mutex Priority Protection option. _POSIX_THREAD_ROBUST_PRIO_PROTECT = libc::_SC_THREAD_ROBUST_PRIO_PROTECT, /// The implementation supports thread-safe functions. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREAD_SAFE_FUNCTIONS = libc::_SC_THREAD_SAFE_FUNCTIONS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Thread Sporadic Server option. _POSIX_THREAD_SPORADIC_SERVER = libc::_SC_THREAD_SPORADIC_SERVER, /// The implementation supports threads. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_THREADS = libc::_SC_THREADS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports timeouts. _POSIX_TIMEOUTS = libc::_SC_TIMEOUTS, /// The implementation supports timers. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX_TIMERS = libc::_SC_TIMERS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Trace option. _POSIX_TRACE = libc::_SC_TRACE, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Trace Event Filter option. _POSIX_TRACE_EVENT_FILTER = libc::_SC_TRACE_EVENT_FILTER, - #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum size of a trace event name in characters. + #[cfg(any( + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] _POSIX_TRACE_EVENT_NAME_MAX = libc::_SC_TRACE_EVENT_NAME_MAX, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Trace Inherit option. _POSIX_TRACE_INHERIT = libc::_SC_TRACE_INHERIT, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Trace Log option. _POSIX_TRACE_LOG = libc::_SC_TRACE_LOG, - #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// The length in bytes of a trace generation version string or a trace stream name. + #[cfg(any( + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] _POSIX_TRACE_NAME_MAX = libc::_SC_TRACE_NAME_MAX, - #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum number of times `posix_trace_create` may be called from the same or different + /// processes. + #[cfg(any( + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] _POSIX_TRACE_SYS_MAX = libc::_SC_TRACE_SYS_MAX, - #[cfg(any(target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum number of user trace event type identifiers for a single process. + #[cfg(any( + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] _POSIX_TRACE_USER_EVENT_MAX = libc::_SC_TRACE_USER_EVENT_MAX, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the Typed Memory Objects option. _POSIX_TYPED_MEMORY_OBJECTS = libc::_SC_TYPED_MEMORY_OBJECTS, /// Integer value indicating version of this standard (C-language binding) /// to which the implementation conforms. For implementations conforming to /// POSIX.1-2008, the value shall be 200809L. _POSIX_VERSION = libc::_SC_VERSION, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation provides a C-language compilation environment with /// 32-bit `int`, `long`, `pointer`, and `off_t` types. _POSIX_V6_ILP32_OFF32 = libc::_SC_V6_ILP32_OFF32, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation provides a C-language compilation environment with /// 32-bit `int`, `long`, and pointer types and an `off_t` type using at /// least 64 bits. _POSIX_V6_ILP32_OFFBIG = libc::_SC_V6_ILP32_OFFBIG, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation provides a C-language compilation environment with /// 32-bit `int` and 64-bit `long`, `pointer`, and `off_t` types. _POSIX_V6_LP64_OFF64 = libc::_SC_V6_LP64_OFF64, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation provides a C-language compilation environment with an /// `int` type using at least 32 bits and `long`, pointer, and `off_t` types /// using at least 64 bits. _POSIX_V6_LPBIG_OFFBIG = libc::_SC_V6_LPBIG_OFFBIG, /// The implementation supports the C-Language Binding option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_C_BIND = libc::_SC_2_C_BIND, /// The implementation supports the C-Language Development Utilities option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_C_DEV = libc::_SC_2_C_DEV, /// The implementation supports the Terminal Characteristics option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_CHAR_TERM = libc::_SC_2_CHAR_TERM, /// The implementation supports the FORTRAN Development Utilities option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_FORT_DEV = libc::_SC_2_FORT_DEV, /// The implementation supports the FORTRAN Runtime Utilities option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_FORT_RUN = libc::_SC_2_FORT_RUN, /// The implementation supports the creation of locales by the localedef /// utility. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_LOCALEDEF = libc::_SC_2_LOCALEDEF, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Batch Environment Services and Utilities /// option. _POSIX2_PBS = libc::_SC_2_PBS, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Batch Accounting option. _POSIX2_PBS_ACCOUNTING = libc::_SC_2_PBS_ACCOUNTING, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Batch Checkpoint/Restart option. _POSIX2_PBS_CHECKPOINT = libc::_SC_2_PBS_CHECKPOINT, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Locate Batch Job Request option. _POSIX2_PBS_LOCATE = libc::_SC_2_PBS_LOCATE, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Batch Job Message Request option. _POSIX2_PBS_MESSAGE = libc::_SC_2_PBS_MESSAGE, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any(bsd, target_os = "linux"))] /// The implementation supports the Track Batch Job Request option. _POSIX2_PBS_TRACK = libc::_SC_2_PBS_TRACK, /// The implementation supports the Software Development Utilities option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_SW_DEV = libc::_SC_2_SW_DEV, /// The implementation supports the User Portability Utilities option. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_UPE = libc::_SC_2_UPE, /// Integer value indicating version of the Shell and Utilities volume of /// POSIX.1 to which the implementation conforms. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _POSIX2_VERSION = libc::_SC_2_VERSION, /// The size of a system page in bytes. /// /// POSIX also defines an alias named `PAGESIZE`, but Rust does not allow two /// enum constants to have the same value, so nix omits `PAGESIZE`. PAGE_SIZE = libc::_SC_PAGE_SIZE, + /// Maximum number of attempts made to destroy a thread's thread-specific data values on thread + /// exit. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] PTHREAD_DESTRUCTOR_ITERATIONS = libc::_SC_THREAD_DESTRUCTOR_ITERATIONS, + /// Maximum number of data keys that can be created by a process. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] PTHREAD_KEYS_MAX = libc::_SC_THREAD_KEYS_MAX, + /// Minimum size in bytes of thread stack storage. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] PTHREAD_STACK_MIN = libc::_SC_THREAD_STACK_MIN, + /// Maximum number of threads that can be created per process. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] PTHREAD_THREADS_MAX = libc::_SC_THREAD_THREADS_MAX, + /// The maximum number of repeated occurrences of a regular expression permitted when using + /// interval notation. #[cfg(not(target_os = "haiku"))] RE_DUP_MAX = libc::_SC_RE_DUP_MAX, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum number of realtime signals reserved for application use. + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] RTSIG_MAX = libc::_SC_RTSIG_MAX, + /// Maximum number of semaphores that a process may have. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] SEM_NSEMS_MAX = libc::_SC_SEM_NSEMS_MAX, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// The maximum value a semaphore may have. + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] SEM_VALUE_MAX = libc::_SC_SEM_VALUE_MAX, - #[cfg(any(target_os = "android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os = "openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum number of queued signals that a process may send and have pending at the + /// receiver(s) at any time. + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] SIGQUEUE_MAX = libc::_SC_SIGQUEUE_MAX, + /// The minimum maximum number of streams that a process may have open at any one time. STREAM_MAX = libc::_SC_STREAM_MAX, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="netbsd", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + /// Maximum number of symbolic links that can be reliably traversed in the resolution of a + /// pathname in the absence of a loop. + #[cfg(any(bsd, target_os = "linux"))] SYMLOOP_MAX = libc::_SC_SYMLOOP_MAX, + /// Maximum number of timers per process supported. #[cfg(not(target_os = "redox"))] - #[cfg_attr(docsrs, doc(cfg(all())))] TIMER_MAX = libc::_SC_TIMER_MAX, + /// Maximum length of terminal device name. TTY_NAME_MAX = libc::_SC_TTY_NAME_MAX, + /// The minimum maximum number of types supported for the name of a timezone. TZNAME_MAX = libc::_SC_TZNAME_MAX, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The implementation supports the X/Open Encryption Option Group. _XOPEN_CRYPT = libc::_SC_XOPEN_CRYPT, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The implementation supports the Issue 4, Version 2 Enhanced /// Internationalization Option Group. _XOPEN_ENH_I18N = libc::_SC_XOPEN_ENH_I18N, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] + /// The implementation supports the XOpen Legacy Option group. + /// + /// See Also _XOPEN_LEGACY = libc::_SC_XOPEN_LEGACY, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The implementation supports the X/Open Realtime Option Group. _XOPEN_REALTIME = libc::_SC_XOPEN_REALTIME, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The implementation supports the X/Open Realtime Threads Option Group. _XOPEN_REALTIME_THREADS = libc::_SC_XOPEN_REALTIME_THREADS, /// The implementation supports the Issue 4, Version 2 Shared Memory Option /// Group. #[cfg(not(any(target_os = "redox", target_os = "haiku")))] - #[cfg_attr(docsrs, doc(cfg(all())))] _XOPEN_SHM = libc::_SC_XOPEN_SHM, - #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", - target_os="linux", target_os = "macos", target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + freebsdlike, + apple_targets, + target_os = "linux", + target_os = "openbsd" + ))] /// The implementation supports the XSI STREAMS Option Group. _XOPEN_STREAMS = libc::_SC_XOPEN_STREAMS, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// The implementation supports the XSI option _XOPEN_UNIX = libc::_SC_XOPEN_UNIX, - #[cfg(any(target_os="android", target_os="dragonfly", target_os="freebsd", - target_os = "ios", target_os="linux", target_os = "macos", - target_os="openbsd"))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(any( + linux_android, + freebsdlike, + apple_targets, + target_os = "openbsd" + ))] /// 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"))] + #[cfg(linux_android)] _PHYS_PAGES = libc::_SC_PHYS_PAGES, /// The number of currently available pages of physical memory. - #[cfg(any(target_os="android", target_os="linux"))] + #[cfg(linux_android)] _AVPHYS_PAGES = libc::_SC_AVPHYS_PAGES, /// The number of processors configured. - #[cfg(any(target_os="android", target_os="linux"))] + #[cfg(linux_android)] _NPROCESSORS_CONF = libc::_SC_NPROCESSORS_CONF, /// The number of processors currently online (available). - #[cfg(any(target_os="android", target_os="linux"))] + #[cfg(linux_android)] _NPROCESSORS_ONLN = libc::_SC_NPROCESSORS_ONLN, } @@ -2728,7 +3172,7 @@ pub fn sysconf(var: SysconfVar) -> Result> { libc::sysconf(var as c_int) }; if raw == -1 { - if errno::errno() == 0 { + if Errno::last_raw() == 0 { Ok(None) } else { Err(Errno::last()) @@ -2739,43 +3183,41 @@ pub fn sysconf(var: SysconfVar) -> Result> { } } -feature! { -#![feature = "fs"] - -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] +#[cfg(feature = "fs")] mod pivot_root { - use crate::{Result, NixPath}; use crate::errno::Errno; + use crate::{NixPath, Result}; + /// Change the root file system. + /// + /// See Also [`pivot_root`](https://man7.org/linux/man-pages/man2/pivot_root.2.html) 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(), + ) }) })??; Errno::result(res).map(drop) } } -} -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +#[cfg(any(linux_android, freebsdlike, target_os = "openbsd"))] mod setres { feature! { #![feature = "user"] - use crate::Result; + 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)) @@ -2788,7 +3230,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) } @@ -2804,42 +3247,43 @@ 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 = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +#[cfg(any(linux_android, freebsdlike, target_os = "openbsd"))] mod getres { feature! { #![feature = "user"] - use crate::Result; + 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 { + /// Real UID pub real: Uid, + /// Effective UID pub effective: Uid, - pub saved: Uid + /// Saved UID + pub saved: Uid, } /// Real, effective and saved group IDs. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ResGid { + /// Real GID pub real: Gid, + /// Effective GID pub effective: Gid, - pub saved: Gid + /// Saved GID + pub saved: Gid, } /// Gets the real, effective, and saved user IDs. @@ -2853,12 +3297,16 @@ mod getres { /// #[inline] pub fn getresuid() -> Result { - let mut ruid = libc::uid_t::max_value(); - let mut euid = libc::uid_t::max_value(); - let mut suid = libc::uid_t::max_value(); + let mut ruid = libc::uid_t::MAX; + let mut euid = libc::uid_t::MAX; + let mut suid = libc::uid_t::MAX; 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. @@ -2872,16 +3320,76 @@ mod getres { /// #[inline] pub fn getresgid() -> Result { - let mut rgid = libc::gid_t::max_value(); - let mut egid = libc::gid_t::max_value(); - let mut sgid = libc::gid_t::max_value(); + let mut rgid = libc::gid_t::MAX; + let mut egid = libc::gid_t::MAX; + let mut sgid = libc::gid_t::MAX; 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), + }) } } } +#[cfg(feature = "process")] +#[cfg(target_os = "freebsd")] +libc_bitflags! { + /// Flags for [`rfork`] + /// + /// subset of flags supported by FreeBSD 12.x and onwards + /// with a safe outcome, thus as `RFMEM` can possibly lead to undefined behavior, + /// it is not in the list. And `rfork_thread` is deprecated. + pub struct RforkFlags: libc::c_int { + /// creates a new process. + RFPROC; + /// the child process will detach from the parent. + /// however, no status will be emitted at child's exit. + RFNOWAIT; + /// the file descriptor's table will be copied + RFFDG; + /// a new file descriptor's table will be created + RFCFDG; + /// force sharing the sigacts structure between + /// the child and the parent. + RFSIGSHARE; + /// enables kernel thread support. + RFTHREAD; + /// sets a status to emit at child's exit. + RFTSIGZMB; + /// linux's behavior compatibility setting. + /// emits SIGUSR1 as opposed to SIGCHLD upon child's exit. + RFLINUXTHPN; + } +} + +feature! { +#![feature = "process"] +#[cfg(target_os = "freebsd")] +/// Like [`fork`], `rfork` can be used to have a tigher control about which +/// resources child and parent process will be sharing, file descriptors, +/// address spaces and child exit's behavior. +/// +/// # Safety +/// +/// The same restrictions apply as for [`fork`]. +/// +/// # See Also +/// +/// * [rfork(2)](https://man.freebsd.org/cgi/man.cgi?query=rfork) +pub unsafe fn rfork(flags: RforkFlags) -> Result { + use ForkResult::*; + let res = unsafe { libc::rfork(flags.bits()) }; + + Errno::result(res).map(|res| match res { + 0 => Child, + res => Parent { child: Pid(res) }, + }) +} +} + #[cfg(feature = "fs")] libc_bitflags! { /// Options for access() @@ -2904,30 +3412,35 @@ feature! { /// 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. +/// Checks the file named by `dirfd` and `path` for accessibility according to +/// the flags given by `mode` /// /// # 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()) - } +pub fn faccessat( + dirfd: Fd, + path: &P, + mode: AccessFlags, + flags: AtFlags, +) -> Result<()> { + use std::os::fd::AsRawFd; + + let res = path.with_nix_path(|cstr| unsafe { + libc::faccessat( + dirfd.as_fd().as_raw_fd(), + cstr.as_ptr(), + mode.bits(), + flags.bits(), + ) })?; Errno::result(res).map(drop) } @@ -2940,15 +3453,12 @@ pub fn faccessat(dirfd: Option, path: &P, mode: Acce /// * [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( + freebsdlike, 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) - } + let res = path.with_nix_path(|cstr| unsafe { + libc::eaccess(cstr.as_ptr(), mode.bits()) })?; Errno::result(res).map(drop) } @@ -2982,32 +3492,41 @@ pub struct User { /// Path to shell pub shell: PathBuf, /// Login class - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] pub class: CString, /// Last password change - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] - #[cfg_attr(docsrs, doc(cfg(all())))] + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] pub change: libc::time_t, /// Expiration time of account - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] - #[cfg_attr(docsrs, doc(cfg(all())))] - pub expire: libc::time_t + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] + pub expire: libc::time_t, } #[cfg(not(target_os = "redox"))] //RedoxFS does not support passwd @@ -3015,35 +3534,77 @@ impl From<&libc::passwd> for User { fn from(pw: &libc::passwd) -> User { unsafe { User { - name: if pw.pw_name.is_null() { Default::default() } else { CStr::from_ptr(pw.pw_name).to_string_lossy().into_owned() }, - passwd: if pw.pw_passwd.is_null() { Default::default() } else { CString::new(CStr::from_ptr(pw.pw_passwd).to_bytes()).unwrap() }, - #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] - gecos: if pw.pw_gecos.is_null() { Default::default() } else { CString::new(CStr::from_ptr(pw.pw_gecos).to_bytes()).unwrap() }, - dir: if pw.pw_dir.is_null() { Default::default() } else { PathBuf::from(OsStr::from_bytes(CStr::from_ptr(pw.pw_dir).to_bytes())) }, - shell: if pw.pw_shell.is_null() { Default::default() } else { PathBuf::from(OsStr::from_bytes(CStr::from_ptr(pw.pw_shell).to_bytes())) }, + name: if pw.pw_name.is_null() { + Default::default() + } else { + CStr::from_ptr(pw.pw_name).to_string_lossy().into_owned() + }, + passwd: if pw.pw_passwd.is_null() { + Default::default() + } else { + CString::new(CStr::from_ptr(pw.pw_passwd).to_bytes()) + .unwrap() + }, + #[cfg(not(all( + target_os = "android", + target_pointer_width = "32" + )))] + gecos: if pw.pw_gecos.is_null() { + Default::default() + } else { + CString::new(CStr::from_ptr(pw.pw_gecos).to_bytes()) + .unwrap() + }, + dir: if pw.pw_dir.is_null() { + Default::default() + } else { + PathBuf::from(OsStr::from_bytes( + CStr::from_ptr(pw.pw_dir).to_bytes(), + )) + }, + shell: if pw.pw_shell.is_null() { + Default::default() + } else { + PathBuf::from(OsStr::from_bytes( + CStr::from_ptr(pw.pw_shell).to_bytes(), + )) + }, uid: Uid::from_raw(pw.pw_uid), gid: Gid::from_raw(pw.pw_gid), - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] - class: CString::new(CStr::from_ptr(pw.pw_class).to_bytes()).unwrap(), - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] + class: CString::new(CStr::from_ptr(pw.pw_class).to_bytes()) + .unwrap(), + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] change: pw.pw_change, - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] - expire: pw.pw_expire + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] + expire: pw.pw_expire, } } } @@ -3067,38 +3628,53 @@ 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 = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] pw_class: u.class.into_raw(), - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] pw_change: u.change, - #[cfg(not(any(target_os = "android", - target_os = "fuchsia", - target_os = "haiku", - target_os = "illumos", - target_os = "linux", - target_os = "solaris")))] + #[cfg(not(any( + linux_android, + solarish, + target_os = "aix", + target_os = "fuchsia", + target_os = "haiku", + target_os = "hurd", + target_os = "emscripten", + target_os = "cygwin", + )))] pw_expire: u.expire, - #[cfg(target_os = "illumos")] + #[cfg(solarish)] pw_age: CString::new("").unwrap().into_raw(), - #[cfg(target_os = "illumos")] + #[cfg(any(solarish, target_os = "cygwin"))] pw_comment: CString::new("").unwrap().into_raw(), - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg(freebsdlike)] pw_fields: 0, } } @@ -3106,12 +3682,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 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) { @@ -3124,11 +3707,18 @@ 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 { + // SAFETY: `f` guarantees that `pwd` is initialized if `res` + // is not null. let pwd = unsafe { pwd.assume_init() }; return Ok(Some(User::from(&pwd))); } @@ -3154,16 +3744,21 @@ impl User { /// let res = User::from_uid(Uid::from_raw(0)).unwrap().unwrap(); /// assert_eq!(res.name, "root"); /// ``` + #[doc(alias("getpwuid", "getpwuid_r"))] 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 /// @@ -3173,14 +3768,19 @@ impl User { /// let res = User::from_name("root").unwrap().unwrap(); /// assert_eq!(res.name, "root"); /// ``` + #[doc(alias("getpwnam", "getpwnam_r"))] pub fn from_name(name: &str) -> Result> { let name = match CString::new(name) { Ok(c_str) => c_str, Err(_nul_error) => return Ok(None), }; - User::from_anything(|pwd, cbuf, cap, res| { - unsafe { libc::getpwnam_r(name.as_ptr(), pwd, cbuf, cap, res) } - }) + // 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) + }) + } } } @@ -3195,7 +3795,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 @@ -3203,10 +3803,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(), + 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: Group::members(gr.gr_mem) + mem: if gr.gr_mem.is_null() { + Default::default() + } else { + Group::members(gr.gr_mem) + }, } } } @@ -3218,24 +3831,30 @@ impl Group { let mut ret = Vec::new(); for i in 0.. { - let u = mem.offset(i); - if (*u).is_null() { + let u = unsafe { mem.offset(i).read_unaligned() }; + if u.is_null() { break; } else { - let s = CStr::from_ptr(*u).to_string_lossy().into_owned(); + let s = unsafe {CStr::from_ptr(u).to_string_lossy().into_owned()}; ret.push(s); } } 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 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) { @@ -3248,11 +3867,18 @@ 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 { + // SAFETY: `f` guarantees that `grp` is initialized if `res` + // is not null. let grp = unsafe { grp.assume_init() }; return Ok(Some(Group::from(&grp))); } @@ -3281,9 +3907,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. @@ -3306,9 +3936,13 @@ impl Group { Ok(c_str) => c_str, Err(_nul_error) => return Ok(None), }; - Group::from_anything(|grp, cbuf, cap, res| { - unsafe { libc::getgrnam_r(name.as_ptr(), grp, cbuf, cap, res) } - }) + // 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) + }) + } } } } @@ -3319,19 +3953,24 @@ feature! { /// Get the name of the terminal device that is open on file descriptor fd /// (see [`ttyname(3)`](https://man7.org/linux/man-pages/man3/ttyname.3.html)). #[cfg(not(target_os = "fuchsia"))] -pub fn ttyname(fd: RawFd) -> Result { - const PATH_MAX: usize = libc::PATH_MAX as usize; - let mut buf = vec![0_u8; PATH_MAX]; - let c_buf = buf.as_mut_ptr() as *mut libc::c_char; +pub fn ttyname(fd: F) -> Result { + use std::os::fd::AsRawFd; - let ret = unsafe { libc::ttyname_r(fd, c_buf, buf.len()) }; + #[cfg(not(target_os = "hurd"))] + const PATH_MAX: usize = libc::PATH_MAX as usize; + #[cfg(target_os = "hurd")] + const PATH_MAX: usize = 1024; // Hurd does not define a hard limit, so try a guess first + let mut buf = vec![0_u8; PATH_MAX]; + let c_buf = buf.as_mut_ptr().cast(); + + let ret = unsafe { libc::ttyname_r(fd.as_fd().as_raw_fd(), c_buf, buf.len()) }; if ret != 0 { - return Err(Errno::from_i32(ret)); + return Err(Errno::from_raw(ret)); } - let nul = buf.iter().position(|c| *c == b'\0').unwrap(); - buf.truncate(nul); - Ok(OsString::from_vec(buf).into()) + CStr::from_bytes_until_nul(&buf[..]) + .map(|s| OsStr::from_bytes(s.to_bytes()).into()) + .map_err(|_| Errno::EINVAL) } } @@ -3341,19 +3980,14 @@ feature! { /// Get the effective user ID and group ID associated with a Unix domain socket. /// /// See also [getpeereid(3)](https://www.freebsd.org/cgi/man.cgi?query=getpeereid) -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "dragonfly", -))] -pub fn getpeereid(fd: RawFd) -> Result<(Uid, Gid)> { +#[cfg(bsd)] +pub fn getpeereid(fd: F) -> Result<(Uid, Gid)> { + use std::os::fd::AsRawFd; + let mut uid = 1; let mut gid = 1; - let ret = unsafe { libc::getpeereid(fd, &mut uid, &mut gid) }; + let ret = unsafe { libc::getpeereid(fd.as_fd().as_raw_fd(), &mut uid, &mut gid) }; Errno::result(ret).map(|_| (Uid(uid), Gid(gid))) } @@ -3365,14 +3999,7 @@ feature! { /// 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" -))] +#[cfg(bsd)] pub fn chflags(path: &P, flags: FileFlag) -> Result<()> { let res = path.with_nix_path(|cstr| unsafe { libc::chflags(cstr.as_ptr(), flags.bits()) diff --git a/test/common/mod.rs b/test/common/mod.rs index bb056aab..ab0e7463 100644 --- a/test/common/mod.rs +++ b/test/common/mod.rs @@ -2,18 +2,18 @@ use cfg_if::cfg_if; #[macro_export] macro_rules! skip { - ($($reason: expr),+) => { + ($($reason: expr),+) => {{ use ::std::io::{self, Write}; let stderr = io::stderr(); let mut handle = stderr.lock(); writeln!(handle, $($reason),+).unwrap(); return; - } + }} } cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(linux_android)] { #[macro_export] macro_rules! require_capability { ($name:expr, $capname:ident) => { use ::caps::{Capability, CapSet, has_cap}; @@ -37,8 +37,8 @@ cfg_if! { #[macro_export] macro_rules! require_mount { ($name:expr) => { - use ::sysctl::{CtlValue, Sysctl}; use nix::unistd::Uid; + use sysctl::{CtlValue, Sysctl}; let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() @@ -51,7 +51,7 @@ macro_rules! require_mount { }; } -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[macro_export] macro_rules! skip_if_cirrus { ($reason:expr) => { @@ -65,7 +65,7 @@ macro_rules! skip_if_cirrus { #[macro_export] macro_rules! skip_if_jailed { ($name:expr) => { - use ::sysctl::{CtlValue, Sysctl}; + use sysctl::{CtlValue, Sysctl}; let ctl = ::sysctl::Ctl::new("security.jail.jailed").unwrap(); if let CtlValue::Int(1) = ctl.value().unwrap() { @@ -87,7 +87,7 @@ macro_rules! skip_if_not_root { } cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(linux_android)] { #[macro_export] macro_rules! skip_if_seccomp { ($name:expr) => { if let Ok(s) = std::fs::read_to_string("/proc/self/status") { diff --git a/test/mount/mod.rs b/test/mount/mod.rs new file mode 100644 index 00000000..2764b83f --- /dev/null +++ b/test/mount/mod.rs @@ -0,0 +1,6 @@ +#[cfg(target_os = "linux")] +mod test_mount; +#[cfg(apple_targets)] +mod test_mount_apple; +#[cfg(target_os = "freebsd")] +mod test_nmount; diff --git a/test/mount/test_mount.rs b/test/mount/test_mount.rs new file mode 100644 index 00000000..9cb77417 --- /dev/null +++ b/test/mount/test_mount.rs @@ -0,0 +1,189 @@ +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::fs::PermissionsExt; +use std::process::Command; + +use libc::{EACCES, EROFS}; + +use nix::mount::{mount, umount, MsFlags}; +use nix::sys::stat::{self, Mode}; + +use crate::*; + +static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh +exit 23"; + +const EXPECTED_STATUS: i32 = 23; + +const NONE: Option<&'static [u8]> = None; + +#[test] +fn test_mount_tmpfs_without_flags_allows_rwx() { + require_capability!( + "test_mount_tmpfs_without_flags_allows_rwx", + CAP_SYS_ADMIN + ); + let tempdir = tempfile::tempdir().unwrap(); + + mount( + NONE, + tempdir.path(), + Some(b"tmpfs".as_ref()), + MsFlags::empty(), + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {e}")); + + let test_path = tempdir.path().join("test"); + + // Verify write. + fs::OpenOptions::new() + .create(true) + .write(true) + .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) + .open(&test_path) + .and_then(|mut f| f.write(SCRIPT_CONTENTS)) + .unwrap_or_else(|e| panic!("write failed: {e}")); + + // Verify read. + let mut buf = Vec::new(); + File::open(&test_path) + .and_then(|mut f| f.read_to_end(&mut buf)) + .unwrap_or_else(|e| panic!("read failed: {e}")); + assert_eq!(buf, SCRIPT_CONTENTS); + + // while forking and unmounting prevent other child processes + let _m = FORK_MTX.lock(); + // 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}")); +} + +#[test] +fn test_mount_rdonly_disallows_write() { + require_capability!("test_mount_rdonly_disallows_write", CAP_SYS_ADMIN); + let tempdir = tempfile::tempdir().unwrap(); + + mount( + NONE, + tempdir.path(), + Some(b"tmpfs".as_ref()), + MsFlags::MS_RDONLY, + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {e}")); + + // EROFS: Read-only file system + assert_eq!( + EROFS, + File::create(tempdir.path().join("test")) + .unwrap_err() + .raw_os_error() + .unwrap() + ); + + umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}")); +} + +#[test] +fn test_mount_noexec_disallows_exec() { + require_capability!("test_mount_noexec_disallows_exec", CAP_SYS_ADMIN); + let tempdir = tempfile::tempdir().unwrap(); + + mount( + NONE, + tempdir.path(), + Some(b"tmpfs".as_ref()), + MsFlags::MS_NOEXEC, + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {e}")); + + let test_path = tempdir.path().join("test"); + + fs::OpenOptions::new() + .create(true) + .write(true) + .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) + .open(&test_path) + .and_then(|mut f| f.write(SCRIPT_CONTENTS)) + .unwrap_or_else(|e| panic!("write failed: {e}")); + + // Verify that we cannot execute despite a+x permissions being set. + let mode = stat::Mode::from_bits_truncate( + fs::metadata(&test_path) + .map(|md| md.permissions().mode()) + .unwrap_or_else(|e| panic!("metadata failed: {e}")), + ); + + assert!( + mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH), + "{:?} did not have execute permissions", + &test_path + ); + + // while forking and unmounting prevent other child processes + let _m = FORK_MTX.lock(); + // EACCES: Permission denied + assert_eq!( + EACCES, + Command::new(&test_path) + .status() + .unwrap_err() + .raw_os_error() + .unwrap() + ); + + umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}")); +} + +#[test] +fn test_mount_bind() { + require_capability!("test_mount_bind", CAP_SYS_ADMIN); + let tempdir = tempfile::tempdir().unwrap(); + let file_name = "test"; + + { + let mount_point = tempfile::tempdir().unwrap(); + + mount( + Some(tempdir.path()), + mount_point.path(), + NONE, + MsFlags::MS_BIND, + NONE, + ) + .unwrap_or_else(|e| panic!("mount failed: {e}")); + + fs::OpenOptions::new() + .create(true) + .write(true) + .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) + .open(mount_point.path().join(file_name)) + .and_then(|mut f| f.write(SCRIPT_CONTENTS)) + .unwrap_or_else(|e| panic!("write failed: {e}")); + + // wait for child processes to prevent EBUSY + let _m = FORK_MTX.lock(); + umount(mount_point.path()) + .unwrap_or_else(|e| panic!("umount failed: {e}")); + } + + // Verify the file written in the mount shows up in source directory, even + // after unmounting. + + let mut buf = Vec::new(); + File::open(tempdir.path().join(file_name)) + .and_then(|mut f| f.read_to_end(&mut buf)) + .unwrap_or_else(|e| panic!("read failed: {e}")); + assert_eq!(buf, SCRIPT_CONTENTS); +} diff --git a/test/mount/test_mount_apple.rs b/test/mount/test_mount_apple.rs new file mode 100644 index 00000000..f2868500 --- /dev/null +++ b/test/mount/test_mount_apple.rs @@ -0,0 +1,8 @@ +use nix::errno::Errno; +use nix::mount::{mount, MntFlags}; + +#[test] +fn test_mount() { + let res = mount::("", "", MntFlags::empty(), None); + assert_eq!(res, Err(Errno::ENOENT)); +} diff --git a/test/test_nmount.rs b/test/mount/test_nmount.rs similarity index 100% rename from test/test_nmount.rs rename to test/mount/test_nmount.rs diff --git a/test/sys/mod.rs b/test/sys/mod.rs index 20312120..ab7c5167 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -7,16 +7,20 @@ mod test_signal; // cases on DragonFly. #[cfg(any( target_os = "freebsd", - target_os = "ios", - all(target_os = "linux", not(target_env = "uclibc")), - target_os = "macos", + apple_targets, + all( + target_os = "linux", + not(any(target_env = "uclibc", target_env = "ohos")) + ), target_os = "netbsd" ))] mod test_aio; #[cfg(not(any( target_os = "redox", target_os = "fuchsia", - target_os = "haiku" + target_os = "haiku", + target_os = "hurd", + target_os = "cygwin" )))] mod test_ioctl; #[cfg(not(target_os = "redox"))] @@ -30,7 +34,7 @@ mod test_socket; #[cfg(not(any(target_os = "redox")))] mod test_sockopt; mod test_stat; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] mod test_sysinfo; #[cfg(not(any( target_os = "redox", @@ -41,20 +45,54 @@ mod test_termios; mod test_uio; mod test_wait; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] mod test_epoll; #[cfg(target_os = "linux")] +mod test_fanotify; +#[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(linux_android, freebsdlike, netbsdlike, apple_targets))] mod test_ptrace; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] mod test_timerfd; + +#[cfg(all( + any( + target_os = "freebsd", + solarish, + target_os = "linux", + target_os = "netbsd" + ), + feature = "time", + feature = "signal" +))] +mod test_timer; + +#[cfg(bsd)] +mod test_event; +mod test_statvfs; +mod test_time; +mod test_utsname; + +#[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "openbsd"))] +mod test_statfs; + +#[cfg(not(any( + target_os = "redox", + target_os = "fuchsia", + solarish, + target_os = "haiku" +)))] +mod test_resource; + +// This test module should be enabled for both linux_android and freebsd, but +// the `memfd_create(2)` symbol is not available under Linux QEMU, +// +// https://github.com/nix-rust/nix/actions/runs/9427112650/job/25970870477 +// +// and I haven't found a way to stop the linker from linking that symbol, so +// only enable this for FreeBSD for now. +#[cfg(target_os = "freebsd")] +mod test_memfd; diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index d9e2e085..2f4494fa 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -1,7 +1,7 @@ use std::{ io::{Read, Seek, Write}, ops::Deref, - os::unix::io::AsRawFd, + os::unix::io::{AsFd, AsRawFd, BorrowedFd}, pin::Pin, sync::atomic::{AtomicBool, Ordering}, thread, time, @@ -21,9 +21,7 @@ use nix::{ }; use tempfile::tempfile; -lazy_static! { - pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); -} +pub static SIGNALED: AtomicBool = AtomicBool::new(false); extern "C" fn sigfunc(_: c_int) { SIGNALED.store(true, Ordering::Relaxed); @@ -47,8 +45,9 @@ mod aio_fsync { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let aiocb = AioFsync::new( - 1001, + f.as_fd(), AioFsyncMode::O_SYNC, 42, SigevNotify::SigevSignal { @@ -56,7 +55,7 @@ mod aio_fsync { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(AioFsyncMode::O_SYNC, aiocb.mode()); assert_eq!(42, aiocb.priority()); let sev = aiocb.sigevent().sigevent(); @@ -69,21 +68,17 @@ mod aio_fsync { // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg_attr(any(target_os = "android", target_os = "linux"), ignore)] fn error() { use std::mem; const INITIAL: &[u8] = b"abcdef123456"; // Create an invalid AioFsyncMode - let mode = unsafe { mem::transmute(666) }; + 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 mut aiof = + Box::pin(AioFsync::new(f.as_fd(), mode, 0, SigevNotify::SigevNone)); let err = aiof.as_mut().submit(); err.expect_err("assertion failed"); } @@ -94,9 +89,8 @@ mod aio_fsync { 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, + f.as_fd(), AioFsyncMode::O_SYNC, 0, SigevNotify::SigevNone, @@ -112,9 +106,10 @@ mod aio_read { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let mut rbuf = vec![0; 4]; let aiocb = AioRead::new( - 1001, + f.as_fd(), 2, //offset &mut rbuf, 42, //priority @@ -123,7 +118,7 @@ mod aio_read { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(4, aiocb.nbytes()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -142,7 +137,7 @@ mod aio_read { let mut rbuf = vec![0; 4]; let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = Box::pin(AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone)); aior.as_mut().submit().unwrap(); @@ -159,14 +154,14 @@ mod aio_read { // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg(any(target_os = "freebsd", apple_targets))] 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(), + f.as_fd(), -1, //an invalid offset &mut rbuf, 0, //priority @@ -186,7 +181,7 @@ mod aio_read { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = Box::pin(AioRead::new( fd, 2, @@ -213,7 +208,7 @@ mod aio_read { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone); let mut aior = unsafe { Pin::new_unchecked(&mut aior) }; @@ -236,12 +231,13 @@ mod aio_readv { #[test] fn test_accessors() { + let f = tempfile().unwrap(); 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, + f.as_fd(), 2, //offset &mut rbufs, 42, //priority @@ -250,7 +246,7 @@ mod aio_readv { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(2, aiocb.iovlen()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -272,7 +268,7 @@ mod aio_readv { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = Box::pin(AioReadv::new( fd, 2, @@ -299,9 +295,10 @@ mod aio_write { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let wbuf = vec![0; 4]; let aiocb = AioWrite::new( - 1001, + f.as_fd(), 2, //offset &wbuf, 42, //priority @@ -310,7 +307,7 @@ mod aio_write { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(4, aiocb.nbytes()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -329,7 +326,7 @@ mod aio_write { let f = tempfile().unwrap(); let mut aiow = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 0, wbuf, 0, @@ -358,18 +355,20 @@ mod aio_write { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = Box::pin(AioWrite::new( - f.as_raw_fd(), - 2, - &wbuf, - 0, - SigevNotify::SigevNone, - )); - aiow.as_mut().submit().unwrap(); + { + let mut aiow = Box::pin(AioWrite::new( + f.as_fd(), + 2, + &wbuf, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); - let err = poll_aio!(&mut aiow); - assert_eq!(err, Ok(())); - assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + } f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); @@ -388,19 +387,21 @@ mod aio_write { 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 mut aiow = AioWrite::new( + f.as_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()); + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + } f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); @@ -413,12 +414,14 @@ mod aio_write { // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] - #[cfg(any(target_os = "freebsd", target_os = "macos"))] + #[cfg_attr(any(target_os = "android", target_os = "linux"), ignore)] fn error() { + // Not I/O safe! Deliberately create an invalid fd. + let fd = unsafe { BorrowedFd::borrow_raw(666) }; let wbuf = "CDEF".to_string().into_bytes(); let mut aiow = Box::pin(AioWrite::new( - 666, // An invalid file descriptor - 0, //offset + fd, + 0, //offset &wbuf, 0, //priority SigevNotify::SigevNone, @@ -437,11 +440,12 @@ mod aio_writev { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let wbuf0 = vec![0; 4]; let wbuf1 = vec![0; 8]; let wbufs = [IoSlice::new(&wbuf0), IoSlice::new(&wbuf1)]; let aiocb = AioWritev::new( - 1001, + f.as_fd(), 2, //offset &wbufs, 42, //priority @@ -450,7 +454,7 @@ mod aio_writev { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(2, aiocb.iovlen()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -474,18 +478,20 @@ mod aio_writev { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = Box::pin(AioWritev::new( - f.as_raw_fd(), - 1, - &wbufs, - 0, - SigevNotify::SigevNone, - )); - aiow.as_mut().submit().unwrap(); + { + let mut aiow = Box::pin(AioWritev::new( + f.as_fd(), + 1, + &wbufs, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); - let err = poll_aio!(&mut aiow); - assert_eq!(err, Ok(())); - assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen); + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen); + } f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); @@ -500,7 +506,9 @@ mod aio_writev { any( all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips", - target_arch = "mips64" + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" ), ignore )] @@ -521,22 +529,25 @@ fn sigev_signal() { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = Box::pin(AioWrite::new( - f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevSignal { - signal: Signal::SIGUSR2, - si_value: 0, //TODO: validate in sigfunc - }, - )); - aiow.as_mut().submit().unwrap(); - while !SIGNALED.load(Ordering::Relaxed) { - thread::sleep(time::Duration::from_millis(10)); + { + let mut aiow = Box::pin(AioWrite::new( + f.as_fd(), + 2, //offset + WBUF, + 0, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 0, //TODO: validate in sigfunc + }, + )); + aiow.as_mut().submit().unwrap(); + while !SIGNALED.load(Ordering::Relaxed) { + thread::sleep(time::Duration::from_millis(10)); + } + + assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); } - 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()); @@ -551,7 +562,7 @@ fn test_aio_cancel_all() { let f = tempfile().unwrap(); let mut aiocb = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 0, //offset wbuf, 0, //priority @@ -561,7 +572,7 @@ fn test_aio_cancel_all() { let err = aiocb.as_mut().error(); assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); - aio_cancel_all(f.as_raw_fd()).unwrap(); + aio_cancel_all(f.as_fd()).unwrap(); // Wait for aiocb to complete, but don't care whether it succeeded let _ = poll_aio!(&mut aiocb); @@ -569,12 +580,6 @@ fn test_aio_cancel_all() { } #[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"; @@ -585,7 +590,7 @@ fn test_aio_suspend() { f.write_all(INITIAL).unwrap(); let mut wcb = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 2, //offset WBUF, 0, //priority @@ -593,7 +598,7 @@ fn test_aio_suspend() { )); let mut rcb = Box::pin(AioRead::new( - f.as_raw_fd(), + f.as_fd(), 8, //offset &mut rbuf, 0, //priority @@ -610,7 +615,7 @@ fn test_aio_suspend() { let r = aio_suspend(&cbbuf[..], Some(timeout)); match r { Err(Errno::EINTR) => continue, - Err(e) => panic!("aio_suspend returned {:?}", e), + Err(e) => panic!("aio_suspend returned {e:?}"), Ok(_) => (), }; } @@ -624,3 +629,57 @@ fn test_aio_suspend() { assert_eq!(wcb.as_mut().aio_return().unwrap(), WBUF.len()); assert_eq!(rcb.as_mut().aio_return().unwrap(), rlen); } + +/// aio_suspend relies on casting Rust Aio* struct pointers to libc::aiocb +/// pointers. This test ensures that such casts are valid. +#[test] +fn casting() { + let sev = SigevNotify::SigevNone; + // Only safe because we'll never await the futures + let fd = unsafe { BorrowedFd::borrow_raw(666) }; + let aiof = AioFsync::new(fd, 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(fd, 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(fd, 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() { + use std::io::{IoSlice, IoSliceMut}; + + let sev = SigevNotify::SigevNone; + + let mut rbuf = []; + let mut rbufs = [IoSliceMut::new(&mut rbuf)]; + // Only safe because we'll never await the futures + let fd = unsafe { BorrowedFd::borrow_raw(666) }; + let aiorv = AioReadv::new(fd, 0, &mut rbufs[..], 0, sev); + assert_eq!( + aiorv.as_ref() as *const libc::aiocb, + &aiorv as *const AioReadv as *const libc::aiocb + ); + + let wbuf = []; + let wbufs = [IoSlice::new(&wbuf)]; + let aiowv = AioWritev::new(fd, 0, &wbufs, 0, sev); + assert_eq!( + aiowv.as_ref() as *const libc::aiocb, + &aiowv as *const AioWritev as *const libc::aiocb + ); +} diff --git a/test/sys/test_aio_drop.rs b/test/sys/test_aio_drop.rs index bbe6623f..44d7cf69 100644 --- a/test/sys/test_aio_drop.rs +++ b/test/sys/test_aio_drop.rs @@ -6,10 +6,10 @@ #[cfg(all( not(target_env = "musl"), not(target_env = "uclibc"), + not(target_env = "ohos"), any( target_os = "linux", - target_os = "ios", - target_os = "macos", + apple_targets, target_os = "freebsd", target_os = "netbsd" ) @@ -17,7 +17,7 @@ fn test_drop() { use nix::sys::aio::*; use nix::sys::signal::*; - use std::os::unix::io::AsRawFd; + use std::os::unix::io::AsFd; use tempfile::tempfile; const WBUF: &[u8] = b"CDEF"; @@ -25,7 +25,7 @@ fn test_drop() { let f = tempfile().unwrap(); f.set_len(6).unwrap(); let mut aiocb = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 2, //offset WBUF, 0, //priority diff --git a/test/sys/test_epoll.rs b/test/sys/test_epoll.rs index 91569159..84b100c1 100644 --- a/test/sys/test_epoll.rs +++ b/test/sys/test_epoll.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use nix::errno::Errno; use nix::sys::epoll::{epoll_create1, epoll_ctl}; use nix::sys::epoll::{EpollCreateFlags, EpollEvent, EpollFlags, EpollOp}; diff --git a/test/sys/test_event.rs b/test/sys/test_event.rs new file mode 100644 index 00000000..43dc08ff --- /dev/null +++ b/test/sys/test_event.rs @@ -0,0 +1,41 @@ +use libc::intptr_t; +use nix::sys::event::{EvFlags, EventFilter, FilterFlag, KEvent}; + +#[test] +fn test_struct_kevent() { + use std::mem; + + let udata: intptr_t = 12345; + let data: intptr_t = 0x1337; + + let actual = KEvent::new( + 0xdead_beef, + EventFilter::EVFILT_READ, + EvFlags::EV_ONESHOT | EvFlags::EV_ADD, + FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, + data, + udata, + ); + assert_eq!(0xdead_beef, actual.ident()); + assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); + assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits()); + assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits()); + assert_eq!(data, actual.data()); + assert_eq!(udata, actual.udata()); + assert_eq!(mem::size_of::(), mem::size_of::()); +} + +#[test] +fn test_kevent_filter() { + let udata: intptr_t = 12345; + + let actual = KEvent::new( + 0xdead_beef, + EventFilter::EVFILT_READ, + EvFlags::EV_ONESHOT | EvFlags::EV_ADD, + FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, + 0x1337, + udata, + ); + assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); +} diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs new file mode 100644 index 00000000..04b39c4d --- /dev/null +++ b/test/sys/test_fanotify.rs @@ -0,0 +1,220 @@ +use crate::*; +use nix::errno::Errno; +use nix::fcntl::AT_FDCWD; +use nix::sys::fanotify::{ + EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, + Response, +}; +use std::fs::{read_link, read_to_string, File, OpenOptions}; +use std::io::ErrorKind; +use std::io::{Read, Write}; +use std::os::fd::AsRawFd; +use std::thread; + +#[test] +/// Run fanotify tests sequentially to avoid tmp files races +pub fn test_fanotify() { + require_capability!("test_fanotify", CAP_SYS_ADMIN); + + test_fanotify_notifications(); + test_fanotify_responses(); + test_fanotify_overflow(); +} + +fn test_fanotify_notifications() { + let group = + Fanotify::init(InitFlags::FAN_CLASS_NOTIF, EventFFlags::O_RDONLY) + .unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + let tempfile = tempdir.path().join("test"); + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tempfile) + .unwrap(); + + group + .mark( + MarkFlags::FAN_MARK_ADD, + MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE, + AT_FDCWD, + Some(&tempfile), + ) + .unwrap(); + + // modify test file + { + let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap(); + f.write_all(b"hello").unwrap(); + } + + let mut events = group.read_events().unwrap(); + assert_eq!(events.len(), 1, "should have read exactly one event"); + let event = events.pop().unwrap(); + assert!(event.check_version()); + assert_eq!( + event.mask(), + MaskFlags::FAN_OPEN + | MaskFlags::FAN_MODIFY + | MaskFlags::FAN_CLOSE_WRITE + ); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); + + // read test file + { + let mut f = File::open(&tempfile).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + } + + let mut events = group.read_events().unwrap(); + assert_eq!(events.len(), 1, "should have read exactly one event"); + let event = events.pop().unwrap(); + assert!(event.check_version()); + assert_eq!( + event.mask(), + MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE + ); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); +} + +fn test_fanotify_responses() { + let group = + Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY) + .unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + let tempfile = tempdir.path().join("test"); + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tempfile) + .unwrap(); + + group + .mark( + MarkFlags::FAN_MARK_ADD, + MaskFlags::FAN_OPEN_PERM, + AT_FDCWD, + Some(&tempfile), + ) + .unwrap(); + + let file_thread = thread::spawn({ + let tempfile = tempfile.clone(); + + move || { + // first open, should fail + let Err(e) = File::open(&tempfile) else { + panic!("The first open should fail"); + }; + assert_eq!(e.kind(), ErrorKind::PermissionDenied); + + // second open, should succeed + File::open(&tempfile).unwrap(); + } + }); + + // Deny the first open try + let mut events = group.read_events().unwrap(); + assert_eq!(events.len(), 1, "should have read exactly one event"); + let event = events.pop().unwrap(); + assert!(event.check_version()); + assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); + group + .write_response(FanotifyResponse::new(*fd, Response::FAN_DENY)) + .unwrap(); + + // Allow the second open try + let mut events = group.read_events().unwrap(); + assert_eq!(events.len(), 1, "should have read exactly one event"); + let event = events.pop().unwrap(); + assert!(event.check_version()); + assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); + let fd_opt = event.fd(); + let fd = fd_opt.as_ref().unwrap(); + let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); + assert_eq!(path, tempfile); + group + .write_response(FanotifyResponse::new(*fd, Response::FAN_ALLOW)) + .unwrap(); + + file_thread.join().unwrap(); +} + +fn test_fanotify_overflow() { + let max_events: usize = + read_to_string("/proc/sys/fs/fanotify/max_queued_events") + .unwrap() + .trim() + .parse() + .unwrap(); + + // make sure the kernel is configured with the default value, + // just so this test doesn't run forever + assert_eq!(max_events, 16384); + + let group = Fanotify::init( + InitFlags::FAN_CLASS_NOTIF + | InitFlags::FAN_REPORT_TID + | InitFlags::FAN_NONBLOCK, + EventFFlags::O_RDONLY, + ) + .unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + let tempfile = tempdir.path().join("test"); + + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tempfile) + .unwrap(); + + group + .mark( + MarkFlags::FAN_MARK_ADD, + MaskFlags::FAN_OPEN, + AT_FDCWD, + Some(&tempfile), + ) + .unwrap(); + + thread::scope(|s| { + // perform 10 more events to demonstrate some will be dropped + for _ in 0..(max_events + 10) { + s.spawn(|| { + File::open(&tempfile).unwrap(); + }); + } + }); + + // flush the queue until it's empty + let mut n = 0; + let mut last_event = None; + loop { + match group.read_events() { + Ok(events) => { + n += events.len(); + if let Some(event) = events.last() { + last_event = Some(event.mask()); + } + } + Err(e) if e == Errno::EWOULDBLOCK => break, + Err(e) => panic!("{e:?}"), + } + } + + // make sure we read all we expected. + // the +1 is for the overflow event. + assert_eq!(n, max_events + 1); + assert_eq!(last_event, Some(MaskFlags::FAN_Q_OVERFLOW)); +} diff --git a/test/sys/test_ioctl.rs b/test/sys/test_ioctl.rs index 40f60cfd..08843bf6 100644 --- a/test/sys/test_ioctl.rs +++ b/test/sys/test_ioctl.rs @@ -28,7 +28,7 @@ ioctl_readwrite_buf!(readwritebuf_test, 0, 0, u32); // TODO: Need a way to compute these constants at test time. Using precomputed // values is fragile and needs to be maintained. -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] mod linux { // The cast is not unnecessary on all platforms. #[allow(clippy::unnecessary_cast)] @@ -36,7 +36,9 @@ mod linux { fn test_op_none() { if cfg!(any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "powerpc", target_arch = "powerpc64" )) { @@ -54,7 +56,9 @@ mod linux { fn test_op_write() { if cfg!(any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "powerpc", target_arch = "powerpc64" )) { @@ -69,7 +73,11 @@ mod linux { #[cfg(target_pointer_width = "64")] #[test] fn test_op_write_64() { - if cfg!(any(target_arch = "mips64", target_arch = "powerpc64")) { + if cfg!(any( + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "powerpc64" + )) { assert_eq!( request_code_write!(b'z', 10, 1u64 << 32) as u32, 0x8000_7A0A @@ -88,7 +96,9 @@ mod linux { fn test_op_read() { if cfg!(any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "powerpc", target_arch = "powerpc64" )) { @@ -103,7 +113,11 @@ mod linux { #[cfg(target_pointer_width = "64")] #[test] fn test_op_read_64() { - if cfg!(any(target_arch = "mips64", target_arch = "powerpc64")) { + if cfg!(any( + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "powerpc64" + )) { assert_eq!( request_code_read!(b'z', 10, 1u64 << 32) as u32, 0x4000_7A0A @@ -134,14 +148,7 @@ mod linux { } } -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd" -))] +#[cfg(bsd)] mod bsd { #[test] fn test_op_none() { @@ -149,7 +156,7 @@ mod bsd { assert_eq!(request_code_none!(b'a', 255), 0x2000_61FF); } - #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + #[cfg(freebsdlike)] #[test] fn test_op_write_int() { assert_eq!(request_code_write_int!(b'v', 4), 0x2004_7604); @@ -193,7 +200,7 @@ mod bsd { } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] mod linux_ioctls { use std::mem; use std::os::unix::io::AsRawFd; diff --git a/test/sys/test_memfd.rs b/test/sys/test_memfd.rs new file mode 100644 index 00000000..4b304797 --- /dev/null +++ b/test/sys/test_memfd.rs @@ -0,0 +1,20 @@ +#[test] +fn test_memfd_create() { + use nix::sys::memfd::memfd_create; + use nix::sys::memfd::MFdFlags; + use nix::unistd::lseek; + use nix::unistd::read; + use nix::unistd::{write, Whence}; + + let fd = + memfd_create("test_memfd_create_name", MFdFlags::MFD_CLOEXEC).unwrap(); + let contents = b"hello"; + assert_eq!(write(&fd, contents).unwrap(), 5); + + lseek(&fd, 0, Whence::SeekSet).unwrap(); + + let mut buf = vec![0_u8; contents.len()]; + assert_eq!(read(&fd, &mut buf).unwrap(), 5); + + assert_eq!(contents, buf.as_slice()); +} diff --git a/test/sys/test_mman.rs b/test/sys/test_mman.rs index e748427b..ad70e7ed 100644 --- a/test/sys/test_mman.rs +++ b/test/sys/test_mman.rs @@ -1,44 +1,44 @@ -use nix::sys::mman::{mmap, MapFlags, ProtFlags}; +#![allow(clippy::redundant_slicing)] + +use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags}; use std::num::NonZeroUsize; #[test] fn test_mmap_anonymous() { unsafe { - let ptr = mmap( + let mut ptr = mmap_anonymous( None, NonZeroUsize::new(1).unwrap(), ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, - MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, - -1, - 0, + MapFlags::MAP_PRIVATE, ) - .unwrap() as *mut u8; - assert_eq!(*ptr, 0x00u8); - *ptr = 0xffu8; - assert_eq!(*ptr, 0xffu8); + .unwrap() + .cast::(); + assert_eq!(*ptr.as_ref(), 0x00u8); + *ptr.as_mut() = 0xffu8; + assert_eq!(*ptr.as_ref(), 0xffu8); } } #[test] #[cfg(any(target_os = "linux", target_os = "netbsd"))] fn test_mremap_grow() { - use nix::libc::{c_void, size_t}; + use nix::libc::size_t; use nix::sys::mman::{mremap, MRemapFlags}; + use std::ptr::NonNull; const ONE_K: size_t = 1024; let one_k_non_zero = NonZeroUsize::new(ONE_K).unwrap(); let slice: &mut [u8] = unsafe { - let mem = mmap( + let mem = mmap_anonymous( None, one_k_non_zero, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, - MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, - -1, - 0, + MapFlags::MAP_PRIVATE, ) .unwrap(); - std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) + std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) }; assert_eq!(slice[ONE_K - 1], 0x00); slice[ONE_K - 1] = 0xFF; @@ -47,7 +47,7 @@ fn test_mremap_grow() { let slice: &mut [u8] = unsafe { #[cfg(target_os = "linux")] let mem = mremap( - slice.as_mut_ptr() as *mut c_void, + NonNull::from(&mut slice[..]).cast(), ONE_K, 10 * ONE_K, MRemapFlags::MREMAP_MAYMOVE, @@ -56,14 +56,14 @@ fn test_mremap_grow() { .unwrap(); #[cfg(target_os = "netbsd")] let mem = mremap( - slice.as_mut_ptr() as *mut c_void, + NonNull::from(&mut slice[..]).cast(), ONE_K, 10 * ONE_K, MRemapFlags::MAP_REMAPDUP, None, ) .unwrap(); - std::slice::from_raw_parts_mut(mem as *mut u8, 10 * ONE_K) + std::slice::from_raw_parts_mut(mem.cast().as_ptr(), 10 * ONE_K) }; // The first KB should still have the old data in it. @@ -80,23 +80,22 @@ 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::libc::{c_void, size_t}; + use nix::libc::size_t; use nix::sys::mman::{mremap, MRemapFlags}; use std::num::NonZeroUsize; + use std::ptr::NonNull; const ONE_K: size_t = 1024; let ten_one_k = NonZeroUsize::new(10 * ONE_K).unwrap(); let slice: &mut [u8] = unsafe { - let mem = mmap( + let mem = mmap_anonymous( None, ten_one_k, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, - MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, - -1, - 0, + MapFlags::MAP_PRIVATE, ) .unwrap(); - std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) + std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) }; assert_eq!(slice[ONE_K - 1], 0x00); slice[ONE_K - 1] = 0xFF; @@ -104,7 +103,7 @@ fn test_mremap_shrink() { let slice: &mut [u8] = unsafe { let mem = mremap( - slice.as_mut_ptr() as *mut c_void, + NonNull::from(&mut slice[..]).cast(), ten_one_k.into(), ONE_K, MRemapFlags::empty(), @@ -113,10 +112,89 @@ fn test_mremap_shrink() { .unwrap(); // Since we didn't supply MREMAP_MAYMOVE, the address should be the // same. - assert_eq!(mem, slice.as_mut_ptr() as *mut c_void); - std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K) + assert_eq!(mem.as_ptr(), NonNull::from(&mut slice[..]).cast().as_ptr()); + std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) }; // The first KB should still be accessible and have the old data in it. assert_eq!(slice[ONE_K - 1], 0xFF); } + +#[test] +#[cfg(target_os = "linux")] +fn test_mremap_dontunmap() { + use nix::libc::size_t; + use nix::sys::mman::{mremap, MRemapFlags}; + use std::num::NonZeroUsize; + use std::ptr::NonNull; + + const ONE_K: size_t = 1024; + let one_k_non_zero = NonZeroUsize::new(ONE_K).unwrap(); + + let slice: &mut [u8] = unsafe { + let mem = mmap_anonymous( + None, + one_k_non_zero, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) + }; + + // because we do not unmap `slice`, `old_size` and `new_size` + // need to be equal or `EINVAL` is set. + let _new_slice: &mut [u8] = unsafe { + let mem = mremap( + NonNull::from(&mut slice[..]).cast(), + ONE_K, + ONE_K, + MRemapFlags::MREMAP_MAYMOVE | MRemapFlags::MREMAP_DONTUNMAP, + None, + ) + .unwrap(); + std::slice::from_raw_parts_mut(mem.cast().as_ptr(), 10 * ONE_K) + }; +} + +#[test] +#[cfg(target_os = "linux")] +fn test_madv_wipeonfork() { + use nix::libc::size_t; + use nix::sys::mman::{madvise, MmapAdvise}; + use nix::unistd::{fork, ForkResult}; + use std::num::NonZeroUsize; + + const ONE_K: size_t = 1024; + let ten_one_k = NonZeroUsize::new(10 * ONE_K).unwrap(); + let slice: &mut [u8] = unsafe { + let mem = mmap_anonymous( + None, + ten_one_k, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE, + ) + .unwrap(); + madvise(mem, ONE_K, MmapAdvise::MADV_WIPEONFORK) + .expect("madvise failed"); + std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) + }; + slice[ONE_K - 1] = 0xFF; + let _m = crate::FORK_MTX.lock(); + + unsafe { + let res = fork().expect("fork failed"); + match res { + ForkResult::Child => { + // that s the whole point of MADV_WIPEONFORK + assert_eq!(slice[ONE_K - 1], 0x00); + libc::_exit(0); + } + ForkResult::Parent { child } => { + nix::sys::signal::kill(child, nix::sys::signal::SIGTERM) + .unwrap(); + let _ = nix::sys::wait::wait().unwrap(); + } + } + } +} diff --git a/test/sys/test_prctl.rs b/test/sys/test_prctl.rs new file mode 100644 index 00000000..bfcca8f0 --- /dev/null +++ b/test/sys/test_prctl.rs @@ -0,0 +1,172 @@ +#[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() as libc::c_ulong; + + let slack = 60_000; + prctl::set_timerslack(slack).unwrap(); + let res = prctl::get_timerslack().unwrap() as libc::c_ulong; + assert_eq!(slack, res); + + prctl::set_timerslack(original).unwrap(); + } + + // Loongarch need to use a newer QEMU that disabled these PRCTL subcodes/methods. + // https://github.com/qemu/qemu/commit/220717a6f46a99031a5b1af964bbf4dec1310440 + // So we should ignore them when testing in QEMU environments. + #[cfg_attr(all(qemu, target_arch = "loongarch64"), ignore)] + #[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); + } + + // Loongarch need to use a newer QEMU that disabled these PRCTL subcodes/methods + // https://github.com/qemu/qemu/commit/220717a6f46a99031a5b1af964bbf4dec1310440 + // So we should ignore them when testing in QEMU environments. + #[cfg_attr(all(qemu, target_arch = "loongarch64"), ignore)] + #[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(); + } + + // Ignore this test under QEMU, as it started failing after updating the Linux CI + // runner image, for reasons unknown. + // + // See: https://github.com/nix-rust/nix/issues/2418 + #[test] + #[cfg_attr(qemu, ignore)] + fn test_set_vma_anon_name() { + use nix::errno::Errno; + use nix::sys::mman; + use std::num::NonZeroUsize; + + const ONE_K: libc::size_t = 1024; + let sz = NonZeroUsize::new(ONE_K).unwrap(); + let ptr = unsafe { + mman::mmap_anonymous( + None, + sz, + mman::ProtFlags::PROT_READ, + mman::MapFlags::MAP_SHARED, + ) + .unwrap() + }; + let err = prctl::set_vma_anon_name( + ptr, + sz, + Some(CStr::from_bytes_with_nul(b"[,$\0").unwrap()), + ) + .unwrap_err(); + assert_eq!(err, Errno::EINVAL); + // `CONFIG_ANON_VMA_NAME` kernel config might not be set + prctl::set_vma_anon_name( + ptr, + sz, + Some(CStr::from_bytes_with_nul(b"Nix\0").unwrap()), + ) + .unwrap_or_default(); + prctl::set_vma_anon_name(ptr, sz, None).unwrap_or_default(); + } +} diff --git a/test/sys/test_pthread.rs b/test/sys/test_pthread.rs index ce048bae..0403ffad 100644 --- a/test/sys/test_pthread.rs +++ b/test/sys/test_pthread.rs @@ -1,13 +1,23 @@ use nix::sys::pthread::*; -#[cfg(any(target_env = "musl", target_os = "redox"))] +#[cfg(any( + target_env = "musl", + target_os = "redox", + target_env = "ohos", + target_os = "cygwin" +))] #[test] fn test_pthread_self() { let tid = pthread_self(); assert!(!tid.is_null()); } -#[cfg(not(any(target_env = "musl", target_os = "redox")))] +#[cfg(not(any( + target_env = "musl", + target_os = "redox", + target_env = "ohos", + target_os = "cygwin" +)))] #[test] fn test_pthread_self() { let tid = pthread_self(); diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 530560fe..9f1a3c3b 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -1,16 +1,16 @@ #[cfg(all( target_os = "linux", - any(target_arch = "x86_64", target_arch = "x86"), - target_env = "gnu" + target_env = "gnu", + any(target_arch = "x86_64", target_arch = "x86") ))] use memoffset::offset_of; use nix::errno::Errno; use nix::sys::ptrace; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] use nix::sys::ptrace::Options; use nix::unistd::getpid; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] use std::mem; use crate::*; @@ -28,7 +28,7 @@ fn test_ptrace() { // Just make sure ptrace_setoptions can be called at all, for now. #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptrace_setoptions() { require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE); let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD) @@ -38,7 +38,7 @@ fn test_ptrace_setoptions() { // Just make sure ptrace_getevent can be called at all, for now. #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptrace_getevent() { require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE); let err = ptrace::getevent(getpid()).unwrap_err(); @@ -47,7 +47,7 @@ fn test_ptrace_getevent() { // Just make sure ptrace_getsiginfo can be called at all, for now. #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptrace_getsiginfo() { require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE); if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) { @@ -57,7 +57,7 @@ fn test_ptrace_getsiginfo() { // Just make sure ptrace_setsiginfo can be called at all, for now. #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptrace_setsiginfo() { require_capability!("test_ptrace_setsiginfo", CAP_SYS_PTRACE); let siginfo = unsafe { mem::zeroed() }; @@ -179,8 +179,18 @@ fn test_ptrace_interrupt() { // 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" + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64" + ) + ), + all(target_env = "musl", target_arch = "aarch64") + ) ))] #[test] fn test_ptrace_syscall() { @@ -226,12 +236,21 @@ fn test_ptrace_syscall() { let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as libc::c_long; + #[cfg(target_arch = "aarch64")] + let get_syscall_id = + || ptrace::getregs(child).unwrap().regs[8] as libc::c_long; + + #[cfg(target_arch = "riscv64")] + let get_syscall_id = + || ptrace::getregs(child).unwrap().a7 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); + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] 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; @@ -246,6 +265,7 @@ fn test_ptrace_syscall() { Ok(WaitStatus::PtraceSyscall(child)) ); assert_eq!(get_syscall_id(), ::libc::SYS_kill); + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill); // kill exit @@ -255,6 +275,7 @@ fn test_ptrace_syscall() { Ok(WaitStatus::PtraceSyscall(child)) ); assert_eq!(get_syscall_id(), ::libc::SYS_kill); + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill); // receive signal @@ -273,3 +294,119 @@ fn test_ptrace_syscall() { } } } + +#[cfg(all( + target_os = "linux", + any( + all( + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64" + ) + ), + all(target_env = "musl", target_arch = "aarch64") + ) +))] +#[test] +fn test_ptrace_regsets() { + use nix::sys::ptrace::{self, getregset, regset, setregset}; + use nix::sys::signal::*; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + + require_capability!("test_ptrace_regsets", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + // As recommended by ptrace(2), raise SIGTRAP to pause the child + // until the parent is ready to continue + loop { + raise(Signal::SIGTRAP).unwrap(); + } + } + + Parent { child } => { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)) + ); + let mut regstruct = + getregset::(child).unwrap(); + let mut fpregstruct = + getregset::(child).unwrap(); + + #[cfg(target_arch = "x86_64")] + let (reg, fpreg) = + (&mut regstruct.r15, &mut fpregstruct.st_space[5]); + #[cfg(target_arch = "x86")] + let (reg, fpreg) = + (&mut regstruct.edx, &mut fpregstruct.st_space[5]); + #[cfg(target_arch = "aarch64")] + let (reg, fpreg) = + (&mut regstruct.regs[16], &mut fpregstruct.vregs[5]); + #[cfg(target_arch = "riscv64")] + let (reg, fpreg) = (&mut regstruct.t1, &mut fpregstruct.__f[5]); + + *reg = 0xdeadbeefu32 as _; + *fpreg = 0xfeedfaceu32 as _; + let _ = setregset::(child, regstruct); + regstruct = getregset::(child).unwrap(); + let _ = setregset::(child, fpregstruct); + fpregstruct = getregset::(child).unwrap(); + + #[cfg(target_arch = "x86_64")] + let (reg, fpreg) = (regstruct.r15, fpregstruct.st_space[5]); + #[cfg(target_arch = "x86")] + let (reg, fpreg) = (regstruct.edx, fpregstruct.st_space[5]); + #[cfg(target_arch = "aarch64")] + let (reg, fpreg) = (regstruct.regs[16], fpregstruct.vregs[5]); + #[cfg(target_arch = "riscv64")] + let (reg, fpreg) = (regstruct.t1, fpregstruct.__f[5]); + assert_eq!(reg, 0xdeadbeefu32 as _); + assert_eq!(fpreg, 0xfeedfaceu32 as _); + + ptrace::cont(child, Some(Signal::SIGKILL)).unwrap(); + match waitpid(child, None) { + Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) + if pid == child => {} + _ => panic!("The process should have been killed"), + } + } + } +} + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[test] +fn test_ptrace_syscall_info() { + use nix::sys::ptrace; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + + require_capability!("test_ptrace_syscall_info", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + std::thread::sleep(std::time::Duration::from_millis(1000)); + unsafe { + ::libc::_exit(0); + } + } + Parent { child } => loop { + if let Ok(WaitStatus::Exited(_, 0)) = waitpid(child, None) { + break; + } + let si = ptrace::syscall_info(child).unwrap(); + assert!(si.op >= libc::PTRACE_SYSCALL_INFO_ENTRY); + }, + } +} diff --git a/test/test_resource.rs b/test/sys/test_resource.rs similarity index 55% rename from test/test_resource.rs rename to test/sys/test_resource.rs index 2ab581ba..102b1b57 100644 --- a/test/test_resource.rs +++ b/test/sys/test_resource.rs @@ -1,10 +1,5 @@ -#[cfg(not(any( - target_os = "redox", - target_os = "fuchsia", - target_os = "illumos", - target_os = "haiku" -)))] use nix::sys::resource::{getrlimit, setrlimit, Resource}; +use nix::sys::resource::{getrusage, UsageWho}; /// Tests the RLIMIT_NOFILE functionality of getrlimit(), where the resource RLIMIT_NOFILE refers /// to the maximum file descriptor number that can be opened by the process (aka the maximum number @@ -15,12 +10,7 @@ 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", - target_os = "haiku" -)))] +#[cfg_attr(target_os = "cygwin", ignore)] pub fn test_resource_limits_nofile() { let (mut soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); @@ -32,3 +22,23 @@ pub fn test_resource_limits_nofile() { let (new_soft_limit, _) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); assert_eq!(new_soft_limit, soft_limit); } + +#[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/test/sys/test_select.rs b/test/sys/test_select.rs index 40bda4d9..e39a3192 100644 --- a/test/sys/test_select.rs +++ b/test/sys/test_select.rs @@ -1,19 +1,20 @@ use nix::sys::select::*; use nix::sys::signal::SigSet; -use nix::sys::time::{TimeSpec, TimeValLike}; +use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; use nix::unistd::{pipe, write}; +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; #[test] pub fn test_pselect() { let _mtx = crate::SIGNAL_MTX.lock(); let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); + write(&w1, b"hi!").unwrap(); let (r2, _w2) = pipe().unwrap(); let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + fd_set.insert(r1.as_fd()); + fd_set.insert(r2.as_fd()); let timeout = TimeSpec::seconds(10); let sigmask = SigSet::empty(); @@ -21,25 +22,25 @@ 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.as_fd())); + assert!(!fd_set.contains(r2.as_fd())); } #[test] pub fn test_pselect_nfds2() { let (r1, w1) = pipe().unwrap(); - write(w1, b"hi!").unwrap(); + write(&w1, b"hi!").unwrap(); let (r2, _w2) = pipe().unwrap(); let mut fd_set = FdSet::new(); - fd_set.insert(r1); - fd_set.insert(r2); + fd_set.insert(r1.as_fd()); + fd_set.insert(r2.as_fd()); 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, @@ -48,8 +49,8 @@ pub fn test_pselect_nfds2() { ) .unwrap() ); - assert!(fd_set.contains(r1)); - assert!(!fd_set.contains(r2)); + assert!(fd_set.contains(r1.as_fd())); + assert!(!fd_set.contains(r2.as_fd())); } macro_rules! generate_fdset_bad_fd_tests { @@ -58,20 +59,15 @@ 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; generate_fdset_bad_fd_tests!( FD_SETSIZE.try_into().unwrap(), insert, @@ -79,3 +75,219 @@ mod test_fdset_too_large_fd { contains, ); } + +#[test] +fn fdset_insert() { + let mut fd_set = FdSet::new(); + + for i in 0..FD_SETSIZE { + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + assert!(!fd_set.contains(borrowed_i)); + } + + let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; + fd_set.insert(fd_seven); + + assert!(fd_set.contains(fd_seven)); +} + +#[test] +fn fdset_remove() { + let mut fd_set = FdSet::new(); + + for i in 0..FD_SETSIZE { + let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; + assert!(!fd_set.contains(borrowed_i)); + } + + let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; + fd_set.insert(fd_seven); + fd_set.remove(fd_seven); + + for i in 0..FD_SETSIZE { + 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(); + let fd_one = unsafe { BorrowedFd::borrow_raw(1) }; + let fd_FD_SETSIZE_divided_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_divided_by_two); + fd_set.insert(fd_FD_SETSIZE_minus_one); + + fd_set.clear(); + + for i in 0..FD_SETSIZE { + 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().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(); + 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)) + .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(); + let (r2, _w2) = pipe().unwrap(); + + write(&w1, b"hi!").unwrap(); + let mut fd_set = FdSet::new(); + fd_set.insert(r1.as_fd()); + fd_set.insert(r2.as_fd()); + + let mut timeout = TimeVal::seconds(10); + assert_eq!( + 1, + select(None, &mut fd_set, None, None, &mut timeout).unwrap() + ); + assert!(fd_set.contains(r1.as_fd())); + assert!(!fd_set.contains(r2.as_fd())); +} + +#[test] +fn test_select_nfds() { + let (r1, w1) = pipe().unwrap(); + let (r2, _w2) = pipe().unwrap(); + + write(&w1, b"hi!").unwrap(); + let mut fd_set = FdSet::new(); + fd_set.insert(r1.as_fd()); + fd_set.insert(r2.as_fd()); + + let mut timeout = TimeVal::seconds(10); + { + 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.as_fd())); + assert!(!fd_set.contains(r2.as_fd())); +} + +#[test] +fn test_select_nfds2() { + let (r1, w1) = pipe().unwrap(); + write(&w1, b"hi!").unwrap(); + let (r2, _w2) = pipe().unwrap(); + let mut fd_set = FdSet::new(); + fd_set.insert(r1.as_fd()); + fd_set.insert(r2.as_fd()); + + let mut timeout = TimeVal::seconds(10); + 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.as_fd())); + assert!(!fd_set.contains(r2.as_fd())); +} diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs index 3ad14f40..cd4bc3d9 100644 --- a/test/sys/test_signal.rs +++ b/test/sys/test_signal.rs @@ -1,9 +1,10 @@ -#[cfg(not(target_os = "redox"))] use nix::errno::Errno; use nix::sys::signal::*; use nix::unistd::*; -use std::convert::TryFrom; +use std::hash::{Hash, Hasher}; use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(not(target_os = "redox"))] +use std::thread; #[test] fn test_kill_none() { @@ -54,9 +55,8 @@ fn test_sigprocmask() { // test don't make sense. assert!( !old_signal_set.contains(SIGNAL), - "the {:?} signal is already blocked, please change to a \ - different one", - SIGNAL + "the {SIGNAL:?} signal is already blocked, please change to a \ + different one" ); // Now block the signal. @@ -71,8 +71,7 @@ fn test_sigprocmask() { .expect("expect to be able to retrieve old signals"); assert!( old_signal_set.contains(SIGNAL), - "expected the {:?} to be blocked", - SIGNAL + "expected the {SIGNAL:?} to be blocked" ); // Reset the signal. @@ -80,9 +79,7 @@ fn test_sigprocmask() { .expect("expect to be able to block signals"); } -lazy_static! { - static ref SIGNALED: AtomicBool = AtomicBool::new(false); -} +static SIGNALED: AtomicBool = AtomicBool::new(false); extern "C" fn test_sigaction_handler(signal: libc::c_int) { let signal = Signal::try_from(signal).unwrap(); @@ -128,7 +125,7 @@ fn test_signal() { raise(Signal::SIGINT).unwrap(); assert!(SIGNALED.load(Ordering::Relaxed)); - #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] + #[cfg(not(solarish))] assert_eq!( unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), handler @@ -136,7 +133,7 @@ fn test_signal() { // 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"))] + #[cfg(solarish)] assert_eq!( unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), SigHandler::SigDfl @@ -145,3 +142,315 @@ fn test_signal() { // Restore default signal handler unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(); } + +#[test] +fn test_contains() { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + assert!(mask.contains(SIGUSR1)); + assert!(!mask.contains(SIGUSR2)); + + let all = SigSet::all(); + assert!(all.contains(SIGUSR1)); + assert!(all.contains(SIGUSR2)); +} + +#[test] +fn test_clear() { + let mut set = SigSet::all(); + set.clear(); + for signal in Signal::iterator() { + assert!(!set.contains(signal)); + } +} + +#[test] +fn test_from_str_round_trips() { + for signal in Signal::iterator() { + assert_eq!(signal.as_ref().parse::().unwrap(), signal); + assert_eq!(signal.to_string().parse::().unwrap(), signal); + } +} + +#[test] +fn test_from_str_invalid_value() { + let errval = Err(Errno::EINVAL); + assert_eq!("NOSIGNAL".parse::(), errval); + assert_eq!("kill".parse::(), errval); + assert_eq!("9".parse::(), errval); +} + +#[test] +fn test_extend() { + let mut one_signal = SigSet::empty(); + one_signal.add(SIGUSR1); + + let mut two_signals = SigSet::empty(); + two_signals.add(SIGUSR2); + two_signals.extend(&one_signal); + + assert!(two_signals.contains(SIGUSR1)); + assert!(two_signals.contains(SIGUSR2)); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_thread_signal_set_mask() { + thread::spawn(|| { + let prev_mask = SigSet::thread_get_mask() + .expect("Failed to get existing signal mask!"); + + let mut test_mask = prev_mask; + test_mask.add(SIGUSR1); + + test_mask.thread_set_mask().expect("assertion failed"); + let new_mask = + SigSet::thread_get_mask().expect("Failed to get new mask!"); + + assert!(new_mask.contains(SIGUSR1)); + assert!(!new_mask.contains(SIGUSR2)); + + prev_mask + .thread_set_mask() + .expect("Failed to revert signal mask!"); + }) + .join() + .unwrap(); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_thread_signal_block() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + mask.thread_block().expect("assertion failed"); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + }) + .join() + .unwrap(); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_thread_signal_unblock() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + mask.thread_unblock().expect("assertion failed"); + + assert!(!SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + }) + .join() + .unwrap(); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_thread_signal_swap() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + mask.thread_block().unwrap(); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1)); + + let mut mask2 = SigSet::empty(); + mask2.add(SIGUSR2); + + let oldmask = mask2.thread_swap_mask(SigmaskHow::SIG_SETMASK).unwrap(); + + assert!(oldmask.contains(SIGUSR1)); + assert!(!oldmask.contains(SIGUSR2)); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR2)); + }) + .join() + .unwrap(); +} + +#[test] +fn test_from_and_into_iterator() { + let sigset = SigSet::from_iter(vec![Signal::SIGUSR1, Signal::SIGUSR2]); + let signals = sigset.into_iter().collect::>(); + assert_eq!(signals, [Signal::SIGUSR1, Signal::SIGUSR2]); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_sigaction() { + let _m = crate::SIGNAL_MTX.lock(); + thread::spawn(|| { + extern "C" fn test_sigaction_handler(_: libc::c_int) {} + extern "C" fn test_sigaction_action( + _: libc::c_int, + _: *mut libc::siginfo_t, + _: *mut libc::c_void, + ) { + } + + let handler_sig = SigHandler::Handler(test_sigaction_handler); + + let flags = + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART | SaFlags::SA_SIGINFO; + + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + + let action_sig = SigAction::new(handler_sig, flags, mask); + + assert_eq!( + action_sig.flags(), + SaFlags::SA_ONSTACK | SaFlags::SA_RESTART + ); + assert_eq!(action_sig.handler(), handler_sig); + + mask = action_sig.mask(); + assert!(mask.contains(SIGUSR1)); + assert!(!mask.contains(SIGUSR2)); + + let handler_act = SigHandler::SigAction(test_sigaction_action); + let action_act = SigAction::new(handler_act, flags, mask); + assert_eq!(action_act.handler(), handler_act); + + let action_dfl = SigAction::new(SigHandler::SigDfl, flags, mask); + assert_eq!(action_dfl.handler(), SigHandler::SigDfl); + + let action_ign = SigAction::new(SigHandler::SigIgn, flags, mask); + assert_eq!(action_ign.handler(), SigHandler::SigIgn); + }) + .join() + .unwrap(); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_sigwait() { + thread::spawn(|| { + let mut mask = SigSet::empty(); + mask.add(SIGUSR1); + mask.add(SIGUSR2); + mask.thread_block().unwrap(); + + raise(SIGUSR1).unwrap(); + assert_eq!(mask.wait().unwrap(), SIGUSR1); + }) + .join() + .unwrap(); +} + +#[cfg(any( + bsd, + linux_android, + solarish, + target_os = "haiku", + target_os = "hurd", + target_os = "aix", + target_os = "fuchsia" +))] +#[test] +fn test_sigsuspend() { + // This test change signal handler + let _m = crate::SIGNAL_MTX.lock(); + static SIGNAL_RECIEVED: AtomicBool = AtomicBool::new(false); + extern "C" fn test_sigsuspend_handler(_: libc::c_int) { + assert!(!SIGNAL_RECIEVED.swap(true, Ordering::SeqCst)); + } + thread::spawn(|| { + const SIGNAL: Signal = Signal::SIGUSR1; + + // Add signal mask to this thread + let mut signal_set = SigSet::empty(); + signal_set.add(SIGNAL); + signal_set.thread_block().unwrap(); + + // Set signal handler and save old one. + let act = SigAction::new( + SigHandler::Handler(test_sigsuspend_handler), + SaFlags::empty(), + SigSet::empty(), + ); + let old_act = unsafe { sigaction(SIGNAL, &act) } + .expect("expect to be able to set new action and get old action"); + + raise(SIGNAL).expect("expect be able to send signal"); + // Now `SIGNAL` was sended but it is blocked. + let mut not_wait_set = SigSet::all(); + not_wait_set.remove(SIGNAL); + // signal handler must run in SigSet::suspend() + assert!(!SIGNAL_RECIEVED.load(Ordering::SeqCst)); + not_wait_set.suspend().unwrap(); + assert!(SIGNAL_RECIEVED.load(Ordering::SeqCst)); + + // Restore the signal handler. + unsafe { sigaction(SIGNAL, &old_act) } + .expect("expect to be able to restore old action "); + }) + .join() + .unwrap(); +} + +#[test] +fn test_from_sigset_t_unchecked() { + let src_set = SigSet::empty(); + let set = unsafe { SigSet::from_sigset_t_unchecked(*src_set.as_ref()) }; + + for signal in Signal::iterator() { + assert!(!set.contains(signal)); + } + + let src_set = SigSet::all(); + let set = unsafe { SigSet::from_sigset_t_unchecked(*src_set.as_ref()) }; + + for signal in Signal::iterator() { + assert!(set.contains(signal)); + } +} + +#[test] +fn test_eq_empty() { + let set0 = SigSet::empty(); + let set1 = SigSet::empty(); + assert_eq!(set0, set1); +} + +#[test] +fn test_eq_all() { + let set0 = SigSet::all(); + let set1 = SigSet::all(); + assert_eq!(set0, set1); +} + +#[test] +fn test_hash_empty() { + use std::collections::hash_map::DefaultHasher; + + let set0 = SigSet::empty(); + let mut h0 = DefaultHasher::new(); + set0.hash(&mut h0); + + let set1 = SigSet::empty(); + let mut h1 = DefaultHasher::new(); + set1.hash(&mut h1); + + assert_eq!(h0.finish(), h1.finish()); +} + +#[test] +fn test_hash_all() { + use std::collections::hash_map::DefaultHasher; + + let set0 = SigSet::all(); + let mut h0 = DefaultHasher::new(); + set0.hash(&mut h0); + + let set1 = SigSet::all(); + let mut h1 = DefaultHasher::new(); + set1.hash(&mut h1); + + assert_eq!(h0.finish(), h1.finish()); +} diff --git a/test/sys/test_signalfd.rs b/test/sys/test_signalfd.rs index 87153c95..d3158484 100644 --- a/test/sys/test_signalfd.rs +++ b/test/sys/test_signalfd.rs @@ -1,5 +1,39 @@ use std::convert::TryFrom; +#[test] +fn create_signalfd() { + use nix::sys::{signal::SigSet, signalfd::SignalFd}; + + let mask = SigSet::empty(); + SignalFd::new(&mask).unwrap(); +} + +#[test] +fn create_signalfd_with_opts() { + use nix::sys::{ + signal::SigSet, + signalfd::{SfdFlags, SignalFd}, + }; + + let mask = SigSet::empty(); + SignalFd::with_flags(&mask, SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK) + .unwrap(); +} + +#[test] +fn read_empty_signalfd() { + use nix::sys::{ + signal::SigSet, + signalfd::{SfdFlags, SignalFd}, + }; + + let mask = SigSet::empty(); + let fd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); + + let res = fd.read_signal(); + assert!(res.unwrap().is_none()); +} + #[test] fn test_signalfd() { use nix::sys::signal::{self, raise, SigSet, Signal}; @@ -13,7 +47,36 @@ fn test_signalfd() { mask.add(signal::SIGUSR1); mask.thread_block().unwrap(); - let mut fd = SignalFd::new(&mask).unwrap(); + let fd = SignalFd::new(&mask).unwrap(); + + // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill` + // because `kill` with `getpid` isn't correct during multi-threaded execution like during a + // cargo test session. Instead use `raise` which does the correct thing by default. + raise(signal::SIGUSR1).expect("Error: raise(SIGUSR1) failed"); + + // And now catch that same signal. + let res = fd.read_signal().unwrap().unwrap(); + let signo = Signal::try_from(res.ssi_signo as i32).unwrap(); + assert_eq!(signo, signal::SIGUSR1); +} + +/// Update the signal mask of an already existing signalfd. +#[test] +fn test_signalfd_setmask() { + use nix::sys::signal::{self, raise, SigSet, Signal}; + use nix::sys::signalfd::SignalFd; + + // Grab the mutex for altering signals so we don't interfere with other tests. + let _m = crate::SIGNAL_MTX.lock(); + + // Block the SIGUSR1 signal from automatic processing for this thread + let mut mask = SigSet::empty(); + + let fd = SignalFd::new(&mask).unwrap(); + + mask.add(signal::SIGUSR1); + mask.thread_block().unwrap(); + fd.set_mask(&mask).unwrap(); // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill` // because `kill` with `getpid` isn't correct during multi-threaded execution like during a diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 158b745a..42906a65 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -1,73 +1,15 @@ -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] use crate::*; -use libc::{c_char, sockaddr_storage}; -#[allow(deprecated)] -use nix::sys::socket::InetAddr; -use nix::sys::socket::{ - getsockname, sockaddr, sockaddr_in6, AddressFamily, UnixAddr, -}; +use 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, SocketAddrV4, 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; -#[allow(deprecated)] -#[test] -pub fn test_inetv4_addr_to_sock_addr() { - let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap(); - let addr = InetAddr::from_std(&actual); - - match addr { - InetAddr::V4(addr) => { - let ip: u32 = 0x7f00_0001; - let port: u16 = 3000; - let saddr = addr.sin_addr.s_addr; - - assert_eq!(saddr, ip.to_be()); - assert_eq!(addr.sin_port, port.to_be()); - } - _ => panic!("nope"), - } - - assert_eq!(addr.to_string(), "127.0.0.1:3000"); - - let inet = addr.to_std(); - assert_eq!(actual, inet); -} - -#[allow(deprecated)] -#[test] -pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() { - use nix::sys::socket::{sockaddr_storage_to_addr, SockAddr}; - - let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap(); - let addr = InetAddr::from_std(&actual); - let sockaddr = SockAddr::new_inet(addr); - - let (storage, ffi_size) = { - let mut storage = MaybeUninit::::zeroed(); - let storage_ptr = storage.as_mut_ptr().cast::(); - let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); - assert_eq!(mem::size_of::(), ffi_size as usize); - unsafe { - storage_ptr.copy_from_nonoverlapping(ffi_ptr as *const sockaddr, 1); - (storage.assume_init(), ffi_size) - } - }; - - let from_storage = - sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); - assert_eq!(from_storage, sockaddr); - let from_storage = - sockaddr_storage_to_addr(&storage, mem::size_of::()) - .unwrap(); - assert_eq!(from_storage, sockaddr); -} - #[cfg(target_os = "linux")] #[cfg_attr(qemu, ignore)] #[test] @@ -79,7 +21,7 @@ pub fn test_timestamping() { }; use std::io::{IoSlice, IoSliceMut}; - let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); + let sock_addr = SockaddrIn::from_str("127.0.0.1:6797").unwrap(); let ssock = socket( AddressFamily::Inet, @@ -96,9 +38,9 @@ pub fn test_timestamping() { None, ) .unwrap(); - nix::sys::socket::bind(rsock, &sock_addr).unwrap(); + nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); - setsockopt(rsock, Timestamping, &TimestampingFlag::all()).unwrap(); + setsockopt(&rsock, Timestamping, &TimestampingFlag::all()).unwrap(); let sbuf = [0u8; 2048]; let mut rbuf = [0u8; 2048]; @@ -107,11 +49,13 @@ pub fn test_timestamping() { let mut iov2 = [IoSliceMut::new(&mut rbuf)]; let mut cmsg = cmsg_space!(nix::sys::socket::Timestamps); - sendmsg(ssock, &iov1, &[], flags, Some(&sock_addr)).unwrap(); - let recv = recvmsg::<()>(rsock, &mut iov2, Some(&mut cmsg), flags).unwrap(); + 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() { + for c in recv.cmsgs().unwrap() { if let ControlMessageOwned::ScmTimestampsns(timestamps) = c { ts = Some(timestamps.system); } @@ -128,42 +72,124 @@ pub fn test_timestamping() { assert!(std::time::Duration::from(diff).as_secs() < 60); } -#[allow(deprecated)] +#[cfg(target_os = "freebsd")] #[test] -pub fn test_inetv6_addr_roundtrip_sockaddr_storage_to_addr() { - use nix::sys::socket::{sockaddr_storage_to_addr, SockAddr}; - - let port: u16 = 3000; - let flowinfo: u32 = 1; - let scope_id: u32 = 2; - let ip: Ipv6Addr = "fe80::1".parse().unwrap(); - - let actual = - SocketAddr::V6(SocketAddrV6::new(ip, port, flowinfo, scope_id)); - let addr = InetAddr::from_std(&actual); - let sockaddr = SockAddr::new_inet(addr); - - let (storage, ffi_size) = { - let mut storage = MaybeUninit::::zeroed(); - let storage_ptr = storage.as_mut_ptr().cast::(); - let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); - assert_eq!(mem::size_of::(), ffi_size as usize); - unsafe { - storage_ptr.copy_from_nonoverlapping( - (ffi_ptr as *const sockaddr).cast::(), - 1, - ); - (storage.assume_init(), ffi_size) - } +pub fn test_timestamping_realtime() { + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socket, sockopt::ReceiveTimestamp, + sockopt::TsClock, ControlMessageOwned, MsgFlags, SockFlag, SockType, + SockaddrIn, SocketTimestamp, }; + use std::io::{IoSlice, IoSliceMut}; - 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::()) + let sock_addr = SockaddrIn::from_str("127.0.0.1:6792").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); + + setsockopt(&rsock, ReceiveTimestamp, &true).unwrap(); + setsockopt(&rsock, TsClock, &SocketTimestamp::SO_TS_REALTIME).unwrap(); + + let sbuf = [0u8; 2048]; + let mut rbuf = [0u8; 2048]; + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + let mut iov2 = [IoSliceMut::new(&mut rbuf)]; + + let mut cmsg = cmsg_space!(nix::sys::time::TimeVal); + 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(); - assert_eq!(from_storage, sockaddr); + + let mut ts = None; + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::ScmRealtime(timeval) = c { + ts = Some(timeval); + } + } + let ts = ts.expect("ScmRealtime is present"); + let sys_time = + ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_REALTIME) + .unwrap(); + let diff = if ts > sys_time { + ts - sys_time + } else { + sys_time - ts + }; + assert!(std::time::Duration::from(diff).as_secs() < 60); +} + +#[cfg(target_os = "freebsd")] +#[test] +pub fn test_timestamping_monotonic() { + use nix::sys::socket::{ + recvmsg, sendmsg, setsockopt, socket, sockopt::ReceiveTimestamp, + sockopt::TsClock, ControlMessageOwned, MsgFlags, SockFlag, SockType, + SockaddrIn, SocketTimestamp, + }; + use std::io::{IoSlice, IoSliceMut}; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6803").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); + + setsockopt(&rsock, ReceiveTimestamp, &true).unwrap(); + setsockopt(&rsock, TsClock, &SocketTimestamp::SO_TS_MONOTONIC).unwrap(); + + let sbuf = [0u8; 2048]; + let mut rbuf = [0u8; 2048]; + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + let mut iov2 = [IoSliceMut::new(&mut rbuf)]; + + let mut cmsg = cmsg_space!(nix::sys::time::TimeVal); + 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().unwrap() { + if let ControlMessageOwned::ScmMonotonic(timeval) = c { + ts = Some(timeval); + } + } + let ts = ts.expect("ScmMonotonic is present"); + let sys_time = + ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_MONOTONIC) + .unwrap(); + let diff = sys_time - ts; // Monotonic clock sys_time must be greater + assert!(std::time::Duration::from(diff).as_secs() < 60); } #[test] @@ -172,10 +198,9 @@ pub fn test_path_to_sock_addr() { let actual = Path::new(path); let addr = UnixAddr::new(actual).unwrap(); - let expect: &[c_char] = unsafe { - slice::from_raw_parts(path.as_ptr() as *const c_char, path.len()) - }; - assert_eq!(unsafe { &(*addr.as_ptr()).sun_path[..8] }, expect); + let expect: &[c_char] = + unsafe { slice::from_raw_parts(path.as_ptr().cast(), path.len()) }; + assert_eq!(unsafe { &(&(*addr.as_ptr()).sun_path)[..8] }, expect); assert_eq!(addr.path(), Some(actual)); } @@ -199,7 +224,7 @@ pub fn test_addr_equality_path() { assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_abstract_sun_path_too_long() { let name = String::from("nix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0testttttnix\0abstract\0test\0make\0sure\0this\0is\0long\0enough"); @@ -207,7 +232,7 @@ pub fn test_abstract_sun_path_too_long() { addr.expect_err("assertion failed"); } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_addr_equality_abstract() { let name = String::from("nix\0abstract\0test"); @@ -223,7 +248,7 @@ pub fn test_addr_equality_abstract() { } // Test getting/setting abstract addresses (without unix socket creation) -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_abstract_uds_addr() { let empty = String::new(); @@ -245,7 +270,7 @@ pub fn test_abstract_uds_addr() { } // Test getting an unnamed address (without unix socket creation) -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_unnamed_uds_addr() { use crate::nix::sys::socket::SockaddrLike; @@ -275,8 +300,11 @@ pub fn test_getsockname() { ) .expect("socket failed"); let sockaddr = UnixAddr::new(&sockname).unwrap(); - bind(sock, &sockaddr).expect("bind failed"); - assert_eq!(sockaddr, getsockname(sock).expect("getsockname failed")); + bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); + assert_eq!( + sockaddr, + getsockname(sock.as_raw_fd()).expect("getsockname failed") + ); } #[test] @@ -291,14 +319,15 @@ pub fn test_socketpair() { SockFlag::empty(), ) .unwrap(); - write(fd1, b"hello").unwrap(); + write(&fd1, b"hello").unwrap(); let mut buf = [0; 5]; - read(fd2, &mut buf).unwrap(); + read(&fd2, &mut buf).unwrap(); assert_eq!(&buf[..], b"hello"); } #[test] +#[cfg_attr(target_os = "cygwin", ignore)] pub fn test_recvmsg_sockaddr_un() { use nix::sys::socket::{ self, bind, socket, AddressFamily, MsgFlags, SockFlag, SockType, @@ -314,25 +343,26 @@ pub fn test_recvmsg_sockaddr_un() { ) .expect("socket failed"); let sockaddr = UnixAddr::new(&sockname).unwrap(); - bind(sock, &sockaddr).expect("bind failed"); + 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, + sock.as_raw_fd(), &[std::io::IoSlice::new(send_buffer)], &[], MsgFlags::empty(), Some(&sockaddr), ) { - crate::skip!("Couldn't send ({:?}), so skipping test", e); + 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, &mut iov, None, MsgFlags::empty()).unwrap(); + 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()); } @@ -400,12 +430,12 @@ mod recvfrom { ) .unwrap(); // Ignore from for stream sockets - let _ = sendrecv(fd1, fd2, send, |_, _| {}); + let _ = sendrecv(fd1.as_raw_fd(), fd2.as_raw_fd(), send, |_, _| {}); } #[test] pub fn udp() { - let std_sa = SocketAddrV4::from_str("127.0.0.1:6789").unwrap(); + let std_sa = SocketAddrV4::from_str("127.0.0.1:6795").unwrap(); let sock_addr = SockaddrIn::from(std_sa); let rsock = socket( AddressFamily::Inet, @@ -414,7 +444,7 @@ mod recvfrom { None, ) .unwrap(); - bind(rsock, &sock_addr).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, @@ -423,9 +453,9 @@ mod recvfrom { ) .expect("send socket failed"); let from = sendrecv( - rsock, - ssock, - move |s, m, flags| sendto(s, m, &sock_addr, flags), + 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 @@ -459,10 +489,10 @@ mod recvfrom { ) .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, @@ -474,12 +504,18 @@ mod recvfrom { let mut num_packets_received: i32 = 0; sendrecv( - rsock, - ssock, + 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, &iov, &[cmsg], flags, Some(&sock_addr)) + sendmsg( + s.as_raw_fd(), + &iov, + &[cmsg], + flags, + Some(&sock_addr), + ) }, { let num_packets_received_ref = &mut num_packets_received; @@ -516,17 +552,12 @@ mod recvfrom { ) .unwrap(); - setsockopt(rsock, UdpGroSegment, &true) + setsockopt(&rsock, UdpGroSegment, &true) .expect("setsockopt UDP_GRO failed"); } } - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - ))] + #[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] #[test] pub fn udp_sendmmsg() { use std::io::IoSlice; @@ -543,7 +574,7 @@ mod recvfrom { None, ) .unwrap(); - bind(rsock, &sock_addr).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, @@ -553,8 +584,8 @@ mod recvfrom { .expect("send socket failed"); let from = sendrecv( - rsock, - ssock, + 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); @@ -588,12 +619,7 @@ mod recvfrom { assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); } - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - ))] + #[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] #[test] pub fn udp_recvmmsg() { use nix::sys::socket::{recvmmsg, MsgFlags}; @@ -612,7 +638,7 @@ mod recvfrom { None, ) .unwrap(); - bind(rsock, &sock_addr).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, @@ -623,8 +649,13 @@ mod recvfrom { 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(); } }); @@ -641,10 +672,15 @@ mod recvfrom { let mut data = MultiHeaders::::preallocate(msgs.len(), None); - let res: Vec> = - recvmmsg(rsock, &mut data, msgs.iter(), MsgFlags::empty(), None) - .expect("recvmmsg") - .collect(); + let res: Vec> = recvmmsg( + rsock.as_raw_fd(), + &mut data, + msgs.iter_mut(), + MsgFlags::empty(), + None, + ) + .expect("recvmmsg") + .collect(); assert_eq!(res.len(), DATA.len()); for RecvMsg { address, bytes, .. } in res.into_iter() { @@ -659,12 +695,7 @@ mod recvfrom { send_thread.join().unwrap(); } - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - ))] + #[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] #[test] pub fn udp_recvmmsg_dontwait_short_read() { use nix::sys::socket::{recvmmsg, MsgFlags}; @@ -683,7 +714,7 @@ mod recvfrom { None, ) .unwrap(); - bind(rsock, &sock_addr).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let ssock = socket( AddressFamily::Inet, SockType::Datagram, @@ -694,8 +725,13 @@ mod recvfrom { 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` @@ -720,9 +756,9 @@ mod recvfrom { ); let res: Vec> = recvmmsg( - rsock, + rsock.as_raw_fd(), &mut data, - msgs.iter(), + msgs.iter_mut(), MsgFlags::MSG_DONTWAIT, None, ) @@ -743,10 +779,10 @@ mod recvfrom { #[test] pub fn udp_inet6() { let addr = std::net::Ipv6Addr::from_str("::1").unwrap(); - let rport = 6789; + let rport = 6796; let rstd_sa = SocketAddrV6::new(addr, rport, 0, 0); let raddr = SockaddrIn6::from(rstd_sa); - let sport = 6790; + let sport = 6798; let sstd_sa = SocketAddrV6::new(addr, sport, 0, 0); let saddr = SockaddrIn6::from(sstd_sa); let rsock = socket( @@ -756,12 +792,12 @@ mod recvfrom { None, ) .expect("receive socket failed"); - match bind(rsock, &raddr) { + match bind(rsock.as_raw_fd(), &raddr) { Err(Errno::EADDRNOTAVAIL) => { println!("IPv6 not available, skipping test."); return; } - Err(e) => panic!("bind: {}", e), + Err(e) => panic!("bind: {e}"), Ok(()) => (), } let ssock = socket( @@ -771,11 +807,11 @@ mod recvfrom { None, ) .expect("send socket failed"); - bind(ssock, &saddr).unwrap(); + bind(ssock.as_raw_fd(), &saddr).unwrap(); let from = sendrecv( - rsock, - ssock, - move |s, m, flags| sendto(s, m, &raddr, flags), + 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()); @@ -797,7 +833,7 @@ pub fn test_recvmsg_ebadf() { let mut iov = [IoSliceMut::new(&mut buf[..])]; let fd = -1; // Bad file descriptor - let r = recvmsg::<()>(fd, &mut iov, None, MsgFlags::empty()); + let r = recvmsg::<()>(fd.as_raw_fd(), &mut iov, None, MsgFlags::empty()); assert_eq!(r.err().unwrap(), Errno::EBADF); } @@ -806,6 +842,7 @@ pub fn test_recvmsg_ebadf() { // 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 #[cfg_attr(qemu, ignore)] #[test] +#[cfg_attr(target_os = "cygwin", ignore)] pub fn test_scm_rights() { use nix::sys::socket::{ recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage, @@ -826,14 +863,19 @@ pub fn test_scm_rights() { { let iov = [IoSlice::new(b"hello")]; - let fds = [r]; + let fds = [r.as_raw_fd()]; let cmsg = ControlMessage::ScmRights(&fds); assert_eq!( - sendmsg::<()>(fd1, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(), + sendmsg::<()>( + fd1.as_raw_fd(), + &iov, + &[cmsg], + MsgFlags::empty(), + None + ) + .unwrap(), 5 ); - close(r).unwrap(); - close(fd1).unwrap(); } { @@ -842,14 +884,14 @@ pub fn test_scm_rights() { let mut iov = [IoSliceMut::new(&mut buf[..])]; let mut cmsgspace = cmsg_space!([RawFd; 1]); let msg = recvmsg::<()>( - fd2, + fd2.as_raw_fd(), &mut iov, Some(&mut cmsgspace), MsgFlags::empty(), ) .unwrap(); - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { if let ControlMessageOwned::ScmRights(fd) = cmsg { assert_eq!(received_r, None); assert_eq!(fd.len(), 1); @@ -862,22 +904,30 @@ pub fn test_scm_rights() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - close(fd2).unwrap(); } let received_r = received_r.expect("Did not receive passed fd"); // Ensure that the received file descriptor works - write(w, b"world").unwrap(); + write(&w, b"world").unwrap(); let mut buf = [0u8; 5]; - read(received_r, &mut buf).unwrap(); + // SAFETY: + // should be safe since we don't use it after close + let borrowed_received_r = + unsafe { std::os::fd::BorrowedFd::borrow_raw(received_r) }; + read(borrowed_received_r, &mut buf).unwrap(); assert_eq!(&buf[..], b"world"); close(received_r).unwrap(); - close(w).unwrap(); } -// Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross -#[cfg(any(target_os = "linux", target_os = "android"))] -#[cfg_attr(qemu, ignore)] +// 1. Disable the test on emulated platforms due to not enabled support of +// AF_ALG in QEMU from rust cross +// 2. Disable the test on aarch64/Linux CI because bind() fails with ENOENT +// https://github.com/nix-rust/nix/issues/1352 +#[cfg(linux_android)] +#[cfg_attr( + any(qemu, all(target_os = "linux", target_arch = "aarch64")), + ignore +)] #[test] pub fn test_af_alg_cipher() { use nix::sys::socket::sockopt::AlgSetKey; @@ -888,11 +938,6 @@ pub fn test_af_alg_cipher() { use nix::unistd::read; use std::io::IoSlice; - skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); - // Travis's seccomp profile blocks AF_ALG - // https://docs.docker.com/engine/security/seccomp/ - skip_if_seccomp!(test_af_alg_cipher); - let alg_type = "skcipher"; let alg_name = "ctr-aes-aesni"; // 256-bits secret key @@ -913,25 +958,36 @@ pub fn test_af_alg_cipher() { .expect("socket failed"); let sockaddr = AlgAddr::new(alg_type, alg_name); - bind(sock, &sockaddr).expect("bind failed"); + bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); assert_eq!(sockaddr.alg_name().to_string_lossy(), alg_name); assert_eq!(sockaddr.alg_type().to_string_lossy(), alg_type); - setsockopt(sock, AlgSetKey::default(), &key).expect("setsockopt"); - let session_socket = accept(sock).expect("accept failed"); + 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 = IoSlice::new(&payload); - sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None) - .expect("sendmsg encrypt"); + 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"); + // SAFETY: + // should be safe since session_socket won't be closed before the use of this borrowed one + let borrowed_session_socket = + unsafe { std::os::fd::BorrowedFd::borrow_raw(session_socket) }; + let num_bytes = + read(borrowed_session_socket, &mut encrypted).expect("read encrypt"); assert_eq!(num_bytes, payload_len); let iov = IoSlice::new(&encrypted); @@ -942,12 +998,23 @@ pub fn test_af_alg_cipher() { ControlMessage::AlgSetOp(&libc::ALG_OP_DECRYPT), ControlMessage::AlgSetIv(iv.as_slice()), ]; - 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]; - let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt"); + // SAFETY: + // should be safe since session_socket won't be closed before the use of this borrowed one + let borrowed_session_socket = + unsafe { std::os::fd::BorrowedFd::borrow_raw(session_socket) }; + let num_bytes = + read(borrowed_session_socket, &mut decrypted).expect("read decrypt"); assert_eq!(num_bytes, payload_len); assert_eq!(decrypted, payload); @@ -955,7 +1022,7 @@ 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(linux_android)] #[cfg_attr(qemu, ignore)] #[test] pub fn test_af_alg_aead() { @@ -966,7 +1033,7 @@ pub fn test_af_alg_aead() { accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr, ControlMessage, MsgFlags, SockFlag, SockType, }; - use nix::unistd::{close, read}; + use nix::unistd::read; use std::io::IoSlice; skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); @@ -1008,12 +1075,13 @@ pub fn test_af_alg_aead() { .expect("socket failed"); let sockaddr = AlgAddr::new(alg_type, alg_name); - bind(sock, &sockaddr).expect("bind failed"); + bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); - setsockopt(sock, AlgSetAeadAuthSize, &auth_size) + 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, AlgSetKey::default(), &key) + .expect("setsockopt AlgSetKey"); + let session_socket = accept(sock.as_raw_fd()).expect("accept failed"); let msgs = [ ControlMessage::AlgSetOp(&ALG_OP_ENCRYPT), @@ -1022,15 +1090,25 @@ pub fn test_af_alg_aead() { ]; let iov = IoSlice::new(&payload); - sendmsg::<()>(session_socket, &[iov], &msgs, MsgFlags::empty(), None) - .expect("sendmsg encrypt"); + 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"); + // SAFETY: + // should be safe since session_socket won't be closed before the use of this borrowed one + let borrowed_session_socket = + unsafe { std::os::fd::BorrowedFd::borrow_raw(session_socket) }; + let num_bytes = + read(borrowed_session_socket, &mut encrypted).expect("read encrypt"); assert_eq!(num_bytes, payload_len + auth_size + (assoc_size as usize)); - close(session_socket).expect("close"); for i in 0..assoc_size { encrypted[i as usize] = 10; @@ -1040,15 +1118,21 @@ pub fn test_af_alg_aead() { 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 = @@ -1057,9 +1141,16 @@ pub fn test_af_alg_aead() { // 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)) + + // SAFETY: + // + // `session_socket` will be valid for the lifetime of this test + // TODO: remove this workaround when accept(2) becomes I/O-safe. + let borrowed_fd = + unsafe { std::os::fd::BorrowedFd::borrow_raw(session_socket) }; + fcntl(borrowed_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) .expect("fcntl non_blocking"); - let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt"); + let num_bytes = read(borrowed_fd, &mut decrypted).expect("read decrypt"); assert!(num_bytes >= payload_len + (assoc_size as usize)); assert_eq!( @@ -1075,7 +1166,7 @@ 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", apple_targets, target_os = "netbsd"))] #[test] pub fn test_sendmsg_ipv4packetinfo() { use cfg_if::cfg_if; @@ -1095,7 +1186,7 @@ pub fn test_sendmsg_ipv4packetinfo() { 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 = [IoSlice::new(&slice)]; @@ -1117,8 +1208,14 @@ pub fn test_sendmsg_ipv4packetinfo() { let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)]; - sendmsg(sock, &iov, &cmsg, MsgFlags::empty(), Some(&sock_addr)) - .expect("sendmsg"); + sendmsg( + sock.as_raw_fd(), + &iov, + &cmsg, + MsgFlags::empty(), + Some(&sock_addr), + ) + .expect("sendmsg"); } // Verify `ControlMessage::Ipv6PacketInfo` for `sendmsg`. @@ -1131,7 +1228,7 @@ pub fn test_sendmsg_ipv4packetinfo() { // test from). #[cfg(any( target_os = "linux", - target_os = "macos", + apple_targets, target_os = "netbsd", target_os = "freebsd" ))] @@ -1155,7 +1252,7 @@ pub fn test_sendmsg_ipv6packetinfo() { 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; } @@ -1171,7 +1268,7 @@ pub fn test_sendmsg_ipv6packetinfo() { let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)]; sendmsg::( - sock, + sock.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), @@ -1188,12 +1285,7 @@ pub fn test_sendmsg_ipv6packetinfo() { // // 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", -))] +#[cfg(any(freebsdlike, netbsdlike))] #[test] pub fn test_sendmsg_ipv4sendsrcaddr() { use nix::sys::socket::{ @@ -1211,8 +1303,8 @@ pub fn test_sendmsg_ipv4sendsrcaddr() { .expect("socket failed"); let unspec_sock_addr = SockaddrIn::new(0, 0, 0, 0, 0); - bind(sock, &unspec_sock_addr).expect("bind failed"); - let bound_sock_addr: SockaddrIn = getsockname(sock).unwrap(); + 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()); @@ -1223,7 +1315,7 @@ pub fn test_sendmsg_ipv4sendsrcaddr() { )]; sendmsg( - sock, + sock.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), @@ -1237,6 +1329,7 @@ pub fn test_sendmsg_ipv4sendsrcaddr() { // 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 #[cfg_attr(qemu, ignore)] #[test] +#[cfg_attr(target_os = "cygwin", ignore)] fn test_scm_rights_single_cmsg_multiple_fds() { use nix::sys::socket::{ recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, @@ -1263,7 +1356,7 @@ fn test_scm_rights_single_cmsg_multiple_fds() { .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - let mut cmsgs = msg.cmsgs(); + let mut cmsgs = msg.cmsgs().unwrap(); match cmsgs.next() { Some(ControlMessageOwned::ScmRights(fds)) => { assert_eq!( @@ -1300,7 +1393,6 @@ pub fn test_sendmsg_empty_cmsgs() { recvmsg, sendmsg, socketpair, AddressFamily, MsgFlags, SockFlag, SockType, }; - use nix::unistd::close; use std::io::{IoSlice, IoSliceMut}; let (fd1, fd2) = socketpair( @@ -1314,10 +1406,10 @@ pub fn test_sendmsg_empty_cmsgs() { { let iov = [IoSlice::new(b"hello")]; assert_eq!( - sendmsg::<()>(fd1, &iov, &[], MsgFlags::empty(), None).unwrap(), + sendmsg::<()>(fd1.as_raw_fd(), &iov, &[], MsgFlags::empty(), None) + .unwrap(), 5 ); - close(fd1).unwrap(); } { @@ -1326,39 +1418,33 @@ pub fn test_sendmsg_empty_cmsgs() { let mut cmsgspace = cmsg_space!([RawFd; 1]); let msg = recvmsg::<()>( - fd2, + fd2.as_raw_fd(), &mut iov, Some(&mut cmsgspace), MsgFlags::empty(), ) .unwrap(); - for _ in msg.cmsgs() { + if msg.cmsgs().unwrap().next().is_some() { panic!("unexpected cmsg"); } assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); assert_eq!(msg.bytes, 5); - close(fd2).unwrap(); } } -#[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "freebsd", - target_os = "dragonfly", -))] +#[cfg(any(linux_android, freebsdlike))] #[test] fn test_scm_credentials() { use nix::sys::socket::{ recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage, ControlMessageOwned, MsgFlags, SockFlag, SockType, UnixCredentials, }; - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] use nix::sys::socket::{setsockopt, sockopt::PassCred}; - use nix::unistd::{close, getgid, getpid, getuid}; + use nix::unistd::{getgid, getpid, getuid}; use std::io::{IoSlice, IoSliceMut}; let (send, recv) = socketpair( @@ -1368,23 +1454,28 @@ fn test_scm_credentials() { SockFlag::empty(), ) .unwrap(); - #[cfg(any(target_os = "android", target_os = "linux"))] - setsockopt(recv, PassCred, &true).unwrap(); + #[cfg(linux_android)] + setsockopt(&recv, PassCred, &true).unwrap(); { let iov = [IoSlice::new(b"hello")]; - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] let cred = UnixCredentials::new(); - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] let cmsg = ControlMessage::ScmCredentials(&cred); - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] let cmsg = ControlMessage::ScmCreds; assert_eq!( - sendmsg::<()>(send, &iov, &[cmsg], MsgFlags::empty(), None) - .unwrap(), + sendmsg::<()>( + send.as_raw_fd(), + &iov, + &[cmsg], + MsgFlags::empty(), + None + ) + .unwrap(), 5 ); - close(send).unwrap(); } { @@ -1393,7 +1484,7 @@ fn test_scm_credentials() { let mut cmsgspace = cmsg_space!(UnixCredentials); let msg = recvmsg::<()>( - recv, + recv.as_raw_fd(), &mut iov, Some(&mut cmsgspace), MsgFlags::empty(), @@ -1401,13 +1492,13 @@ fn test_scm_credentials() { .unwrap(); let mut received_cred = None; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { let cred = match cmsg { - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(linux_android)] ControlMessageOwned::ScmCredentials(cred) => cred, - #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + #[cfg(freebsdlike)] 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()); @@ -1420,36 +1511,47 @@ fn test_scm_credentials() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - close(recv).unwrap(); } } /// Ensure that we can send `SCM_CREDENTIALS` and `SCM_RIGHTS` with a single /// `sendmsg` call. -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] // qemu's handling of multiple cmsgs is bugged, ignore tests under emulation // see https://bugs.launchpad.net/qemu/+bug/1781280 #[cfg_attr(qemu, ignore)] #[test] fn test_scm_credentials_and_rights() { let space = cmsg_space!(libc::ucred, RawFd); - test_impl_scm_credentials_and_rights(space); + test_impl_scm_credentials_and_rights(space).unwrap(); } /// Ensure that passing a an oversized control message buffer to recvmsg /// still works. -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] // qemu's handling of multiple cmsgs is bugged, ignore tests under emulation // see https://bugs.launchpad.net/qemu/+bug/1781280 #[cfg_attr(qemu, ignore)] #[test] fn test_too_large_cmsgspace() { let space = vec![0u8; 1024]; - test_impl_scm_credentials_and_rights(space); + test_impl_scm_credentials_and_rights(space).unwrap(); } -#[cfg(any(target_os = "android", target_os = "linux"))] -fn test_impl_scm_credentials_and_rights(mut space: Vec) { +#[cfg(linux_android)] +#[test] +fn test_too_small_cmsgspace() { + let space = vec![0u8; 4]; + assert_eq!( + test_impl_scm_credentials_and_rights(space), + Err(nix::errno::Errno::ENOBUFS) + ); +} + +#[cfg(linux_android)] +fn test_impl_scm_credentials_and_rights( + mut space: Vec, +) -> Result<(), nix::errno::Errno> { use libc::ucred; use nix::sys::socket::sockopt::PassCred; use nix::sys::socket::{ @@ -1466,7 +1568,7 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { SockFlag::empty(), ) .unwrap(); - setsockopt(recv, PassCred, &true).unwrap(); + setsockopt(&recv, PassCred, &true).unwrap(); let (r, w) = pipe().unwrap(); let mut received_r: Option = None; @@ -1479,30 +1581,39 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { gid: getgid().as_raw(), } .into(); - let fds = [r]; + let fds = [r.as_raw_fd()]; let cmsgs = [ ControlMessage::ScmCredentials(&cred), ControlMessage::ScmRights(&fds), ]; assert_eq!( - sendmsg::<()>(send, &iov, &cmsgs, MsgFlags::empty(), None).unwrap(), + sendmsg::<()>( + send.as_raw_fd(), + &iov, + &cmsgs, + MsgFlags::empty(), + None + ) + .unwrap(), 5 ); - close(r).unwrap(); - close(send).unwrap(); } { let mut buf = [0u8; 5]; let mut iov = [IoSliceMut::new(&mut buf[..])]; - let msg = - recvmsg::<()>(recv, &mut iov, Some(&mut space), MsgFlags::empty()) - .unwrap(); + let 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"); + assert_eq!(msg.cmsgs()?.count(), 2, "expected 2 cmsgs"); - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs()? { match cmsg { ControlMessageOwned::ScmRights(fds) => { assert_eq!(received_r, None, "already received fd"); @@ -1524,25 +1635,31 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - close(recv).unwrap(); } let received_r = received_r.expect("Did not receive passed fd"); // Ensure that the received file descriptor works - write(w, b"world").unwrap(); + write(&w, b"world").unwrap(); let mut buf = [0u8; 5]; - read(received_r, &mut buf).unwrap(); + // SAFETY: + // It should be safe if we don't use this BorrowedFd after close. + let received_r_borrowed = + unsafe { std::os::fd::BorrowedFd::borrow_raw(received_r) }; + read(received_r_borrowed, &mut buf).unwrap(); assert_eq!(&buf[..], b"world"); close(received_r).unwrap(); - close(w).unwrap(); + + Ok(()) } // Test creating and using named unix domain sockets #[test] pub fn test_named_unixdomain() { - use nix::sys::socket::{accept, bind, connect, listen, socket, UnixAddr}; + use nix::sys::socket::{ + accept, bind, connect, listen, socket, Backlog, UnixAddr, + }; use nix::sys::socket::{SockFlag, SockType}; - use nix::unistd::{close, read, write}; + use nix::unistd::{read, write}; use std::thread; let tempdir = tempfile::tempdir().unwrap(); @@ -1555,8 +1672,8 @@ pub fn test_named_unixdomain() { ) .expect("socket failed"); let sockaddr = UnixAddr::new(&sockname).unwrap(); - bind(s1, &sockaddr).expect("bind failed"); - listen(s1, 10).expect("listen failed"); + bind(s1.as_raw_fd(), &sockaddr).expect("bind failed"); + listen(&s1, Backlog::new(10).unwrap()).expect("listen failed"); let thr = thread::spawn(move || { let s2 = socket( @@ -1566,31 +1683,46 @@ pub fn test_named_unixdomain() { None, ) .expect("socket failed"); - connect(s2, &sockaddr).expect("connect failed"); - write(s2, b"hello").expect("write failed"); - close(s2).unwrap(); + connect(s2.as_raw_fd(), &sockaddr).expect("connect failed"); + write(&s2, b"hello").expect("write failed"); }); - let s3 = accept(s1).expect("accept failed"); + let s3 = accept(s1.as_raw_fd()).expect("accept failed"); + // SAFETY: + // It should be safe considering that s3 will be open within this test + let s3 = unsafe { std::os::fd::BorrowedFd::borrow_raw(s3) }; let mut buf = [0; 5]; read(s3, &mut buf).unwrap(); - close(s3).unwrap(); - close(s1).unwrap(); thr.join().unwrap(); assert_eq!(&buf[..], b"hello"); } +#[test] +pub fn test_listen_maxbacklog() { + use nix::sys::socket::Backlog; + + assert!(Backlog::new(libc::SOMAXCONN).is_ok()); +} + +#[test] +pub fn test_listen_wrongbacklog() { + use nix::sys::socket::Backlog; + + #[cfg(not(target_os = "cygwin"))] + assert!(Backlog::new(libc::SOMAXCONN + 1).is_err()); + assert!(Backlog::new(-2).is_err()); +} + // Test using unnamed unix domain addresses -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_unnamed_unixdomain() { use nix::sys::socket::{getsockname, socketpair}; use nix::sys::socket::{SockFlag, SockType}; - use nix::unistd::close; - let (fd_1, fd_2) = socketpair( + let (fd_1, _fd_2) = socketpair( AddressFamily::Unix, SockType::Stream, None, @@ -1598,20 +1730,17 @@ pub fn test_unnamed_unixdomain() { ) .expect("socketpair failed"); - let addr_1: UnixAddr = getsockname(fd_1).expect("getsockname failed"); + let addr_1: UnixAddr = + getsockname(fd_1.as_raw_fd()).expect("getsockname failed"); assert!(addr_1.is_unnamed()); - - close(fd_1).unwrap(); - close(fd_2).unwrap(); } // Test creating and using unnamed unix domain addresses for autobinding sockets -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_unnamed_unixdomain_autobind() { use nix::sys::socket::{bind, getsockname, socket}; use nix::sys::socket::{SockFlag, SockType}; - use nix::unistd::close; let fd = socket( AddressFamily::Unix, @@ -1623,20 +1752,19 @@ pub fn test_unnamed_unixdomain_autobind() { // unix(7): "If a bind(2) call specifies addrlen as `sizeof(sa_family_t)`, or [...], then the // socket is autobound to an abstract address" - bind(fd, &UnixAddr::new_unnamed()).expect("bind failed"); + bind(fd.as_raw_fd(), &UnixAddr::new_unnamed()).expect("bind failed"); - let addr: UnixAddr = getsockname(fd).expect("getsockname 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); - - close(fd).unwrap(); } // Test creating and using named system control sockets -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(apple_targets)] #[test] pub fn test_syscontrol() { use nix::errno::Errno; @@ -1651,26 +1779,18 @@ pub fn test_syscontrol() { SockProtocol::KextControl, ) .expect("socket failed"); - SysControlAddr::from_name(fd, "com.apple.net.utun_control", 0) + 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, "foo.bar.lol", 0).err(), + 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( - target_os = "android", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(any(bsd, linux_android))] fn loopback_address( family: AddressFamily, ) -> Option { @@ -1685,7 +1805,7 @@ fn loopback_address( Err(e) => { let stdioerr = io::stderr(); let mut handle = stdioerr.lock(); - writeln!(handle, "getifaddrs: {:?}", e).unwrap(); + writeln!(handle, "getifaddrs: {e:?}").unwrap(); return None; } }; @@ -1697,20 +1817,16 @@ fn loopback_address( }) } -#[cfg(any( - target_os = "android", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", -))] +#[cfg(any(linux_android, apple_targets, target_os = "netbsd"))] // qemu doesn't seem to be emulating this correctly in these architectures #[cfg_attr( all( qemu, any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "powerpc64", ) ), @@ -1740,9 +1856,10 @@ pub fn test_recv_ipv4pktinfo() { None, ) .expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv4PacketInfo, &true).expect("setsockopt failed"); + 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]; @@ -1755,7 +1872,7 @@ pub fn test_recv_ipv4pktinfo() { None, ) .expect("send socket failed"); - sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) .expect("sendmsg failed"); } @@ -1765,7 +1882,7 @@ pub fn test_recv_ipv4pktinfo() { let mut space = cmsg_space!(libc::in_pktinfo); let msg = recvmsg::<()>( - receive, + receive.as_raw_fd(), &mut iovec, Some(&mut space), MsgFlags::empty(), @@ -1775,7 +1892,7 @@ pub fn test_recv_ipv4pktinfo() { .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - let mut cmsgs = msg.cmsgs(); + let mut cmsgs = msg.cmsgs().unwrap(); if let Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) = cmsgs.next() { let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); @@ -1791,20 +1908,16 @@ pub fn test_recv_ipv4pktinfo() { } } -#[cfg(any( - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(bsd)] // qemu doesn't seem to be emulating this correctly in these architectures #[cfg_attr( all( qemu, any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "powerpc64", ) ), @@ -1834,11 +1947,12 @@ pub fn test_recvif() { None, ) .expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv4RecvIf, &true) + 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) + setsockopt(&receive, Ipv4RecvDstAddr, &true) .expect("setsockopt IP_RECVDSTADDR failed"); { @@ -1852,7 +1966,7 @@ pub fn test_recvif() { None, ) .expect("send socket failed"); - sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) .expect("sendmsg failed"); } @@ -1861,7 +1975,7 @@ pub fn test_recvif() { let mut iovec = [IoSliceMut::new(&mut buf)]; let mut space = cmsg_space!(libc::sockaddr_dl, libc::in_addr); let msg = recvmsg::<()>( - receive, + receive.as_raw_fd(), &mut iovec, Some(&mut space), MsgFlags::empty(), @@ -1870,11 +1984,11 @@ pub fn test_recvif() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs"); + assert_eq!(msg.cmsgs().unwrap().count(), 2, "expected 2 cmsgs"); let mut rx_recvif = false; let mut rx_recvdstaddr = false; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { match cmsg { ControlMessageOwned::Ipv4RecvIf(dl) => { rx_recvif = true; @@ -1908,7 +2022,7 @@ pub fn test_recvif() { } } -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg_attr(qemu, ignore)] #[test] pub fn test_recvif_ipv4() { @@ -1933,9 +2047,10 @@ pub fn test_recvif_ipv4() { None, ) .expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv4OrigDstAddr, &true) + 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"); { @@ -1949,7 +2064,7 @@ pub fn test_recvif_ipv4() { None, ) .expect("send socket failed"); - sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) .expect("sendmsg failed"); } @@ -1958,7 +2073,7 @@ pub fn test_recvif_ipv4() { let mut iovec = [IoSliceMut::new(&mut buf)]; let mut space = cmsg_space!(libc::sockaddr_in); let msg = recvmsg::<()>( - receive, + receive.as_raw_fd(), &mut iovec, Some(&mut space), MsgFlags::empty(), @@ -1967,10 +2082,10 @@ pub fn test_recvif_ipv4() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + assert_eq!(msg.cmsgs().unwrap().count(), 1, "expected 1 cmsgs"); let mut rx_recvorigdstaddr = false; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { match cmsg { ControlMessageOwned::Ipv4OrigDstAddr(addr) => { rx_recvorigdstaddr = true; @@ -1993,7 +2108,7 @@ pub fn test_recvif_ipv4() { } } -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] #[cfg_attr(qemu, ignore)] #[test] pub fn test_recvif_ipv6() { @@ -2018,9 +2133,10 @@ pub fn test_recvif_ipv6() { None, ) .expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv6OrigDstAddr, &true) + 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"); { @@ -2034,7 +2150,7 @@ pub fn test_recvif_ipv6() { None, ) .expect("send socket failed"); - sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) .expect("sendmsg failed"); } @@ -2043,7 +2159,7 @@ pub fn test_recvif_ipv6() { let mut iovec = [IoSliceMut::new(&mut buf)]; let mut space = cmsg_space!(libc::sockaddr_in6); let msg = recvmsg::<()>( - receive, + receive.as_raw_fd(), &mut iovec, Some(&mut space), MsgFlags::empty(), @@ -2052,10 +2168,10 @@ pub fn test_recvif_ipv6() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + assert_eq!(msg.cmsgs().unwrap().count(), 1, "expected 1 cmsgs"); let mut rx_recvorigdstaddr = false; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { match cmsg { ControlMessageOwned::Ipv6OrigDstAddr(addr) => { rx_recvorigdstaddr = true; @@ -2078,22 +2194,16 @@ pub fn test_recvif_ipv6() { } } -#[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", -))] +#[cfg(any(linux_android, target_os = "freebsd", apple_targets, netbsdlike))] // qemu doesn't seem to be emulating this correctly in these architectures #[cfg_attr( all( qemu, any( target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "powerpc64", ) ), @@ -2123,9 +2233,10 @@ pub fn test_recv_ipv6pktinfo() { None, ) .expect("receive socket failed"); - bind(receive, &lo).expect("bind failed"); - let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed"); - setsockopt(receive, Ipv6RecvPacketInfo, &true).expect("setsockopt failed"); + 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]; @@ -2138,7 +2249,7 @@ pub fn test_recv_ipv6pktinfo() { None, ) .expect("send socket failed"); - sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + sendmsg(send.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sa)) .expect("sendmsg failed"); } @@ -2148,7 +2259,7 @@ pub fn test_recv_ipv6pktinfo() { let mut space = cmsg_space!(libc::in6_pktinfo); let msg = recvmsg::<()>( - receive, + receive.as_raw_fd(), &mut iovec, Some(&mut space), MsgFlags::empty(), @@ -2158,7 +2269,7 @@ pub fn test_recv_ipv6pktinfo() { .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - let mut cmsgs = msg.cmsgs(); + let mut cmsgs = msg.cmsgs().unwrap(); if let Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) = cmsgs.next() { let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); @@ -2174,12 +2285,11 @@ pub fn test_recv_ipv6pktinfo() { } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] pub fn test_vsock() { use nix::sys::socket::SockaddrLike; use nix::sys::socket::{AddressFamily, VsockAddr}; - use std::convert::TryInto; use std::mem; let port: u32 = 3000; @@ -2215,6 +2325,49 @@ pub fn test_vsock() { assert_eq!(addr3.as_ref().svm_port, addr1.port()); } +#[cfg(apple_targets)] +#[test] +pub fn test_vsock() { + use nix::sys::socket::SockaddrLike; + use nix::sys::socket::{AddressFamily, VsockAddr}; + use std::mem; + + let port: u32 = 3000; + + // 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); + + 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)] @@ -2235,25 +2388,31 @@ fn test_recvmsg_timestampns() { None, ) .unwrap(); - setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + setsockopt(&in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); let localhost = SockaddrIn::new(127, 0, 0, 1, 0); - bind(in_socket, &localhost).unwrap(); - let address: SockaddrIn = getsockname(in_socket).unwrap(); + 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 = [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 mut iov = [IoSliceMut::new(&mut buffer)]; - let r = recvmsg::<()>(in_socket, &mut iov, Some(&mut cmsgspace), flags) - .unwrap(); - let rtime = match r.cmsgs().next() { + let r = recvmsg::<()>( + in_socket.as_raw_fd(), + &mut iov, + Some(&mut cmsgspace), + flags, + ) + .unwrap(); + let rtime = match r.cmsgs().unwrap().next() { Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, Some(_) => panic!("Unexpected control message"), None => panic!("No control message"), @@ -2266,8 +2425,6 @@ fn test_recvmsg_timestampns() { 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 @@ -2290,27 +2447,33 @@ fn test_recvmmsg_timestampns() { None, ) .unwrap(); - setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + setsockopt(&in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); - bind(in_socket, &localhost).unwrap(); - let address: SockaddrIn = getsockname(in_socket).unwrap(); + 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 = [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 cmsgspace = nix::cmsg_space!(TimeSpec); - let iov = vec![[IoSliceMut::new(&mut buffer)]]; + let mut iov = [[IoSliceMut::new(&mut buffer)]]; let mut data = MultiHeaders::preallocate(1, Some(cmsgspace)); - let r: Vec> = - recvmmsg(in_socket, &mut data, iov.iter(), flags, None) - .unwrap() - .collect(); - let rtime = match r[0].cmsgs().next() { + let r: Vec> = recvmmsg( + in_socket.as_raw_fd(), + &mut data, + iov.iter_mut(), + flags, + None, + ) + .unwrap() + .collect(); + let rtime = match r[0].cmsgs().unwrap().next() { Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, Some(_) => panic!("Unexpected control message"), None => panic!("No control message"), @@ -2323,14 +2486,12 @@ fn test_recvmmsg_timestampns() { Duration::new(rtime.tv_sec() as u64, rtime.tv_nsec() as u32); assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); - // Close socket - nix::unistd::close(in_socket).unwrap(); } // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack // of QEMU support is suspected. #[cfg_attr(qemu, ignore)] -#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "fuchsia"))] #[test] fn test_recvmsg_rxq_ovfl() { use nix::sys::socket::sockopt::{RcvBuf, RxqOvfl}; @@ -2357,16 +2518,16 @@ fn test_recvmsg_rxq_ovfl() { .unwrap(); let localhost = SockaddrIn::from_str("127.0.0.1:0").unwrap(); - bind(in_socket, &localhost).unwrap(); + bind(in_socket.as_raw_fd(), &localhost).unwrap(); - let address: SockaddrIn = 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; @@ -2377,8 +2538,14 @@ fn test_recvmsg_rxq_ovfl() { // 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); } @@ -2390,13 +2557,13 @@ fn test_recvmsg_rxq_ovfl() { let mut iov = [IoSliceMut::new(&mut buffer)]; match recvmsg::<()>( - in_socket, + in_socket.as_raw_fd(), &mut iov, Some(&mut cmsgspace), MsgFlags::MSG_DONTWAIT, ) { Ok(r) => { - drop_counter = match r.cmsgs().next() { + drop_counter = match r.cmsgs().unwrap().next() { Some(ControlMessageOwned::RxqOvfl(drop_counter)) => { drop_counter } @@ -2416,16 +2583,198 @@ fn test_recvmsg_rxq_ovfl() { // 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(linux_android, target_os = "freebsd"))] +#[cfg(feature = "net")] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + ) + ), + ignore +)] +#[test] +pub fn test_ip_tos_udp() { + use nix::sys::socket::ControlMessageOwned; + use nix::sys::socket::{ + bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage, + MsgFlags, SockFlag, SockType, SockaddrIn, + }; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6909").unwrap(); + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&rsock, sockopt::IpRecvTos, &true).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); + + let sbuf = [0u8; 2048]; + let iov1 = [std::io::IoSlice::new(&sbuf)]; + + let mut rbuf = [0u8; 2048]; + let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; + let mut rcmsg = cmsg_space!(libc::c_int); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + setsockopt(&ssock, sockopt::Ipv4Tos, &20).unwrap(); + + // Test the sendmsg control message and check the received packet has the same TOS. + let scmsg = ControlMessage::Ipv4Tos(&20); + sendmsg( + ssock.as_raw_fd(), + &iov1, + &[scmsg], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + // TODO: this test is weak, but testing for the actual ToS value results in sporadic + // failures in CI where the ToS in the message header is not the one set by the + // sender, so for now the test only checks for the presence of the ToS in the message + // header. + let mut tc = None; + let recv = recvmsg::<()>( + rsock.as_raw_fd(), + &mut iov2, + Some(&mut rcmsg), + MsgFlags::empty(), + ) + .unwrap(); + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::Ipv4Tos(t) = c { + tc = Some(t); + } + } + assert!(tc.is_some()); +} + +#[cfg(target_os = "linux")] +// qemu doesn't seem to be emulating this correctly in these architectures +#[cfg_attr( + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6", + ) + ), + ignore +)] +#[cfg(feature = "net")] +#[test] +pub fn test_ipv6_tclass_udp() { + use nix::sys::socket::ControlMessageOwned; + use nix::sys::socket::{ + bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage, + MsgFlags, SockFlag, SockType, SockaddrIn6, + }; + + let std_sa = SocketAddrV6::from_str("[::1]:6902").unwrap(); + let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa); + let rsock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&rsock, sockopt::Ipv6RecvTClass, &true).unwrap(); + // This bind call with IPV6_RECVTCLASS fails on the Linux aarch64 target with EADDRNOTAVAIL, + // so the test will only run if `bind` does not return an error.. + if bind(rsock.as_raw_fd(), &sock_addr).is_ok() { + let sbuf = [0u8; 2048]; + let iov1 = [std::io::IoSlice::new(&sbuf)]; + + let mut rbuf = [0u8; 2048]; + let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; + let mut rcmsg = cmsg_space!(libc::c_int); + + let ssock = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + setsockopt(&ssock, sockopt::Ipv6TClass, &10).unwrap(); + + sendmsg( + ssock.as_raw_fd(), + &iov1, + &[], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + let mut tc = None; + let recv = recvmsg::<()>( + rsock.as_raw_fd(), + &mut iov2, + Some(&mut rcmsg), + MsgFlags::empty(), + ) + .unwrap(); + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::Ipv6TClass(t) = c { + tc = Some(t); + } + } + assert_eq!(tc, Some(10)); + + let scmsg = ControlMessage::Ipv6TClass(&20); + sendmsg( + ssock.as_raw_fd(), + &iov1, + &[scmsg], + MsgFlags::empty(), + Some(&sock_addr), + ) + .unwrap(); + + let mut tc = None; + let recv = recvmsg::<()>( + rsock.as_raw_fd(), + &mut iov2, + Some(&mut rcmsg), + MsgFlags::empty(), + ) + .unwrap(); + for c in recv.cmsgs().unwrap() { + if let ControlMessageOwned::Ipv6TClass(t) = c { + tc = Some(t); + } + } + + assert_eq!(tc, Some(20)); + } +} + +#[cfg(linux_android)] mod linux_errqueue { use super::FromStr; use nix::sys::socket::*; + use std::os::unix::io::AsRawFd; // Send a UDP datagram to a bogus destination address and observe an ICMP error (v4). // @@ -2469,7 +2818,7 @@ mod linux_errqueue { } *ext_err } else { - panic!("Unexpected control message {:?}", cmsg); + panic!("Unexpected control message {cmsg:?}"); } }, ) @@ -2520,7 +2869,7 @@ mod linux_errqueue { } *ext_err } else { - panic!("Unexpected control message {:?}", cmsg); + panic!("Unexpected control message {cmsg:?}"); } }, ) @@ -2546,15 +2895,15 @@ mod linux_errqueue { let sock_addr = SockaddrStorage::from(std_sa); let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None) .unwrap(); - setsockopt(sock, opt, &true).unwrap(); + setsockopt(&sock, opt, &true).unwrap(); if let Err(e) = sendto( - sock, + 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; } @@ -2563,14 +2912,14 @@ mod linux_errqueue { let mut cspace = cmsg_space!(libc::sock_extended_err, SA); let msg = recvmsg( - sock, + 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!(msg.bytes, MESSAGE_CONTENTS.len()); // recvmsg(2): "The original destination address of the datagram that caused the error is // supplied via msg_name;" however, this is not literally true. E.g., an earlier version // of this test used 0.0.0.0 (::0) as the destination address, which was mutated into @@ -2578,7 +2927,7 @@ mod linux_errqueue { assert_eq!(msg.address, Some(sock_addr)); // Check for expected control message. - let ext_err = match msg.cmsgs().next() { + let ext_err = match msg.cmsgs().unwrap().next() { Some(cmsg) => testf(&cmsg), None => panic!("No control message"), }; @@ -2626,7 +2975,7 @@ pub fn test_txtime() { clockid: libc::CLOCK_MONOTONIC, flags: 0, }; - setsockopt(ssock, sockopt::TxTime, &txtime_cfg).unwrap(); + setsockopt(&ssock, sockopt::TxTime, &txtime_cfg).unwrap(); let rsock = socket( AddressFamily::Inet, @@ -2635,7 +2984,7 @@ pub fn test_txtime() { None, ) .unwrap(); - bind(rsock, &sock_addr).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); let sbuf = [0u8; 2048]; let iov1 = [std::io::IoSlice::new(&sbuf)]; @@ -2645,10 +2994,179 @@ pub fn test_txtime() { let txtime = (now + delay).num_nanoseconds() as u64; let cmsg = ControlMessage::TxTime(&txtime); - sendmsg(ssock, &iov1, &[cmsg], MsgFlags::empty(), Some(&sock_addr)) - .unwrap(); + 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, &mut iov2, None, MsgFlags::empty()).unwrap(); + recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, None, MsgFlags::empty()) + .unwrap(); +} + +// cfg needed for capability check. +#[cfg(linux_android)] +#[test] +fn test_icmp_protocol() { + use nix::sys::socket::{ + sendto, socket, AddressFamily, MsgFlags, SockFlag, SockProtocol, + SockType, SockaddrIn, + }; + + require_capability!("test_icmp_protocol", CAP_NET_RAW); + + let owned_fd = socket( + AddressFamily::Inet, + SockType::Raw, + SockFlag::empty(), + SockProtocol::Icmp, + ) + .unwrap(); + + // Send a minimal ICMP packet with no payload. + let packet = [ + 0x08, // Type + 0x00, // Code + 0x84, 0x85, // Checksum + 0x73, 0x8a, // ID + 0x00, 0x00, // Sequence Number + ]; + + let dest_addr = SockaddrIn::new(127, 0, 0, 1, 0); + sendto(owned_fd.as_raw_fd(), &packet, &dest_addr, MsgFlags::empty()) + .unwrap(); +} + +// test contains both recvmmsg and timestaping which is linux only +// there are existing tests for recvmmsg only in tests/ +#[cfg_attr(qemu, ignore)] +#[cfg(target_os = "linux")] +#[test] +fn test_recvmm2() -> nix::Result<()> { + use nix::sys::{ + socket::{ + bind, recvmmsg, sendmsg, setsockopt, socket, sockopt::Timestamping, + AddressFamily, ControlMessageOwned, MsgFlags, MultiHeaders, + SockFlag, SockType, SockaddrIn, TimestampingFlag, Timestamps, + }, + time::TimeSpec, + }; + use std::io::{IoSlice, IoSliceMut}; + use std::os::unix::io::AsRawFd; + use std::str::FromStr; + + let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); + + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + )?; + + let rsock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::SOCK_NONBLOCK, + None, + )?; + + bind(rsock.as_raw_fd(), &sock_addr)?; + + setsockopt(&rsock, Timestamping, &TimestampingFlag::all())?; + + let sbuf = (0..400).map(|i| i as u8).collect::>(); + + let mut recv_buf = vec![0; 1024]; + + let mut recv_iovs = Vec::new(); + let mut pkt_iovs = Vec::new(); + + for (ix, chunk) in recv_buf.chunks_mut(256).enumerate() { + pkt_iovs.push(IoSliceMut::new(chunk)); + if ix % 2 == 1 { + recv_iovs.push(pkt_iovs); + pkt_iovs = Vec::new(); + } + } + drop(pkt_iovs); + + let flags = MsgFlags::empty(); + let iov1 = [IoSlice::new(&sbuf)]; + + let cmsg = cmsg_space!(Timestamps); + sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); + + let mut data = MultiHeaders::<()>::preallocate(recv_iovs.len(), Some(cmsg)); + + let t = TimeSpec::from_duration(std::time::Duration::from_secs(10)); + + let recv = recvmmsg( + rsock.as_raw_fd(), + &mut data, + recv_iovs.iter_mut(), + 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().unwrap() { + if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg { + let ts = timestamps.system; + + let sys_time = nix::time::clock_gettime( + nix::time::ClockId::CLOCK_REALTIME, + )?; + let diff = if ts > sys_time { + ts - sys_time + } else { + sys_time - ts + }; + assert!(std::time::Duration::from(diff).as_secs() < 60); + #[cfg(not(any(qemu, target_arch = "aarch64")))] + { + saw_time = true; + } + } + } + + #[cfg(not(any(qemu, target_arch = "aarch64")))] + assert!(saw_time); + + for iov in rmsg.iovs() { + recvd += iov.len(); + } + assert_eq!(recvd, 400); + } + + Ok(()) +} + +#[cfg(not(target_os = "redox"))] +#[test] +fn can_use_cmsg_space() { + let _ = cmsg_space!(u8); +} + +#[cfg(not(any( + linux_android, + target_os = "redox", + target_os = "haiku", + target_os = "cygwin" +)))] +#[test] +fn can_open_routing_socket() { + use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; + + let _ = + socket(AddressFamily::Route, SockType::Raw, SockFlag::empty(), None) + .expect("Failed to open routing socket"); } diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index 34bef945..87ad72a9 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -1,13 +1,14 @@ -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] use crate::*; use nix::sys::socket::{ getsockopt, setsockopt, socket, sockopt, AddressFamily, SockFlag, SockProtocol, SockType, }; -use rand::{thread_rng, Rng}; +use rand::{rng, Rng}; +use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd}; // NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not. -#[cfg(any(target_os = "dragonfly", target_os = "freebsd",))] +#[cfg(freebsdlike)] #[test] pub fn test_local_peercred_seqpacket() { use nix::{ @@ -22,18 +23,13 @@ pub fn test_local_peercred_seqpacket() { SockFlag::empty(), ) .unwrap(); - let xucred = getsockopt(fd1, sockopt::LocalPeerCred).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" -))] +#[cfg(any(freebsdlike, apple_targets))] #[test] pub fn test_local_peercred_stream() { use nix::{ @@ -48,12 +44,58 @@ pub fn test_local_peercred_stream() { SockFlag::empty(), ) .unwrap(); - let xucred = getsockopt(fd1, sockopt::LocalPeerCred).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(apple_targets)] +#[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(apple_targets)] +#[test] +pub fn test_local_peer_token() { + use nix::sys::socket::{audit_token_t, socketpair}; + + #[link(name = "bsm", kind = "dylib")] + extern "C" { + /// Extract the process ID from an `audit_token_t`, used to identify + /// Mach tasks and senders of Mach messages as subjects of the audit + /// system. + /// + /// - `atoken`: The Mach audit token. + /// - Returns: The process ID extracted from the Mach audit token. + fn audit_token_to_pid(atoken: audit_token_t) -> libc::pid_t; + } + + let (fd1, _fd2) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let audit_token = getsockopt(&fd1, sockopt::LocalPeerToken).unwrap(); + assert_eq!( + unsafe { audit_token_to_pid(audit_token) }, + std::process::id() as _ + ); +} + #[cfg(target_os = "linux")] #[test] fn is_so_mark_functional() { @@ -68,8 +110,8 @@ fn is_so_mark_functional() { None, ) .unwrap(); - setsockopt(s, sockopt::Mark, &1337).unwrap(); - let mark = getsockopt(s, sockopt::Mark).unwrap(); + setsockopt(&s, sockopt::Mark, &1337).unwrap(); + let mark = getsockopt(&s, sockopt::Mark).unwrap(); assert_eq!(mark, 1337); } @@ -82,23 +124,23 @@ fn test_so_buf() { 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(); + let bufsize: usize = rng().random_range(4096..131_072); + setsockopt(&fd, sockopt::SndBuf, &bufsize).unwrap(); + let actual = getsockopt(&fd, sockopt::SndBuf).unwrap(); assert!(actual >= bufsize); - setsockopt(fd, sockopt::RcvBuf, &bufsize).unwrap(); - let actual = getsockopt(fd, sockopt::RcvBuf).unwrap(); + setsockopt(&fd, sockopt::RcvBuf, &bufsize).unwrap(); + let actual = getsockopt(&fd, sockopt::RcvBuf).unwrap(); assert!(actual >= bufsize); } +#[cfg(target_os = "freebsd")] #[test] -fn test_so_tcp_maxseg() { - use nix::sys::socket::{accept, bind, connect, listen, SockaddrIn}; - use nix::unistd::{close, write}; +fn test_so_listen_q_limit() { + use nix::sys::socket::{bind, listen, Backlog, SockaddrIn}; use std::net::SocketAddrV4; use std::str::FromStr; - let std_sa = SocketAddrV4::from_str("127.0.0.1:4001").unwrap(); + let std_sa = SocketAddrV4::from_str("127.0.0.1:4004").unwrap(); let sock_addr = SockaddrIn::from(std_sa); let rsock = socket( @@ -108,19 +150,45 @@ fn test_so_tcp_maxseg() { SockProtocol::Tcp, ) .unwrap(); - bind(rsock, &sock_addr).unwrap(); - listen(rsock, 10).unwrap(); - let initial = getsockopt(rsock, sockopt::TcpMaxSeg).unwrap(); + bind(rsock.as_raw_fd(), &sock_addr).unwrap(); + let pre_limit = getsockopt(&rsock, sockopt::ListenQLimit).unwrap(); + assert_eq!(pre_limit, 0); + listen(&rsock, Backlog::new(42).unwrap()).unwrap(); + let post_limit = getsockopt(&rsock, sockopt::ListenQLimit).unwrap(); + assert_eq!(post_limit, 42); +} + +#[test] +#[cfg_attr(target_os = "cygwin", ignore)] +fn test_so_tcp_maxseg() { + use nix::sys::socket::{ + accept, bind, connect, getsockname, listen, Backlog, SockaddrIn, + }; + use std::net::SocketAddrV4; + use std::str::FromStr; + + let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap(); + let mut 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(); + sock_addr = getsockname(rsock.as_raw_fd()).unwrap(); + listen(&rsock, Backlog::new(10).unwrap()).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 + // than `segsize` + let segsize: u32 = 873; + assert!(initial < segsize); cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { - let segsize: u32 = 873; - assert!(initial < segsize); - setsockopt(rsock, sockopt::TcpMaxSeg, &segsize).unwrap(); - } else { - assert!(initial < 700); + if #[cfg(linux_android)] { + setsockopt(&rsock, sockopt::TcpMaxSeg, &segsize).unwrap(); } } @@ -132,23 +200,40 @@ fn test_so_tcp_maxseg() { SockProtocol::Tcp, ) .unwrap(); - connect(ssock, &sock_addr).unwrap(); - let rsess = accept(rsock).unwrap(); - write(rsess, b"hello").unwrap(); - let actual = getsockopt(ssock, sockopt::TcpMaxSeg).unwrap(); - // Actual max segment size takes header lengths into account, max IPv4 options (60 bytes) + max - // TCP options (40 bytes) are subtracted from the requested maximum as a lower boundary. + + connect(ssock.as_raw_fd(), &sock_addr).unwrap(); + + let rsess = accept(rsock.as_raw_fd()).unwrap(); + let rsess = unsafe { OwnedFd::from_raw_fd(rsess) }; + cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { - assert!((segsize - 100) <= actual); - assert!(actual <= segsize); + if #[cfg(apple_targets)] { + // on apple targets (and unlike linux), we can only set the MSS on a *connected* + // socket. Also, the same MSS can't be read using getsockopt from the other end. + + assert_ne!(segsize, getsockopt(&rsess, sockopt::TcpMaxSeg).unwrap()); + setsockopt(&rsess, sockopt::TcpMaxSeg, &segsize).unwrap(); + assert_eq!(segsize, getsockopt(&rsess, sockopt::TcpMaxSeg).unwrap()); + + assert_ne!(segsize, getsockopt(&ssock, sockopt::TcpMaxSeg).unwrap()); + setsockopt(&ssock, sockopt::TcpMaxSeg, &segsize).unwrap(); + assert_eq!(segsize, getsockopt(&ssock, sockopt::TcpMaxSeg).unwrap()); } else { - assert!(initial < actual); - assert!(536 < actual); + use nix::unistd::write; + + write(&rsess, b"hello").unwrap(); + let actual = getsockopt(&ssock, sockopt::TcpMaxSeg).unwrap(); + // Actual max segment size takes header lengths into account, max IPv4 options (60 bytes) + max + // TCP options (40 bytes) are subtracted from the requested maximum as a lower boundary. + if cfg!(linux_android) { + assert!((segsize - 100) <= actual); + assert!(actual <= segsize); + } else { + assert!(initial < actual); + assert!(536 < actual); + } } } - close(rsock).unwrap(); - close(ssock).unwrap(); } #[test] @@ -161,34 +246,37 @@ fn test_so_type() { ) .unwrap(); - assert_eq!(Ok(SockType::Stream), getsockopt(sockfd, sockopt::SockType)); + 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",))] +#[cfg(linux_android)] #[test] fn test_so_type_unknown() { use nix::errno::Errno; require_capability!("test_so_type", CAP_NET_RAW); - let sockfd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_PACKET, 0) }; - assert!(sockfd >= 0, "Error opening socket: {}", nix::Error::last()); + // SOCK_PACKET is deprecated, but since it is used for testing here, we allow it + #[allow(deprecated)] + 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)); + assert_eq!(Err(Errno::EINVAL), getsockopt(&sockfd, sockopt::SockType)); } // The CI doesn't supported getsockopt and setsockopt on emulated processors. -// It's believed that a QEMU issue, the tests run ok on a fully emulated system. -// Current CI just run the binary with QEMU but the Kernel remains the same as the host. +// It's believed to be a QEMU issue; the tests run ok on a fully emulated +// system. Current CI just runs the binary with QEMU but the kernel remains the +// same as the host. // So the syscall doesn't work properly unless the kernel is also emulated. #[test] -#[cfg(all( - any(target_arch = "x86", target_arch = "x86_64"), - any(target_os = "freebsd", target_os = "linux") -))] +#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[cfg_attr(qemu, ignore)] fn test_tcp_congestion() { use std::ffi::OsString; + use std::os::unix::ffi::OsStrExt; let fd = socket( AddressFamily::Inet, @@ -198,21 +286,54 @@ fn test_tcp_congestion() { ) .unwrap(); - let val = getsockopt(fd, sockopt::TcpCongestion).unwrap(); - setsockopt(fd, sockopt::TcpCongestion, &val).unwrap(); + let val = getsockopt(&fd, sockopt::TcpCongestion).unwrap(); + let bytes = val.as_os_str().as_bytes(); + for b in bytes.iter() { + assert_ne!(*b, 0, "OsString should contain no embedded NULs: {val:?}"); + } + setsockopt(&fd, sockopt::TcpCongestion, &val).unwrap(); setsockopt( - fd, + &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] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(target_os = "freebsd")] +fn test_tcp_function_blk_alias() { + use std::ffi::CStr; + + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let tfs = getsockopt(&fd, sockopt::TcpFunctionBlk).unwrap(); + let name = unsafe { CStr::from_ptr(tfs.function_set_name.as_ptr()) }; + assert!(!name.to_bytes().is_empty()); + + let aliastfs = getsockopt(&fd, sockopt::TcpFunctionAlias).unwrap(); + let aliasname = + unsafe { CStr::from_ptr(aliastfs.function_set_name.as_ptr()) }; + // freebsd default tcp stack has no alias. + assert!(aliasname.to_bytes().is_empty()); + + // We can't know at compile time what options are available. So just test the setter by a + // no-op set. + // TODO: test if we can load for example BBR tcp stack kernel module. + setsockopt(&fd, sockopt::TcpFunctionBlk, &tfs).unwrap(); +} + +#[test] +#[cfg(linux_android)] fn test_bindtodevice() { skip_if_not_root!("test_bindtodevice"); @@ -224,10 +345,10 @@ fn test_bindtodevice() { ) .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] @@ -239,39 +360,34 @@ fn test_so_tcp_keepalive() { SockProtocol::Tcp, ) .unwrap(); - setsockopt(fd, sockopt::KeepAlive, &true).unwrap(); - assert!(getsockopt(fd, sockopt::KeepAlive).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" - ))] + #[cfg(any(linux_android, freebsdlike))] { - 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::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::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 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(linux_android)] #[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_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap(); let std_sb = SocketAddrV4::from_str("127.0.0.1:4002").unwrap(); let usock = socket( @@ -283,15 +399,15 @@ fn test_get_mtu() { .unwrap(); // Bind and initiate connection - bind(usock, &SockaddrIn::from(std_sa)).unwrap(); - connect(usock, &SockaddrIn::from(std_sb)).unwrap(); + 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)) + assert_eq!(getsockopt(&usock, sockopt::IpMtu), Ok(u16::MAX as i32)) } #[test] -#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] +#[cfg(any(linux_android, target_os = "freebsd"))] fn test_ttl_opts() { let fd4 = socket( AddressFamily::Inet, @@ -300,7 +416,7 @@ fn test_ttl_opts() { None, ) .unwrap(); - setsockopt(fd4, sockopt::Ipv4Ttl, &1) + setsockopt(&fd4, sockopt::Ipv4Ttl, &1) .expect("setting ipv4ttl on an inet socket should succeed"); let fd6 = socket( AddressFamily::Inet6, @@ -309,12 +425,53 @@ fn test_ttl_opts() { None, ) .unwrap(); - setsockopt(fd6, sockopt::Ipv6Ttl, &1) + setsockopt(&fd6, sockopt::Ipv6Ttl, &1) .expect("setting ipv6ttl on an inet6 socket should succeed"); } #[test] -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_multicast_ttl_opts_ipv4() { + let fd4 = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd4, sockopt::IpMulticastTtl, &2) + .expect("setting ipmulticastttl on an inet socket should succeed"); +} + +#[test] +#[cfg(linux_android)] +fn test_multicast_ttl_opts_ipv6() { + let fd6 = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd6, sockopt::IpMulticastTtl, &2) + .expect("setting ipmulticastttl on an inet6 socket should succeed"); +} + +#[test] +fn test_ipv6_multicast_hops() { + let fd6 = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd6, sockopt::Ipv6MulticastHops, &7) + .expect("setting ipv6multicasthops on an inet6 socket should succeed"); +} + +#[test] +#[cfg(apple_targets)] fn test_dontfrag_opts() { let fd4 = socket( AddressFamily::Inet, @@ -323,9 +480,9 @@ fn test_dontfrag_opts() { SockProtocol::Tcp, ) .unwrap(); - setsockopt(fd4, sockopt::IpDontFrag, &true) + setsockopt(&fd4, sockopt::IpDontFrag, &true) .expect("setting IP_DONTFRAG on an inet stream socket should succeed"); - setsockopt(fd4, sockopt::IpDontFrag, &false).expect( + setsockopt(&fd4, sockopt::IpDontFrag, &false).expect( "unsetting IP_DONTFRAG on an inet stream socket should succeed", ); let fd4d = socket( @@ -335,21 +492,16 @@ fn test_dontfrag_opts() { None, ) .unwrap(); - setsockopt(fd4d, sockopt::IpDontFrag, &true).expect( + setsockopt(&fd4d, sockopt::IpDontFrag, &true).expect( "setting IP_DONTFRAG on an inet datagram socket should succeed", ); - setsockopt(fd4d, sockopt::IpDontFrag, &false).expect( + 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", -))] +#[cfg(any(linux_android, apple_targets))] // Disable the test under emulation because it fails in Cirrus-CI. Lack // of QEMU support is suspected. #[cfg_attr(qemu, ignore)] @@ -361,10 +513,10 @@ fn test_v6dontfrag_opts() { SockProtocol::Tcp, ) .unwrap(); - setsockopt(fd6, sockopt::Ipv6DontFrag, &true).expect( + setsockopt(&fd6, sockopt::Ipv6DontFrag, &true).expect( "setting IPV6_DONTFRAG on an inet6 stream socket should succeed", ); - setsockopt(fd6, sockopt::Ipv6DontFrag, &false).expect( + setsockopt(&fd6, sockopt::Ipv6DontFrag, &false).expect( "unsetting IPV6_DONTFRAG on an inet6 stream socket should succeed", ); let fd6d = socket( @@ -374,10 +526,10 @@ fn test_v6dontfrag_opts() { None, ) .unwrap(); - setsockopt(fd6d, sockopt::Ipv6DontFrag, &true).expect( + setsockopt(&fd6d, sockopt::Ipv6DontFrag, &true).expect( "setting IPV6_DONTFRAG on an inet6 datagram socket should succeed", ); - setsockopt(fd6d, sockopt::Ipv6DontFrag, &false).expect( + setsockopt(&fd6d, sockopt::Ipv6DontFrag, &false).expect( "unsetting IPV6_DONTFRAG on an inet6 datagram socket should succeed", ); } @@ -393,12 +545,12 @@ fn test_so_priority() { ) .unwrap(); let priority = 3; - setsockopt(fd, sockopt::Priority, &priority).unwrap(); - assert_eq!(getsockopt(fd, sockopt::Priority).unwrap(), priority); + setsockopt(&fd, sockopt::Priority, &priority).unwrap(); + assert_eq!(getsockopt(&fd, sockopt::Priority).unwrap(), priority); } #[test] -#[cfg(target_os = "linux")] +#[cfg(any(linux_android, target_os = "freebsd"))] fn test_ip_tos() { let fd = socket( AddressFamily::Inet, @@ -408,12 +560,12 @@ fn test_ip_tos() { ) .unwrap(); let tos = 0x80; // CS4 - setsockopt(fd, sockopt::IpTos, &tos).unwrap(); - assert_eq!(getsockopt(fd, sockopt::IpTos).unwrap(), tos); + setsockopt(&fd, sockopt::Ipv4Tos, &tos).unwrap(); + assert_eq!(getsockopt(&fd, sockopt::Ipv4Tos).unwrap(), tos); } #[test] -#[cfg(target_os = "linux")] +#[cfg(any(linux_android, target_os = "freebsd"))] // Disable the test under emulation because it fails in Cirrus-CI. Lack // of QEMU support is suspected. #[cfg_attr(qemu, ignore)] @@ -426,6 +578,695 @@ fn test_ipv6_tclass() { ) .unwrap(); let class = 0x80; // CS4 - setsockopt(fd, sockopt::Ipv6TClass, &class).unwrap(); - assert_eq!(getsockopt(fd, sockopt::Ipv6TClass).unwrap(), class); + setsockopt(&fd, sockopt::Ipv6TClass, &class).unwrap(); + assert_eq!(getsockopt(&fd, sockopt::Ipv6TClass).unwrap(), class); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_receive_timestamp() { + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + assert!(getsockopt(&fd, sockopt::ReceiveTimestamp).unwrap()); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_realtime_micro() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt( + &fd, + sockopt::TsClock, + &SocketTimestamp::SO_TS_REALTIME_MICRO, + ) + .unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_REALTIME_MICRO + ); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_bintime() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_BINTIME).unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_BINTIME + ); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_realtime() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_REALTIME) + .unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_REALTIME + ); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_ts_clock_monotonic() { + use nix::sys::socket::SocketTimestamp; + + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + + // FreeBSD setsockopt docs say to set SO_TS_CLOCK after setting SO_TIMESTAMP. + setsockopt(&fd, sockopt::ReceiveTimestamp, &true).unwrap(); + + setsockopt(&fd, sockopt::TsClock, &SocketTimestamp::SO_TS_MONOTONIC) + .unwrap(); + assert_eq!( + getsockopt(&fd, sockopt::TsClock).unwrap(), + SocketTimestamp::SO_TS_MONOTONIC + ); +} + +#[test] +#[cfg(linux_android)] +// Disable the test under emulation because it fails with ENOPROTOOPT in CI +// on cross target. Lack of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +fn test_ip_bind_address_no_port() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::IpBindAddressNoPort, &true).expect( + "setting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed", + ); + assert!(getsockopt(&fd, sockopt::IpBindAddressNoPort).expect( + "getting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed", + )); + setsockopt(&fd, sockopt::IpBindAddressNoPort, &false).expect( + "unsetting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed", + ); + assert!(!getsockopt(&fd, sockopt::IpBindAddressNoPort).expect( + "getting IP_BIND_ADDRESS_NO_PORT on an inet stream socket should succeed", + )); +} + +#[test] +#[cfg(linux_android)] +fn test_tcp_fast_open_connect() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::TcpFastOpenConnect, &true).expect( + "setting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + ); + assert!(getsockopt(&fd, sockopt::TcpFastOpenConnect).expect( + "getting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + )); + setsockopt(&fd, sockopt::TcpFastOpenConnect, &false).expect( + "unsetting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + ); + assert!(!getsockopt(&fd, sockopt::TcpFastOpenConnect).expect( + "getting TCP_FASTOPEN_CONNECT on an inet stream socket should succeed", + )); +} + +#[cfg(linux_android)] +#[test] +fn can_get_peercred_on_unix_socket() { + use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType}; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let a_cred = getsockopt(&a, sockopt::PeerCredentials).unwrap(); + let b_cred = getsockopt(&b, sockopt::PeerCredentials).unwrap(); + assert_eq!(a_cred, b_cred); + assert_ne!(a_cred.pid(), 0); +} + +#[cfg(target_os = "linux")] +fn pid_from_pidfd(pidfd: OwnedFd) -> u32 { + use std::fs::read_to_string; + + let fd = pidfd.as_raw_fd(); + let fdinfo = read_to_string(format!("/proc/self/fdinfo/{fd}")).unwrap(); + let pidline = fdinfo.split('\n').find(|s| s.starts_with("Pid:")).unwrap(); + pidline.split('\t').next_back().unwrap().parse().unwrap() +} + +#[cfg(target_os = "linux")] +#[test] +fn can_get_peerpidfd_on_unix_socket() { + use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType}; + + let (a, b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + + match ( + getsockopt(&a, sockopt::PeerPidfd), + getsockopt(&b, sockopt::PeerPidfd), + ) { + (Ok(a_pidfd), Ok(b_pidfd)) => { + let a_pid = pid_from_pidfd(a_pidfd); + let b_pid = pid_from_pidfd(b_pidfd); + assert_eq!(a_pid, b_pid); + assert_ne!(a_pid, 0); + } + (Err(nix::Error::ENOPROTOOPT), Err(nix::Error::ENOPROTOOPT)) => { + // Pidfd can still be unsupported on some CI runners + } + (Err(err), _) | (_, Err(err)) => panic!("{err:?}"), + }; +} + +#[test] +fn is_socket_type_unix() { + use nix::sys::socket::{socketpair, sockopt, SockFlag, SockType}; + + let (a, _b) = socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) + .unwrap(); + let a_type = getsockopt(&a, sockopt::SockType).unwrap(); + assert_eq!(a_type, SockType::Stream); +} + +#[test] +fn is_socket_type_dgram() { + use nix::sys::socket::{ + getsockopt, sockopt, AddressFamily, SockFlag, SockType, + }; + + let s = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + let s_type = getsockopt(&s, sockopt::SockType).unwrap(); + assert_eq!(s_type, SockType::Datagram); +} + +#[cfg(any(target_os = "freebsd", target_os = "linux"))] +#[test] +fn can_get_listen_on_tcp_socket() { + use nix::sys::socket::{ + getsockopt, listen, socket, sockopt, AddressFamily, Backlog, SockFlag, + SockType, + }; + + let s = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + let s_listening = getsockopt(&s, sockopt::AcceptConn).unwrap(); + assert!(!s_listening); + listen(&s, Backlog::new(10).unwrap()).unwrap(); + let s_listening2 = getsockopt(&s, sockopt::AcceptConn).unwrap(); + assert!(s_listening2); +} + +#[cfg(target_os = "linux")] +// Some architectures running under cross don't support `setsockopt(SOL_TCP, TCP_ULP)` +// because the cross image is based on Ubuntu 16.04 which predates TCP ULP support +// (it was added in kernel v4.13 released in 2017). For these architectures, +// the `setsockopt(SOL_TCP, TCP_ULP, "tls", sizeof("tls"))` call succeeds +// but the subsequent `setsockopt(SOL_TLS, TLS_TX, ...)` call fails with `ENOPROTOOPT`. +// It's as if the first `setsockopt` call enabled some other option, not `TCP_ULP`. +// For example, `strace` says: +// +// [pid 813] setsockopt(4, SOL_TCP, 0x1f /* TCP_??? */, [7564404], 4) = 0 +// +// It's not clear why `setsockopt(SOL_TCP, TCP_ULP)` succeeds if the container image libc doesn't support it, +// but in any case we can't run the test on such an architecture, so skip it. +#[cfg_attr(qemu, ignore)] +#[test] +fn test_ktls() { + use nix::sys::socket::{ + accept, bind, connect, getsockname, listen, Backlog, SockaddrIn, + }; + use std::net::SocketAddrV4; + use std::str::FromStr; + + let std_sa = SocketAddrV4::from_str("127.0.0.1:0").unwrap(); + let mut 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(); + sock_addr = getsockname(rsock.as_raw_fd()).unwrap(); + listen(&rsock, Backlog::new(10).unwrap()).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(); + + match setsockopt(&ssock, sockopt::TcpUlp::default(), b"tls") { + Ok(()) => (), + + // TLS ULP is not enabled, so we can't test kTLS. + Err(nix::Error::ENOENT) => skip!("TLS ULP is not enabled"), + + Err(err) => panic!("{err:?}"), + } + + // In real life we would do a TLS handshake and extract the protocol version and secrets. + // For this test we just make some up. + + let tx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 { + info: libc::tls_crypto_info { + version: libc::TLS_1_2_VERSION, + cipher_type: libc::TLS_CIPHER_AES_GCM_128, + }, + iv: *b"\x04\x05\x06\x07\x08\x09\x0a\x0b", + key: *b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + salt: *b"\x00\x01\x02\x03", + rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00", + }); + setsockopt(&ssock, sockopt::TcpTlsTx, &tx) + .expect("setting TLS_TX after enabling TLS ULP should succeed"); + + let rx = sockopt::TlsCryptoInfo::Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128 { + info: libc::tls_crypto_info { + version: libc::TLS_1_2_VERSION, + cipher_type: libc::TLS_CIPHER_AES_GCM_128, + }, + iv: *b"\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb", + key: *b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + salt: *b"\xf0\xf1\xf2\xf3", + rec_seq: *b"\x00\x00\x00\x00\x00\x00\x00\x00", + }); + match setsockopt(&ssock, sockopt::TcpTlsRx, &rx) { + Ok(()) => (), + Err(nix::Error::ENOPROTOOPT) => { + // TLS_TX was added in v4.13 and TLS_RX in v4.17, so we appear to be between that range. + // It's good enough that TLS_TX worked, so let the test succeed. + } + Err(err) => panic!("{err:?}"), + } +} + +#[test] +#[cfg(apple_targets)] +fn test_utun_ifname() { + skip_if_not_root!("test_utun_ifname"); + + use nix::sys::socket::connect; + use nix::sys::socket::SysControlAddr; + + let fd = socket( + AddressFamily::System, + SockType::Datagram, + SockFlag::empty(), + SockProtocol::KextControl, + ) + .unwrap(); + + let unit = 123; + let addr = SysControlAddr::from_name( + fd.as_raw_fd(), + "com.apple.net.utun_control", + unit, + ) + .unwrap(); + + connect(fd.as_raw_fd(), &addr).unwrap(); + + let name = getsockopt(&fd, sockopt::UtunIfname) + .expect("getting UTUN_OPT_IFNAME on a utun interface should succeed"); + + let expected_name = format!("utun{}", unit - 1); + assert_eq!(name.into_string(), Ok(expected_name)); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_reuseport_lb() { + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd, sockopt::ReusePortLb, &false).unwrap(); + assert!(!getsockopt(&fd, sockopt::ReusePortLb).unwrap()); + setsockopt(&fd, sockopt::ReusePortLb, &true).unwrap(); + assert!(getsockopt(&fd, sockopt::ReusePortLb).unwrap()); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv4_recv_ttl_opts() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::Ipv4RecvTtl, &true) + .expect("setting IP_RECVTTL on an inet stream socket should succeed"); + setsockopt(&fd, sockopt::Ipv4RecvTtl, &false) + .expect("unsetting IP_RECVTTL on an inet stream socket should succeed"); + let fdd = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::Ipv4RecvTtl, &true) + .expect("setting IP_RECVTTL on an inet datagram socket should succeed"); + setsockopt(&fdd, sockopt::Ipv4RecvTtl, &false).expect( + "unsetting IP_RECVTTL on an inet datagram socket should succeed", + ); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv6_recv_hop_limit_opts() { + let fd = socket( + AddressFamily::Inet6, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::Ipv6RecvHopLimit, &true).expect( + "setting IPV6_RECVHOPLIMIT on an inet6 stream socket should succeed", + ); + setsockopt(&fd, sockopt::Ipv6RecvHopLimit, &false).expect( + "unsetting IPV6_RECVHOPLIMIT on an inet6 stream socket should succeed", + ); + let fdd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::Ipv6RecvHopLimit, &true).expect( + "setting IPV6_RECVHOPLIMIT on an inet6 datagram socket should succeed", + ); + setsockopt(&fdd, sockopt::Ipv6RecvHopLimit, &false).expect( + "unsetting IPV6_RECVHOPLIMIT on an inet6 datagram socket should succeed", + ); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv4_recv_tos_opts() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::IpRecvTos, &true) + .expect("setting IP_RECVTOS on an inet stream socket should succeed"); + setsockopt(&fd, sockopt::IpRecvTos, &false) + .expect("unsetting IP_RECVTOS on an inet stream socket should succeed"); + let fdd = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::IpRecvTos, &true) + .expect("setting IP_RECVTOS on an inet datagram socket should succeed"); + setsockopt(&fdd, sockopt::IpRecvTos, &false).expect( + "unsetting IP_RECVTOS on an inet datagram socket should succeed", + ); +} + +#[test] +#[cfg(any(linux_android, target_os = "freebsd"))] +fn test_ipv6_recv_traffic_class_opts() { + let fd = socket( + AddressFamily::Inet6, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, sockopt::Ipv6RecvTClass, &true).expect( + "setting IPV6_RECVTCLASS on an inet6 stream socket should succeed", + ); + setsockopt(&fd, sockopt::Ipv6RecvTClass, &false).expect( + "unsetting IPV6_RECVTCLASS on an inet6 stream socket should succeed", + ); + let fdd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fdd, sockopt::Ipv6RecvTClass, &true).expect( + "setting IPV6_RECVTCLASS on an inet6 datagram socket should succeed", + ); + setsockopt(&fdd, sockopt::Ipv6RecvTClass, &false).expect( + "unsetting IPV6_RECVTCLASS on an inet6 datagram socket should succeed", + ); +} + +#[cfg(apple_targets)] +#[test] +fn test_linger_sec() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let set_linger = libc::linger { + l_onoff: 1, + l_linger: 1, + }; + setsockopt(&fd, sockopt::LingerSec, &set_linger).unwrap(); + + let get_linger = getsockopt(&fd, sockopt::Linger).unwrap(); + assert_eq!(get_linger.l_linger, set_linger.l_linger * 100); +} + +/// Users should be able to define their own sockopts. +mod sockopt_impl { + use nix::sys::socket::{ + getsockopt, setsockopt, socket, AddressFamily, SockFlag, SockProtocol, + SockType, + }; + + sockopt_impl!(KeepAlive, Both, libc::SOL_SOCKET, libc::SO_KEEPALIVE, bool); + + #[test] + fn test_so_tcp_keepalive() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + setsockopt(&fd, KeepAlive, &true).unwrap(); + assert!(getsockopt(&fd, KeepAlive).unwrap()); + } + + sockopt_impl!( + Linger, + Both, + libc::SOL_SOCKET, + libc::SO_LINGER, + libc::linger + ); + #[test] + fn test_linger() { + let fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let set_linger = libc::linger { + l_onoff: 1, + l_linger: 42, + }; + setsockopt(&fd, Linger, &set_linger).unwrap(); + + let get_linger = getsockopt(&fd, Linger).unwrap(); + assert_eq!(get_linger.l_linger, set_linger.l_linger); + } +} + +#[cfg(solarish)] +#[test] +fn test_exclbind() { + use nix::errno::Errno; + use nix::sys::socket::{ + bind, socket, AddressFamily, SockFlag, SockType, SockaddrIn, + }; + use std::net::SocketAddrV4; + use std::str::FromStr; + let fd1 = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + let addr = SocketAddrV4::from_str("127.0.0.1:8081").unwrap(); + let excl = true; + + setsockopt(&fd1, sockopt::ExclBind, &excl).unwrap(); + bind(fd1.as_raw_fd(), &SockaddrIn::from(addr)).unwrap(); + assert_eq!(getsockopt(&fd1, sockopt::ExclBind), Ok(true)); + let fd2 = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + assert_eq!( + bind(fd2.as_raw_fd(), &SockaddrIn::from(addr)), + Err(Errno::EADDRINUSE) + ); +} + +#[cfg(target_os = "illumos")] +#[test] +fn test_solfilter() { + use nix::errno::Errno; + let s = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + SockProtocol::Tcp, + ) + .unwrap(); + let data = std::ffi::OsStr::new("httpf"); + let attach = sockopt::FilterAttach; + let detach = sockopt::FilterDetach; + + // These 2 options won't work unless the needed kernel module is installed: + // https://github.com/nix-rust/nix/pull/2611#issuecomment-2750237782 + // + // So we only test the binding here + assert_eq!(Err(Errno::ENOENT), setsockopt(&s, attach, data)); + assert_eq!(Err(Errno::ENOENT), setsockopt(&s, detach, data)); +} + +#[cfg(target_os = "linux")] +#[test] +pub fn test_so_attach_reuseport_cbpf() { + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd, sockopt::ReusePort, &true).unwrap(); + setsockopt(&fd, sockopt::ReuseAddr, &true).unwrap(); + let mut flt: [libc::sock_filter; 2] = unsafe { std::mem::zeroed() }; + flt[0].code = (libc::BPF_LD | libc::BPF_W | libc::BPF_ABS) as u16; + flt[0].k = (libc::SKF_AD_OFF + libc::SKF_AD_CPU) as u32; + flt[1].code = (libc::BPF_RET | 0x10) as u16; + let fp = libc::sock_fprog { + len: flt.len() as u16, + filter: flt.as_mut_ptr(), + }; + setsockopt(&fd, sockopt::AttachReusePortCbpf, &fp).unwrap_or_else(|e| { + assert_eq!(e, nix::errno::Errno::ENOPROTOOPT); + }); } diff --git a/test/sys/test_stat.rs b/test/sys/test_stat.rs index 426b4b65..fed517cf 100644 --- a/test/sys/test_stat.rs +++ b/test/sys/test_stat.rs @@ -1,3 +1,485 @@ +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::fs; +use std::fs::File; +#[cfg(not(target_os = "redox"))] +use std::os::unix::fs::symlink; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::os::unix::fs::PermissionsExt; +#[cfg(not(target_os = "redox"))] +use std::path::Path; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use std::time::{Duration, UNIX_EPOCH}; + +use libc::mode_t; +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +use libc::{S_IFLNK, S_IFMT}; + +#[cfg(not(target_os = "redox"))] +use nix::errno::Errno; +#[cfg(not(target_os = "redox"))] +use nix::fcntl; +#[cfg(any( + target_os = "linux", + apple_targets, + target_os = "freebsd", + target_os = "netbsd" +))] +use nix::sys::stat::lutimes; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::stat::utimensat; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::FchmodatFlags; +use nix::sys::stat::Mode; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::stat::UtimensatFlags; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::{self}; +use nix::sys::stat::{fchmod, stat}; +#[cfg(not(target_os = "redox"))] +use nix::sys::stat::{fchmodat, mkdirat}; +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::stat::{futimens, utimes}; + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +use nix::sys::stat::FileStat; + +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; +#[cfg(not(target_os = "redox"))] +use nix::unistd::chdir; + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +use nix::Result; + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn assert_stat_results(stat_result: Result) { + let stats = stat_result.expect("stat call failed"); + assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent + assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent + assert!(stats.st_mode > 0); // must be positive integer + assert_eq!(stats.st_nlink, 1); // there links created, must be 1 + assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file + assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent + assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file +} + +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +// (Android's st_blocks is ulonglong which is always non-negative.) +#[cfg_attr(target_os = "android", allow(unused_comparisons))] +#[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes +fn assert_lstat_results(stat_result: Result) { + let stats = stat_result.expect("stat call failed"); + assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent + assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent + assert!(stats.st_mode > 0); // must be positive integer + + // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t + // (u16 on Android), and that will be a compile error. + // On other platforms they are the same (either both are u16 or u32). + assert_eq!( + (stats.st_mode as usize) & (S_IFMT as usize), + S_IFLNK as usize + ); // should be a link + assert_eq!(stats.st_nlink, 1); // there links created, must be 1 + assert!(stats.st_size > 0); // size is > 0 because it points to another file + assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent + + // st_blocks depends on whether the machine's file system uses fast + // or slow symlinks, so just make sure it's not negative + assert!(stats.st_blocks >= 0); +} + +#[test] +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn test_stat_and_fstat() { + use nix::sys::stat::fstat; + + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("foo.txt"); + let file = File::create(&filename).unwrap(); + + let stat_result = stat(&filename); + assert_stat_results(stat_result); + + let fstat_result = fstat(&file); + assert_stat_results(fstat_result); +} + +#[test] +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn test_fstatat() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("foo.txt"); + File::create(&filename).unwrap(); + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + let result = stat::fstatat(&dirfd, &filename, fcntl::AtFlags::empty()); + assert_stat_results(result); +} + +#[test] +#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] +fn test_stat_fstat_lstat() { + use nix::sys::stat::{fstat, lstat}; + + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("bar.txt"); + let linkname = tempdir.path().join("barlink"); + + File::create(&filename).unwrap(); + symlink("bar.txt", &linkname).unwrap(); + let link = File::open(&linkname).unwrap(); + + // should be the same result as calling stat, + // since it's a regular file + let stat_result = stat(&filename); + assert_stat_results(stat_result); + + let lstat_result = lstat(&linkname); + assert_lstat_results(lstat_result); + + let fstat_result = fstat(&link); + assert_stat_results(fstat_result); +} + +#[test] +fn test_fchmod() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = tempdir.path().join("foo.txt"); + let file = File::create(&filename).unwrap(); + + let mut mode1 = Mode::empty(); + mode1.insert(Mode::S_IRUSR); + mode1.insert(Mode::S_IWUSR); + fchmod(&file, mode1).unwrap(); + + let file_stat1 = stat(&filename).unwrap(); + assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); + + let mut mode2 = Mode::empty(); + mode2.insert(Mode::S_IROTH); + fchmod(&file, mode2).unwrap(); + + let file_stat2 = stat(&filename).unwrap(); + assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_fchmodat() { + let _dr = crate::DirRestore::new(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let fullpath = tempdir.path().join(filename); + File::create(&fullpath).unwrap(); + + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + let mut mode1 = Mode::empty(); + mode1.insert(Mode::S_IRUSR); + mode1.insert(Mode::S_IWUSR); + fchmodat(&dirfd, filename, mode1, FchmodatFlags::FollowSymlink).unwrap(); + + let file_stat1 = stat(&fullpath).unwrap(); + assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); + + chdir(tempdir.path()).unwrap(); + + let mut mode2 = Mode::empty(); + mode2.insert(Mode::S_IROTH); + fchmodat( + fcntl::AT_FDCWD, + filename, + mode2, + FchmodatFlags::FollowSymlink, + ) + .unwrap(); + + let file_stat2 = stat(&fullpath).unwrap(); + assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); +} + +/// Asserts that the atime and mtime in a file's metadata match expected values. +/// +/// The atime and mtime are expressed with a resolution of seconds because some file systems +/// (like macOS's HFS+) do not have higher granularity. +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn assert_times_eq( + exp_atime_sec: u64, + exp_mtime_sec: u64, + attr: &fs::Metadata, +) { + assert_eq!( + Duration::new(exp_atime_sec, 0), + attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap() + ); + assert_eq!( + Duration::new(exp_mtime_sec, 0), + attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap() + ); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_utimes() { + let tempdir = tempfile::tempdir().unwrap(); + let fullpath = tempdir.path().join("file"); + drop(File::create(&fullpath).unwrap()); + + utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550)) + .unwrap(); + assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +#[cfg(any( + target_os = "linux", + apple_targets, + target_os = "freebsd", + target_os = "netbsd" +))] +fn test_lutimes() { + let tempdir = tempfile::tempdir().unwrap(); + let target = tempdir.path().join("target"); + let fullpath = tempdir.path().join("symlink"); + drop(File::create(&target).unwrap()); + symlink(&target, &fullpath).unwrap(); + + let exp_target_metadata = fs::symlink_metadata(&target).unwrap(); + lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)) + .unwrap(); + assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap()); + + let target_metadata = fs::symlink_metadata(&target).unwrap(); + assert_eq!( + exp_target_metadata.accessed().unwrap(), + target_metadata.accessed().unwrap(), + "atime of symlink target was unexpectedly modified" + ); + assert_eq!( + exp_target_metadata.modified().unwrap(), + target_metadata.modified().unwrap(), + "mtime of symlink target was unexpectedly modified" + ); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_futimens() { + let tempdir = tempfile::tempdir().unwrap(); + let fullpath = tempdir.path().join("file"); + drop(File::create(&fullpath).unwrap()); + + let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + futimens(&fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap(); + assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_utimensat() { + let _dr = crate::DirRestore::new(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let fullpath = tempdir.path().join(filename); + drop(File::create(&fullpath).unwrap()); + + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + utimensat( + &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( + fcntl::AT_FDCWD, + filename, + &TimeSpec::seconds(500), + &TimeSpec::seconds(800), + UtimensatFlags::FollowSymlink, + ) + .unwrap(); + assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_mkdirat_success_path() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = "example_subdir"; + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + mkdirat(&dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); + assert!(Path::exists(&tempdir.path().join(filename))); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_mkdirat_success_mode() { + let expected_bits = + stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "example_subdir"; + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + mkdirat(&dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); + let permissions = fs::metadata(tempdir.path().join(filename)) + .unwrap() + .permissions(); + let mode = permissions.mode(); + assert_eq!(mode as mode_t, expected_bits) +} + +#[test] +#[cfg(not(target_os = "redox"))] +fn test_mkdirat_fail() { + let tempdir = tempfile::tempdir().unwrap(); + let not_dir_filename = "example_not_dir"; + let filename = "example_subdir_dir"; + let dirfd = fcntl::open( + &tempdir.path().join(not_dir_filename), + fcntl::OFlag::O_CREAT, + stat::Mode::empty(), + ) + .unwrap(); + let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err(); + assert_eq!(result, Errno::ENOTDIR); +} + +#[test] +#[cfg(not(any( + freebsdlike, + apple_targets, + target_os = "haiku", + target_os = "redox", + target_os = "solaris" +)))] +fn test_mknod() { + use stat::{lstat, mknod, SFlag}; + + let file_name = "test_file"; + let tempdir = tempfile::tempdir().unwrap(); + let target = tempdir.path().join(file_name); + mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap(); + let mode = lstat(&target).unwrap().st_mode as mode_t; + assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); + assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); +} + +#[test] +#[cfg(not(any( + solarish, + freebsdlike, + apple_targets, + target_os = "haiku", + target_os = "redox" +)))] +fn test_mknodat() { + use fcntl::{AtFlags, OFlag}; + use nix::dir::Dir; + use stat::{fstatat, mknodat, SFlag}; + + let file_name = "test_file"; + let tempdir = tempfile::tempdir().unwrap(); + let target_dir = + Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); + mknodat(&target_dir, file_name, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap(); + let mode = fstatat(&target_dir, file_name, AtFlags::AT_SYMLINK_NOFOLLOW) + .unwrap() + .st_mode as mode_t; + assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); + assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_futimens_unchanged() { + 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 old_atime = fs::metadata(fullpath.as_path()) + .unwrap() + .accessed() + .unwrap(); + let old_mtime = fs::metadata(fullpath.as_path()) + .unwrap() + .modified() + .unwrap(); + + futimens(&fd, &TimeSpec::UTIME_OMIT, &TimeSpec::UTIME_OMIT).unwrap(); + + let new_atime = fs::metadata(fullpath.as_path()) + .unwrap() + .accessed() + .unwrap(); + let new_mtime = fs::metadata(fullpath.as_path()) + .unwrap() + .modified() + .unwrap(); + assert_eq!(old_atime, new_atime); + assert_eq!(old_mtime, new_mtime); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +fn test_utimensat_unchanged() { + let _dr = crate::DirRestore::new(); + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let fullpath = tempdir.path().join(filename); + drop(File::create(&fullpath).unwrap()); + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + let old_atime = fs::metadata(fullpath.as_path()) + .unwrap() + .accessed() + .unwrap(); + let old_mtime = fs::metadata(fullpath.as_path()) + .unwrap() + .modified() + .unwrap(); + utimensat( + &dirfd, + filename, + &TimeSpec::UTIME_OMIT, + &TimeSpec::UTIME_OMIT, + UtimensatFlags::NoFollowSymlink, + ) + .unwrap(); + let new_atime = fs::metadata(fullpath.as_path()) + .unwrap() + .accessed() + .unwrap(); + let new_mtime = fs::metadata(fullpath.as_path()) + .unwrap() + .modified() + .unwrap(); + assert_eq!(old_atime, new_atime); + assert_eq!(old_mtime, new_mtime); +} + // The conversion is not useless on all platforms. #[allow(clippy::useless_conversion)] #[cfg(target_os = "freebsd")] @@ -7,23 +489,20 @@ fn test_chflags() { 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(), - ); + let initial = + FileFlag::from_bits_truncate(fstat(&f).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(), - ); + let changed = + FileFlag::from_bits_truncate(fstat(&f).unwrap().st_flags.into()); assert_eq!(commanded, changed); } diff --git a/test/sys/test_statfs.rs b/test/sys/test_statfs.rs new file mode 100644 index 00000000..ca7934e6 --- /dev/null +++ b/test/sys/test_statfs.rs @@ -0,0 +1,98 @@ +use nix::sys::statfs::*; +use nix::sys::statvfs::*; +use std::fs::File; +use std::path::Path; + +fn check_fstatfs(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()).unwrap(); + let file = File::open(path).unwrap(); + let fs = fstatfs(&file).unwrap(); + assert_fs_equals(fs, vfs); +} + +fn check_statfs(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()).unwrap(); + let fs = statfs(path.as_bytes()).unwrap(); + assert_fs_equals(fs, vfs); +} + +fn check_fstatfs_strict(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()); + let file = File::open(path).unwrap(); + let fs = fstatfs(&file); + assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) +} + +fn check_statfs_strict(path: &str) { + if !Path::new(path).exists() { + return; + } + let vfs = statvfs(path.as_bytes()); + let fs = statfs(path.as_bytes()); + assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) +} + +// The cast is not unnecessary on all platforms. +#[allow(clippy::unnecessary_cast)] +fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { + assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); + assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); +} + +#[test] +fn statfs_call() { + check_statfs("/tmp"); + check_statfs("/dev"); + check_statfs("/run"); + check_statfs("/"); +} + +#[test] +fn fstatfs_call() { + check_fstatfs("/tmp"); + check_fstatfs("/dev"); + check_fstatfs("/run"); + check_fstatfs("/"); +} + +// This test is ignored because files_free/blocks_free can change after statvfs call and before +// statfs call. +#[test] +#[ignore] +fn statfs_call_strict() { + check_statfs_strict("/tmp"); + check_statfs_strict("/dev"); + check_statfs_strict("/run"); + check_statfs_strict("/"); +} + +// This test is ignored because files_free/blocks_free can change after statvfs call and before +// fstatfs call. +#[test] +#[ignore] +fn fstatfs_call_strict() { + check_fstatfs_strict("/tmp"); + check_fstatfs_strict("/dev"); + check_fstatfs_strict("/run"); + check_fstatfs_strict("/"); +} + +// The cast is not unnecessary on all platforms. +#[allow(clippy::unnecessary_cast)] +fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) { + assert_eq!(fs.files_free() as u64, vfs.files_free() as u64); + assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64); + assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64); + assert_eq!(fs.files() as u64, vfs.files() as u64); + assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); + assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); +} diff --git a/test/sys/test_statvfs.rs b/test/sys/test_statvfs.rs new file mode 100644 index 00000000..5c809652 --- /dev/null +++ b/test/sys/test_statvfs.rs @@ -0,0 +1,13 @@ +use nix::sys::statvfs::*; +use std::fs::File; + +#[test] +fn statvfs_call() { + statvfs(&b"/"[..]).unwrap(); +} + +#[test] +fn fstatvfs_call() { + let root = File::open("/").unwrap(); + fstatvfs(&root).unwrap(); +} diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs index aaf00084..98de86bc 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -1,20 +1,29 @@ -use std::os::unix::prelude::*; +use std::os::unix::io::AsFd; use tempfile::tempfile; use nix::errno::Errno; use nix::fcntl; use nix::pty::openpty; -use nix::sys::termios::{self, tcgetattr, LocalFlags, OutputFlags}; -use nix::unistd::{close, read, write}; +use nix::sys::termios::{self, tcgetattr, BaudRate, 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(), &buf[len..]).unwrap(); } } +#[test] +fn test_baudrate_try_from() { + assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0)); + #[cfg(not(target_os = "haiku"))] + BaudRate::try_from(999999999).expect_err("assertion failed"); + #[cfg(target_os = "haiku")] + BaudRate::try_from(99).expect_err("assertion failed"); +} + // Test tcgetattr on a terminal #[test] fn test_tcgetattr_pty() { @@ -22,25 +31,14 @@ fn test_tcgetattr_pty() { let _m = crate::PTSNAME_MTX.lock(); let pty = openpty(None, None).expect("openpty failed"); - termios::tcgetattr(pty.slave).unwrap(); - close(pty.master).expect("closing the master failed"); - close(pty.slave).expect("closing the slave failed"); + 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 @@ -52,12 +50,7 @@ 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. @@ -73,24 +66,21 @@ fn test_output_flags() { // 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()); } // Test modifying local flags #[test] +#[cfg(not(target_os = "solaris"))] fn test_local_flags() { // openpty uses ptname(3) internally let _m = crate::PTSNAME_MTX.lock(); @@ -98,12 +88,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. @@ -114,23 +99,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 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(); + fcntl::fcntl(pty.master.as_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, &mut buf).unwrap_err(); assert_eq!(read, Errno::EAGAIN); } diff --git a/test/sys/test_time.rs b/test/sys/test_time.rs new file mode 100644 index 00000000..0510225a --- /dev/null +++ b/test/sys/test_time.rs @@ -0,0 +1,91 @@ +use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; +use std::time::Duration; + +#[test] +pub fn test_timespec() { + assert_ne!(TimeSpec::seconds(1), TimeSpec::zero()); + assert_eq!( + TimeSpec::seconds(1) + TimeSpec::seconds(2), + TimeSpec::seconds(3) + ); + assert_eq!( + TimeSpec::minutes(3) + TimeSpec::seconds(2), + TimeSpec::seconds(182) + ); +} + +#[test] +pub fn test_timespec_from() { + let duration = Duration::new(123, 123_456_789); + let timespec = TimeSpec::nanoseconds(123_123_456_789); + + assert_eq!(TimeSpec::from(duration), timespec); + assert_eq!(Duration::from(timespec), duration); +} + +#[test] +pub fn test_timespec_neg() { + let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); + let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); + + assert_eq!(a, -b); +} + +#[test] +pub fn test_timespec_ord() { + assert_eq!(TimeSpec::seconds(1), TimeSpec::nanoseconds(1_000_000_000)); + assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001)); + assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999)); + assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999)); + assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001)); +} + +#[test] +pub fn test_timespec_fmt() { + assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); + assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeSpec::nanoseconds(42).to_string(), "0.000000042 seconds"); + assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); +} + +#[test] +pub fn test_timeval() { + assert_ne!(TimeVal::seconds(1), TimeVal::zero()); + assert_eq!( + TimeVal::seconds(1) + TimeVal::seconds(2), + TimeVal::seconds(3) + ); + assert_eq!( + TimeVal::minutes(3) + TimeVal::seconds(2), + TimeVal::seconds(182) + ); +} + +#[test] +pub fn test_timeval_ord() { + assert_eq!(TimeVal::seconds(1), TimeVal::microseconds(1_000_000)); + assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001)); + assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999)); + assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999)); + assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001)); +} + +#[test] +pub fn test_timeval_neg() { + let a = TimeVal::seconds(1) + TimeVal::microseconds(123); + let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); + + assert_eq!(a, -b); +} + +#[test] +pub fn test_timeval_fmt() { + assert_eq!(TimeVal::zero().to_string(), "0 seconds"); + assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); + assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); +} diff --git a/test/test_timer.rs b/test/sys/test_timer.rs similarity index 100% rename from test/test_timer.rs rename to test/sys/test_timer.rs diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs index 0f4b8a65..38f35fa5 100644 --- a/test/sys/test_uio.rs +++ b/test/sys/test_uio.rs @@ -1,10 +1,9 @@ use nix::sys::uio::*; use nix::unistd::*; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; +use rand::distr::Alphanumeric; +use rand::{rng, Rng}; use std::fs::OpenOptions; use std::io::IoSlice; -use std::os::unix::io::AsRawFd; use std::{cmp, iter}; #[cfg(not(target_os = "redox"))] @@ -15,10 +14,12 @@ use tempfile::tempdir; use tempfile::tempfile; #[test] +// On Solaris sometimes wrtitev() returns EINVAL. +#[cfg(not(target_os = "solaris"))] fn test_writev() { let mut to_write = Vec::with_capacity(16 * 128); for _ in 0..16 { - let s: String = thread_rng() + let s: String = rng() .sample_iter(&Alphanumeric) .map(char::from) .take(128) @@ -34,35 +35,33 @@ fn test_writev() { let slice_len = if left <= 64 { left } else { - thread_rng().gen_range(64..cmp::min(256, left)) + rng().random_range(64..cmp::min(256, left)) }; let b = &to_write[consumed..consumed + slice_len]; iovecs.push(IoSlice::new(b)); consumed += slice_len; } - let pipe_res = pipe(); - let (reader, writer) = pipe_res.expect("Couldn't create pipe"); + 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(); + // Blocking io, should write all data. - let write_res = writev(writer, &iovecs); + let write_res = writev(&writer, &iovecs); let written = write_res.expect("couldn't write"); // Check whether we written all data assert_eq!(to_write.len(), written); - let read_res = read(reader, &mut read_buf[..]); + let read_res = read(&reader, &mut read_buf[..]); let read = read_res.expect("couldn't read"); // Check we have read as much as we written assert_eq!(read, written); // Check equality of written and read data assert_eq!(&to_write, &read_buf); - close(writer).expect("closed writer"); - close(reader).expect("closed reader"); } #[test] #[cfg(not(target_os = "redox"))] fn test_readv() { - let s: String = thread_rng() + let s: String = rng() .sample_iter(&Alphanumeric) .map(char::from) .take(128) @@ -75,7 +74,7 @@ fn test_readv() { let vec_len = if left <= 64 { left } else { - thread_rng().gen_range(64..cmp::min(256, left)) + rng().random_range(64..cmp::min(256, left)) }; let v: Vec = iter::repeat(0u8).take(vec_len).collect(); storage.push(v); @@ -88,7 +87,8 @@ fn test_readv() { let (reader, writer) = pipe().expect("couldn't create pipe"); // Blocking io, should write all data. write(writer, &to_write).expect("write failed"); - let read = readv(reader, &mut iovecs[..]).expect("read failed"); + + 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 @@ -100,8 +100,6 @@ fn test_readv() { assert_eq!(read_buf.len(), to_write.len()); // Check equality of written and read data assert_eq!(&read_buf, &to_write); - close(reader).expect("couldn't close reader"); - close(writer).expect("couldn't close writer"); } #[test] @@ -111,7 +109,7 @@ fn test_pwrite() { let mut file = tempfile().unwrap(); let buf = [1u8; 8]; - assert_eq!(Ok(8), pwrite(file.as_raw_fd(), &buf, 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]; @@ -137,13 +135,18 @@ fn test_pread() { file.write_all(&file_content).unwrap(); let mut buf = [0u8; 16]; - assert_eq!(Ok(16), pread(file.as_raw_fd(), &mut buf, 16)); + assert_eq!(Ok(16), pread(&file, &mut buf, 16)); let expected: Vec<_> = (16..32).collect(); assert_eq!(&buf[..], &expected[..]); } #[test] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg(not(any( + target_os = "redox", + target_os = "haiku", + target_os = "solaris", + target_os = "cygwin" +)))] fn test_pwritev() { use std::io::Read; @@ -168,7 +171,7 @@ fn test_pwritev() { .open(path) .unwrap(); - let written = pwritev(file.as_raw_fd(), &iovecs, 100).ok().unwrap(); + let written = pwritev(&file, &iovecs, 100).ok().unwrap(); assert_eq!(written, to_write.len()); // Read the data back and make sure it matches @@ -178,7 +181,12 @@ fn test_pwritev() { } #[test] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +#[cfg(not(any( + target_os = "redox", + target_os = "haiku", + target_os = "solaris", + target_os = "cygwin" +)))] fn test_preadv() { use std::io::Write; @@ -206,7 +214,7 @@ fn test_preadv() { .iter_mut() .map(|buf| IoSliceMut::new(&mut buf[..])) .collect(); - assert_eq!(Ok(100), preadv(file.as_raw_fd(), &mut iovecs, 100)); + assert_eq!(Ok(100), preadv(&file, &mut iovecs, 100)); } let all = buffers.concat(); @@ -234,10 +242,10 @@ fn test_process_vm_readv() { let (r, w) = pipe().unwrap(); match unsafe { fork() }.expect("Error: Fork Failed") { Parent { child } => { - close(w).unwrap(); + drop(w); // wait for child - read(r, &mut [0u8]).unwrap(); - close(r).unwrap(); + read(&r, &mut [0u8]).unwrap(); + drop(r); let ptr = vector.as_ptr() as usize; let remote_iov = RemoteIoVec { base: ptr, len: 5 }; @@ -256,12 +264,11 @@ fn test_process_vm_readv() { assert_eq!(20u8, buf.iter().sum()); } Child => { - let _ = close(r); + drop(r); for i in &mut vector { *i += 1; } let _ = write(w, b"\0"); - let _ = close(w); loop { pause(); } diff --git a/test/sys/test_utsname.rs b/test/sys/test_utsname.rs new file mode 100644 index 00000000..8f84ac07 --- /dev/null +++ b/test/sys/test_utsname.rs @@ -0,0 +1,17 @@ +#[cfg(target_os = "linux")] +#[test] +pub fn test_uname_linux() { + assert_eq!(nix::sys::utsname::uname().unwrap().sysname(), "Linux"); +} + +#[cfg(apple_targets)] +#[test] +pub fn test_uname_darwin() { + assert_eq!(nix::sys::utsname::uname().unwrap().sysname(), "Darwin"); +} + +#[cfg(target_os = "freebsd")] +#[test] +pub fn test_uname_freebsd() { + assert_eq!(nix::sys::utsname::uname().unwrap().sysname(), "FreeBSD"); +} diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index d472f1ec..365b0165 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -33,7 +33,12 @@ fn test_wait_signal() { //target_os = "haiku", all(target_os = "linux", not(target_env = "uclibc")), ))] -#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] +#[cfg(not(any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" +)))] fn test_waitid_signal() { let _m = crate::FORK_MTX.lock(); @@ -76,7 +81,12 @@ fn test_wait_exit() { target_os = "haiku", all(target_os = "linux", not(target_env = "uclibc")), ))] -#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] +#[cfg(not(any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" +)))] fn test_waitid_exit() { let _m = crate::FORK_MTX.lock(); @@ -140,7 +150,7 @@ fn test_waitid_pid() { } } -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] // FIXME: qemu-user doesn't implement ptrace on most arches #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod ptrace { diff --git a/test/test.rs b/test/test.rs index 6b42aad9..71a42a64 100644 --- a/test/test.rs +++ b/test/test.rs @@ -1,30 +1,27 @@ #[macro_use] extern crate cfg_if; -#[cfg_attr(not(any(target_os = "redox", target_os = "haiku")), macro_use)] +#[cfg_attr(not(any(target_os = "redox")), macro_use)] extern crate nix; -#[macro_use] -extern crate lazy_static; +#[macro_use] mod common; +mod mount; mod sys; #[cfg(not(target_os = "redox"))] mod test_dir; +mod test_errno; mod test_fcntl; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] mod test_kmod; #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "fushsia", - target_os = "linux", + freebsdlike, + all(target_os = "linux", not(target_env = "ohos")), target_os = "netbsd" ))] mod test_mq; #[cfg(not(target_os = "redox"))] mod test_net; mod test_nix_path; -#[cfg(target_os = "freebsd")] -mod test_nmount; mod test_poll; #[cfg(not(any( target_os = "redox", @@ -32,71 +29,61 @@ mod test_poll; target_os = "haiku" )))] mod test_pty; -mod test_resource; #[cfg(any( - target_os = "android", + linux_android, target_os = "dragonfly", all(target_os = "freebsd", fbsd14), - target_os = "linux" ))] mod test_sched; -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "linux", - target_os = "macos" -))] +#[cfg(any(linux_android, freebsdlike, apple_targets, solarish))] 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" +#[cfg(any( + target_os = "freebsd", + target_os = "haiku", + target_os = "linux", + target_os = "netbsd", + apple_targets ))] -mod test_timer; +mod test_spawn; + +mod test_syslog; + +mod test_time; mod test_unistd; use nix::unistd::{chdir, getcwd, read}; use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; -use std::os::unix::io::RawFd; +use std::os::unix::io::AsFd; 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, 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 or can be affected by child processes +/// must grab this mutex, regardless of what it does with those children. +/// +/// It must hold the mutex until the child processes are waited upon. +pub static FORK_MTX: Mutex<()> = 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> { @@ -104,7 +91,7 @@ struct DirRestore<'a> { _g: RwLockWriteGuard<'a, ()>, } -impl<'a> DirRestore<'a> { +impl DirRestore<'_> { fn new() -> Self { let guard = crate::CWD_LOCK.write(); DirRestore { @@ -114,7 +101,7 @@ impl<'a> DirRestore<'a> { } } -impl<'a> Drop for DirRestore<'a> { +impl Drop for DirRestore<'_> { fn drop(&mut self) { let r = chdir(&self.d); if std::thread::panicking() { diff --git a/test/test_dir.rs b/test/test_dir.rs index 2af4aa5c..c9713c64 100644 --- a/test/test_dir.rs +++ b/test/test_dir.rs @@ -6,17 +6,16 @@ use tempfile::tempdir; #[cfg(test)] fn flags() -> OFlag { - #[cfg(target_os = "illumos")] + #[cfg(solarish)] let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC; - #[cfg(not(target_os = "illumos"))] + #[cfg(not(solarish))] let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_DIRECTORY; f } #[test] -#[allow(clippy::unnecessary_sort_by)] // False positive fn read() { let tmp = tempdir().unwrap(); File::create(tmp.path().join("foo")).unwrap(); @@ -57,9 +56,3 @@ fn rewind() { 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_errno.rs b/test/test_errno.rs new file mode 100644 index 00000000..750fc924 --- /dev/null +++ b/test/test_errno.rs @@ -0,0 +1,16 @@ +use nix::errno::Errno; + +#[test] +fn errno_set_and_read() { + Errno::ENFILE.set(); + assert_eq!(Errno::last(), Errno::ENFILE); +} + +#[test] +fn errno_set_and_clear() { + Errno::ENFILE.set(); + assert_eq!(Errno::last(), Errno::ENFILE); + + Errno::clear(); + assert_eq!(Errno::last(), Errno::from_raw(0)); +} diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs index 0d285a87..76c3b9f2 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -4,12 +4,15 @@ use nix::errno::*; use nix::fcntl::{open, readlink, OFlag}; #[cfg(not(target_os = "redox"))] use nix::fcntl::{openat, readlinkat, renameat}; + +#[cfg(target_os = "linux")] +use nix::fcntl::{openat2, OpenHow, ResolveFlag}; + #[cfg(all( target_os = "linux", target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -18,7 +21,7 @@ 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}; +use nix::unistd::read; #[cfg(not(target_os = "redox"))] use std::fs::File; #[cfg(not(target_os = "redox"))] @@ -26,7 +29,7 @@ use std::io::prelude::*; #[cfg(not(target_os = "redox"))] use std::os::unix::fs; #[cfg(not(target_os = "redox"))] -use tempfile::{self, NamedTempFile}; +use tempfile::NamedTempFile; #[test] #[cfg(not(target_os = "redox"))] @@ -50,11 +53,63 @@ fn test_openat() { .unwrap(); let mut buf = [0u8; 1024]; - assert_eq!(4, read(fd, &mut buf).unwrap()); + assert_eq!(4, read(&fd, &mut buf).unwrap()); assert_eq!(CONTENTS, &buf[0..4]); +} - close(fd).unwrap(); - close(dirfd).unwrap(); +#[test] +#[cfg(target_os = "linux")] +// 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_openat2() { + 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 = openat2( + dirfd, + tmp.path().file_name().unwrap(), + OpenHow::new() + .flags(OFlag::O_RDONLY) + .mode(Mode::empty()) + .resolve(ResolveFlag::RESOLVE_BENEATH), + ) + .unwrap(); + + let mut buf = [0u8; 1024]; + assert_eq!(4, read(&fd, &mut buf).unwrap()); + assert_eq!(CONTENTS, &buf[0..4]); +} + +#[test] +#[cfg(target_os = "linux")] +// 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_openat2_forbidden() { + let mut tmp = NamedTempFile::new().unwrap(); + tmp.write_all(b"let me out").unwrap(); + + let dirfd = + open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty()) + .unwrap(); + + let escape_attempt = + tmp.path().parent().unwrap().join("../../../hello.txt"); + + let res = openat2( + dirfd, + &escape_attempt, + OpenHow::new() + .flags(OFlag::O_RDONLY) + .resolve(ResolveFlag::RESOLVE_BENEATH), + ); + assert_eq!(res.unwrap_err(), Errno::EXDEV); } #[test] @@ -68,13 +123,11 @@ fn test_renameat() { let new_dir = tempfile::tempdir().unwrap(); let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); - renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap(); + renameat(&old_dirfd, "old", &new_dirfd, "new").unwrap(); assert_eq!( - renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(), + renameat(&old_dirfd, "old", &new_dirfd, "new").unwrap_err(), Errno::ENOENT ); - close(old_dirfd).unwrap(); - close(new_dirfd).unwrap(); assert!(new_dir.path().join("new").exists()); } @@ -84,7 +137,6 @@ fn test_renameat() { target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -98,27 +150,13 @@ fn test_renameat2_behaves_like_renameat_with_no_flags() { let new_dir = tempfile::tempdir().unwrap(); let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap(); - renameat2( - Some(old_dirfd), - "old", - Some(new_dirfd), - "new", - RenameFlags::empty(), - ) - .unwrap(); + renameat2(&old_dirfd, "old", &new_dirfd, "new", RenameFlags::empty()) + .unwrap(); assert_eq!( - renameat2( - Some(old_dirfd), - "old", - Some(new_dirfd), - "new", - RenameFlags::empty() - ) - .unwrap_err(), + renameat2(&old_dirfd, "old", &new_dirfd, "new", RenameFlags::empty()) + .unwrap_err(), Errno::ENOENT ); - close(old_dirfd).unwrap(); - close(new_dirfd).unwrap(); assert!(new_dir.path().join("new").exists()); } @@ -128,7 +166,6 @@ fn test_renameat2_behaves_like_renameat_with_no_flags() { target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -151,9 +188,9 @@ fn test_renameat2_exchange() { new_f.write_all(b"new").unwrap(); } renameat2( - Some(old_dirfd), + &old_dirfd, "old", - Some(new_dirfd), + &new_dirfd, "new", RenameFlags::RENAME_EXCHANGE, ) @@ -166,8 +203,6 @@ fn test_renameat2_exchange() { let mut old_f = File::open(&old_path).unwrap(); old_f.read_to_string(&mut buf).unwrap(); assert_eq!(buf, "new"); - close(old_dirfd).unwrap(); - close(new_dirfd).unwrap(); } #[test] @@ -176,7 +211,6 @@ fn test_renameat2_exchange() { target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -194,17 +228,15 @@ fn test_renameat2_noreplace() { File::create(new_path).unwrap(); assert_eq!( renameat2( - Some(old_dirfd), + &old_dirfd, "old", - Some(new_dirfd), + &new_dirfd, "new", RenameFlags::RENAME_NOREPLACE ) .unwrap_err(), Errno::EEXIST ); - close(old_dirfd).unwrap(); - close(new_dirfd).unwrap(); assert!(new_dir.path().join("new").exists()); assert!(old_dir.path().join("old").exists()); } @@ -227,15 +259,50 @@ fn test_readlink() { ); } -#[cfg(any(target_os = "linux", target_os = "android"))] +/// 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( + linux_android, + // Not available until FreeBSD 13.0 + all(target_os = "freebsd", fbsd14), +))] +#[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; + + 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, Some(&mut from_offset), &tmp2, 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(linux_android)] mod linux_android { use libc::loff_t; use std::io::prelude::*; use std::io::IoSlice; - use std::os::unix::prelude::*; use nix::fcntl::*; - use nix::unistd::{close, pipe, read, write}; + use nix::unistd::{pipe, read, write}; use tempfile::tempfile; #[cfg(target_os = "linux")] @@ -243,42 +310,6 @@ mod linux_android { use crate::*; - /// This test creates a temporary file containing the contents - /// 'foobarbaz' and uses the `copy_file_range` call to transfer - /// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The - /// resulting file is read and should contain the contents `bar`. - /// The from_offset should be updated by the call to reflect - /// the 3 bytes read (6). - #[test] - // QEMU does not support copy_file_range. Skip under qemu - #[cfg_attr(qemu, ignore)] - fn test_copy_file_range() { - const CONTENTS: &[u8] = b"foobarbaz"; - - let mut tmp1 = tempfile().unwrap(); - let mut tmp2 = tempfile().unwrap(); - - tmp1.write_all(CONTENTS).unwrap(); - tmp1.flush().unwrap(); - - let mut from_offset: i64 = 3; - copy_file_range( - tmp1.as_raw_fd(), - Some(&mut from_offset), - tmp2.as_raw_fd(), - None, - 3, - ) - .unwrap(); - - let mut res: String = String::new(); - tmp2.rewind().unwrap(); - tmp2.read_to_string(&mut res).unwrap(); - - assert_eq!(res, String::from("bar")); - assert_eq!(from_offset, 6); - } - #[test] fn test_splice() { const CONTENTS: &[u8] = b"abcdef123456"; @@ -287,25 +318,16 @@ 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, Some(&mut offset), wr, None, 2, SpliceFFlags::empty()) + .unwrap(); assert_eq!(2, res); let mut buf = [0u8; 1024]; - assert_eq!(2, read(rd, &mut buf).unwrap()); + assert_eq!(2, read(&rd, &mut buf).unwrap()); assert_eq!(b"f1", &buf[0..2]); assert_eq!(7, offset); - - close(rd).unwrap(); - close(wr).unwrap(); } #[test] @@ -314,24 +336,20 @@ mod linux_android { let (rd2, wr2) = pipe().unwrap(); write(wr1, b"abc").unwrap(); - let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap(); + let res = tee(rd1.try_clone().unwrap(), wr2, 2, SpliceFFlags::empty()) + .unwrap(); assert_eq!(2, res); let mut buf = [0u8; 1024]; // Check the tee'd bytes are at rd2. - assert_eq!(2, read(rd2, &mut buf).unwrap()); + assert_eq!(2, read(&rd2, &mut buf).unwrap()); assert_eq!(b"ab", &buf[0..2]); // Check all the bytes are still at rd1. - assert_eq!(3, read(rd1, &mut buf).unwrap()); + assert_eq!(3, read(&rd1, &mut buf).unwrap()); assert_eq!(b"abc", &buf[0..3]); - - close(rd1).unwrap(); - close(wr1).unwrap(); - close(rd2).unwrap(); - close(wr2).unwrap(); } #[test] @@ -348,11 +366,8 @@ mod linux_android { // Check the bytes can be read at rd. let mut buf = [0u8; 32]; - assert_eq!(6, read(rd, &mut buf).unwrap()); + assert_eq!(6, read(&rd, &mut buf).unwrap()); assert_eq!(b"abcdef", &buf[0..6]); - - close(rd).unwrap(); - close(wr).unwrap(); } #[cfg(target_os = "linux")] @@ -360,12 +375,11 @@ mod linux_android { fn test_fallocate() { let tmp = NamedTempFile::new().unwrap(); - let fd = tmp.as_raw_fd(); - fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap(); + fallocate(&tmp, FallocateFlags::empty(), 0, 100).unwrap(); // Check if we read exactly 100 bytes let mut buf = [0u8; 200]; - assert_eq!(100, read(fd, &mut buf).unwrap()); + assert_eq!(100, read(&tmp, &mut buf).unwrap()); } // The tests below are disabled for the listed targets @@ -382,15 +396,14 @@ 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 // skip the test. skip!("/proc/locks does not work on overlayfs"); } - let inode = fstat(fd).expect("fstat failed").st_ino as usize; + let inode = fstat(&tmp).expect("fstat failed").st_ino as usize; let mut flock: libc::flock = unsafe { mem::zeroed() // required for Linux/mips @@ -400,14 +413,15 @@ mod linux_android { flock.l_start = 0; flock.l_len = 0; flock.l_pid = 0; - fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed"); + fcntl(&tmp, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed"); assert_eq!( Some(("OFDLCK".to_string(), "WRITE".to_string())), lock_info(inode) ); flock.l_type = libc::F_UNLCK as libc::c_short; - fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed"); + fcntl(&tmp, FcntlArg::F_OFD_SETLKW(&flock)) + .expect("write unlock failed"); assert_eq!(None, lock_info(inode)); } @@ -420,15 +434,14 @@ 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 // skip the test. skip!("/proc/locks does not work on overlayfs"); } - let inode = fstat(fd).expect("fstat failed").st_ino as usize; + let inode = fstat(&tmp).expect("fstat failed").st_ino as usize; let mut flock: libc::flock = unsafe { mem::zeroed() // required for Linux/mips @@ -438,14 +451,15 @@ mod linux_android { flock.l_start = 0; flock.l_len = 0; flock.l_pid = 0; - fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed"); + fcntl(&tmp, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed"); assert_eq!( Some(("OFDLCK".to_string(), "READ".to_string())), lock_info(inode) ); flock.l_type = libc::F_UNLCK as libc::c_short; - fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed"); + fcntl(&tmp, FcntlArg::F_OFD_SETLKW(&flock)) + .expect("read unlock failed"); assert_eq!(None, lock_info(inode)); } @@ -472,8 +486,7 @@ mod linux_android { } #[cfg(any( - target_os = "linux", - target_os = "android", + linux_android, target_os = "emscripten", target_os = "fuchsia", target_os = "wasi", @@ -481,60 +494,47 @@ mod linux_android { target_os = "freebsd" ))] mod test_posix_fadvise { - use nix::errno::Errno; use nix::fcntl::*; use nix::unistd::pipe; - use std::os::unix::io::{AsRawFd, RawFd}; use tempfile::NamedTempFile; #[test] fn test_success() { let tmp = NamedTempFile::new().unwrap(); - let fd = tmp.as_raw_fd(); - posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED) + posix_fadvise(&tmp, 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, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED); assert_eq!(res, Err(Errno::ESPIPE)); } } #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "dragonfly", + linux_android, + freebsdlike, target_os = "emscripten", target_os = "fuchsia", target_os = "wasi", - target_os = "freebsd" ))] mod test_posix_fallocate { use nix::errno::Errno; use nix::fcntl::*; use nix::unistd::pipe; - use std::{ - io::Read, - os::unix::io::{AsRawFd, RawFd}, - }; + use std::io::Read; use tempfile::NamedTempFile; #[test] fn success() { const LEN: usize = 100; let mut tmp = NamedTempFile::new().unwrap(); - let fd = tmp.as_raw_fd(); - let res = posix_fallocate(fd, 0, LEN as libc::off_t); + let res = posix_fallocate(&tmp, 0, LEN as libc::off_t); match res { Ok(_) => { let mut data = [1u8; LEN]; @@ -556,10 +556,265 @@ mod test_posix_fallocate { #[test] fn errno() { let (rd, _wr) = pipe().unwrap(); - let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err(); + let err = posix_fallocate(&rd, 0, 100).unwrap_err(); match err { Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (), - errno => panic!("unexpected errno {}", errno,), + errno => panic!("unexpected errno {errno}",), } } } + +#[cfg(any(target_os = "dragonfly", target_os = "netbsd", apple_targets))] +#[test] +fn test_f_get_path() { + use nix::fcntl::*; + use std::path::PathBuf; + + let tmp = NamedTempFile::new().unwrap(); + let mut path = PathBuf::new(); + let res = + fcntl(&tmp, FcntlArg::F_GETPATH(&mut path)).expect("get path failed"); + assert_ne!(res, -1); + assert_eq!( + path.as_path().canonicalize().unwrap(), + tmp.path().canonicalize().unwrap() + ); +} + +#[cfg(apple_targets)] +#[test] +fn test_f_preallocate() { + use nix::fcntl::*; + + let tmp = NamedTempFile::new().unwrap(); + let mut st: libc::fstore_t = unsafe { std::mem::zeroed() }; + + st.fst_flags = libc::F_ALLOCATECONTIG as libc::c_uint; + st.fst_posmode = libc::F_PEOFPOSMODE; + st.fst_length = 1024; + let res = fcntl(tmp, FcntlArg::F_PREALLOCATE(&mut st)) + .expect("preallocation failed"); + + assert_eq!(res, 0); + assert!(st.fst_bytesalloc > 0); +} + +#[cfg(apple_targets)] +#[test] +fn test_f_get_path_nofirmlink() { + use nix::fcntl::*; + use std::path::PathBuf; + + let tmp = NamedTempFile::new().unwrap(); + let mut path = PathBuf::new(); + let res = fcntl(&tmp, FcntlArg::F_GETPATH_NOFIRMLINK(&mut path)) + .expect("get path failed"); + let mut tmpstr = String::from("/System/Volumes/Data"); + tmpstr.push_str( + &tmp.path() + .canonicalize() + .unwrap() + .into_os_string() + .into_string() + .unwrap(), + ); + assert_ne!(res, -1); + assert_eq!( + path.as_path() + .canonicalize() + .unwrap() + .into_os_string() + .into_string() + .unwrap(), + tmpstr + ); +} + +#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] +#[test] +fn test_f_kinfo() { + use nix::fcntl::*; + use std::path::PathBuf; + + let tmp = NamedTempFile::new().unwrap(); + // With TMPDIR set with UFS, the vnode name is not entered + // into the name cache thus path is always empty. + // Therefore, we reopen the tempfile a second time for the test + // to pass. + let tmp2 = File::open(tmp.path()).unwrap(); + let mut path = PathBuf::new(); + let res = + fcntl(&tmp2, FcntlArg::F_KINFO(&mut path)).expect("get path failed"); + assert_ne!(res, -1); + assert_eq!(path, tmp.path()); +} + +/// Test `Flock` and associated functions. +/// +#[cfg(not(any(target_os = "redox", target_os = "solaris")))] +mod test_flock { + use nix::fcntl::*; + use tempfile::NamedTempFile; + + /// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop. + #[test] + fn lock_and_drop() { + // Get 2 `File` handles to same underlying file. + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap(); + + // Attempt to lock second handle + let file2 = match Flock::lock(file2, FlockArg::LockExclusiveNonblock) { + Ok(_) => panic!("Expected second exclusive lock to fail."), + Err((f, _)) => f, + }; + + // Drop first lock + std::mem::drop(lock1); + + // Attempt to lock second handle again (but successfully) + if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() { + panic!("Expected locking to be successful."); + } + } + + /// An exclusive lock can be downgraded + #[test] + fn downgrade() { + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap(); + + // Attempt to lock second handle + let file2 = Flock::lock(file2, FlockArg::LockSharedNonblock) + .unwrap_err() + .0; + + // Downgrade the lock + lock1.relock(FlockArg::LockShared).unwrap(); + + // Attempt to lock second handle again (but successfully) + Flock::lock(file2, FlockArg::LockSharedNonblock) + .expect("Expected locking to be successful."); + } + + /// Verify that `Flock::unlock()` correctly obtains unlocks. + #[test] + fn unlock() { + // Get 2 `File` handles to same underlying file. + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap(); + + // Unlock and retain file so any erroneous flocks also remain present. + let _file1 = lock1.unlock().unwrap(); + + // Attempt to lock second handle. + if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() { + panic!("Expected locking to be successful."); + } + } + + /// A shared lock can be upgraded + #[test] + fn upgrade() { + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file3 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockShared).unwrap(); + + // Attempt to lock second handle + { + Flock::lock(file2, FlockArg::LockSharedNonblock) + .expect("Locking should've succeeded"); + } + + // Upgrade the lock + lock1.relock(FlockArg::LockExclusive).unwrap(); + + // Acquiring an additional shared lock should fail + Flock::lock(file3, FlockArg::LockSharedNonblock) + .expect_err("Should not have been able to lock the file"); + } +} + +#[cfg(apple_targets)] +#[test] +fn test_f_rdadvise() { + use nix::fcntl::*; + + let contents = vec![1; 1024]; + let mut buf = [0; 1024]; + let mut tmp = NamedTempFile::new().unwrap(); + tmp.write_all(&contents).unwrap(); + let fd = open(tmp.path(), OFlag::empty(), Mode::empty()).unwrap(); + let rad = libc::radvisory { + ra_offset: 0, + ra_count: contents.len() as _, + }; + let res = fcntl(&tmp, FcntlArg::F_RDADVISE(rad)).expect("rdadivse failed"); + assert_ne!(res, -1); + assert_eq!(contents.len(), read(&fd, &mut buf).unwrap()); + assert_eq!(contents, &buf[0..contents.len()]); +} + +#[cfg(apple_targets)] +#[test] +fn test_f_log2phys() { + use nix::fcntl::*; + + const CONTENTS: &[u8] = b"abcd"; + let mut tmp = NamedTempFile::new().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + let mut offset: libc::off_t = 0; + let mut res = fcntl(&tmp, FcntlArg::F_LOG2PHYS(&mut offset)) + .expect("log2phys failed"); + assert_ne!(res, -1); + assert_ne!(offset, 0); + let mut info: libc::log2phys = unsafe { std::mem::zeroed() }; + info.l2p_contigbytes = CONTENTS.len() as _; + info.l2p_devoffset = 3; + res = fcntl(&tmp, FcntlArg::F_LOG2PHYS_EXT(&mut info)) + .expect("log2phys failed"); + assert_ne!(res, -1); + assert_ne!({ info.l2p_devoffset }, 3); +} + +#[cfg(apple_targets)] +#[test] +fn test_f_transferextents() { + use nix::fcntl::*; + use std::os::fd::AsRawFd; + + let tmp1 = NamedTempFile::new().unwrap(); + let tmp2 = NamedTempFile::new().unwrap(); + let res = fcntl(&tmp1, FcntlArg::F_TRANSFEREXTENTS(tmp2.as_raw_fd())) + .expect("transferextents failed"); + assert_ne!(res, -1); +} + +#[cfg(target_os = "freebsd")] +#[test] +fn test_f_readahead() { + use nix::fcntl::*; + + let tmp = NamedTempFile::new().unwrap(); + let mut res = fcntl(&tmp, FcntlArg::F_READAHEAD(1_000_000)) + .expect("read ahead failed"); + assert_ne!(res, -1); + res = fcntl(&tmp, FcntlArg::F_READAHEAD(-1024)).expect("read ahead failed"); + assert_ne!(res, -1); +} diff --git a/test/test_mount.rs b/test/test_mount.rs deleted file mode 100644 index 2fd612e3..00000000 --- a/test/test_mount.rs +++ /dev/null @@ -1,271 +0,0 @@ -mod common; - -// Implementation note: to allow unprivileged users to run it, this test makes -// use of user and mount namespaces. On systems that allow unprivileged user -// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run -// without root. - -#[cfg(target_os = "linux")] -mod test_mount { - use std::fs::{self, File}; - use std::io::{self, Read, Write}; - use std::os::unix::fs::OpenOptionsExt; - use std::os::unix::fs::PermissionsExt; - use std::process::{self, Command}; - - use libc::{EACCES, EROFS}; - - use nix::errno::Errno; - use nix::mount::{mount, umount, MsFlags}; - use nix::sched::{unshare, CloneFlags}; - use nix::sys::stat::{self, Mode}; - use nix::unistd::getuid; - - static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh -exit 23"; - - const EXPECTED_STATUS: i32 = 23; - - const NONE: Option<&'static [u8]> = None; - #[allow(clippy::bind_instead_of_map)] // False positive - pub fn test_mount_tmpfs_without_flags_allows_rwx() { - let tempdir = tempfile::tempdir().unwrap(); - - mount( - NONE, - tempdir.path(), - Some(b"tmpfs".as_ref()), - MsFlags::empty(), - NONE, - ) - .unwrap_or_else(|e| panic!("mount failed: {}", e)); - - let test_path = tempdir.path().join("test"); - - // Verify write. - fs::OpenOptions::new() - .create(true) - .write(true) - .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) - .open(&test_path) - .or_else(|e| { - if Errno::from_i32(e.raw_os_error().unwrap()) - == Errno::EOVERFLOW - { - // Skip tests on certain Linux kernels which have a bug - // regarding tmpfs in namespaces. - // Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is - // not. There is no legitimate reason for open(2) to return - // EOVERFLOW here. - // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087 - let stderr = io::stderr(); - let mut handle = stderr.lock(); - writeln!( - handle, - "Buggy Linux kernel detected. Skipping test." - ) - .unwrap(); - process::exit(0); - } else { - panic!("open failed: {}", e); - } - }) - .and_then(|mut f| f.write(SCRIPT_CONTENTS)) - .unwrap_or_else(|e| panic!("write failed: {}", e)); - - // Verify read. - let mut buf = Vec::new(); - File::open(&test_path) - .and_then(|mut f| f.read_to_end(&mut buf)) - .unwrap_or_else(|e| panic!("read failed: {}", e)); - assert_eq!(buf, SCRIPT_CONTENTS); - - // Verify execute. - assert_eq!( - EXPECTED_STATUS, - Command::new(&test_path) - .status() - .unwrap_or_else(|e| panic!("exec failed: {}", e)) - .code() - .unwrap_or_else(|| panic!("child killed by signal")) - ); - - umount(tempdir.path()) - .unwrap_or_else(|e| panic!("umount failed: {}", e)); - } - - pub fn test_mount_rdonly_disallows_write() { - let tempdir = tempfile::tempdir().unwrap(); - - mount( - NONE, - tempdir.path(), - Some(b"tmpfs".as_ref()), - MsFlags::MS_RDONLY, - NONE, - ) - .unwrap_or_else(|e| panic!("mount failed: {}", e)); - - // EROFS: Read-only file system - assert_eq!( - EROFS, - File::create(tempdir.path().join("test")) - .unwrap_err() - .raw_os_error() - .unwrap() - ); - - umount(tempdir.path()) - .unwrap_or_else(|e| panic!("umount failed: {}", e)); - } - - pub fn test_mount_noexec_disallows_exec() { - let tempdir = tempfile::tempdir().unwrap(); - - mount( - NONE, - tempdir.path(), - Some(b"tmpfs".as_ref()), - MsFlags::MS_NOEXEC, - NONE, - ) - .unwrap_or_else(|e| panic!("mount failed: {}", e)); - - let test_path = tempdir.path().join("test"); - - fs::OpenOptions::new() - .create(true) - .write(true) - .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) - .open(&test_path) - .and_then(|mut f| f.write(SCRIPT_CONTENTS)) - .unwrap_or_else(|e| panic!("write failed: {}", e)); - - // Verify that we cannot execute despite a+x permissions being set. - let mode = stat::Mode::from_bits_truncate( - fs::metadata(&test_path) - .map(|md| md.permissions().mode()) - .unwrap_or_else(|e| panic!("metadata failed: {}", e)), - ); - - assert!( - mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH), - "{:?} did not have execute permissions", - &test_path - ); - - // EACCES: Permission denied - assert_eq!( - EACCES, - Command::new(&test_path) - .status() - .unwrap_err() - .raw_os_error() - .unwrap() - ); - - umount(tempdir.path()) - .unwrap_or_else(|e| panic!("umount failed: {}", e)); - } - - pub fn test_mount_bind() { - let tempdir = tempfile::tempdir().unwrap(); - let file_name = "test"; - - { - let mount_point = tempfile::tempdir().unwrap(); - - mount( - Some(tempdir.path()), - mount_point.path(), - NONE, - MsFlags::MS_BIND, - NONE, - ) - .unwrap_or_else(|e| panic!("mount failed: {}", e)); - - fs::OpenOptions::new() - .create(true) - .write(true) - .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) - .open(mount_point.path().join(file_name)) - .and_then(|mut f| f.write(SCRIPT_CONTENTS)) - .unwrap_or_else(|e| panic!("write failed: {}", e)); - - umount(mount_point.path()) - .unwrap_or_else(|e| panic!("umount failed: {}", e)); - } - - // Verify the file written in the mount shows up in source directory, even - // after unmounting. - - let mut buf = Vec::new(); - File::open(tempdir.path().join(file_name)) - .and_then(|mut f| f.read_to_end(&mut buf)) - .unwrap_or_else(|e| panic!("read failed: {}", e)); - assert_eq!(buf, SCRIPT_CONTENTS); - } - - pub fn setup_namespaces() { - // Hold on to the uid in the parent namespace. - let uid = getuid(); - - unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - writeln!(handle, - "unshare failed: {}. Are unprivileged user namespaces available?", - e).unwrap(); - writeln!(handle, "mount is not being tested").unwrap(); - // Exit with success because not all systems support unprivileged user namespaces, and - // that's not what we're testing for. - process::exit(0); - }); - - // Map user as uid 1000. - fs::OpenOptions::new() - .write(true) - .open("/proc/self/uid_map") - .and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes())) - .unwrap_or_else(|e| panic!("could not write uid map: {}", e)); - } -} - -// Test runner - -/// Mimic normal test output (hackishly). -#[cfg(target_os = "linux")] -macro_rules! run_tests { - ( $($test_fn:ident),* ) => {{ - println!(); - - $( - print!("test test_mount::{} ... ", stringify!($test_fn)); - $test_fn(); - println!("ok"); - )* - - println!(); - }} -} - -#[cfg(target_os = "linux")] -fn main() { - use test_mount::{ - setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec, - test_mount_rdonly_disallows_write, - test_mount_tmpfs_without_flags_allows_rwx, - }; - skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351"); - setup_namespaces(); - - run_tests!( - test_mount_tmpfs_without_flags_allows_rwx, - test_mount_rdonly_disallows_write, - test_mount_noexec_disallows_exec, - test_mount_bind - ); -} - -#[cfg(not(target_os = "linux"))] -fn main() {} diff --git a/test/test_mq.rs b/test/test_mq.rs index 7b48e7ac..874a72b4 100644 --- a/test/test_mq.rs +++ b/test/test_mq.rs @@ -1,11 +1,14 @@ use cfg_if::cfg_if; -use std::ffi::CString; use std::str; use nix::errno::Errno; -use nix::mqueue::{mq_attr_member_t, mq_close, mq_open, mq_receive, mq_send}; +use nix::mqueue::{ + mq_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 { @@ -30,7 +33,7 @@ macro_rules! assert_attr_eq { 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 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; @@ -55,12 +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] 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 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)); @@ -78,14 +112,22 @@ fn test_mq_getattr() { // FIXME: Fix failures for mips in QEMU #[test] #[cfg_attr( - all(qemu, any(target_arch = "mips", target_arch = "mips64")), + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" + ) + ), 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 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)); @@ -128,14 +170,22 @@ fn test_mq_setattr() { // FIXME: Fix failures for mips in QEMU #[test] #[cfg_attr( - all(qemu, any(target_arch = "mips", target_arch = "mips64")), + all( + qemu, + any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" + ) + ), ignore )] fn test_mq_set_nonblocking() { use nix::mqueue::{mq_getattr, mq_remove_nonblock, mq_set_nonblock}; const MSG_SIZE: mq_attr_member_t = 32; let initial_attr = MqAttr::new(0, 10, MSG_SIZE, 0); - let mq_name = &CString::new(b"/attr_test_get_attr".as_ref()).unwrap(); + let 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)); @@ -159,10 +209,9 @@ 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_opened = "/mq_unlink_test"; #[cfg(not(any(target_os = "dragonfly", target_os = "netbsd")))] - let mq_name_not_opened = - &CString::new(b"/mq_unlink_test".as_ref()).unwrap(); + 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)); diff --git a/test/test_net.rs b/test/test_net.rs index c44655a4..be86b65d 100644 --- a/test/test_net.rs +++ b/test/test_net.rs @@ -1,19 +1,28 @@ use nix::net::if_::*; -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] const LOOPBACK: &[u8] = b"lo"; -#[cfg(not(any( - target_os = "android", - target_os = "linux", - target_os = "haiku" -)))] +#[cfg(not(any(linux_android, target_os = "haiku")))] const LOOPBACK: &[u8] = b"lo0"; #[cfg(target_os = "haiku")] const LOOPBACK: &[u8] = b"loop"; #[test] +#[cfg_attr(target_os = "cygwin", ignore)] fn test_if_nametoindex() { if_nametoindex(LOOPBACK).expect("assertion failed"); } + +#[test] +#[cfg_attr(target_os = "cygwin", ignore)] +fn test_if_indextoname() { + let loopback_index = if_nametoindex(LOOPBACK).expect("assertion failed"); + assert_eq!( + if_indextoname(loopback_index) + .expect("assertion failed") + .as_bytes(), + LOOPBACK + ); +} diff --git a/test/test_poll.rs b/test/test_poll.rs index 53964e26..fcb32549 100644 --- a/test/test_poll.rs +++ b/test/test_poll.rs @@ -1,8 +1,9 @@ use nix::{ errno::Errno, - poll::{poll, PollFd, PollFlags}, + poll::{poll, PollFd, PollFlags, PollTimeout}, unistd::{pipe, write}, }; +use std::os::unix::io::{AsFd, BorrowedFd}; macro_rules! loop_while_eintr { ($poll_expr: expr) => { @@ -19,17 +20,17 @@ macro_rules! loop_while_eintr { #[test] fn test_poll() { let (r, w) = pipe().unwrap(); - let mut fds = [PollFd::new(r, PollFlags::POLLIN)]; + let mut fds = [PollFd::new(r.as_fd(), PollFlags::POLLIN)]; // Poll an idle pipe. Should timeout - let nfds = loop_while_eintr!(poll(&mut fds, 100)); + let nfds = loop_while_eintr!(poll(&mut fds, PollTimeout::from(100u8))); assert_eq!(nfds, 0); assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN)); - write(w, b".").unwrap(); + write(&w, b".").unwrap(); // Poll a readable pipe. Should return an event. - let nfds = poll(&mut fds, 100).unwrap(); + let nfds = poll(&mut fds, PollTimeout::from(100u8)).unwrap(); assert_eq!(nfds, 1); assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); } @@ -37,12 +38,7 @@ fn test_poll() { // 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(linux_android, freebsdlike))] #[test] fn test_ppoll() { use nix::poll::ppoll; @@ -51,7 +47,7 @@ fn test_ppoll() { let timeout = TimeSpec::milliseconds(1); let (r, w) = pipe().unwrap(); - let mut fds = [PollFd::new(r, PollFlags::POLLIN)]; + let mut fds = [PollFd::new(r.as_fd(), PollFlags::POLLIN)]; // Poll an idle pipe. Should timeout let sigset = SigSet::empty(); @@ -59,7 +55,7 @@ fn test_ppoll() { assert_eq!(nfds, 0); assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN)); - write(w, b".").unwrap(); + write(&w, b".").unwrap(); // Poll a readable pipe. Should return an event. let nfds = ppoll(&mut fds, Some(timeout), None).unwrap(); @@ -67,17 +63,10 @@ fn test_ppoll() { assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); } -#[test] -fn test_pollfd_fd() { - use std::os::unix::io::AsRawFd; - - let pfd = PollFd::new(0x1234, PollFlags::empty()); - assert_eq!(pfd.as_raw_fd(), 0x1234); -} - #[test] fn test_pollfd_events() { - let mut pfd = PollFd::new(-1, PollFlags::POLLIN); + let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; + let mut pfd = PollFd::new(fd_zero.as_fd(), 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 5c27e2d6..a5808327 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -1,39 +1,26 @@ use std::fs::File; -use std::io::{Read, Write}; +use std::io::{stdout, Read, Write}; use std::os::unix::prelude::*; use std::path::Path; -use tempfile::tempfile; -use libc::{_exit, STDOUT_FILENO}; +use libc::_exit; use nix::fcntl::{open, OFlag}; use nix::pty::*; use nix::sys::stat; use nix::sys::termios::*; -use nix::unistd::{close, pause, write}; - -/// Regression test for Issue #659 -/// This is the correct way to explicitly close a `PtyMaster` -#[test] -fn test_explicit_close() { - let mut f = { - let m = posix_openpt(OFlag::O_RDWR).unwrap(); - close(m.into_raw_fd()).unwrap(); - tempfile().unwrap() - }; - // This should work. But if there's been a double close, then it will - // return EBADF - f.write_all(b"whatever").unwrap(); -} +use nix::sys::wait::WaitStatus; +use nix::unistd::{pause, write}; /// Test equivalence of `ptsname` and `ptsname_r` #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptsname_equivalence() { let _m = crate::PTSNAME_MTX.lock(); - // Open a new PTTY master + // Open a new PTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master_fd.as_raw_fd() > 0); + assert!(master_fd.as_fd().as_raw_fd() == master_fd.as_raw_fd()); // Get the name of the slave let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); @@ -44,13 +31,12 @@ fn test_ptsname_equivalence() { /// Test data copying of `ptsname` // TODO need to run in a subprocess, since ptsname is non-reentrant #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptsname_copy() { let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); - assert!(master_fd.as_raw_fd() > 0); // Get the name of the slave let slave_name1 = unsafe { ptsname(&master_fd) }.unwrap(); @@ -63,11 +49,10 @@ fn test_ptsname_copy() { /// Test data copying of `ptsname_r` #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] 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(); @@ -78,17 +63,15 @@ fn test_ptsname_r_copy() { /// Test that `ptsname` returns different names for different devices #[test] -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] fn test_ptsname_unique() { let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap(); - assert!(master1_fd.as_raw_fd() > 0); // Open a second PTTY master let master2_fd = posix_openpt(OFlag::O_RDWR).unwrap(); - assert!(master2_fd.as_raw_fd() > 0); // Get the name of the slave let slave_name1 = unsafe { ptsname(&master1_fd) }.unwrap(); @@ -115,7 +98,7 @@ fn open_ptty_pair() -> (PtyMaster, File) { open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()) .unwrap(); - #[cfg(target_os = "illumos")] + #[cfg(solarish)] // TODO: rewrite using ioctl! #[allow(clippy::comparison_chain)] { @@ -125,20 +108,23 @@ fn open_ptty_pair() -> (PtyMaster, File) { // after opening a device path returned from ptsname(). let ptem = b"ptem\0"; let ldterm = b"ldterm\0"; - let r = unsafe { ioctl(slave_fd, I_FIND, ldterm.as_ptr()) }; + let r = unsafe { ioctl(slave_fd.as_raw_fd(), I_FIND, ldterm.as_ptr()) }; if r < 0 { panic!("I_FIND failure"); } else if r == 0 { - if unsafe { ioctl(slave_fd, I_PUSH, ptem.as_ptr()) } < 0 { + if unsafe { ioctl(slave_fd.as_raw_fd(), I_PUSH, ptem.as_ptr()) } < 0 + { panic!("I_PUSH ptem failure"); } - if unsafe { ioctl(slave_fd, I_PUSH, ldterm.as_ptr()) } < 0 { + if unsafe { ioctl(slave_fd.as_raw_fd(), I_PUSH, ldterm.as_ptr()) } + < 0 + { panic!("I_PUSH ldterm failure"); } } } - let slave = unsafe { File::from_raw_fd(slave_fd) }; + let slave = File::from(slave_fd); (master, slave) } @@ -147,26 +133,25 @@ 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] +#[cfg(not(target_os = "solaris"))] 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(); @@ -183,7 +168,7 @@ fn test_read_ptty_pair() { #[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(); @@ -202,33 +187,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, 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, 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] @@ -239,51 +219,40 @@ 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, 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, string2.as_bytes()).unwrap(); + crate::read_exact(&pty.master, &mut buf); assert_eq!(&buf, echoed_string2.as_bytes()); - - close(pty.master).unwrap(); - close(pty.slave).unwrap(); } #[test] fn test_forkpty() { use nix::sys::signal::*; use nix::sys::wait::wait; - use nix::unistd::ForkResult::*; // forkpty calls openpty which uses ptname(3) internally. let _m0 = crate::PTSNAME_MTX.lock(); // forkpty spawns a child process @@ -291,23 +260,23 @@ fn test_forkpty() { let string = "naninani\n"; let echoed_string = "naninani\r\n"; - let pty = unsafe { forkpty(None, None).unwrap() }; - match pty.fork_result { - Child => { - write(STDOUT_FILENO, string.as_bytes()).unwrap(); + let res = unsafe { forkpty(None, None).unwrap() }; + match res { + ForkptyResult::Child => { + write(stdout(), string.as_bytes()).unwrap(); pause(); // we need the child to stay alive until the parent calls read unsafe { _exit(0); } } - Parent { child } => { + ForkptyResult::Parent { child, master } => { let mut buf = [0u8; 10]; assert!(child.as_raw() > 0); - crate::read_exact(pty.master, &mut buf); + crate::read_exact(&master, &mut buf); kill(child, SIGTERM).unwrap(); - wait().unwrap(); // keep other tests using generic wait from getting our child + let status = wait().unwrap(); // keep other tests using generic wait from getting our child + assert_eq!(status, WaitStatus::Signaled(child, SIGTERM, false)); 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 ffbaa569..00000000 --- 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_sendfile.rs b/test/test_sendfile.rs index f442a0ca..ab50a1d0 100644 --- a/test/test_sendfile.rs +++ b/test/test_sendfile.rs @@ -1,20 +1,22 @@ use std::io::prelude::*; -use std::os::unix::prelude::*; use libc::off_t; use nix::sys::sendfile::*; use tempfile::tempfile; cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { - use nix::unistd::{close, pipe, read}; - } else if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos"))] { + if #[cfg(linux_android)] { + use nix::unistd::{pipe, read}; + } else if #[cfg(any(freebsdlike, apple_targets))] { use std::net::Shutdown; use std::os::unix::net::UnixStream; + } else if #[cfg(solarish)] { + use std::net::Shutdown; + use std::net::{TcpListener, TcpStream}; } } -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(linux_android)] #[test] fn test_sendfile_linux() { const CONTENTS: &[u8] = b"abcdef123456"; @@ -23,17 +25,14 @@ 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(); + let res = sendfile(&wr, &tmp, Some(&mut offset), 2).unwrap(); assert_eq!(2, res); let mut buf = [0u8; 1024]; - assert_eq!(2, read(rd, &mut buf).unwrap()); + assert_eq!(2, read(&rd, &mut buf).unwrap()); assert_eq!(b"f1", &buf[0..2]); assert_eq!(7, offset); - - close(rd).unwrap(); - close(wr).unwrap(); } #[cfg(target_os = "linux")] @@ -45,17 +44,14 @@ 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(); + let res = sendfile64(&wr, &tmp, Some(&mut offset), 2).unwrap(); assert_eq!(2, res); let mut buf = [0u8; 1024]; - assert_eq!(2, read(rd, &mut buf).unwrap()); + assert_eq!(2, read(&rd, &mut buf).unwrap()); assert_eq!(b"f1", &buf[0..2]); assert_eq!(7, offset); - - close(rd).unwrap(); - close(wr).unwrap(); } #[cfg(target_os = "freebsd")] @@ -83,8 +79,8 @@ fn test_sendfile_freebsd() { // 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()), @@ -101,7 +97,7 @@ fn test_sendfile_freebsd() { + &trailer_strings.concat(); // Verify the message that was sent - assert_eq!(bytes_written as usize, expected_string.as_bytes().len()); + assert_eq!(bytes_written as usize, expected_string.len()); let mut read_string = String::new(); let bytes_read = rd.read_to_string(&mut read_string).unwrap(); @@ -134,8 +130,8 @@ fn test_sendfile_dragonfly() { // 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()), @@ -150,7 +146,7 @@ fn test_sendfile_dragonfly() { + &trailer_strings.concat(); // Verify the message that was sent - assert_eq!(bytes_written as usize, expected_string.as_bytes().len()); + assert_eq!(bytes_written as usize, expected_string.len()); let mut read_string = String::new(); let bytes_read = rd.read_to_string(&mut read_string).unwrap(); @@ -158,7 +154,7 @@ fn test_sendfile_dragonfly() { assert_eq!(expected_string, read_string); } -#[cfg(any(target_os = "ios", target_os = "macos"))] +#[cfg(apple_targets)] #[test] fn test_sendfile_darwin() { // Declare the content @@ -183,8 +179,8 @@ fn test_sendfile_darwin() { // 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()), @@ -206,3 +202,66 @@ fn test_sendfile_darwin() { assert_eq!(bytes_written as usize, bytes_read); assert_eq!(expected_string, read_string); } + +#[cfg(solarish)] +#[test] +fn test_sendfilev() { + use std::os::fd::AsFd; + // Declare the content + let header_strings = + ["HTTP/1.1 200 OK\n", "Content-Type: text/plain\n", "\n"]; + let body = "Xabcdef123456"; + let body_offset = 1usize; + let trailer_strings = ["\n", "Served by Make Believe\n"]; + + // Write data to files + let mut header_data = tempfile().unwrap(); + header_data + .write_all(header_strings.concat().as_bytes()) + .unwrap(); + let mut body_data = tempfile().unwrap(); + body_data.write_all(body.as_bytes()).unwrap(); + let mut trailer_data = tempfile().unwrap(); + trailer_data + .write_all(trailer_strings.concat().as_bytes()) + .unwrap(); + // Create a TCP socket pair (listener and client) + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let mut rd = TcpStream::connect(addr).unwrap(); + let (wr, _) = listener.accept().unwrap(); + let vec: &[SendfileVec] = &[ + SendfileVec::new( + header_data.as_fd(), + 0, + header_strings.iter().map(|s| s.len()).sum(), + ), + SendfileVec::new( + body_data.as_fd(), + body_offset as off_t, + body.len() - body_offset, + ), + SendfileVec::new( + trailer_data.as_fd(), + 0, + trailer_strings.iter().map(|s| s.len()).sum(), + ), + ]; + + let (res, bytes_written) = sendfilev(&wr, vec); + assert!(res.is_ok()); + wr.shutdown(Shutdown::Write).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, 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, bytes_read); + assert_eq!(expected_string, read_string); +} diff --git a/test/test_spawn.rs b/test/test_spawn.rs new file mode 100644 index 00000000..a5e69b97 --- /dev/null +++ b/test/test_spawn.rs @@ -0,0 +1,193 @@ +use super::FORK_MTX; +use nix::errno::Errno; +use nix::spawn::{self, PosixSpawnAttr, PosixSpawnFileActions}; +use nix::sys::signal; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use std::ffi::{CStr, CString}; + +/// Helper function to find a binary in the $PATH +fn which(exe_name: &str) -> Option { + std::env::var_os("PATH").and_then(|paths| { + std::env::split_paths(&paths) + .filter_map(|dir| { + let full_path = dir.join(exe_name); + if full_path.is_file() { + Some(full_path) + } else { + None + } + }) + .next() + }) +} + +#[test] +fn spawn_true() { + let _guard = FORK_MTX.lock(); + + let bin = which("true").unwrap(); + let args = &[ + CString::new("true").unwrap(), + CString::new("story").unwrap(), + ]; + let vars: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let pid = + spawn::posix_spawn(bin.as_path(), &actions, &attr, args, vars).unwrap(); + + let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); + + match status { + WaitStatus::Exited(wpid, ret) => { + assert_eq!(pid, wpid); + assert_eq!(ret, 0); + } + _ => { + panic!("Invalid WaitStatus"); + } + }; +} + +#[test] +fn spawn_sleep() { + let _guard = FORK_MTX.lock(); + + let bin = which("sleep").unwrap(); + let args = &[CString::new("sleep").unwrap(), CString::new("30").unwrap()]; + let vars: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let pid = + spawn::posix_spawn(bin.as_path(), &actions, &attr, args, vars).unwrap(); + + let status = + waitpid(pid, WaitPidFlag::from_bits(WaitPidFlag::WNOHANG.bits())) + .unwrap(); + match status { + WaitStatus::StillAlive => {} + _ => { + panic!("Invalid WaitStatus"); + } + }; + + signal::kill(pid, signal::SIGTERM).unwrap(); + + let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); + match status { + WaitStatus::Signaled(wpid, wsignal, _) => { + assert_eq!(pid, wpid); + assert_eq!(wsignal, signal::SIGTERM); + } + _ => { + panic!("Invalid WaitStatus"); + } + }; +} + +#[test] +// `posix_spawn(path_not_exist)` succeeds under QEMU, so ignore the test. No need +// to investigate the root cause, this test still works in native environments, which +// is sufficient to test the binding. +#[cfg_attr(qemu, ignore)] +fn spawn_cmd_does_not_exist() { + let _guard = FORK_MTX.lock(); + + let args = &[CString::new("buzz").unwrap()]; + let envs: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let bin = "2b7433c4-523b-470c-abb5-d7ee9fd295d5-fdasf"; + let errno = + spawn::posix_spawn(bin, &actions, &attr, args, envs).unwrap_err(); + assert_eq!(errno, Errno::ENOENT); +} + +#[test] +fn spawnp_true() { + let _guard = FORK_MTX.lock(); + + let bin = &CString::new("true").unwrap(); + let args = &[ + CString::new("true").unwrap(), + CString::new("story").unwrap(), + ]; + let vars: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let pid = spawn::posix_spawnp(bin, &actions, &attr, args, vars).unwrap(); + + let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); + + match status { + WaitStatus::Exited(wpid, ret) => { + assert_eq!(pid, wpid); + assert_eq!(ret, 0); + } + _ => { + panic!("Invalid WaitStatus"); + } + }; +} + +#[test] +fn spawnp_sleep() { + let _guard = FORK_MTX.lock(); + + let bin = &CString::new("sleep").unwrap(); + let args = &[CString::new("sleep").unwrap(), CString::new("30").unwrap()]; + let vars: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let pid = spawn::posix_spawnp(bin, &actions, &attr, args, vars).unwrap(); + + let status = + waitpid(pid, WaitPidFlag::from_bits(WaitPidFlag::WNOHANG.bits())) + .unwrap(); + match status { + WaitStatus::StillAlive => {} + _ => { + panic!("Invalid WaitStatus"); + } + }; + + signal::kill(pid, signal::SIGTERM).unwrap(); + + let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); + match status { + WaitStatus::Signaled(wpid, wsignal, _) => { + assert_eq!(pid, wpid); + assert_eq!(wsignal, signal::SIGTERM); + } + _ => { + panic!("Invalid WaitStatus"); + } + }; +} + +#[test] +// `posix_spawnp(bin_not_exist)` succeeds under QEMU, so ignore the test. No need +// to investigate the root cause, this test still works in native environments, which +// is sufficient to test the binding. +#[cfg_attr(qemu, ignore)] +fn spawnp_cmd_does_not_exist() { + let _guard = FORK_MTX.lock(); + + let args = &[CString::new("buzz").unwrap()]; + let envs: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let bin = CStr::from_bytes_with_nul( + "2b7433c4-523b-470c-abb5-d7ee9fd295d5-fdasf\0".as_bytes(), + ) + .unwrap(); + let errno = + spawn::posix_spawnp(bin, &actions, &attr, args, envs).unwrap_err(); + assert_eq!(errno, Errno::ENOENT); +} diff --git a/test/test_stat.rs b/test/test_stat.rs deleted file mode 100644 index 55f15c07..00000000 --- a/test/test_stat.rs +++ /dev/null @@ -1,421 +0,0 @@ -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use std::fs; -use std::fs::File; -#[cfg(not(target_os = "redox"))] -use std::os::unix::fs::symlink; -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use std::os::unix::fs::PermissionsExt; -use std::os::unix::prelude::AsRawFd; -#[cfg(not(target_os = "redox"))] -use std::path::Path; -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use std::time::{Duration, UNIX_EPOCH}; - -use libc::mode_t; -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -use libc::{S_IFLNK, S_IFMT}; - -#[cfg(not(target_os = "redox"))] -use nix::errno::Errno; -#[cfg(not(target_os = "redox"))] -use nix::fcntl; -#[cfg(any( - target_os = "linux", - target_os = "ios", - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd" -))] -use nix::sys::stat::lutimes; -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use nix::sys::stat::utimensat; -#[cfg(not(target_os = "redox"))] -use nix::sys::stat::FchmodatFlags; -use nix::sys::stat::Mode; -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use nix::sys::stat::UtimensatFlags; -#[cfg(not(target_os = "redox"))] -use nix::sys::stat::{self}; -use nix::sys::stat::{fchmod, stat}; -#[cfg(not(target_os = "redox"))] -use nix::sys::stat::{fchmodat, mkdirat}; -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use nix::sys::stat::{futimens, utimes}; - -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -use nix::sys::stat::FileStat; - -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; -#[cfg(not(target_os = "redox"))] -use nix::unistd::chdir; - -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -use nix::Result; - -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -fn assert_stat_results(stat_result: Result) { - let stats = stat_result.expect("stat call failed"); - assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent - assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent - assert!(stats.st_mode > 0); // must be positive integer - assert_eq!(stats.st_nlink, 1); // there links created, must be 1 - assert_eq!(stats.st_size, 0); // size is 0 because we did not write anything to the file - assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent - assert!(stats.st_blocks <= 16); // Up to 16 blocks can be allocated for a blank file -} - -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -// (Android's st_blocks is ulonglong which is always non-negative.) -#[cfg_attr(target_os = "android", allow(unused_comparisons))] -#[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes -fn assert_lstat_results(stat_result: Result) { - let stats = stat_result.expect("stat call failed"); - assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent - assert!(stats.st_ino > 0); // inode is positive integer, exact number machine dependent - assert!(stats.st_mode > 0); // must be positive integer - - // st_mode is c_uint (u32 on Android) while S_IFMT is mode_t - // (u16 on Android), and that will be a compile error. - // On other platforms they are the same (either both are u16 or u32). - assert_eq!( - (stats.st_mode as usize) & (S_IFMT as usize), - S_IFLNK as usize - ); // should be a link - assert_eq!(stats.st_nlink, 1); // there links created, must be 1 - assert!(stats.st_size > 0); // size is > 0 because it points to another file - assert!(stats.st_blksize > 0); // must be positive integer, exact number machine dependent - - // st_blocks depends on whether the machine's file system uses fast - // or slow symlinks, so just make sure it's not negative - assert!(stats.st_blocks >= 0); -} - -#[test] -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -fn test_stat_and_fstat() { - use nix::sys::stat::fstat; - - let tempdir = tempfile::tempdir().unwrap(); - let filename = tempdir.path().join("foo.txt"); - let file = File::create(&filename).unwrap(); - - let stat_result = stat(&filename); - assert_stat_results(stat_result); - - let fstat_result = fstat(file.as_raw_fd()); - assert_stat_results(fstat_result); -} - -#[test] -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -fn test_fstatat() { - let tempdir = tempfile::tempdir().unwrap(); - let filename = tempdir.path().join("foo.txt"); - File::create(&filename).unwrap(); - let dirfd = - fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()); - - let result = - stat::fstatat(dirfd.unwrap(), &filename, fcntl::AtFlags::empty()); - assert_stat_results(result); -} - -#[test] -#[cfg(not(any(target_os = "netbsd", target_os = "redox")))] -fn test_stat_fstat_lstat() { - use nix::sys::stat::{fstat, lstat}; - - let tempdir = tempfile::tempdir().unwrap(); - let filename = tempdir.path().join("bar.txt"); - let linkname = tempdir.path().join("barlink"); - - File::create(&filename).unwrap(); - symlink("bar.txt", &linkname).unwrap(); - let link = File::open(&linkname).unwrap(); - - // should be the same result as calling stat, - // since it's a regular file - let stat_result = stat(&filename); - assert_stat_results(stat_result); - - let lstat_result = lstat(&linkname); - assert_lstat_results(lstat_result); - - let fstat_result = fstat(link.as_raw_fd()); - assert_stat_results(fstat_result); -} - -#[test] -fn test_fchmod() { - let tempdir = tempfile::tempdir().unwrap(); - let filename = tempdir.path().join("foo.txt"); - let file = File::create(&filename).unwrap(); - - let mut mode1 = Mode::empty(); - mode1.insert(Mode::S_IRUSR); - mode1.insert(Mode::S_IWUSR); - fchmod(file.as_raw_fd(), mode1).unwrap(); - - let file_stat1 = stat(&filename).unwrap(); - assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); - - let mut mode2 = Mode::empty(); - mode2.insert(Mode::S_IROTH); - fchmod(file.as_raw_fd(), mode2).unwrap(); - - let file_stat2 = stat(&filename).unwrap(); - assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); -} - -#[test] -#[cfg(not(target_os = "redox"))] -fn test_fchmodat() { - let _dr = crate::DirRestore::new(); - let tempdir = tempfile::tempdir().unwrap(); - let filename = "foo.txt"; - let fullpath = tempdir.path().join(filename); - File::create(&fullpath).unwrap(); - - let dirfd = - fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) - .unwrap(); - - let mut mode1 = Mode::empty(); - mode1.insert(Mode::S_IRUSR); - mode1.insert(Mode::S_IWUSR); - fchmodat(Some(dirfd), filename, mode1, FchmodatFlags::FollowSymlink) - .unwrap(); - - let file_stat1 = stat(&fullpath).unwrap(); - assert_eq!(file_stat1.st_mode as mode_t & 0o7777, mode1.bits()); - - chdir(tempdir.path()).unwrap(); - - let mut mode2 = Mode::empty(); - mode2.insert(Mode::S_IROTH); - fchmodat(None, filename, mode2, FchmodatFlags::FollowSymlink).unwrap(); - - let file_stat2 = stat(&fullpath).unwrap(); - assert_eq!(file_stat2.st_mode as mode_t & 0o7777, mode2.bits()); -} - -/// Asserts that the atime and mtime in a file's metadata match expected values. -/// -/// The atime and mtime are expressed with a resolution of seconds because some file systems -/// (like macOS's HFS+) do not have higher granularity. -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -fn assert_times_eq( - exp_atime_sec: u64, - exp_mtime_sec: u64, - attr: &fs::Metadata, -) { - assert_eq!( - Duration::new(exp_atime_sec, 0), - attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap() - ); - assert_eq!( - Duration::new(exp_mtime_sec, 0), - attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap() - ); -} - -#[test] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -fn test_utimes() { - let tempdir = tempfile::tempdir().unwrap(); - let fullpath = tempdir.path().join("file"); - drop(File::create(&fullpath).unwrap()); - - utimes(&fullpath, &TimeVal::seconds(9990), &TimeVal::seconds(5550)) - .unwrap(); - assert_times_eq(9990, 5550, &fs::metadata(&fullpath).unwrap()); -} - -#[test] -#[cfg(any( - target_os = "linux", - target_os = "ios", - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd" -))] -fn test_lutimes() { - let tempdir = tempfile::tempdir().unwrap(); - let target = tempdir.path().join("target"); - let fullpath = tempdir.path().join("symlink"); - drop(File::create(&target).unwrap()); - symlink(&target, &fullpath).unwrap(); - - let exp_target_metadata = fs::symlink_metadata(&target).unwrap(); - lutimes(&fullpath, &TimeVal::seconds(4560), &TimeVal::seconds(1230)) - .unwrap(); - assert_times_eq(4560, 1230, &fs::symlink_metadata(&fullpath).unwrap()); - - let target_metadata = fs::symlink_metadata(&target).unwrap(); - assert_eq!( - exp_target_metadata.accessed().unwrap(), - target_metadata.accessed().unwrap(), - "atime of symlink target was unexpectedly modified" - ); - assert_eq!( - exp_target_metadata.modified().unwrap(), - target_metadata.modified().unwrap(), - "mtime of symlink target was unexpectedly modified" - ); -} - -#[test] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -fn test_futimens() { - let tempdir = tempfile::tempdir().unwrap(); - let fullpath = tempdir.path().join("file"); - drop(File::create(&fullpath).unwrap()); - - let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()) - .unwrap(); - - futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap(); - assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap()); -} - -#[test] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -fn test_utimensat() { - let _dr = crate::DirRestore::new(); - let tempdir = tempfile::tempdir().unwrap(); - let filename = "foo.txt"; - let fullpath = tempdir.path().join(filename); - drop(File::create(&fullpath).unwrap()); - - let dirfd = - fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) - .unwrap(); - - utimensat( - Some(dirfd), - filename, - &TimeSpec::seconds(12345), - &TimeSpec::seconds(678), - UtimensatFlags::FollowSymlink, - ) - .unwrap(); - assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap()); - - chdir(tempdir.path()).unwrap(); - - utimensat( - None, - filename, - &TimeSpec::seconds(500), - &TimeSpec::seconds(800), - UtimensatFlags::FollowSymlink, - ) - .unwrap(); - assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap()); -} - -#[test] -#[cfg(not(target_os = "redox"))] -fn test_mkdirat_success_path() { - let tempdir = tempfile::tempdir().unwrap(); - let filename = "example_subdir"; - let dirfd = - fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) - .unwrap(); - mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); - assert!(Path::exists(&tempdir.path().join(filename))); -} - -#[test] -#[cfg(not(any(target_os = "redox", target_os = "haiku")))] -fn test_mkdirat_success_mode() { - let expected_bits = - stat::SFlag::S_IFDIR.bits() | stat::Mode::S_IRWXU.bits(); - let tempdir = tempfile::tempdir().unwrap(); - let filename = "example_subdir"; - let dirfd = - fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) - .unwrap(); - mkdirat(dirfd, filename, Mode::S_IRWXU).expect("mkdirat failed"); - let permissions = fs::metadata(tempdir.path().join(filename)) - .unwrap() - .permissions(); - let mode = permissions.mode(); - assert_eq!(mode as mode_t, expected_bits) -} - -#[test] -#[cfg(not(target_os = "redox"))] -fn test_mkdirat_fail() { - let tempdir = tempfile::tempdir().unwrap(); - let not_dir_filename = "example_not_dir"; - let filename = "example_subdir_dir"; - let dirfd = fcntl::open( - &tempdir.path().join(not_dir_filename), - fcntl::OFlag::O_CREAT, - stat::Mode::empty(), - ) - .unwrap(); - let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err(); - assert_eq!(result, Errno::ENOTDIR); -} - -#[test] -#[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "ios", - target_os = "macos", - target_os = "haiku", - target_os = "redox" -)))] -fn test_mknod() { - use stat::{lstat, mknod, SFlag}; - - let file_name = "test_file"; - let tempdir = tempfile::tempdir().unwrap(); - let target = tempdir.path().join(file_name); - mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap(); - let mode = lstat(&target).unwrap().st_mode as mode_t; - assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); - assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); -} - -#[test] -#[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "haiku", - target_os = "redox" -)))] -fn test_mknodat() { - use fcntl::{AtFlags, OFlag}; - use nix::dir::Dir; - use stat::{fstatat, mknodat, SFlag}; - - let file_name = "test_file"; - let tempdir = tempfile::tempdir().unwrap(); - let target_dir = - Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); - mknodat( - target_dir.as_raw_fd(), - file_name, - SFlag::S_IFREG, - Mode::S_IRWXU, - 0, - ) - .unwrap(); - let mode = fstatat( - target_dir.as_raw_fd(), - file_name, - AtFlags::AT_SYMLINK_NOFOLLOW, - ) - .unwrap() - .st_mode as mode_t; - assert_eq!(mode & libc::S_IFREG, libc::S_IFREG); - assert_eq!(mode & libc::S_IRWXU, libc::S_IRWXU); -} diff --git a/test/test_syslog.rs b/test/test_syslog.rs new file mode 100644 index 00000000..fc8f9080 --- /dev/null +++ b/test/test_syslog.rs @@ -0,0 +1,38 @@ +use nix::syslog::{openlog, syslog, Facility, LogFlags, Severity}; + +#[test] +fn test_syslog_hello_world() { + let flags = LogFlags::LOG_PID; + + #[cfg(not(target_os = "linux"))] + openlog(None::<&str>, flags, Facility::LOG_USER).unwrap(); + #[cfg(target_os = "linux")] + openlog(None, flags, Facility::LOG_USER).unwrap(); + + syslog(Severity::LOG_EMERG, "Hello, nix!").unwrap(); + let name = "syslog"; + syslog(Severity::LOG_NOTICE, &format!("Hello, {name}!")).unwrap(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_openlog_with_ident() { + use std::ffi::CStr; + + const IDENT: &CStr = unsafe { + CStr::from_bytes_with_nul_unchecked(b"test_openlog_with_ident\0") + }; + + let flags = LogFlags::LOG_PID; + openlog(Some(IDENT), flags, Facility::LOG_USER).unwrap(); + syslog(Severity::LOG_EMERG, "Hello, ident!").unwrap(); +} + +#[test] +#[cfg(not(target_os = "linux"))] +fn test_openlog_with_ident() { + let flags = LogFlags::LOG_PID; + openlog(Some("test_openlog_with_ident"), flags, Facility::LOG_USER) + .unwrap(); + syslog(Severity::LOG_EMERG, "Hello, ident!").unwrap(); +} diff --git a/test/test_time.rs b/test/test_time.rs index 5f76e61a..64c8161d 100644 --- a/test/test_time.rs +++ b/test/test_time.rs @@ -1,10 +1,4 @@ -#[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux", - target_os = "android", - target_os = "emscripten", -))] +#[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] use nix::time::clock_getcpuclockid; use nix::time::{clock_gettime, ClockId}; @@ -19,13 +13,7 @@ pub fn test_clock_gettime() { clock_gettime(ClockId::CLOCK_REALTIME).expect("assertion failed"); } -#[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux", - target_os = "android", - target_os = "emscripten", -))] +#[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] #[test] pub fn test_clock_getcpuclockid() { let clock_id = clock_getcpuclockid(nix::unistd::Pid::this()).unwrap(); @@ -43,13 +31,7 @@ pub fn test_clock_id_now() { ClockId::CLOCK_REALTIME.now().unwrap(); } -#[cfg(any( - target_os = "freebsd", - target_os = "dragonfly", - target_os = "linux", - target_os = "android", - target_os = "emscripten", -))] +#[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] #[test] pub fn test_clock_id_pid_cpu_clock_id() { ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) @@ -57,3 +39,28 @@ pub fn test_clock_id_pid_cpu_clock_id() { .unwrap() .unwrap(); } + +#[cfg(any( + linux_android, + solarish, + freebsdlike, + target_os = "netbsd", + target_os = "hurd", + target_os = "aix" +))] +#[test] +pub fn test_clock_nanosleep() { + use nix::{ + sys::time::{TimeSpec, TimeValLike}, + time::{clock_nanosleep, ClockNanosleepFlags}, + }; + + let sleep_time = TimeSpec::microseconds(1); + let res = clock_nanosleep( + ClockId::CLOCK_MONOTONIC, + ClockNanosleepFlags::empty(), + &sleep_time, + ); + let expected = TimeSpec::microseconds(0); + assert_eq!(res, Ok(expected)); +} diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 9e20f977..6037bb7a 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -26,7 +26,6 @@ use std::ffi::CString; 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", @@ -56,11 +55,42 @@ fn test_fork_and_waitpid() { // panic, must never happen s @ Ok(_) => { - panic!("Child exited {:?}, should never happen", s) + 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:?}"), + } + } + } +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_rfork_and_waitpid() { + let _m = crate::FORK_MTX.lock(); + + // Safe: Child only calls `_exit`, which is signal-safe + match unsafe { rfork(RforkFlags::RFPROC | RforkFlags::RFTHREAD) } + .expect("Error: Rfork Failed") + { + Child => unsafe { _exit(0) }, + Parent { child } => { + // assert that child was created and pid > 0 + let child_raw: ::libc::pid_t = child.into(); + assert!(child_raw > 0); + let wait_status = waitpid(child, None); + match wait_status { + // assert that waitpid returned correct status and the pid is the one of the child + Ok(WaitStatus::Exited(pid_t, _)) => assert_eq!(pid_t, child), + + // panic, must never happen + s @ Ok(_) => { + panic!("Child exited {s:?}, should never happen") + } + + // panic, waitpid should never fail + Err(s) => panic!("Error: waitpid returned Err({s:?}"), } } } @@ -90,11 +120,10 @@ fn test_mkstemp() { let result = mkstemp(&path); match result { - Ok((fd, path)) => { - close(fd).unwrap(); + Ok((_, path)) => { unlink(path.as_path()).unwrap(); } - Err(e) => panic!("mkstemp failed: {}", e), + Err(e) => panic!("mkstemp failed: {e}"), } } @@ -126,19 +155,20 @@ fn test_mkfifo_directory() { #[test] #[cfg(not(any( - target_os = "macos", - target_os = "ios", + apple_targets, target_os = "android", target_os = "redox", target_os = "haiku" )))] fn test_mkfifoat_none() { + use nix::fcntl::AT_FDCWD; + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo"); - mkfifoat(None, &mkfifoat_fifo, Mode::S_IRUSR).unwrap(); + mkfifoat(AT_FDCWD, &mkfifoat_fifo, Mode::S_IRUSR).unwrap(); let stats = stat::stat(&mkfifoat_fifo).unwrap(); let typ = stat::SFlag::from_bits_truncate(stats.st_mode); @@ -147,8 +177,7 @@ fn test_mkfifoat_none() { #[test] #[cfg(not(any( - target_os = "macos", - target_os = "ios", + apple_targets, target_os = "android", target_os = "redox", target_os = "haiku" @@ -160,34 +189,34 @@ fn test_mkfifoat() { let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); let mkfifoat_name = "mkfifoat_name"; - mkfifoat(Some(dirfd), mkfifoat_name, Mode::S_IRUSR).unwrap(); + mkfifoat(&dirfd, mkfifoat_name, Mode::S_IRUSR).unwrap(); let stats = - stat::fstatat(dirfd, mkfifoat_name, fcntl::AtFlags::empty()).unwrap(); + 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", + apple_targets, target_os = "android", target_os = "redox", target_os = "haiku" )))] fn test_mkfifoat_directory_none() { + use nix::fcntl::AT_FDCWD; + let _m = crate::CWD_LOCK.read(); // mkfifoat should fail if a directory is given - mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR) + mkfifoat(AT_FDCWD, &env::temp_dir(), Mode::S_IRUSR) .expect_err("assertion failed"); } #[test] #[cfg(not(any( - target_os = "macos", - target_os = "ios", + apple_targets, target_os = "android", target_os = "redox", target_os = "haiku" @@ -197,9 +226,9 @@ fn test_mkfifoat_directory() { let tempdir = tempdir().unwrap(); let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); let mkfifoat_dir = "mkfifoat_dir"; - stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap(); + stat::mkdirat(&dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap(); - mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR) + mkfifoat(&dirfd, mkfifoat_dir, Mode::S_IRUSR) .expect_err("assertion failed"); } @@ -220,7 +249,7 @@ fn test_getsid() { assert_eq!(none_sid, pid_sid); } -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] mod linux_android { use nix::unistd::gettid; @@ -234,8 +263,7 @@ mod linux_android { #[test] // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms #[cfg(not(any( - target_os = "ios", - target_os = "macos", + apple_targets, target_os = "redox", target_os = "fuchsia", target_os = "haiku" @@ -263,12 +291,11 @@ fn test_setgroups() { #[test] // `getgroups()` and `setgroups()` do not behave as expected on Apple platforms #[cfg(not(any( - target_os = "ios", - target_os = "macos", + apple_targets, target_os = "redox", target_os = "fuchsia", target_os = "haiku", - target_os = "illumos" + solarish )))] fn test_initgroups() { // Skip this test when not run as root as `initgroups()` and `setgroups()` @@ -287,12 +314,15 @@ fn test_initgroups() { // groups that the user belongs to are also set. let user = CString::new("root").unwrap(); let group = Gid::from_raw(123); - let group_list = getgrouplist(&user, group).unwrap(); + let mut group_list = getgrouplist(&user, group).unwrap(); assert!(group_list.contains(&group)); initgroups(&user, group).unwrap(); - let new_groups = getgroups().unwrap(); + let mut new_groups = getgroups().unwrap(); + + new_groups.sort_by_key(|gid| gid.as_raw()); + group_list.sort_by_key(|gid| gid.as_raw()); assert_eq!(new_groups, group_list); // Revert back to the old groups @@ -356,7 +386,7 @@ macro_rules! execve_test_factory ( match unsafe{fork()}.unwrap() { Child => { // Make `writer` be the stdout of the new process. - dup2(writer, 1).unwrap(); + nix::unistd::dup2_stdout(&writer).unwrap(); let r = syscall(); let _ = std::io::stderr() .write_all(format!("{:?}", r).as_bytes()); @@ -370,7 +400,7 @@ macro_rules! execve_test_factory ( assert_eq!(ws, Ok(WaitStatus::Exited(child, 0))); // Read 1024 bytes. let mut buf = [0u8; 1024]; - read(reader, &mut buf).unwrap(); + read(&reader, &mut buf).unwrap(); // It should contain the things we printed using `/bin/sh`. let string = String::from_utf8_lossy(&buf); assert!(string.contains("nix!!!")); @@ -403,47 +433,45 @@ macro_rules! execve_test_factory ( cfg_if! { if #[cfg(target_os = "android")] { execve_test_factory!(test_execve, execve, CString::new("/system/bin/sh").unwrap().as_c_str()); - execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd()); - } else if #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux"))] { + execve_test_factory!(test_fexecve, fexecve, &File::open("/system/bin/sh").unwrap()); + } else if #[cfg(any(freebsdlike, target_os = "linux", target_os = "hurd"))] { // These tests frequently fail on musl, probably due to // https://github.com/nix-rust/nix/issues/555 execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str()); - execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd()); - } else if #[cfg(any(target_os = "illumos", - target_os = "ios", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris"))] { + execve_test_factory!(test_fexecve, fexecve, &File::open("/bin/sh").unwrap()); + } else if #[cfg(any(solarish, apple_targets, netbsdlike))] { execve_test_factory!(test_execve, execve, CString::new("/bin/sh").unwrap().as_c_str()); // No fexecve() on ios, macos, NetBSD, OpenBSD. } } -#[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))] +#[cfg(any( + target_os = "haiku", + target_os = "hurd", + target_os = "linux", + target_os = "openbsd" +))] execve_test_factory!(test_execvpe, execvpe, &CString::new("sh").unwrap()); cfg_if! { if #[cfg(target_os = "android")] { use nix::fcntl::AtFlags; execve_test_factory!(test_execveat_empty, execveat, - File::open("/system/bin/sh").unwrap().into_raw_fd(), + &File::open("/system/bin/sh").unwrap(), "", AtFlags::AT_EMPTY_PATH); execve_test_factory!(test_execveat_relative, execveat, - File::open("/system/bin/").unwrap().into_raw_fd(), + &File::open("/system/bin/").unwrap(), "./sh", AtFlags::empty()); execve_test_factory!(test_execveat_absolute, execveat, - File::open("/").unwrap().into_raw_fd(), + &File::open("/").unwrap(), "/system/bin/sh", AtFlags::empty()); } else if #[cfg(all(target_os = "linux", any(target_arch ="x86_64", target_arch ="x86")))] { use nix::fcntl::AtFlags; - execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(), + execve_test_factory!(test_execveat_empty, execveat, &File::open("/bin/sh").unwrap(), "", AtFlags::AT_EMPTY_PATH); - execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(), + execve_test_factory!(test_execveat_relative, execveat, &File::open("/bin/").unwrap(), "./sh", AtFlags::empty()); - execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(), + execve_test_factory!(test_execveat_absolute, execveat, &File::open("/").unwrap(), "/bin/sh", AtFlags::empty()); } } @@ -456,12 +484,10 @@ fn test_fchdir() { let tmpdir = tempdir().unwrap(); let tmpdir_path = tmpdir.path().canonicalize().unwrap(); - let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd(); + let tmpdir_fd = File::open(&tmpdir_path).unwrap(); - fchdir(tmpdir_fd).expect("assertion failed"); + fchdir(&tmpdir_fd).expect("assertion failed"); assert_eq!(getcwd().unwrap(), tmpdir_path); - - close(tmpdir_fd).expect("assertion failed"); } #[test] @@ -515,18 +541,19 @@ fn test_fchown() { let uid = Some(getuid()); let gid = Some(getgid()); - let path = tempfile().unwrap(); - let fd = path.as_raw_fd(); + let file = tempfile().unwrap(); - fchown(fd, uid, gid).unwrap(); - fchown(fd, uid, None).unwrap(); - fchown(fd, None, gid).unwrap(); - fchown(999999999, uid, gid).unwrap_err(); + fchown(&file, uid, gid).unwrap(); + fchown(&file, uid, None).unwrap(); + fchown(&file, None, gid).unwrap(); } #[test] #[cfg(not(target_os = "redox"))] fn test_fchownat() { + use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let _dr = crate::DirRestore::new(); // Testing for anything other than our own UID/GID is hard. let uid = Some(getuid()); @@ -540,14 +567,13 @@ fn test_fchownat() { let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); - fchownat(Some(dirfd), "file", uid, gid, FchownatFlags::FollowSymlink) - .unwrap(); + fchownat(&dirfd, "file", uid, gid, AtFlags::empty()).unwrap(); chdir(tempdir.path()).unwrap(); - fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap(); + fchownat(AT_FDCWD, "file", uid, gid, AtFlags::empty()).unwrap(); fs::remove_file(&path).unwrap(); - fchownat(None, "file", uid, gid, FchownatFlags::FollowSymlink).unwrap_err(); + fchownat(AT_FDCWD, "file", uid, gid, AtFlags::empty()).unwrap_err(); } #[test] @@ -555,37 +581,31 @@ 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, 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"))] +#[cfg(linux_android)] #[test] fn test_lseek64() { const CONTENTS: &[u8] = b"abcdef123456"; let mut tmp = tempfile().unwrap(); tmp.write_all(CONTENTS).unwrap(); - let tmpfd = tmp.into_raw_fd(); - lseek64(tmpfd, 5, Whence::SeekSet).unwrap(); + lseek64(&tmp, 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! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + if #[cfg(linux_android)] { macro_rules! require_acct{ () => { require_capability!("test_acct", CAP_SYS_PACCT); @@ -611,7 +631,8 @@ cfg_if! { #[cfg(not(any( target_os = "redox", target_os = "fuchsia", - target_os = "haiku" + target_os = "haiku", + target_os = "cygwin" )))] fn test_acct() { use std::process::Command; @@ -637,11 +658,12 @@ fn test_acct() { acct::disable().unwrap(); } +#[cfg_attr(target_os = "hurd", ignore)] #[test] fn test_fpathconf_limited() { let f = tempfile().unwrap(); - // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test - let path_max = fpathconf(f.as_raw_fd(), PathconfVar::PATH_MAX); + // PATH_MAX is limited on most platforms, so it makes a good test + let path_max = fpathconf(f, PathconfVar::PATH_MAX); assert!( path_max .expect("fpathconf failed") @@ -650,9 +672,10 @@ fn test_fpathconf_limited() { ); } +#[cfg_attr(target_os = "hurd", ignore)] #[test] fn test_pathconf_limited() { - // AFAIK, PATH_MAX is limited on all platforms, so it makes a good test + // PATH_MAX is limited on most platforms, so it makes a good test let path_max = pathconf("/", PathconfVar::PATH_MAX); assert!( path_max @@ -662,9 +685,10 @@ fn test_pathconf_limited() { ); } +#[cfg_attr(target_os = "hurd", ignore)] #[test] fn test_sysconf_limited() { - // AFAIK, OPEN_MAX is limited on all platforms, so it makes a good test + // OPEN_MAX is limited on most platforms, so it makes a good test let open_max = sysconf(SysconfVar::OPEN_MAX); assert!( open_max @@ -684,13 +708,7 @@ fn test_sysconf_unsupported() { assert!(open_max.expect("sysconf failed").is_none()) } -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +#[cfg(any(linux_android, freebsdlike, target_os = "openbsd"))] #[test] fn test_getresuid() { let resuids = getresuid().unwrap(); @@ -699,13 +717,7 @@ fn test_getresuid() { assert_ne!(resuids.saved.as_raw(), libc::uid_t::MAX); } -#[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +#[cfg(any(linux_android, freebsdlike, target_os = "openbsd"))] #[test] fn test_getresgid() { let resgids = getresgid().unwrap(); @@ -720,12 +732,12 @@ fn test_getresgid() { fn test_pipe() { let (fd0, fd1) = pipe().unwrap(); let m0 = stat::SFlag::from_bits_truncate( - stat::fstat(fd0).unwrap().st_mode as mode_t, + 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, + stat::fstat(&fd1).unwrap().st_mode as mode_t, ); assert_eq!(m1, SFlag::S_IFIFO); } @@ -733,25 +745,23 @@ fn test_pipe() { // 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", + linux_android, + freebsdlike, + solarish, + netbsdlike, target_os = "emscripten", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd", target_os = "redox", - target_os = "solaris" ))] #[test] fn test_pipe2() { use nix::fcntl::{fcntl, FcntlArg, FdFlag}; let (fd0, fd1) = pipe2(OFlag::O_CLOEXEC).unwrap(); - let f0 = FdFlag::from_bits_truncate(fcntl(fd0, FcntlArg::F_GETFD).unwrap()); + let f0 = + FdFlag::from_bits_truncate(fcntl(&fd0, FcntlArg::F_GETFD).unwrap()); assert!(f0.contains(FdFlag::FD_CLOEXEC)); - let f1 = FdFlag::from_bits_truncate(fcntl(fd1, FcntlArg::F_GETFD).unwrap()); + let f1 = + FdFlag::from_bits_truncate(fcntl(&fd1, FcntlArg::F_GETFD).unwrap()); assert!(f1.contains(FdFlag::FD_CLOEXEC)); } @@ -778,15 +788,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()); @@ -799,12 +806,7 @@ static mut ALARM_CALLED: bool = false; // Used in `test_alarm`. #[cfg(not(target_os = "redox"))] pub extern "C" fn alarm_signal_handler(raw_signal: libc::c_int) { - assert_eq!( - raw_signal, - libc::SIGALRM, - "unexpected signal: {}", - raw_signal - ); + assert_eq!(raw_signal, libc::SIGALRM, "unexpected signal: {raw_signal}"); unsafe { ALARM_CALLED = true }; } @@ -867,13 +869,15 @@ fn test_canceling_alarm() { #[test] #[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_symlinkat() { + use nix::fcntl::AT_FDCWD; + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); let target = tempdir.path().join("a"); let linkpath = tempdir.path().join("b"); - symlinkat(&target, None, &linkpath).unwrap(); + symlinkat(&target, AT_FDCWD, &linkpath).unwrap(); assert_eq!( readlink(&linkpath).unwrap().to_str().unwrap(), target.to_str().unwrap() @@ -882,7 +886,7 @@ fn test_symlinkat() { let dirfd = open(tempdir.path(), OFlag::empty(), Mode::empty()).unwrap(); let target = "c"; let linkpath = "d"; - symlinkat(target, Some(dirfd), linkpath).unwrap(); + symlinkat(target, &dirfd, linkpath).unwrap(); assert_eq!( readlink(&tempdir.path().join(linkpath)) .unwrap() @@ -895,6 +899,8 @@ fn test_symlinkat() { #[test] #[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_file() { + use nix::fcntl::AtFlags; + let tempdir = tempdir().unwrap(); let oldfilename = "foo.txt"; let oldfilepath = tempdir.path().join(oldfilename); @@ -912,11 +918,45 @@ fn test_linkat_file() { // Attempt hard link file at relative path linkat( - Some(dirfd), + &dirfd, oldfilename, - Some(dirfd), + &dirfd, newfilename, - LinkatFlags::SymlinkFollow, + AtFlags::AT_SYMLINK_FOLLOW, + ) + .unwrap(); + assert!(newfilepath.exists()); +} + +#[test] +#[cfg(not(any(target_os = "redox", target_os = "haiku")))] +/// This test is the same as [test_linkat_file], but ensures that two different types can be used +/// as the path arguments. +fn test_linkat_pathtypes() { + use nix::fcntl::AtFlags; + + let tempdir = tempdir().unwrap(); + let oldfilename = "foo.txt"; + let oldfilepath = tempdir.path().join(oldfilename); + + let newfilename = "bar.txt"; + let newfilepath = tempdir.path().join(newfilename); + + // Create file + File::create(oldfilepath).unwrap(); + + // Get file descriptor for base directory + let dirfd = + fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()) + .unwrap(); + + // Attempt hard link file at relative path + linkat( + &dirfd, + PathBuf::from(oldfilename).as_path(), + &dirfd, + newfilename, + AtFlags::AT_SYMLINK_FOLLOW, ) .unwrap(); assert!(newfilepath.exists()); @@ -925,6 +965,9 @@ fn test_linkat_file() { #[test] #[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_olddirfd_none() { + use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let _dr = crate::DirRestore::new(); let tempdir_oldfile = tempdir().unwrap(); @@ -949,11 +992,11 @@ fn test_linkat_olddirfd_none() { // Attempt hard link file using curent working directory as relative path for old file path chdir(tempdir_oldfile.path()).unwrap(); linkat( - None, + AT_FDCWD, oldfilename, - Some(dirfd), + &dirfd, newfilename, - LinkatFlags::SymlinkFollow, + AtFlags::AT_SYMLINK_FOLLOW, ) .unwrap(); assert!(newfilepath.exists()); @@ -962,6 +1005,9 @@ fn test_linkat_olddirfd_none() { #[test] #[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_newdirfd_none() { + use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let _dr = crate::DirRestore::new(); let tempdir_oldfile = tempdir().unwrap(); @@ -986,24 +1032,22 @@ fn test_linkat_newdirfd_none() { // Attempt hard link file using current working directory as relative path for new file path chdir(tempdir_newfile.path()).unwrap(); linkat( - Some(dirfd), + &dirfd, oldfilename, - None, + AT_FDCWD, newfilename, - LinkatFlags::SymlinkFollow, + AtFlags::AT_SYMLINK_FOLLOW, ) .unwrap(); assert!(newfilepath.exists()); } #[test] -#[cfg(not(any( - target_os = "ios", - target_os = "macos", - target_os = "redox", - target_os = "haiku" -)))] +#[cfg(not(any(apple_targets, target_os = "redox", target_os = "haiku")))] fn test_linkat_no_follow_symlink() { + use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); @@ -1020,7 +1064,7 @@ fn test_linkat_no_follow_symlink() { File::create(&oldfilepath).unwrap(); // Create symlink to file - symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); + symlinkat(&oldfilepath, AT_FDCWD, &symoldfilepath).unwrap(); // Get file descriptor for base directory let dirfd = @@ -1029,11 +1073,11 @@ fn test_linkat_no_follow_symlink() { // Attempt link symlink of file at relative path linkat( - Some(dirfd), + &dirfd, symoldfilename, - Some(dirfd), + &dirfd, newfilename, - LinkatFlags::NoSymlinkFollow, + AtFlags::empty(), ) .unwrap(); @@ -1047,6 +1091,9 @@ fn test_linkat_no_follow_symlink() { #[test] #[cfg(not(any(target_os = "redox", target_os = "haiku")))] fn test_linkat_follow_symlink() { + use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); @@ -1063,7 +1110,7 @@ fn test_linkat_follow_symlink() { File::create(&oldfilepath).unwrap(); // Create symlink to file - symlinkat(&oldfilepath, None, &symoldfilepath).unwrap(); + symlinkat(&oldfilepath, AT_FDCWD, &symoldfilepath).unwrap(); // Get file descriptor for base directory let dirfd = @@ -1072,11 +1119,11 @@ fn test_linkat_follow_symlink() { // Attempt link target of symlink of file at relative path linkat( - Some(dirfd), + &dirfd, symoldfilename, - Some(dirfd), + &dirfd, newfilename, - LinkatFlags::SymlinkFollow, + AtFlags::AT_SYMLINK_FOLLOW, ) .unwrap(); @@ -1084,8 +1131,8 @@ fn test_linkat_follow_symlink() { // Check the file type of the new link assert_eq!( - (stat::SFlag::from_bits_truncate(newfilestat.st_mode as mode_t) - & SFlag::S_IFMT), + stat::SFlag::from_bits_truncate(newfilestat.st_mode as mode_t) + & SFlag::S_IFMT, SFlag::S_IFREG ); @@ -1110,7 +1157,7 @@ fn test_unlinkat_dir_noremovedir() { // Attempt unlink dir at relative path without proper flag let err_result = - unlinkat(Some(dirfd), dirname, UnlinkatFlags::NoRemoveDir).unwrap_err(); + unlinkat(&dirfd, dirname, UnlinkatFlags::NoRemoveDir).unwrap_err(); assert!(err_result == Errno::EISDIR || err_result == Errno::EPERM); } @@ -1130,7 +1177,7 @@ fn test_unlinkat_dir_removedir() { .unwrap(); // Attempt unlink dir at relative path with proper flag - unlinkat(Some(dirfd), dirname, UnlinkatFlags::RemoveDir).unwrap(); + unlinkat(&dirfd, dirname, UnlinkatFlags::RemoveDir).unwrap(); assert!(!dirpath.exists()); } @@ -1150,7 +1197,7 @@ fn test_unlinkat_file() { .unwrap(); // Attempt unlink file at relative path - unlinkat(Some(dirfd), filename, UnlinkatFlags::NoRemoveDir).unwrap(); + unlinkat(&dirfd, filename, UnlinkatFlags::NoRemoveDir).unwrap(); assert!(!filepath.exists()); } @@ -1173,17 +1220,19 @@ fn test_access_file_exists() { .expect("assertion failed"); } -//Clippy false positive https://github.com/rust-lang/rust-clippy/issues/9111 -#[allow(clippy::needless_borrow)] #[cfg(not(target_os = "redox"))] #[test] fn test_user_into_passwd() { - // get the UID of the "nobody" user - #[cfg(not(target_os = "haiku"))] - let test_username = "nobody"; - // "nobody" unavailable on haiku - #[cfg(target_os = "haiku")] - let test_username = "user"; + let test_username = if cfg!(target_os = "haiku") { + // "nobody" unavailable on haiku + "user" + } else if cfg!(target_os = "cygwin") { + // the Windows admin user + "Administrator" + } else { + // get the UID of the "nobody" user + "nobody" + }; let nobody = User::from_name(test_username).unwrap().unwrap(); let pwd: libc::passwd = nobody.into(); @@ -1191,7 +1240,7 @@ fn test_user_into_passwd() { } /// Tests setting the filesystem UID with `setfsuid`. -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(linux_android)] #[test] fn test_setfsuid() { use std::os::unix::fs::PermissionsExt; @@ -1235,6 +1284,8 @@ fn test_setfsuid() { target_os = "haiku" )))] fn test_ttyname() { + use std::os::fd::AsRawFd; + let fd = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed"); assert!(fd.as_raw_fd() > 0); @@ -1244,9 +1295,11 @@ 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()) + let fds = fs::OpenOptions::new() + .read(true) + .write(true) + .open(Path::new(&sname)) .expect("open failed"); - assert!(fds > 0); let name = ttyname(fds).expect("ttyname failed"); assert!(name.starts_with("/dev")); @@ -1256,35 +1309,17 @@ fn test_ttyname() { #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] fn test_ttyname_not_pty() { let fd = File::open("/dev/zero").unwrap(); - assert!(fd.as_raw_fd() > 0); - assert_eq!(ttyname(fd.as_raw_fd()), Err(Errno::ENOTTY)); + assert_eq!(ttyname(fd), Err(Errno::ENOTTY)); } #[test] -#[cfg(not(any( - target_os = "redox", - target_os = "fuchsia", - target_os = "haiku" -)))] -fn test_ttyname_invalid_fd() { - assert_eq!(ttyname(-1), Err(Errno::EBADF)); -} - -#[test] -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "dragonfly", -))] +#[cfg(bsd)] fn test_getpeereid() { use std::os::unix::net::UnixStream; let (sock_a, sock_b) = UnixStream::pair().unwrap(); - let (uid_a, gid_a) = getpeereid(sock_a.as_raw_fd()).unwrap(); - let (uid_b, gid_b) = getpeereid(sock_b.as_raw_fd()).unwrap(); + let (uid_a, gid_a) = getpeereid(sock_a).unwrap(); + let (uid_b, gid_b) = getpeereid(sock_b).unwrap(); let uid = geteuid(); let gid = getegid(); @@ -1295,28 +1330,16 @@ fn test_getpeereid() { assert_eq!(gid_a, gid_b); } -#[test] -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "dragonfly", -))] -fn test_getpeereid_invalid_fd() { - // getpeereid is not POSIX, so error codes are inconsistent between different Unices. - getpeereid(-1).expect_err("assertion failed"); -} - #[test] #[cfg(not(target_os = "redox"))] fn test_faccessat_none_not_existing() { use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let tempdir = tempfile::tempdir().unwrap(); let dir = tempdir.path().join("does_not_exist.txt"); assert_eq!( - faccessat(None, &dir, AccessFlags::F_OK, AtFlags::empty()) + faccessat(AT_FDCWD, &dir, AccessFlags::F_OK, AtFlags::empty()) .err() .unwrap(), Errno::ENOENT @@ -1327,18 +1350,14 @@ fn test_faccessat_none_not_existing() { #[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(), + faccessat(&dirfd, not_exist_file, AccessFlags::F_OK, AtFlags::empty(),) + .err() + .unwrap(), Errno::ENOENT ); } @@ -1347,11 +1366,13 @@ fn test_faccessat_not_existing() { #[cfg(not(target_os = "redox"))] fn test_faccessat_none_file_exists() { use nix::fcntl::AtFlags; + use nix::fcntl::AT_FDCWD; + let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path().join("does_exist.txt"); let _file = File::create(path.clone()).unwrap(); assert!(faccessat( - None, + AT_FDCWD, &path, AccessFlags::R_OK | AccessFlags::W_OK, AtFlags::empty(), @@ -1363,13 +1384,14 @@ fn test_faccessat_none_file_exists() { #[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), + &dirfd, &path, AccessFlags::R_OK | AccessFlags::W_OK, AtFlags::empty(), @@ -1378,11 +1400,7 @@ fn test_faccessat_file_exists() { } #[test] -#[cfg(any( - all(target_os = "linux", not(target_env = "uclibc")), - target_os = "freebsd", - target_os = "dragonfly" -))] +#[cfg(any(all(target_os = "linux", not(target_env = "uclibc")), freebsdlike))] fn test_eaccess_not_existing() { let tempdir = tempdir().unwrap(); let dir = tempdir.path().join("does_not_exist.txt"); @@ -1393,11 +1411,7 @@ fn test_eaccess_not_existing() { } #[test] -#[cfg(any( - all(target_os = "linux", not(target_env = "uclibc")), - target_os = "freebsd", - target_os = "dragonfly" -))] +#[cfg(any(all(target_os = "linux", not(target_env = "uclibc")), freebsdlike))] fn test_eaccess_file_exists() { let tempdir = tempdir().unwrap(); let path = tempdir.path().join("does_exist.txt"); @@ -1405,3 +1419,14 @@ fn test_eaccess_file_exists() { eaccess(&path, AccessFlags::R_OK | AccessFlags::W_OK) .expect("assertion failed"); } + +#[test] +#[cfg(bsd)] +fn test_group_from() { + let group = Group::from_name("wheel").unwrap().unwrap(); + assert!(group.name == "wheel"); + let group_id = group.gid; + let group = Group::from_gid(group_id).unwrap().unwrap(); + assert_eq!(group.gid, group_id); + assert_eq!(group.name, "wheel"); +} diff --git a/towncrier.toml b/towncrier.toml new file mode 100644 index 00000000..d324433e --- /dev/null +++ b/towncrier.toml @@ -0,0 +1,27 @@ +# towncrier configuration document: +# https://towncrier.readthedocs.io/en/stable/configuration.html + +[tool.towncrier] +# Read news fragments from this directory +directory = "changelog" +# Concatenate fragments, and prepend to this file +filename = "CHANGELOG.md" +title_format = "## [{version}] - {project_date}" +# Used to disable towncrier's "=====" title header +underlines = ["", "", ""] +# Wrap news fragments to a line length of 79 +wrap = true +# Every news fragement under the `changelog` directory is named "..md", +# this `id` field, is called issue/ticket number in towncrier's term +# `issue_format` controls how this will be rendered in the final CHANGELOG +# We use this for Pull Request even though it is called "issue" +issue_format = "[#{issue}](https://github.com/nix-rust/nix/pull/{issue})" +# Ask towncrier to add new notes after this +start_string = "# Change Log\n" + +# nix's change log typs (in alphabetical order) +# These types will be capitalized by default. +[tool.towncrier.fragment.added] +[tool.towncrier.fragment.changed] +[tool.towncrier.fragment.fixed] +[tool.towncrier.fragment.removed]