├── .cirrus.yml ├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── build │ │ └── action.yml │ ├── check_new_changelog │ │ └── action.yml │ └── test │ │ └── action.yml └── workflows │ ├── check_new_changelog.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONVENTIONS.md ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── README.md ├── RELEASE_PROCEDURE.md ├── build.rs ├── changelog └── .keep ├── examples └── getifaddrs.rs ├── justfile ├── rustfmt.toml ├── src ├── dir.rs ├── env.rs ├── errno.rs ├── fcntl.rs ├── features.rs ├── ifaddrs.rs ├── kmod.rs ├── lib.rs ├── macros.rs ├── mount │ ├── apple.rs │ ├── bsd_without_apple.rs │ ├── linux.rs │ └── mod.rs ├── mqueue.rs ├── net │ ├── if_.rs │ └── mod.rs ├── poll.rs ├── poll_timeout.rs ├── pty.rs ├── sched.rs ├── spawn.rs ├── sys │ ├── aio.rs │ ├── epoll.rs │ ├── event.rs │ ├── eventfd.rs │ ├── fanotify.rs │ ├── inotify.rs │ ├── ioctl │ │ ├── bsd.rs │ │ ├── linux.rs │ │ └── mod.rs │ ├── memfd.rs │ ├── mman.rs │ ├── mod.rs │ ├── personality.rs │ ├── prctl.rs │ ├── pthread.rs │ ├── ptrace │ │ ├── bsd.rs │ │ ├── linux.rs │ │ └── mod.rs │ ├── quota.rs │ ├── reboot.rs │ ├── resource.rs │ ├── select.rs │ ├── sendfile.rs │ ├── signal.rs │ ├── signalfd.rs │ ├── socket │ │ ├── addr.rs │ │ ├── mod.rs │ │ └── sockopt.rs │ ├── stat.rs │ ├── statfs.rs │ ├── statvfs.rs │ ├── sysinfo.rs │ ├── termios.rs │ ├── time.rs │ ├── timer.rs │ ├── timerfd.rs │ ├── uio.rs │ ├── utsname.rs │ └── wait.rs ├── syslog.rs ├── time.rs ├── ucontext.rs └── unistd.rs ├── test ├── common │ └── mod.rs ├── mount │ ├── mod.rs │ ├── test_mount.rs │ ├── test_mount_apple.rs │ └── test_nmount.rs ├── sys │ ├── mod.rs │ ├── test_aio.rs │ ├── test_aio_drop.rs │ ├── test_epoll.rs │ ├── test_event.rs │ ├── test_fanotify.rs │ ├── test_inotify.rs │ ├── test_ioctl.rs │ ├── test_memfd.rs │ ├── test_mman.rs │ ├── test_prctl.rs │ ├── test_pthread.rs │ ├── test_ptrace.rs │ ├── test_resource.rs │ ├── test_select.rs │ ├── test_signal.rs │ ├── test_signalfd.rs │ ├── test_socket.rs │ ├── test_sockopt.rs │ ├── test_stat.rs │ ├── test_statfs.rs │ ├── test_statvfs.rs │ ├── test_sysinfo.rs │ ├── test_termios.rs │ ├── test_time.rs │ ├── test_timer.rs │ ├── test_timerfd.rs │ ├── test_uio.rs │ ├── test_utsname.rs │ └── test_wait.rs ├── test.rs ├── test_clearenv.rs ├── test_dir.rs ├── test_errno.rs ├── test_fcntl.rs ├── test_kmod │ ├── hello_mod │ │ ├── Makefile │ │ └── hello.c │ └── mod.rs ├── test_mq.rs ├── test_net.rs ├── test_nix_path.rs ├── test_poll.rs ├── test_pty.rs ├── test_sched.rs ├── test_sendfile.rs ├── test_spawn.rs ├── test_syslog.rs ├── test_time.rs └── test_unistd.rs └── towncrier.toml /.cirrus.yml: -------------------------------------------------------------------------------- 1 | cargo_cache: 2 | folder: $CARGO_HOME/registry 3 | fingerprint_script: cat Cargo.lock || echo "" 4 | 5 | env: 6 | # Build by default; don't just check 7 | BUILD: build 8 | CLIPPYFLAGS: -D warnings -A unknown-lints 9 | RUSTFLAGS: -D warnings 10 | RUSTDOCFLAGS: -D warnings 11 | TOOL: cargo 12 | MSRV: 1.69.0 13 | ZFLAGS: 14 | 15 | # Tests that don't require executing the build binaries 16 | build: &BUILD 17 | build_script: 18 | - . $HOME/.cargo/env || true 19 | - $TOOL -Vv 20 | - rustc -Vv 21 | - $TOOL $BUILD $ZFLAGS --target $TARGET --all-targets --all-features 22 | - $TOOL doc $ZFLAGS --no-deps --target $TARGET --all-features 23 | - $TOOL clippy $ZFLAGS --target $TARGET --all-targets --all-features -- $CLIPPYFLAGS 24 | - if [ -z "$NOHACK" ]; then mkdir -p $HOME/.cargo/bin; export PATH=$HOME/.cargo/bin:$PATH; fi 25 | - 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 26 | - if [ -z "$NOHACK" ]; then $TOOL hack $ZFLAGS check --target $TARGET --each-feature; fi 27 | 28 | # Tests that do require executing the binaries 29 | test: &TEST 30 | << : *BUILD 31 | test_script: 32 | - . $HOME/.cargo/env || true 33 | - $TOOL test --target $TARGET 34 | 35 | # Test FreeBSD in a full VM. Test the i686 target too, in the 36 | # same VM. The binary will be built in 32-bit mode, but will execute on a 37 | # 64-bit kernel and in a 64-bit environment. Our tests don't execute any of 38 | # the system's binaries, so the environment shouldn't matter. 39 | task: 40 | env: 41 | TARGET: x86_64-unknown-freebsd 42 | matrix: 43 | - name: FreeBSD 14 amd64 & i686 44 | freebsd_instance: 45 | image: freebsd-14-1-release-amd64-ufs 46 | cpu: 1 47 | # Enable tests that would fail on FreeBSD 12 48 | RUSTFLAGS: --cfg fbsd14 -D warnings 49 | RUSTDOCFLAGS: --cfg fbsd14 50 | setup_script: 51 | - kldload mqueuefs 52 | - fetch https://sh.rustup.rs -o rustup.sh 53 | - sh rustup.sh -y --profile=minimal --default-toolchain $MSRV 54 | - . $HOME/.cargo/env 55 | - rustup target add i686-unknown-freebsd 56 | - rustup component add clippy 57 | << : *TEST 58 | i386_test_script: 59 | - . $HOME/.cargo/env 60 | - cargo build --target i686-unknown-freebsd --all-features 61 | - cargo doc --no-deps --target i686-unknown-freebsd --all-features 62 | - cargo test --target i686-unknown-freebsd 63 | i386_feature_script: 64 | - . $HOME/.cargo/env 65 | - if [ -z "$NOHACK" ]; then cargo hack check --each-feature --target i686-unknown-freebsd; fi 66 | before_cache_script: rm -rf $CARGO_HOME/registry/index 67 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /CHANGELOG.md merge=union 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What does this PR do 2 | 3 | ## Checklist: 4 | 5 | - [ ] I have read `CONTRIBUTING.md` 6 | - [ ] I have written necessary tests and rustdoc comments 7 | - [ ] A change log has been added if this PR modifies nix's API 8 | -------------------------------------------------------------------------------- /.github/actions/build/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Build' 2 | description: 'Build nix' 3 | inputs: 4 | # This is required 5 | TARGET: 6 | required: true 7 | 8 | BUILD: 9 | required: false 10 | default: build 11 | 12 | CLIPPYFLAGS: 13 | required: false 14 | default: -D warnings -A unknown-lints 15 | 16 | RUSTFLAGS: 17 | required: false 18 | default: -D warnings -A unknown-lints 19 | 20 | RUSTDOCFLAGS: 21 | required: false 22 | default: -D warnings 23 | 24 | TOOL: 25 | description: 'Tool used to involve the BUILD command, can be cargo or cross' 26 | required: false 27 | default: cargo 28 | 29 | ZFLAGS: 30 | required: false 31 | default: 32 | 33 | NOHACK: 34 | description: "whether to run cargo hack" 35 | required: false 36 | default: false 37 | 38 | runs: 39 | using: "composite" 40 | steps: 41 | - name: set up Rust env 42 | shell: bash 43 | run: | 44 | echo "RUSTFLAGS=${{ inputs.RUSTFLAGS }}" >> $GITHUB_ENV 45 | echo "RUSTDOCFLAGS=${{ inputs.RUSTDOCFLAGS }}" >> $GITHUB_ENV 46 | 47 | - name: debug info 48 | shell: bash 49 | run: | 50 | ${{ inputs.TOOL }} -Vv 51 | rustc -Vv 52 | 53 | - name: build 54 | shell: bash 55 | run: ${{ inputs.TOOL }} ${{ inputs.BUILD }} ${{ inputs.ZFLAGS }} --target ${{ inputs.TARGET }} --all-targets --all-features 56 | 57 | - name: doc 58 | shell: bash 59 | run: ${{ inputs.TOOL }} doc ${{ inputs.ZFLAGS }} --no-deps --target ${{ inputs.TARGET }} --all-features 60 | 61 | - name: clippy 62 | shell: bash 63 | run: ${{ inputs.TOOL}} clippy ${{ inputs.ZFLAGS }} --target ${{ inputs.TARGET }} --all-targets --all-features -- ${{ inputs.CLIPPYFLAGS }} 64 | 65 | - name: Set up cargo-hack 66 | if: inputs.NOHACK == 'false' 67 | uses: taiki-e/install-action@cargo-hack 68 | 69 | - name: run cargo hack 70 | shell: bash 71 | if: inputs.NOHACK == 'false' 72 | run: ${{ inputs.TOOL }} hack ${{ inputs.ZFLAGS }} check --target ${{ inputs.TARGET }} --each-feature 73 | -------------------------------------------------------------------------------- /.github/actions/check_new_changelog/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Check new CHANGELOG' 2 | description: 'Check new CHANGELOG kind and PR number' 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - name: Get newly added CHANGELOGs 8 | id: new-changelogs 9 | uses: tj-actions/changed-files@v44 10 | with: 11 | # Only checek the files under the `changelog` directory 12 | files: changelog/** 13 | 14 | - name: Check them 15 | shell: bash 16 | if: steps.new-changelogs.outputs.added_files_count != 0 17 | env: 18 | NEW_CHANGELOGS: ${{ steps.new-changelogs.outputs.added_files }} 19 | PR_NUMBER: ${{ github.event.number }} 20 | run: | 21 | # `cl` will be something like "changelog/1.added.md" 22 | for cl in ${NEW_CHANGELOGS}; do 23 | # Trim the directory name 24 | prefix="changelog/"; trimmed_cl=${cl/#$prefix}; cl="${trimmed_cl}"; 25 | 26 | # parse it 27 | IFS='.' read id kind file_extension <<< "${cl}" 28 | 29 | # Check the kind field 30 | if [ "$kind" != "added" ] && [ "$kind" != "changed" ] && [ "$kind" != "fixed" ] && [ "$kind" != "removed" ]; then 31 | echo "Invalid CHANGELOG kind [${kind}] from [${cl}], available options are [added, changed, fixed, removed]"; 32 | exit 1; 33 | fi 34 | 35 | # Check the file extension 36 | if [ "$file_extension" != "md" ]; then 37 | echo "Invalid file extension [${file_extension}] from [${cl}], it should be [md]"; 38 | exit 1; 39 | fi 40 | 41 | # Check PR number 42 | if [ "$id" != "$PR_NUMBER" ]; then 43 | echo "Mismatched PR number [${id}] from [${cl}], it should be ${PR_NUMBER}"; 44 | exit 1; 45 | fi 46 | done 47 | -------------------------------------------------------------------------------- /.github/actions/test/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Test' 2 | description: 'Test nix' 3 | inputs: 4 | # This is required 5 | TARGET: 6 | required: true 7 | 8 | SUDO: 9 | 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"' 10 | required: false 11 | default: sudo --preserve-env=HOME 12 | 13 | TOOL: 14 | description: 'Tool used to involve the test command, can be cargo or cross' 15 | required: false 16 | default: cargo 17 | 18 | RUSTFLAGS: 19 | required: false 20 | default: -D warnings -A unknown-lints 21 | 22 | runs: 23 | using: "composite" 24 | steps: 25 | - name: set up Rust env 26 | shell: bash 27 | run: | 28 | echo "RUSTFLAGS=${{ inputs.RUSTFLAGS }}" >> $GITHUB_ENV 29 | 30 | - name: test 31 | shell: bash 32 | run: ${{ inputs.SUDO }} $(which ${{ inputs.TOOL }}) test --target ${{ inputs.TARGET }} 33 | -------------------------------------------------------------------------------- /.github/workflows/check_new_changelog.yml: -------------------------------------------------------------------------------- 1 | name: Check new CHANGELOGs 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | check_new_changelog: 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: check new CHANGELOG 18 | uses: ./.github/actions/check_new_changelog 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | Cargo.lock 3 | target 4 | .idea 5 | *.diff 6 | *.rej 7 | *.orig 8 | .*.swn 9 | .*.swo 10 | .*.swp 11 | *.a 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to nix 2 | 3 | We're really glad you're interested in contributing to nix! This 4 | document has a few pointers and guidelines to help get you started. 5 | 6 | To have a welcoming and inclusive project, nix uses the Rust project's 7 | [Code of Conduct][conduct]. All contributors are expected to follow it. 8 | 9 | [conduct]: https://www.rust-lang.org/conduct.html 10 | 11 | 12 | # Issues 13 | 14 | We use GitHub's [issue tracker][issues]. 15 | 16 | [issues]: https://github.com/nix-rust/nix/issues 17 | 18 | 19 | ## Bug reports 20 | 21 | Before submitting a new bug report, please [search existing 22 | issues][issue-search] to see if there's something related. If not, just 23 | [open a new issue][new-issue]! 24 | 25 | As a reminder, the more information you can give in your issue, the 26 | easier it is to figure out how to fix it. For nix, this will likely 27 | include the OS and version, and the architecture. 28 | 29 | [issue-search]: https://github.com/nix-rust/nix/search?utf8=%E2%9C%93&q=is%3Aissue&type=Issues 30 | [new-issue]: https://github.com/nix-rust/nix/issues/new 31 | 32 | 33 | ## Feature / API requests 34 | 35 | If you'd like a new API or feature added, please [open a new 36 | issue][new-issue] requesting it. As with reporting a bug, the more 37 | information you can provide, the better. 38 | 39 | 40 | ## Labels 41 | 42 | We use labels to help manage issues. The structure is modeled after 43 | [Rust's issue labeling scheme][rust-labels]: 44 | - **A-** prefixed labels state which area of the project the issue 45 | relates to 46 | - **E-** prefixed labels explain the level of experience necessary to fix the 47 | issue 48 | - **O-** prefixed labels specify the OS for issues that are OS-specific 49 | - **R-** prefixed labels specify the architecture for issues that are 50 | architecture-specific 51 | 52 | [rust-labels]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#issue-triage 53 | 54 | 55 | # Pull requests 56 | 57 | GitHub pull requests are the primary mechanism we use to change nix. GitHub itself has 58 | some [great documentation][pr-docs] on using the Pull Request feature. We use the 'fork and 59 | pull' model described there. 60 | 61 | Please make pull requests against the `master` branch. 62 | 63 | If you change the API by way of adding, removing or changing something or if 64 | you fix a bug, please add an appropriate note, every note should be a new markdown 65 | file under the [changelog directory][cl] stating the change made by your pull request, 66 | the filename should be in the following format: 67 | 68 | ``` 69 | ..md 70 | ``` 71 | 72 | These are 4 `TYPE`s available: 73 | 74 | 1. `added` 75 | 2. `changed` 76 | 3. `fixed` 77 | 4. `removed` 78 | 79 | Let's say you have added a new API to nix, then a change log like this should 80 | be added (assume it is PR #0) 81 | 82 | ```md 83 | # file: 0.added.md 84 | Added a new API xxx 85 | ``` 86 | 87 | And having multiple change logs for one PR is allowed. 88 | 89 | [cl]: https://github.com/nix-rust/nix/tree/master/changelog 90 | [pr-docs]: https://help.github.com/articles/using-pull-requests/ 91 | 92 | ## Testing 93 | 94 | nix has a test suite that you can run with `cargo test`. Ideally, we'd like pull 95 | requests to include tests where they make sense. For example, when fixing a bug, 96 | add a test that would have failed without the fix. 97 | 98 | After you've made your change, make sure the tests pass in your development 99 | environment. We also have continuous integration set up on [Cirrus-CI][cirrus-ci] 100 | and GitHub Action, which might find some issues on other platforms. The CI will 101 | run once you open a pull request. 102 | 103 | [cirrus-ci]: https://cirrus-ci.com/github/nix-rust/nix 104 | 105 | ### Disabling a test in the CI environment 106 | 107 | Sometimes there are features that cannot be tested in the CI environment. To 108 | stop a test from running under CI, add `skip_if_cirrus!()` to it. Please 109 | describe the reason it shouldn't run under CI, and a link to an issue if 110 | possible! Other tests cannot be run under QEMU, which is used for some 111 | architectures. To skip them, add a `#[cfg_attr(qemu, ignore)]` attribute to 112 | the test. 113 | 114 | ## GitHub Merge Queues 115 | 116 | We use GitHub merge queues to ensure that subtle merge conflicts won't result 117 | in failing code. If you add or remove a CI job, remember to adjust the 118 | required status checks in the repository's branch protection rules! 119 | 120 | ## API conventions 121 | 122 | If you're adding a new API, we have a [document with 123 | conventions][conventions] to use throughout the nix project. 124 | 125 | [conventions]: https://github.com/nix-rust/nix/blob/master/CONVENTIONS.md 126 | -------------------------------------------------------------------------------- /CONVENTIONS.md: -------------------------------------------------------------------------------- 1 | # Conventions 2 | 3 | In order to achieve our goal of wrapping [libc][libc] code in idiomatic rust 4 | constructs with minimal performance overhead, we follow the following 5 | conventions. 6 | 7 | Note that, thus far, not all the code follows these conventions and not all 8 | conventions we try to follow have been documented here. If you find an instance 9 | of either, feel free to remedy the flaw by opening a pull request with 10 | appropriate changes or additions. 11 | 12 | ## Change Log 13 | 14 | We follow the conventions laid out in [Keep A CHANGELOG][kacl]. 15 | 16 | [kacl]: https://github.com/olivierlacan/keep-a-changelog/tree/18adb5f5be7a898d046f6a4acb93e39dcf40c4ad 17 | 18 | ## libc constants, functions and structs 19 | 20 | We do not define ffi functions or their associated constants and types ourselves, 21 | but use or reexport them from the [libc crate][libc], if your PR uses something 22 | that does not exist in the libc crate, you should add it to libc first. Once 23 | your libc PR gets merged, you can adjust our `libc` dependency to include that 24 | libc change. Use a git dependency if necessary. 25 | 26 | ```toml 27 | libc = { git = "https://github.com/rust-lang/libc", rev = "the commit includes your libc PR", ... } 28 | ``` 29 | 30 | We use the functions exported from [libc][libc] instead of writing our own 31 | `extern` declarations. 32 | 33 | We use the `struct` definitions from [libc][libc] internally instead of writing 34 | our own. If we want to add methods to a libc type, we use the newtype pattern. 35 | For example, 36 | 37 | ```rust 38 | pub struct SigSet(libc::sigset_t); 39 | 40 | impl SigSet { 41 | ... 42 | } 43 | ``` 44 | 45 | When creating newtypes, we use Rust's `CamelCase` type naming convention. 46 | 47 | ## cfg gates 48 | 49 | When creating operating-system-specific functionality, we gate it by 50 | `#[cfg(target_os = ...)]`. If **MORE THAN ONE operating system** is affected, we 51 | prefer to use the cfg aliases defined in build.rs, like `#[cfg(bsd)]`. 52 | 53 | Please **DO NOT** use cfg aliases for **ONLY ONE** system as [they are bad][mismatched_target_os]. 54 | 55 | [mismatched_target_os]: https://rust-lang.github.io/rust-clippy/master/index.html#/mismatched_target_os 56 | 57 | ## Bitflags 58 | 59 | Many C functions have flags parameters that are combined from constants using 60 | bitwise operations. We represent the types of these parameters by types defined 61 | using our `libc_bitflags!` macro, which is a convenience wrapper around the 62 | `bitflags!` macro from the [bitflags crate][bitflags] that brings in the 63 | constant value from `libc`. 64 | 65 | We name the type for a set of constants whose element's names start with `FOO_` 66 | `FooFlags`. 67 | 68 | For example, 69 | 70 | ```rust 71 | libc_bitflags!{ 72 | pub struct ProtFlags: libc::c_int { 73 | PROT_NONE; 74 | PROT_READ; 75 | PROT_WRITE; 76 | PROT_EXEC; 77 | #[cfg(linux_android)] 78 | PROT_GROWSDOWN; 79 | #[cfg(linux_android)] 80 | PROT_GROWSUP; 81 | } 82 | } 83 | ``` 84 | 85 | 86 | ## Enumerations 87 | 88 | We represent sets of constants that are intended as mutually exclusive arguments 89 | to parameters of functions by [enumerations][enum]. 90 | 91 | 92 | ## Structures Initialized by libc Functions 93 | 94 | Whenever we need to use a [libc][libc] function to properly initialize a 95 | variable and said function allows us to use uninitialized memory, we use 96 | [`std::mem::MaybeUninit`][std_MaybeUninit] when defining the variable. This 97 | allows us to avoid the overhead incurred by zeroing or otherwise initializing 98 | the variable. 99 | 100 | [bitflags]: https://crates.io/crates/bitflags/ 101 | [enum]: https://doc.rust-lang.org/reference.html#enumerations 102 | [libc]: https://crates.io/crates/libc/ 103 | [std_MaybeUninit]: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html 104 | 105 | ## Pointer type casting 106 | 107 | We prefer [`cast()`], [`cast_mut()`] and [`cast_const()`] to cast pointer types 108 | over the `as` keyword because it is much more difficult to accidentally change 109 | type or mutability that way. 110 | 111 | [`cast()`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast 112 | [`cast_mut()`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast_mut 113 | [`cast_const()`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast_const 114 | 115 | ## Remove/deprecate an interface 116 | 117 | In Nix, if we want to remove something, we don't do it immediately, instead, we 118 | deprecate it for at least one release before removing it. 119 | 120 | To deprecate an interface, put the following attribute on the top of it: 121 | 122 | ``` 123 | #[deprecated(since = "", note = "")] 124 | ``` 125 | 126 | `` is the version where this interface will be deprecated, in most 127 | cases, it will be the version of the next release. And a user-friendly note 128 | should be added. Normally, there should be a new interface that will replace 129 | the old one, so a note should be something like: "`` should be 130 | used instead". 131 | 132 | ## Where to put a test 133 | 134 | If you want to add a test for a feature that is in `xxx.rs`, then the test should 135 | be put in the corresponding `test_xxx.rs` file unless you cannot do this, e.g., 136 | the test involves private stuff and thus cannot be added outside of Nix, then 137 | it is allowed to leave the test in `xxx.rs`. 138 | 139 | ## Syscall/libc function error handling 140 | 141 | Most syscall and libc functions return an [`ErrnoSentinel`][trait] value on error, 142 | we has a nice utility function [`Errno::result()`][util] to convert it to the 143 | Rusty `Result` type, e.g., here is how `dup(2)` uses it: 144 | 145 | ```rs 146 | pub fn dup(oldfd: RawFd) -> Result { 147 | let res = unsafe { libc::dup(oldfd) }; 148 | 149 | Errno::result(res) 150 | } 151 | ``` 152 | 153 | [trait]: https://docs.rs/nix/latest/nix/errno/trait.ErrnoSentinel.html 154 | [util]: https://docs.rs/nix/latest/nix/errno/enum.Errno.html#method.result 155 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nix" 3 | description = "Rust friendly bindings to *nix APIs" 4 | edition = "2021" 5 | version = "0.30.1" 6 | rust-version = "1.69" 7 | authors = ["The nix-rust Project Developers"] 8 | repository = "https://github.com/nix-rust/nix" 9 | license = "MIT" 10 | categories = ["os::unix-apis"] 11 | include = ["build.rs", "src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | targets = [ 17 | "x86_64-unknown-linux-gnu", 18 | "aarch64-linux-android", 19 | "x86_64-apple-darwin", 20 | "aarch64-apple-ios", 21 | "x86_64-unknown-freebsd", 22 | "x86_64-unknown-openbsd", 23 | "x86_64-unknown-netbsd", 24 | "x86_64-unknown-dragonfly", 25 | "x86_64-unknown-fuchsia", 26 | "x86_64-unknown-redox", 27 | "x86_64-unknown-illumos" 28 | ] 29 | 30 | [dependencies] 31 | libc = { version = "0.2.171", features = ["extra_traits"] } 32 | bitflags = "2.3.3" 33 | cfg-if = "1.0" 34 | pin-utils = { version = "0.1.0", optional = true } 35 | memoffset = { version = "0.9", optional = true } 36 | 37 | [features] 38 | default = [] 39 | 40 | acct = [] 41 | aio = ["pin-utils"] 42 | dir = ["fs"] 43 | env = [] 44 | event = ["poll"] 45 | fanotify = [] 46 | feature = [] 47 | fs = [] 48 | hostname = [] 49 | inotify = [] 50 | ioctl = [] 51 | kmod = [] 52 | mman = [] 53 | mount = ["uio"] 54 | mqueue = ["fs"] 55 | net = ["socket"] 56 | personality = [] 57 | poll = [] 58 | pthread = [] 59 | ptrace = ["process"] 60 | quota = [] 61 | process = [] 62 | reboot = [] 63 | resource = [] 64 | sched = ["process"] 65 | signal = ["process"] 66 | socket = ["memoffset"] 67 | syslog = [] 68 | term = [] 69 | time = [] 70 | ucontext = ["signal"] 71 | uio = [] 72 | user = ["feature"] 73 | zerocopy = ["fs", "uio"] 74 | 75 | [dev-dependencies] 76 | assert-impl = "0.1" 77 | parking_lot = "0.12" 78 | rand = "0.9" 79 | tempfile = "3.7.1" 80 | semver = "1.0.7" 81 | nix = { path = ".", features = ["acct", "aio", "dir", "env", "event", "fanotify", 82 | "feature", "fs", "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", 83 | "net", "personality", "poll", "pthread", "ptrace", "quota", "process", "reboot", 84 | "resource", "sched", "signal", "socket", "syslog", "term", "time", "ucontext", "uio", 85 | "user", "zerocopy"] } 86 | 87 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies] 88 | caps = "0.5.3" 89 | 90 | [target.'cfg(target_os = "freebsd")'.dev-dependencies] 91 | sysctl = "0.4" 92 | 93 | [build-dependencies] 94 | cfg_aliases = "0.2.1" 95 | 96 | [[test]] 97 | name = "test" 98 | path = "test/test.rs" 99 | 100 | [[test]] 101 | name = "test-aio-drop" 102 | path = "test/sys/test_aio_drop.rs" 103 | 104 | [[test]] 105 | name = "test-clearenv" 106 | path = "test/test_clearenv.rs" 107 | 108 | [[test]] 109 | name = "test-prctl" 110 | path = "test/sys/test_prctl.rs" 111 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build.env] 2 | passthrough = [ 3 | "RUSTFLAGS", 4 | "RUST_TEST_THREADS" 5 | ] 6 | 7 | [target.loongarch64-unknown-linux-gnu] 8 | image = "ghcr.io/cross-rs/loongarch64-unknown-linux-gnu:edge" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Carl Lerche + nix-rust Authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust bindings to *nix APIs 2 | 3 | [![Cirrus Build Status](https://api.cirrus-ci.com/github/nix-rust/nix.svg)](https://cirrus-ci.com/github/nix-rust/nix) 4 | [![crates.io](https://img.shields.io/crates/v/nix.svg)](https://crates.io/crates/nix) 5 | [![docs.rs](https://img.shields.io/badge/docs.rs-nix-blue?style=flat-square&logo=docs.rs)](https://docs.rs/nix) 6 | ![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) 7 | [![msrv](https://img.shields.io/badge/msrv-1.69-blue?style=flat-square&logo=rust)](https://www.rust-lang.org) 8 | 9 | Nix seeks to provide friendly bindings to various *nix platform APIs (Linux, Darwin, 10 | ...). The goal is to not provide a 100% unified interface, but to unify 11 | what can be while still providing platform specific APIs. 12 | 13 | For many system APIs, Nix provides a safe alternative to the unsafe APIs 14 | exposed by the [libc crate](https://github.com/rust-lang/libc). This is done by 15 | wrapping the libc functionality with types/abstractions that enforce legal/safe 16 | usage. 17 | 18 | 19 | As an example of what Nix provides, examine the differences between what is 20 | exposed by libc and nix for the 21 | [gethostname](https://man7.org/linux/man-pages/man2/gethostname.2.html) system 22 | call: 23 | 24 | ```rust,ignore 25 | // libc api (unsafe, requires handling return code/errno) 26 | pub unsafe extern fn gethostname(name: *mut c_char, len: size_t) -> c_int; 27 | 28 | // nix api (returns a nix::Result) 29 | pub fn gethostname() -> Result; 30 | ``` 31 | 32 | ## Supported Platforms 33 | 34 | nix target support consists of three tiers. While nix attempts to support all 35 | platforms supported by [libc](https://github.com/rust-lang/libc), only some 36 | platforms are actively supported due to either technical or manpower 37 | limitations. Support for platforms is split into three tiers: 38 | 39 | * Tier 1 - Builds and tests for this target are run in CI. Failures of either 40 | block the inclusion of new code. 41 | * Tier 2 - Builds for this target are run in CI. Failures during the build 42 | blocks the inclusion of new code. Tests may be run, but failures 43 | in tests don't block the inclusion of new code. 44 | * Tier 3 - Builds for this target are run in CI. Failures during the build 45 | *do not* necessarily block the inclusion of new code. That is, at 46 | our discretion a Tier 3 target may be dropped at any time, if it 47 | would otherwise block development. 48 | 49 | Platforms not listed are supported on a best-effort basis, relying on our users 50 | to report any problems. 51 | 52 | The following targets are supported by `nix`: 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 80 | 97 | 108 | 109 |
Tier 1Tier 2Tier 3
62 |
    63 |
  • aarch64-apple-darwin
  • 64 |
  • aarch64-unknown-linux-gnu
  • 65 |
  • arm-unknown-linux-gnueabi
  • 66 |
  • armv7-unknown-linux-gnueabihf
  • 67 |
  • i686-unknown-freebsd
  • 68 |
  • i686-unknown-linux-gnu
  • 69 |
  • i686-unknown-linux-musl
  • 70 |
  • mips-unknown-linux-gnu
  • 71 |
  • mips64-unknown-linux-gnuabi64
  • 72 |
  • mips64el-unknown-linux-gnuabi64
  • 73 |
  • mipsel-unknown-linux-gnu
  • 74 |
  • powerpc64le-unknown-linux-gnu
  • 75 |
  • x86_64-unknown-freebsd
  • 76 |
  • x86_64-unknown-linux-gnu
  • 77 |
  • x86_64-unknown-linux-musl
  • 78 |
79 |
81 |
    82 |
  • aarch64-apple-ios
  • 83 |
  • aarch64-linux-android
  • 84 |
  • aarch64-unknown-linux-ohos
  • 85 |
  • arm-linux-androideabi
  • 86 |
  • arm-unknown-linux-musleabi
  • 87 |
  • armv7-linux-androideabi
  • 88 |
  • armv7-unknown-linux-ohos
  • 89 |
  • i686-linux-android
  • 90 |
  • loongarch64-unknown-linux-gnu
  • 91 |
  • s390x-unknown-linux-gnu
  • 92 |
  • x86_64-linux-android
  • 93 |
  • x86_64-unknown-illumos
  • 94 |
  • x86_64-unknown-linux-ohos
  • 95 |
  • x86_64-unknown-netbsd
  • 96 |
98 |
  • armv7-unknown-linux-uclibceabihf
  • 99 |
  • powerpc64-unknown-linux-gnu
  • 100 |
  • x86_64-unknown-fuchsia
  • 101 |
  • x86_64-unknown-dragonfly
  • 102 |
  • x86_64-unknown-haiku
  • 103 |
  • x86_64-unknown-linux-gnux32
  • 104 |
  • x86_64-unknown-openbsd
  • 105 |
  • x86_64-unknown-redox
  • 106 |
  • i686-unknown-hurd-gnu
  • 107 |
    110 | 111 | ## Minimum Supported Rust Version (MSRV) 112 | 113 | nix is supported on Rust 1.69 and higher. Its MSRV will not be 114 | changed in the future without bumping the major or minor version. 115 | 116 | ## Contributing 117 | 118 | Contributions are very welcome. Please See [CONTRIBUTING](CONTRIBUTING.md) for 119 | additional details. 120 | 121 | Feel free to join us in [the nix-rust/nix](https://discord.com/invite/rkBeJUsmyd) channel on Discord to 122 | discuss `nix` development. 123 | 124 | ## License 125 | 126 | Nix is licensed under the MIT license. See [LICENSE](LICENSE) for more details. 127 | -------------------------------------------------------------------------------- /RELEASE_PROCEDURE.md: -------------------------------------------------------------------------------- 1 | This document lists the steps that lead to a successful release of the Nix 2 | library. 3 | 4 | # Before Release 5 | 6 | Nix uses [cargo release](https://github.com/crate-ci/cargo-release) to automate 7 | the release process. Based on changes since the last release, pick a new 8 | version number following semver conventions. For Nix, a change that drops 9 | support for some Rust versions counts as a breaking change, and requires a 10 | major bump. 11 | 12 | The release is prepared as follows: 13 | 14 | > NOTE: the following procedure should be done directly against the master 15 | > branch of the repo. 16 | 17 | - Clone the `nix-rust/nix` repository with your preferred way, and `cd` to it: 18 | 19 | ```sh 20 | $ git clone https://github.com/nix-rust/nix.git 21 | $ cd nix 22 | ``` 23 | 24 | - If we are using `libc` from git, replace it with a usable release from crates.io. 25 | 26 | ```diff 27 | [dependencies] 28 | -libc = { git = "https://github.com/rust-lang/libc", rev = "", features = ["extra_traits"] } 29 | +libc = { version = "", features = ["extra_traits"] } 30 | ``` 31 | 32 | - Update the version number in `Cargo.toml` 33 | - Generate `CHANGELOG.md` for this release by 34 | 35 | ```sh 36 | $ towncrier build --version= --yes 37 | Loading template... 38 | Finding news fragments... 39 | Rendering news fragments... 40 | Writing to newsfile... 41 | Staging newsfile... 42 | Removing the following files: 43 | nix/changelog/xxxx.xxxx.md 44 | nix/changelog/xxxx.xxxx.md 45 | ... 46 | nix/changelog/xxxx.xxxx.md 47 | Removing news fragments... 48 | Done! 49 | ``` 50 | 51 | - Push the changes made by the above steps to the master branch 52 | 53 | - Ensure you have a crates.io token 54 | 1. With the `publish-update` scope 55 | 2. Can be used for crate `nix` 56 | 3. It is set via `cargo login` 57 | 58 | If not, create a new token [here](https://crates.io/settings/tokens), and set 59 | it. 60 | 61 | - Confirm that everything's ready for a release by running 62 | `cargo release ` 63 | - Create the release with `cargo release -x `, this step will publish 64 | the version to crates.io and push the new version tag to GitHub. 65 | 66 | - Congratulations on a new Nix release! 67 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | cfg_aliases! { 5 | android: { target_os = "android" }, 6 | dragonfly: { target_os = "dragonfly" }, 7 | ios: { target_os = "ios" }, 8 | freebsd: { target_os = "freebsd" }, 9 | illumos: { target_os = "illumos" }, 10 | linux: { target_os = "linux" }, 11 | macos: { target_os = "macos" }, 12 | netbsd: { target_os = "netbsd" }, 13 | openbsd: { target_os = "openbsd" }, 14 | solaris: { target_os = "solaris" }, 15 | watchos: { target_os = "watchos" }, 16 | tvos: { target_os = "tvos" }, 17 | visionos: { target_os = "visionos" }, 18 | 19 | 20 | // cfg aliases we would like to use 21 | apple_targets: { any(ios, macos, watchos, tvos, visionos) }, 22 | bsd: { any(freebsd, dragonfly, netbsd, openbsd, apple_targets) }, 23 | bsd_without_apple: { any(freebsd, dragonfly, netbsd, openbsd) }, 24 | linux_android: { any(android, linux) }, 25 | freebsdlike: { any(dragonfly, freebsd) }, 26 | netbsdlike: { any(netbsd, openbsd) }, 27 | solarish: { any(illumos, solaris) }, 28 | } 29 | 30 | // Below are custom cfg values set during some CI steps. 31 | println!("cargo:rustc-check-cfg=cfg(fbsd14)"); 32 | println!("cargo:rustc-check-cfg=cfg(qemu)"); 33 | // Cygwin target, added in 1.86 34 | println!("cargo:rustc-check-cfg=cfg(target_os, values(\"cygwin\"))"); 35 | } 36 | -------------------------------------------------------------------------------- /changelog/.keep: -------------------------------------------------------------------------------- 1 | Do not remove this file. This is used to keep the `changelog` dir around after 2 | generating new changelog file. 3 | 4 | Without this, `towncrier` would remove the changelog files as well as the 5 | directory if it is empty. 6 | -------------------------------------------------------------------------------- /examples/getifaddrs.rs: -------------------------------------------------------------------------------- 1 | //! Print all interfaces and interface addresses on the system, in a format 2 | //! similar to ifconfig(8). 3 | #![cfg(feature = "net")] 4 | #[cfg(any(bsd, linux_android, target_os = "illumos"))] 5 | fn main() { 6 | use nix::ifaddrs::getifaddrs; 7 | use nix::sys::socket::{SockaddrLike, SockaddrStorage}; 8 | 9 | let addrs = getifaddrs().unwrap(); 10 | let mut ifname = None; 11 | for addr in addrs { 12 | if ifname.as_ref() != Some(&addr.interface_name) { 13 | if ifname.is_some() { 14 | println!(); 15 | } 16 | ifname = Some(addr.interface_name.clone()); 17 | println!( 18 | "{}: flags={:x}<{}>", 19 | addr.interface_name, 20 | addr.flags.bits(), 21 | addr.flags 22 | ); 23 | } 24 | if let Some(dl) = addr.address.as_ref().unwrap().as_link_addr() { 25 | if dl.addr().is_none() { 26 | continue; 27 | } 28 | } 29 | let family = addr 30 | .address 31 | .as_ref() 32 | .and_then(SockaddrStorage::family) 33 | .map(|af| format!("{af:?}")) 34 | .unwrap_or("".to_owned()); 35 | match ( 36 | &addr.address, 37 | &addr.netmask, 38 | &addr.broadcast, 39 | &addr.destination, 40 | ) { 41 | (Some(a), Some(nm), Some(b), None) => { 42 | println!("\t{family} {a} netmask {nm} broadcast {b}") 43 | } 44 | (Some(a), Some(nm), None, None) => { 45 | println!("\t{family} {a} netmask {nm}") 46 | } 47 | (Some(a), None, None, None) => println!("\t{family} {a}"), 48 | (Some(a), None, None, Some(d)) => println!("\t{family} {a} -> {d}"), 49 | x => todo!("{x:?}"), 50 | } 51 | } 52 | } 53 | 54 | #[cfg(not(any(bsd, linux_android, target_os = "illumos")))] 55 | fn main() {} 56 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # If no sub-command is given, simply list all the available options 2 | _default: 3 | just --list 4 | 5 | # Build the doc 6 | doc *args='': 7 | RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc --all-features --no-deps {{args}} 8 | 9 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 -------------------------------------------------------------------------------- /src/env.rs: -------------------------------------------------------------------------------- 1 | //! Environment variables 2 | use cfg_if::cfg_if; 3 | use std::fmt; 4 | 5 | /// Indicates that [`clearenv`] failed for some unknown reason 6 | #[derive(Clone, Copy, Debug)] 7 | pub struct ClearEnvError; 8 | 9 | impl fmt::Display for ClearEnvError { 10 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 11 | write!(f, "clearenv failed") 12 | } 13 | } 14 | 15 | impl std::error::Error for ClearEnvError {} 16 | 17 | /// Clear the environment of all name-value pairs. 18 | /// 19 | /// On platforms where libc provides `clearenv()`, it will be used. libc's 20 | /// `clearenv()` is documented to return an error code but not set errno; if the 21 | /// return value indicates a failure, this function will return 22 | /// [`ClearEnvError`]. 23 | /// 24 | /// On platforms where libc does not provide `clearenv()`, a fallback 25 | /// implementation will be used that iterates over all environment variables and 26 | /// removes them one-by-one. 27 | /// 28 | /// # Safety 29 | /// 30 | /// This function is not threadsafe and can cause undefined behavior in 31 | /// combination with `std::env` or other program components that access the 32 | /// environment. See, for example, the discussion on `std::env::remove_var`; this 33 | /// function is a case of an "inherently unsafe non-threadsafe API" dealing with 34 | /// the environment. 35 | /// 36 | /// The caller must ensure no other threads access the process environment while 37 | /// this function executes and that no raw pointers to an element of libc's 38 | /// `environ` is currently held. The latter is not an issue if the only other 39 | /// environment access in the program is via `std::env`, but the requirement on 40 | /// thread safety must still be upheld. 41 | pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> { 42 | cfg_if! { 43 | if #[cfg(any(linux_android, 44 | target_os = "fuchsia", 45 | target_os = "wasi", 46 | target_env = "uclibc", 47 | target_os = "emscripten"))] { 48 | let ret = unsafe { libc::clearenv() }; 49 | } else { 50 | use std::env; 51 | for (name, _) in env::vars_os() { 52 | env::remove_var(name); 53 | } 54 | let ret = 0; 55 | } 56 | } 57 | 58 | if ret == 0 { 59 | Ok(()) 60 | } else { 61 | Err(ClearEnvError) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/features.rs: -------------------------------------------------------------------------------- 1 | //! Feature tests for OS functionality 2 | pub use self::os::*; 3 | 4 | #[cfg(any(linux_android, target_os = "emscripten"))] 5 | mod os { 6 | use crate::sys::utsname::uname; 7 | use crate::Result; 8 | use std::os::unix::ffi::OsStrExt; 9 | use std::sync::atomic::{AtomicUsize, Ordering}; 10 | 11 | // Features: 12 | // * atomic cloexec on socket: 2.6.27 13 | // * pipe2: 2.6.27 14 | // * accept4: 2.6.28 15 | 16 | static VERS_UNKNOWN: usize = 1; 17 | static VERS_2_6_18: usize = 2; 18 | static VERS_2_6_27: usize = 3; 19 | static VERS_2_6_28: usize = 4; 20 | static VERS_3: usize = 5; 21 | 22 | #[inline] 23 | fn digit(dst: &mut usize, b: u8) { 24 | *dst *= 10; 25 | *dst += (b - b'0') as usize; 26 | } 27 | 28 | fn parse_kernel_version() -> Result { 29 | let u = uname()?; 30 | 31 | let mut curr: usize = 0; 32 | let mut major: usize = 0; 33 | let mut minor: usize = 0; 34 | let mut patch: usize = 0; 35 | 36 | for &b in u.release().as_bytes() { 37 | if curr >= 3 { 38 | break; 39 | } 40 | 41 | match b { 42 | b'.' | b'-' => { 43 | curr += 1; 44 | } 45 | b'0'..=b'9' => match curr { 46 | 0 => digit(&mut major, b), 47 | 1 => digit(&mut minor, b), 48 | _ => digit(&mut patch, b), 49 | }, 50 | _ => break, 51 | } 52 | } 53 | 54 | Ok(if major >= 3 { 55 | VERS_3 56 | } else if major >= 2 { 57 | if minor >= 7 { 58 | VERS_UNKNOWN 59 | } else if minor >= 6 { 60 | if patch >= 28 { 61 | VERS_2_6_28 62 | } else if patch >= 27 { 63 | VERS_2_6_27 64 | } else { 65 | VERS_2_6_18 66 | } 67 | } else { 68 | VERS_UNKNOWN 69 | } 70 | } else { 71 | VERS_UNKNOWN 72 | }) 73 | } 74 | 75 | fn kernel_version() -> Result { 76 | static KERNEL_VERS: AtomicUsize = AtomicUsize::new(0); 77 | let mut kernel_vers = KERNEL_VERS.load(Ordering::Relaxed); 78 | 79 | if kernel_vers == 0 { 80 | kernel_vers = parse_kernel_version()?; 81 | KERNEL_VERS.store(kernel_vers, Ordering::Relaxed); 82 | } 83 | 84 | Ok(kernel_vers) 85 | } 86 | 87 | /// Check if the OS supports atomic close-on-exec for sockets 88 | pub fn socket_atomic_cloexec() -> bool { 89 | kernel_version() 90 | .map(|version| version >= VERS_2_6_27) 91 | .unwrap_or(false) 92 | } 93 | 94 | #[test] 95 | fn test_parsing_kernel_version() { 96 | assert!(kernel_version().unwrap() > 0); 97 | } 98 | } 99 | 100 | #[cfg(any( 101 | freebsdlike, // FreeBSD since 10.0 DragonFlyBSD since ??? 102 | netbsdlike, // NetBSD since 6.0 OpenBSD since 5.7 103 | target_os = "hurd", // Since glibc 2.28 104 | target_os = "illumos", // Since ??? 105 | target_os = "redox", // Since 1-july-2020 106 | target_os = "cygwin", 107 | ))] 108 | mod os { 109 | /// Check if the OS supports atomic close-on-exec for sockets 110 | pub const fn socket_atomic_cloexec() -> bool { 111 | true 112 | } 113 | } 114 | 115 | #[cfg(any( 116 | target_os = "aix", 117 | apple_targets, 118 | target_os = "fuchsia", 119 | target_os = "haiku", 120 | target_os = "solaris" 121 | ))] 122 | mod os { 123 | /// Check if the OS supports atomic close-on-exec for sockets 124 | pub const fn socket_atomic_cloexec() -> bool { 125 | false 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/kmod.rs: -------------------------------------------------------------------------------- 1 | //! Load and unload kernel modules. 2 | //! 3 | //! For more details see 4 | 5 | use std::ffi::CStr; 6 | use std::os::unix::io::{AsFd, AsRawFd}; 7 | 8 | use crate::errno::Errno; 9 | use crate::Result; 10 | 11 | /// Loads a kernel module from a buffer. 12 | /// 13 | /// It loads an ELF image into kernel space, 14 | /// performs any necessary symbol relocations, 15 | /// initializes module parameters to values provided by the caller, 16 | /// and then runs the module's init function. 17 | /// 18 | /// This function requires `CAP_SYS_MODULE` privilege. 19 | /// 20 | /// The `module_image` argument points to a buffer containing the binary image 21 | /// to be loaded. The buffer should contain a valid ELF image 22 | /// built for the running kernel. 23 | /// 24 | /// The `param_values` argument is a string containing space-delimited specifications 25 | /// of the values for module parameters. 26 | /// Each of the parameter specifications has the form: 27 | /// 28 | /// `name[=value[,value...]]` 29 | /// 30 | /// # Example 31 | /// 32 | /// ```no_run 33 | /// use std::fs::File; 34 | /// use std::io::Read; 35 | /// use std::ffi::CString; 36 | /// use nix::kmod::init_module; 37 | /// 38 | /// let mut f = File::open("mykernel.ko").unwrap(); 39 | /// let mut contents: Vec = Vec::new(); 40 | /// f.read_to_end(&mut contents).unwrap(); 41 | /// init_module(&mut contents, &CString::new("who=Rust when=Now,12").unwrap()).unwrap(); 42 | /// ``` 43 | /// 44 | /// See [`man init_module(2)`](https://man7.org/linux/man-pages/man2/init_module.2.html) for more information. 45 | pub fn init_module(module_image: &[u8], param_values: &CStr) -> Result<()> { 46 | let res = unsafe { 47 | libc::syscall( 48 | libc::SYS_init_module, 49 | module_image.as_ptr(), 50 | module_image.len(), 51 | param_values.as_ptr(), 52 | ) 53 | }; 54 | 55 | Errno::result(res).map(drop) 56 | } 57 | 58 | libc_bitflags!( 59 | /// Flags used by the `finit_module` function. 60 | pub struct ModuleInitFlags: libc::c_uint { 61 | /// Ignore symbol version hashes. 62 | MODULE_INIT_IGNORE_MODVERSIONS; 63 | /// Ignore kernel version magic. 64 | MODULE_INIT_IGNORE_VERMAGIC; 65 | } 66 | ); 67 | 68 | /// Loads a kernel module from a given file descriptor. 69 | /// 70 | /// # Example 71 | /// 72 | /// ```no_run 73 | /// use std::fs::File; 74 | /// use std::ffi::CString; 75 | /// use nix::kmod::{finit_module, ModuleInitFlags}; 76 | /// 77 | /// let f = File::open("mymod.ko").unwrap(); 78 | /// finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()).unwrap(); 79 | /// ``` 80 | /// 81 | /// See [`man init_module(2)`](https://man7.org/linux/man-pages/man2/init_module.2.html) for more information. 82 | pub fn finit_module( 83 | fd: Fd, 84 | param_values: &CStr, 85 | flags: ModuleInitFlags, 86 | ) -> Result<()> { 87 | let res = unsafe { 88 | libc::syscall( 89 | libc::SYS_finit_module, 90 | fd.as_fd().as_raw_fd(), 91 | param_values.as_ptr(), 92 | flags.bits(), 93 | ) 94 | }; 95 | 96 | Errno::result(res).map(drop) 97 | } 98 | 99 | libc_bitflags!( 100 | /// Flags used by `delete_module`. 101 | /// 102 | /// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html) 103 | /// for a detailed description how these flags work. 104 | pub struct DeleteModuleFlags: libc::c_int { 105 | /// `delete_module` will return immediately, with an error, if the module has a nonzero 106 | /// reference count. 107 | O_NONBLOCK; 108 | /// `delete_module` will unload the module immediately, regardless of whether it has a 109 | /// nonzero reference count. 110 | O_TRUNC; 111 | } 112 | ); 113 | 114 | /// Unloads the kernel module with the given name. 115 | /// 116 | /// # Example 117 | /// 118 | /// ```no_run 119 | /// use std::ffi::CString; 120 | /// use nix::kmod::{delete_module, DeleteModuleFlags}; 121 | /// 122 | /// delete_module(&CString::new("mymod").unwrap(), DeleteModuleFlags::O_NONBLOCK).unwrap(); 123 | /// ``` 124 | /// 125 | /// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html) for more information. 126 | pub fn delete_module(name: &CStr, flags: DeleteModuleFlags) -> Result<()> { 127 | let res = unsafe { 128 | libc::syscall(libc::SYS_delete_module, name.as_ptr(), flags.bits()) 129 | }; 130 | 131 | Errno::result(res).map(drop) 132 | } 133 | -------------------------------------------------------------------------------- /src/mount/apple.rs: -------------------------------------------------------------------------------- 1 | use crate::{Errno, NixPath, Result}; 2 | use libc::c_int; 3 | 4 | libc_bitflags!( 5 | /// Used with [`mount()`] and [`unmount()`]. 6 | pub struct MntFlags: c_int { 7 | /// Do not interpret special files on the filesystem. 8 | MNT_NODEV; 9 | /// Enable data protection on the filesystem if the filesystem is configured for it. 10 | MNT_CPROTECT; 11 | /// file system is quarantined 12 | MNT_QUARANTINE; 13 | /// filesystem is stored locally 14 | MNT_LOCAL; 15 | /// quotas are enabled on filesystem 16 | MNT_QUOTA; 17 | /// identifies the root filesystem 18 | MNT_ROOTFS; 19 | /// file system is not appropriate path to user data 20 | MNT_DONTBROWSE; 21 | /// VFS will ignore ownership information on filesystem objects 22 | MNT_IGNORE_OWNERSHIP; 23 | /// filesystem was mounted by automounter 24 | MNT_AUTOMOUNTED; 25 | /// filesystem is journaled 26 | MNT_JOURNALED; 27 | /// Don't allow user extended attributes 28 | MNT_NOUSERXATTR; 29 | /// filesystem should defer writes 30 | MNT_DEFWRITE; 31 | /// don't block unmount if not responding 32 | MNT_NOBLOCK; 33 | /// file system is exported 34 | MNT_EXPORTED; 35 | /// file system written asynchronously 36 | MNT_ASYNC; 37 | /// Force a read-write mount even if the file system appears to be 38 | /// unclean. 39 | MNT_FORCE; 40 | /// MAC support for objects. 41 | MNT_MULTILABEL; 42 | /// Do not update access times. 43 | MNT_NOATIME; 44 | /// Disallow program execution. 45 | MNT_NOEXEC; 46 | /// Do not honor setuid or setgid bits on files when executing them. 47 | MNT_NOSUID; 48 | /// Mount read-only. 49 | MNT_RDONLY; 50 | /// Causes the vfs subsystem to update its data structures pertaining to 51 | /// the specified already mounted file system. 52 | MNT_RELOAD; 53 | /// Create a snapshot of the file system. 54 | MNT_SNAPSHOT; 55 | /// All I/O to the file system should be done synchronously. 56 | MNT_SYNCHRONOUS; 57 | /// Union with underlying fs. 58 | MNT_UNION; 59 | /// Indicates that the mount command is being applied to an already 60 | /// mounted file system. 61 | MNT_UPDATE; 62 | } 63 | ); 64 | 65 | /// Mount a file system. 66 | /// 67 | /// # Arguments 68 | /// - `source` - Specifies the file system. e.g. `/dev/sd0`. 69 | /// - `target` - Specifies the destination. e.g. `/mnt`. 70 | /// - `flags` - Optional flags controlling the mount. 71 | /// - `data` - Optional file system specific data. 72 | /// 73 | /// # see also 74 | /// [`mount`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mount.2.html) 75 | pub fn mount< 76 | P1: ?Sized + NixPath, 77 | P2: ?Sized + NixPath, 78 | P3: ?Sized + NixPath, 79 | >( 80 | source: &P1, 81 | target: &P2, 82 | flags: MntFlags, 83 | data: Option<&P3>, 84 | ) -> Result<()> { 85 | let res = source.with_nix_path(|s| { 86 | target.with_nix_path(|t| { 87 | crate::with_opt_nix_path(data, |d| unsafe { 88 | libc::mount( 89 | s.as_ptr(), 90 | t.as_ptr(), 91 | flags.bits(), 92 | d.cast_mut().cast(), 93 | ) 94 | }) 95 | }) 96 | })???; 97 | 98 | Errno::result(res).map(drop) 99 | } 100 | 101 | /// Umount the file system mounted at `target`. 102 | pub fn unmount

    (target: &P, flags: MntFlags) -> Result<()> 103 | where 104 | P: ?Sized + NixPath, 105 | { 106 | let res = target.with_nix_path(|cstr| unsafe { 107 | libc::unmount(cstr.as_ptr(), flags.bits()) 108 | })?; 109 | 110 | Errno::result(res).map(drop) 111 | } 112 | -------------------------------------------------------------------------------- /src/mount/linux.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::{NixPath, Result}; 3 | use libc::{self, c_int, c_ulong}; 4 | 5 | libc_bitflags!( 6 | /// Used with [`mount`]. 7 | pub struct MsFlags: c_ulong { 8 | /// Mount read-only 9 | MS_RDONLY; 10 | /// Ignore suid and sgid bits 11 | MS_NOSUID; 12 | /// Disallow access to device special files 13 | MS_NODEV; 14 | /// Disallow program execution 15 | MS_NOEXEC; 16 | /// Writes are synced at once 17 | MS_SYNCHRONOUS; 18 | /// Alter flags of a mounted FS 19 | MS_REMOUNT; 20 | /// Allow mandatory locks on a FS 21 | MS_MANDLOCK; 22 | /// Directory modifications are synchronous 23 | MS_DIRSYNC; 24 | /// Do not update access times 25 | MS_NOATIME; 26 | /// Do not update directory access times 27 | MS_NODIRATIME; 28 | /// Linux 2.4.0 - Bind directory at different place 29 | MS_BIND; 30 | /// Move an existing mount to a new location 31 | MS_MOVE; 32 | /// Used to create a recursive bind mount. 33 | MS_REC; 34 | /// Suppress the display of certain kernel warning messages. 35 | MS_SILENT; 36 | /// VFS does not apply the umask 37 | MS_POSIXACL; 38 | /// The resulting mount cannot subsequently be bind mounted. 39 | MS_UNBINDABLE; 40 | /// Make this mount point private. 41 | MS_PRIVATE; 42 | /// If this is a shared mount point that is a member of a peer group 43 | /// that contains other members, convert it to a slave mount. 44 | MS_SLAVE; 45 | /// Make this mount point shared. 46 | MS_SHARED; 47 | /// When a file on this filesystem is accessed, update the file's 48 | /// last access time (atime) only if the current value of atime is 49 | /// less than or equal to the file's last modification time (mtime) or 50 | /// last status change time (ctime). 51 | MS_RELATIME; 52 | /// Mount request came from within the kernel 53 | #[deprecated(since = "0.27.0", note = "Should only be used in-kernel")] 54 | MS_KERNMOUNT; 55 | /// Update inode I_version field 56 | MS_I_VERSION; 57 | /// Always update the last access time (atime) when files on this 58 | /// filesystem are accessed. 59 | MS_STRICTATIME; 60 | /// Reduce on-disk updates of inode timestamps (atime, mtime, ctime) by 61 | /// maintaining these changes only in memory. 62 | MS_LAZYTIME; 63 | #[deprecated(since = "0.27.0", note = "Should only be used in-kernel")] 64 | #[allow(missing_docs)] // Not documented in Linux 65 | MS_ACTIVE; 66 | #[deprecated(since = "0.27.0", note = "Should only be used in-kernel")] 67 | #[allow(missing_docs)] // Not documented in Linux 68 | MS_NOUSER; 69 | #[allow(missing_docs)] // Not documented in Linux; possibly kernel-only 70 | MS_RMT_MASK; 71 | #[allow(missing_docs)] // Not documented in Linux; possibly kernel-only 72 | MS_MGC_VAL; 73 | #[allow(missing_docs)] // Not documented in Linux; possibly kernel-only 74 | MS_MGC_MSK; 75 | } 76 | ); 77 | 78 | libc_bitflags!( 79 | /// Used with [`umount2]. 80 | pub struct MntFlags: c_int { 81 | /// Attempt to unmount even if still in use, aborting pending requests. 82 | MNT_FORCE; 83 | /// Lazy unmount. Disconnect the file system immediately, but don't 84 | /// actually unmount it until it ceases to be busy. 85 | MNT_DETACH; 86 | /// Mark the mount point as expired. 87 | MNT_EXPIRE; 88 | /// Don't dereference `target` if it is a symlink. 89 | UMOUNT_NOFOLLOW; 90 | } 91 | ); 92 | 93 | /// Mount a file system. 94 | /// 95 | /// # Arguments 96 | /// - `source` - Specifies the file system. e.g. `/dev/sd0`. 97 | /// - `target` - Specifies the destination. e.g. `/mnt`. 98 | /// - `fstype` - The file system type, e.g. `ext4`. 99 | /// - `flags` - Optional flags controlling the mount. 100 | /// - `data` - Optional file system specific data. 101 | /// 102 | /// # See Also 103 | /// [`mount`](https://man7.org/linux/man-pages/man2/mount.2.html) 104 | pub fn mount< 105 | P1: ?Sized + NixPath, 106 | P2: ?Sized + NixPath, 107 | P3: ?Sized + NixPath, 108 | P4: ?Sized + NixPath, 109 | >( 110 | source: Option<&P1>, 111 | target: &P2, 112 | fstype: Option<&P3>, 113 | flags: MsFlags, 114 | data: Option<&P4>, 115 | ) -> Result<()> { 116 | let res = crate::with_opt_nix_path(source, |s| { 117 | target.with_nix_path(|t| { 118 | crate::with_opt_nix_path(fstype, |ty| { 119 | crate::with_opt_nix_path(data, |d| unsafe { 120 | libc::mount( 121 | s, 122 | t.as_ptr(), 123 | ty, 124 | flags.bits(), 125 | d as *const libc::c_void, 126 | ) 127 | }) 128 | }) 129 | }) 130 | })????; 131 | 132 | Errno::result(res).map(drop) 133 | } 134 | 135 | /// Unmount the file system mounted at `target`. 136 | pub fn umount(target: &P) -> Result<()> { 137 | let res = 138 | target.with_nix_path(|cstr| unsafe { libc::umount(cstr.as_ptr()) })?; 139 | 140 | Errno::result(res).map(drop) 141 | } 142 | 143 | /// Unmount the file system mounted at `target`. 144 | /// 145 | /// See also [`umount`](https://man7.org/linux/man-pages/man2/umount.2.html) 146 | pub fn umount2(target: &P, flags: MntFlags) -> Result<()> { 147 | let res = target.with_nix_path(|cstr| unsafe { 148 | libc::umount2(cstr.as_ptr(), flags.bits()) 149 | })?; 150 | 151 | Errno::result(res).map(drop) 152 | } 153 | -------------------------------------------------------------------------------- /src/mount/mod.rs: -------------------------------------------------------------------------------- 1 | //! Mount file systems 2 | #[cfg(linux_android)] 3 | mod linux; 4 | 5 | #[cfg(linux_android)] 6 | pub use self::linux::*; 7 | 8 | #[cfg(bsd_without_apple)] 9 | mod bsd_without_apple; 10 | 11 | #[cfg(bsd_without_apple)] 12 | pub use self::bsd_without_apple::*; 13 | 14 | #[cfg(apple_targets)] 15 | mod apple; 16 | 17 | #[cfg(apple_targets)] 18 | pub use self::apple::*; 19 | -------------------------------------------------------------------------------- /src/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! Functionality involving network interfaces 2 | // To avoid clashing with the keyword "if", we use "if_" as the module name. 3 | // The original header is called "net/if.h". 4 | pub mod if_; 5 | -------------------------------------------------------------------------------- /src/sys/eventfd.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::{unistd, Result}; 3 | use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; 4 | 5 | libc_bitflags! { 6 | /// Eventfd flags. 7 | pub struct EfdFlags: libc::c_int { 8 | /// Set the close-on-exec (`FD_CLOEXEC`) flag on the new event file descriptor. 9 | EFD_CLOEXEC; // Since Linux 2.6.27/FreeBSD 13.0 10 | /// Set the `O_NONBLOCK` file status flag on the new event file description. 11 | EFD_NONBLOCK; // Since Linux 2.6.27/FreeBSD 13.0 12 | /// Provide semaphore-like semantics for reads from the new event file 13 | /// descriptor. 14 | EFD_SEMAPHORE; // Since Linux 2.6.30/FreeBSD 13.0 15 | } 16 | } 17 | 18 | #[deprecated( 19 | since = "0.28.0", 20 | note = "Use EventFd::from_value_and_flags() instead" 21 | )] 22 | #[allow(missing_docs)] 23 | pub fn eventfd(initval: libc::c_uint, flags: EfdFlags) -> Result { 24 | let res = unsafe { libc::eventfd(initval, flags.bits()) }; 25 | 26 | Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r) }) 27 | } 28 | 29 | /// An eventfd file descriptor. 30 | #[derive(Debug)] 31 | #[repr(transparent)] 32 | pub struct EventFd(OwnedFd); 33 | 34 | impl EventFd { 35 | /// [`EventFd::from_value_and_flags`] with `init_val = 0` and `flags = EfdFlags::empty()`. 36 | pub fn new() -> Result { 37 | Self::from_value_and_flags(0, EfdFlags::empty()) 38 | } 39 | 40 | /// Constructs [`EventFd`] with the given `init_val` and `flags`. 41 | /// 42 | /// Wrapper around [`libc::eventfd`]. 43 | pub fn from_value_and_flags( 44 | init_val: u32, 45 | flags: EfdFlags, 46 | ) -> Result { 47 | let res = unsafe { libc::eventfd(init_val, flags.bits()) }; 48 | Errno::result(res).map(|r| Self(unsafe { OwnedFd::from_raw_fd(r) })) 49 | } 50 | 51 | /// [`EventFd::from_value_and_flags`] with `init_val = 0` and given `flags`. 52 | pub fn from_flags(flags: EfdFlags) -> Result { 53 | Self::from_value_and_flags(0, flags) 54 | } 55 | 56 | /// [`EventFd::from_value_and_flags`] with given `init_val` and `flags = EfdFlags::empty()`. 57 | pub fn from_value(init_val: u32) -> Result { 58 | Self::from_value_and_flags(init_val, EfdFlags::empty()) 59 | } 60 | 61 | /// Constructs an `EventFd` wrapping an existing `OwnedFd`. 62 | /// 63 | /// # Safety 64 | /// 65 | /// `OwnedFd` is a valid eventfd. 66 | pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { 67 | Self(fd) 68 | } 69 | 70 | /// Enqueues `value` triggers, i.e., adds the integer value supplied in `value` 71 | /// to the counter. 72 | /// 73 | /// The next `value` calls to `poll`, `select` or `epoll` will return immediately. 74 | /// 75 | /// [`EventFd::write`] with `value`. 76 | pub fn write(&self, value: u64) -> Result { 77 | unistd::write(&self.0, &value.to_ne_bytes()) 78 | } 79 | 80 | /// Reads the value from the file descriptor. 81 | /// 82 | /// * If [`EFD_SEMAPHORE`](EfdFlags::EFD_SEMAPHORE) was not specified and 83 | /// the eventfd counter has a nonzero value, then this function returns 84 | /// an `u64` containing that value, and the counter's value is reset to 85 | /// zero. 86 | /// 87 | /// * If [`EFD_SEMAPHORE`](EfdFlags::EFD_SEMAPHORE) was specified and the 88 | /// eventfd counter has a nonzero value, then this function returns an 89 | /// `u64` containing the value 1, and the counter's value is decremented 90 | /// by 1. 91 | /// 92 | /// * If the eventfd counter is zero at the time of this call, then the 93 | /// call either blocks until the counter becomes nonzero (at which time, 94 | /// this function proceeds as described above) or fails with the error 95 | /// `EAGAIN` if the file descriptor has been made nonblocking with 96 | /// [`EFD_NONBLOCK`](EfdFlags::EFD_NONBLOCK). 97 | pub fn read(&self) -> Result { 98 | let mut arr = [0; std::mem::size_of::()]; 99 | unistd::read(&self.0, &mut arr)?; 100 | Ok(u64::from_ne_bytes(arr)) 101 | } 102 | } 103 | impl AsFd for EventFd { 104 | fn as_fd(&self) -> BorrowedFd { 105 | self.0.as_fd() 106 | } 107 | } 108 | impl AsRawFd for EventFd { 109 | fn as_raw_fd(&self) -> RawFd { 110 | self.0.as_raw_fd() 111 | } 112 | } 113 | 114 | impl From for OwnedFd { 115 | fn from(value: EventFd) -> Self { 116 | value.0 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/sys/ioctl/bsd.rs: -------------------------------------------------------------------------------- 1 | /// The datatype used for the ioctl number 2 | #[doc(hidden)] 3 | #[cfg(not(solarish))] 4 | pub type ioctl_num_type = ::libc::c_ulong; 5 | 6 | #[doc(hidden)] 7 | #[cfg(solarish)] 8 | pub type ioctl_num_type = ::libc::c_int; 9 | 10 | /// The datatype used for the 3rd argument 11 | #[doc(hidden)] 12 | pub type ioctl_param_type = ::libc::c_int; 13 | 14 | mod consts { 15 | use crate::sys::ioctl::ioctl_num_type; 16 | #[doc(hidden)] 17 | pub const VOID: ioctl_num_type = 0x2000_0000; 18 | #[doc(hidden)] 19 | pub const OUT: ioctl_num_type = 0x4000_0000; 20 | #[doc(hidden)] 21 | #[allow(overflowing_literals)] 22 | pub const IN: ioctl_num_type = 0x8000_0000; 23 | #[doc(hidden)] 24 | pub const INOUT: ioctl_num_type = IN | OUT; 25 | #[doc(hidden)] 26 | pub const IOCPARM_MASK: ioctl_num_type = 0x1fff; 27 | } 28 | 29 | pub use self::consts::*; 30 | 31 | #[macro_export] 32 | #[doc(hidden)] 33 | macro_rules! ioc { 34 | ($inout:expr, $group:expr, $num:expr, $len:expr) => { 35 | $inout 36 | | (($len as $crate::sys::ioctl::ioctl_num_type 37 | & $crate::sys::ioctl::IOCPARM_MASK) 38 | << 16) 39 | | (($group as $crate::sys::ioctl::ioctl_num_type) << 8) 40 | | ($num as $crate::sys::ioctl::ioctl_num_type) 41 | }; 42 | } 43 | 44 | /// Generate an ioctl request code for a command that passes no data. 45 | /// 46 | /// This is equivalent to the `_IO()` macro exposed by the C ioctl API. 47 | /// 48 | /// You should only use this macro directly if the `ioctl` you're working 49 | /// with is "bad" and you cannot use `ioctl_none!()` directly. 50 | /// 51 | /// # Example 52 | /// 53 | /// ``` 54 | /// # #[macro_use] extern crate nix; 55 | /// const KVMIO: u8 = 0xAE; 56 | /// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); 57 | /// # fn main() {} 58 | /// ``` 59 | #[macro_export(local_inner_macros)] 60 | macro_rules! request_code_none { 61 | ($g:expr, $n:expr) => { 62 | ioc!($crate::sys::ioctl::VOID, $g, $n, 0) 63 | }; 64 | } 65 | 66 | /// Generate an ioctl request code for a command that passes an integer 67 | /// 68 | /// This is equivalent to the `_IOWINT()` macro exposed by the C ioctl API. 69 | /// 70 | /// You should only use this macro directly if the `ioctl` you're working 71 | /// with is "bad" and you cannot use `ioctl_write_int!()` directly. 72 | #[macro_export(local_inner_macros)] 73 | macro_rules! request_code_write_int { 74 | ($g:expr, $n:expr) => { 75 | ioc!( 76 | $crate::sys::ioctl::VOID, 77 | $g, 78 | $n, 79 | ::std::mem::size_of::<$crate::libc::c_int>() 80 | ) 81 | }; 82 | } 83 | 84 | /// Generate an ioctl request code for a command that reads. 85 | /// 86 | /// This is equivalent to the `_IOR()` macro exposed by the C ioctl API. 87 | /// 88 | /// You should only use this macro directly if the `ioctl` you're working 89 | /// with is "bad" and you cannot use `ioctl_read!()` directly. 90 | /// 91 | /// The read/write direction is relative to userland, so this 92 | /// command would be userland is reading and the kernel is 93 | /// writing. 94 | #[macro_export(local_inner_macros)] 95 | macro_rules! request_code_read { 96 | ($g:expr, $n:expr, $len:expr) => { 97 | ioc!($crate::sys::ioctl::OUT, $g, $n, $len) 98 | }; 99 | } 100 | 101 | /// Generate an ioctl request code for a command that writes. 102 | /// 103 | /// This is equivalent to the `_IOW()` macro exposed by the C ioctl API. 104 | /// 105 | /// You should only use this macro directly if the `ioctl` you're working 106 | /// with is "bad" and you cannot use `ioctl_write!()` directly. 107 | /// 108 | /// The read/write direction is relative to userland, so this 109 | /// command would be userland is writing and the kernel is 110 | /// reading. 111 | #[macro_export(local_inner_macros)] 112 | macro_rules! request_code_write { 113 | ($g:expr, $n:expr, $len:expr) => { 114 | ioc!($crate::sys::ioctl::IN, $g, $n, $len) 115 | }; 116 | } 117 | 118 | /// Generate an ioctl request code for a command that reads and writes. 119 | /// 120 | /// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API. 121 | /// 122 | /// You should only use this macro directly if the `ioctl` you're working 123 | /// with is "bad" and you cannot use `ioctl_readwrite!()` directly. 124 | #[macro_export(local_inner_macros)] 125 | macro_rules! request_code_readwrite { 126 | ($g:expr, $n:expr, $len:expr) => { 127 | ioc!($crate::sys::ioctl::INOUT, $g, $n, $len) 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /src/sys/ioctl/linux.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | /// The datatype used for the ioctl number 4 | #[cfg(any( 5 | target_os = "android", 6 | target_os = "fuchsia", 7 | target_env = "musl", 8 | target_env = "ohos" 9 | ))] 10 | #[doc(hidden)] 11 | pub type ioctl_num_type = ::libc::c_int; 12 | #[cfg(not(any( 13 | target_os = "android", 14 | target_os = "fuchsia", 15 | target_env = "musl", 16 | target_env = "ohos" 17 | )))] 18 | #[doc(hidden)] 19 | pub type ioctl_num_type = ::libc::c_ulong; 20 | /// The datatype used for the 3rd argument 21 | #[doc(hidden)] 22 | pub type ioctl_param_type = ::libc::c_ulong; 23 | 24 | #[doc(hidden)] 25 | pub const NRBITS: ioctl_num_type = 8; 26 | #[doc(hidden)] 27 | pub const TYPEBITS: ioctl_num_type = 8; 28 | 29 | cfg_if! { 30 | if #[cfg(any( 31 | target_arch = "mips", 32 | target_arch = "mips32r6", 33 | target_arch = "mips64", 34 | target_arch = "mips64r6", 35 | target_arch = "powerpc", 36 | target_arch = "powerpc64", 37 | target_arch = "sparc64" 38 | ))] { 39 | mod consts { 40 | #[doc(hidden)] 41 | pub const NONE: u8 = 1; 42 | #[doc(hidden)] 43 | pub const READ: u8 = 2; 44 | #[doc(hidden)] 45 | pub const WRITE: u8 = 4; 46 | #[doc(hidden)] 47 | pub const SIZEBITS: u8 = 13; 48 | #[doc(hidden)] 49 | pub const DIRBITS: u8 = 3; 50 | } 51 | } else { 52 | // "Generic" ioctl protocol 53 | mod consts { 54 | #[doc(hidden)] 55 | pub const NONE: u8 = 0; 56 | #[doc(hidden)] 57 | pub const READ: u8 = 2; 58 | #[doc(hidden)] 59 | pub const WRITE: u8 = 1; 60 | #[doc(hidden)] 61 | pub const SIZEBITS: u8 = 14; 62 | #[doc(hidden)] 63 | pub const DIRBITS: u8 = 2; 64 | } 65 | } 66 | } 67 | 68 | pub use self::consts::*; 69 | 70 | #[doc(hidden)] 71 | pub const NRSHIFT: ioctl_num_type = 0; 72 | #[doc(hidden)] 73 | pub const TYPESHIFT: ioctl_num_type = NRSHIFT + NRBITS as ioctl_num_type; 74 | #[doc(hidden)] 75 | pub const SIZESHIFT: ioctl_num_type = TYPESHIFT + TYPEBITS as ioctl_num_type; 76 | #[doc(hidden)] 77 | pub const DIRSHIFT: ioctl_num_type = SIZESHIFT + SIZEBITS as ioctl_num_type; 78 | 79 | #[doc(hidden)] 80 | pub const NRMASK: ioctl_num_type = (1 << NRBITS) - 1; 81 | #[doc(hidden)] 82 | pub const TYPEMASK: ioctl_num_type = (1 << TYPEBITS) - 1; 83 | #[doc(hidden)] 84 | pub const SIZEMASK: ioctl_num_type = (1 << SIZEBITS) - 1; 85 | #[doc(hidden)] 86 | pub const DIRMASK: ioctl_num_type = (1 << DIRBITS) - 1; 87 | 88 | /// Encode an ioctl command. 89 | #[macro_export] 90 | #[doc(hidden)] 91 | macro_rules! ioc { 92 | ($dir:expr, $ty:expr, $nr:expr, $sz:expr) => { 93 | (($dir as $crate::sys::ioctl::ioctl_num_type 94 | & $crate::sys::ioctl::DIRMASK) 95 | << $crate::sys::ioctl::DIRSHIFT) 96 | | (($ty as $crate::sys::ioctl::ioctl_num_type 97 | & $crate::sys::ioctl::TYPEMASK) 98 | << $crate::sys::ioctl::TYPESHIFT) 99 | | (($nr as $crate::sys::ioctl::ioctl_num_type 100 | & $crate::sys::ioctl::NRMASK) 101 | << $crate::sys::ioctl::NRSHIFT) 102 | | (($sz as $crate::sys::ioctl::ioctl_num_type 103 | & $crate::sys::ioctl::SIZEMASK) 104 | << $crate::sys::ioctl::SIZESHIFT) 105 | }; 106 | } 107 | 108 | /// Generate an ioctl request code for a command that passes no data. 109 | /// 110 | /// This is equivalent to the `_IO()` macro exposed by the C ioctl API. 111 | /// 112 | /// You should only use this macro directly if the `ioctl` you're working 113 | /// with is "bad" and you cannot use `ioctl_none!()` directly. 114 | /// 115 | /// # Example 116 | /// 117 | /// ``` 118 | /// # #[macro_use] extern crate nix; 119 | /// const KVMIO: u8 = 0xAE; 120 | /// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03)); 121 | /// # fn main() {} 122 | /// ``` 123 | #[macro_export(local_inner_macros)] 124 | macro_rules! request_code_none { 125 | ($ty:expr, $nr:expr) => { 126 | ioc!($crate::sys::ioctl::NONE, $ty, $nr, 0) 127 | }; 128 | } 129 | 130 | /// Generate an ioctl request code for a command that reads. 131 | /// 132 | /// This is equivalent to the `_IOR()` macro exposed by the C ioctl API. 133 | /// 134 | /// You should only use this macro directly if the `ioctl` you're working 135 | /// with is "bad" and you cannot use `ioctl_read!()` directly. 136 | /// 137 | /// The read/write direction is relative to userland, so this 138 | /// command would be userland is reading and the kernel is 139 | /// writing. 140 | #[macro_export(local_inner_macros)] 141 | macro_rules! request_code_read { 142 | ($ty:expr, $nr:expr, $sz:expr) => { 143 | ioc!($crate::sys::ioctl::READ, $ty, $nr, $sz) 144 | }; 145 | } 146 | 147 | /// Generate an ioctl request code for a command that writes. 148 | /// 149 | /// This is equivalent to the `_IOW()` macro exposed by the C ioctl API. 150 | /// 151 | /// You should only use this macro directly if the `ioctl` you're working 152 | /// with is "bad" and you cannot use `ioctl_write!()` directly. 153 | /// 154 | /// The read/write direction is relative to userland, so this 155 | /// command would be userland is writing and the kernel is 156 | /// reading. 157 | #[macro_export(local_inner_macros)] 158 | macro_rules! request_code_write { 159 | ($ty:expr, $nr:expr, $sz:expr) => { 160 | ioc!($crate::sys::ioctl::WRITE, $ty, $nr, $sz) 161 | }; 162 | } 163 | 164 | /// Generate an ioctl request code for a command that reads and writes. 165 | /// 166 | /// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API. 167 | /// 168 | /// You should only use this macro directly if the `ioctl` you're working 169 | /// with is "bad" and you cannot use `ioctl_readwrite!()` directly. 170 | #[macro_export(local_inner_macros)] 171 | macro_rules! request_code_readwrite { 172 | ($ty:expr, $nr:expr, $sz:expr) => { 173 | ioc!( 174 | $crate::sys::ioctl::READ | $crate::sys::ioctl::WRITE, 175 | $ty, 176 | $nr, 177 | $sz 178 | ) 179 | }; 180 | } 181 | -------------------------------------------------------------------------------- /src/sys/memfd.rs: -------------------------------------------------------------------------------- 1 | //! Interfaces for managing memory-backed files. 2 | 3 | use cfg_if::cfg_if; 4 | use std::os::unix::io::{FromRawFd, OwnedFd, RawFd}; 5 | 6 | use crate::errno::Errno; 7 | use crate::{NixPath, Result}; 8 | 9 | libc_bitflags!( 10 | /// Options that change the behavior of [`memfd_create`]. 11 | pub struct MFdFlags: libc::c_uint { 12 | /// Set the close-on-exec ([`FD_CLOEXEC`]) flag on the new file descriptor. 13 | /// 14 | /// By default, the new file descriptor is set to remain open across an [`execve`] 15 | /// (the `FD_CLOEXEC` flag is initially disabled). This flag can be used to change 16 | /// this default. The file offset is set to the beginning of the file (see [`lseek`]). 17 | /// 18 | /// See also the description of the `O_CLOEXEC` flag in [`open(2)`]. 19 | /// 20 | /// [`execve`]: crate::unistd::execve 21 | /// [`lseek`]: crate::unistd::lseek 22 | /// [`FD_CLOEXEC`]: crate::fcntl::FdFlag::FD_CLOEXEC 23 | /// [`open(2)`]: https://man7.org/linux/man-pages/man2/open.2.html 24 | MFD_CLOEXEC; 25 | /// Allow sealing operations on this file. 26 | /// 27 | /// See also the file sealing notes given in [`memfd_create(2)`]. 28 | /// 29 | /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html 30 | MFD_ALLOW_SEALING; 31 | /// Anonymous file will be created using huge pages. It should be safe now to 32 | /// combine with [`MFD_ALLOW_SEALING`] too. 33 | /// However, despite its presence, on FreeBSD it is unimplemented for now (ENOSYS). 34 | /// 35 | /// See also the hugetlb filesystem in [`memfd_create(2)`]. 36 | /// 37 | /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html 38 | #[cfg(linux_android)] 39 | MFD_HUGETLB; 40 | /// Shift to get the huge page size. 41 | #[cfg(target_env = "ohos")] 42 | MFD_HUGE_SHIFT; 43 | /// Mask to get the huge page size. 44 | #[cfg(target_env = "ohos")] 45 | MFD_HUGE_MASK; 46 | /// hugetlb size of 64KB. 47 | #[cfg(target_env = "ohos")] 48 | MFD_HUGE_64KB; 49 | /// hugetlb size of 512KB. 50 | #[cfg(target_env = "ohos")] 51 | MFD_HUGE_512KB; 52 | /// Following are to be used with [`MFD_HUGETLB`], indicating the desired hugetlb size. 53 | /// 54 | /// See also the hugetlb filesystem in [`memfd_create(2)`]. 55 | /// 56 | /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html 57 | #[cfg(linux_android)] 58 | MFD_HUGE_1MB; 59 | /// hugetlb size of 2MB. 60 | #[cfg(linux_android)] 61 | MFD_HUGE_2MB; 62 | /// hugetlb size of 8MB. 63 | #[cfg(linux_android)] 64 | MFD_HUGE_8MB; 65 | /// hugetlb size of 16MB. 66 | #[cfg(linux_android)] 67 | MFD_HUGE_16MB; 68 | /// hugetlb size of 32MB. 69 | #[cfg(linux_android)] 70 | MFD_HUGE_32MB; 71 | /// hugetlb size of 256MB. 72 | #[cfg(linux_android)] 73 | MFD_HUGE_256MB; 74 | /// hugetlb size of 512MB. 75 | #[cfg(linux_android)] 76 | MFD_HUGE_512MB; 77 | /// hugetlb size of 1GB. 78 | #[cfg(linux_android)] 79 | MFD_HUGE_1GB; 80 | /// hugetlb size of 2GB. 81 | #[cfg(linux_android)] 82 | MFD_HUGE_2GB; 83 | /// hugetlb size of 16GB. 84 | #[cfg(linux_android)] 85 | MFD_HUGE_16GB; 86 | } 87 | ); 88 | 89 | #[deprecated(since = "0.30.0", note = "Use `MFdFlags instead`")] 90 | /// The deprecated MemFdCreateFlag type alias 91 | pub type MemFdCreateFlag = MFdFlags; 92 | 93 | /// Creates an anonymous file that lives in memory, and return a file-descriptor to it. 94 | /// 95 | /// The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on. 96 | /// However, unlike a regular file, it lives in RAM and has a volatile backing storage. 97 | /// 98 | /// For more information, see [`memfd_create(2)`]. 99 | /// 100 | /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html 101 | #[inline] // Delays codegen, preventing linker errors with dylibs and --no-allow-shlib-undefined 102 | pub fn memfd_create( 103 | name: &P, 104 | flags: MFdFlags, 105 | ) -> Result { 106 | let res = name.with_nix_path(|cstr| { 107 | unsafe { 108 | cfg_if! { 109 | if #[cfg(all( 110 | // Android does not have a memfd_create symbol 111 | not(target_os = "android"), 112 | any( 113 | target_os = "freebsd", 114 | // If the OS is Linux, gnu/musl/ohos expose a memfd_create symbol but not uclibc 115 | target_env = "gnu", 116 | target_env = "musl", 117 | target_env = "ohos" 118 | )))] 119 | { 120 | libc::memfd_create(cstr.as_ptr(), flags.bits()) 121 | } else { 122 | libc::syscall(libc::SYS_memfd_create, cstr.as_ptr(), flags.bits()) 123 | } 124 | } 125 | } 126 | })?; 127 | 128 | Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r as RawFd) }) 129 | } 130 | -------------------------------------------------------------------------------- /src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Mostly platform-specific functionality 2 | #[cfg(any( 3 | freebsdlike, 4 | all( 5 | target_os = "linux", 6 | not(any(target_env = "uclibc", target_env = "ohos")) 7 | ), 8 | apple_targets, 9 | target_os = "netbsd" 10 | ))] 11 | feature! { 12 | #![feature = "aio"] 13 | pub mod aio; 14 | } 15 | 16 | feature! { 17 | #![feature = "event"] 18 | 19 | #[cfg(linux_android)] 20 | #[allow(missing_docs)] 21 | pub mod epoll; 22 | 23 | #[cfg(bsd)] 24 | pub mod event; 25 | 26 | /// Event file descriptor. 27 | #[cfg(any(linux_android, target_os = "freebsd"))] 28 | pub mod eventfd; 29 | } 30 | 31 | #[cfg(target_os = "linux")] 32 | feature! { 33 | #![feature = "fanotify"] 34 | pub mod fanotify; 35 | } 36 | 37 | #[cfg(any( 38 | bsd, 39 | linux_android, 40 | solarish, 41 | target_os = "fuchsia", 42 | target_os = "redox", 43 | ))] 44 | #[cfg(feature = "ioctl")] 45 | #[cfg_attr(docsrs, doc(cfg(feature = "ioctl")))] 46 | #[macro_use] 47 | pub mod ioctl; 48 | 49 | #[cfg(any(linux_android, target_os = "freebsd"))] 50 | feature! { 51 | #![feature = "fs"] 52 | pub mod memfd; 53 | } 54 | 55 | feature! { 56 | #![feature = "mman"] 57 | pub mod mman; 58 | } 59 | 60 | #[cfg(target_os = "linux")] 61 | feature! { 62 | #![feature = "personality"] 63 | pub mod personality; 64 | } 65 | 66 | #[cfg(target_os = "linux")] 67 | feature! { 68 | #![feature = "process"] 69 | pub mod prctl; 70 | } 71 | 72 | feature! { 73 | #![feature = "pthread"] 74 | pub mod pthread; 75 | } 76 | 77 | #[cfg(any(linux_android, bsd))] 78 | feature! { 79 | #![feature = "ptrace"] 80 | #[allow(missing_docs)] 81 | pub mod ptrace; 82 | } 83 | 84 | #[cfg(target_os = "linux")] 85 | feature! { 86 | #![feature = "quota"] 87 | pub mod quota; 88 | } 89 | 90 | #[cfg(any(target_os = "linux", netbsdlike))] 91 | feature! { 92 | #![feature = "reboot"] 93 | pub mod reboot; 94 | } 95 | 96 | #[cfg(not(any( 97 | target_os = "redox", 98 | target_os = "fuchsia", 99 | solarish, 100 | target_os = "haiku" 101 | )))] 102 | feature! { 103 | #![feature = "resource"] 104 | pub mod resource; 105 | } 106 | 107 | feature! { 108 | #![feature = "poll"] 109 | pub mod select; 110 | } 111 | 112 | #[cfg(any(linux_android, freebsdlike, apple_targets, solarish))] 113 | feature! { 114 | #![feature = "zerocopy"] 115 | pub mod sendfile; 116 | } 117 | 118 | pub mod signal; 119 | 120 | #[cfg(linux_android)] 121 | feature! { 122 | #![feature = "signal"] 123 | #[allow(missing_docs)] 124 | pub mod signalfd; 125 | } 126 | 127 | feature! { 128 | #![feature = "socket"] 129 | #[allow(missing_docs)] 130 | pub mod socket; 131 | } 132 | 133 | feature! { 134 | #![feature = "fs"] 135 | #[allow(missing_docs)] 136 | pub mod stat; 137 | } 138 | 139 | #[cfg(any( 140 | linux_android, 141 | freebsdlike, 142 | apple_targets, 143 | target_os = "openbsd", 144 | target_os = "cygwin" 145 | ))] 146 | feature! { 147 | #![feature = "fs"] 148 | pub mod statfs; 149 | } 150 | 151 | feature! { 152 | #![feature = "fs"] 153 | pub mod statvfs; 154 | } 155 | 156 | #[cfg(linux_android)] 157 | #[allow(missing_docs)] 158 | pub mod sysinfo; 159 | 160 | feature! { 161 | #![feature = "term"] 162 | #[allow(missing_docs)] 163 | pub mod termios; 164 | } 165 | 166 | #[allow(missing_docs)] 167 | pub mod time; 168 | 169 | feature! { 170 | #![feature = "uio"] 171 | pub mod uio; 172 | } 173 | 174 | feature! { 175 | #![feature = "feature"] 176 | pub mod utsname; 177 | } 178 | 179 | feature! { 180 | #![feature = "process"] 181 | pub mod wait; 182 | } 183 | 184 | #[cfg(linux_android)] 185 | feature! { 186 | #![feature = "inotify"] 187 | pub mod inotify; 188 | } 189 | 190 | #[cfg(linux_android)] 191 | feature! { 192 | #![feature = "time"] 193 | pub mod timerfd; 194 | } 195 | 196 | #[cfg(all( 197 | any( 198 | target_os = "freebsd", 199 | solarish, 200 | target_os = "linux", 201 | target_os = "netbsd" 202 | ), 203 | feature = "time", 204 | feature = "signal" 205 | ))] 206 | feature! { 207 | #![feature = "time"] 208 | pub mod timer; 209 | } 210 | -------------------------------------------------------------------------------- /src/sys/personality.rs: -------------------------------------------------------------------------------- 1 | //! Process execution domains 2 | use crate::errno::Errno; 3 | use crate::Result; 4 | 5 | use libc::{self, c_int, c_ulong}; 6 | 7 | libc_bitflags! { 8 | /// Flags used and returned by [`get()`](fn.get.html) and 9 | /// [`set()`](fn.set.html). 10 | pub struct Persona: c_int { 11 | /// Provide the legacy virtual address space layout. 12 | ADDR_COMPAT_LAYOUT; 13 | /// Disable address-space-layout randomization. 14 | ADDR_NO_RANDOMIZE; 15 | /// Limit the address space to 32 bits. 16 | ADDR_LIMIT_32BIT; 17 | /// Use `0xc0000000` as the offset at which to search a virtual memory 18 | /// chunk on [`mmap(2)`], otherwise use `0xffffe000`. 19 | /// 20 | /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html 21 | ADDR_LIMIT_3GB; 22 | /// User-space function pointers to signal handlers point to descriptors. 23 | #[cfg(not(any(target_env = "musl", target_env = "uclibc", target_env = "ohos")))] 24 | FDPIC_FUNCPTRS; 25 | /// Map page 0 as read-only. 26 | MMAP_PAGE_ZERO; 27 | /// `PROT_READ` implies `PROT_EXEC` for [`mmap(2)`]. 28 | /// 29 | /// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html 30 | READ_IMPLIES_EXEC; 31 | /// No effects. 32 | SHORT_INODE; 33 | /// [`select(2)`], [`pselect(2)`], and [`ppoll(2)`] do not modify the 34 | /// returned timeout argument when interrupted by a signal handler. 35 | /// 36 | /// [`select(2)`]: https://man7.org/linux/man-pages/man2/select.2.html 37 | /// [`pselect(2)`]: https://man7.org/linux/man-pages/man2/pselect.2.html 38 | /// [`ppoll(2)`]: https://man7.org/linux/man-pages/man2/ppoll.2.html 39 | STICKY_TIMEOUTS; 40 | /// Have [`uname(2)`] report a 2.6.40+ version number rather than a 3.x 41 | /// version number. 42 | /// 43 | /// [`uname(2)`]: https://man7.org/linux/man-pages/man2/uname.2.html 44 | #[cfg(not(any(target_env = "musl", target_env = "uclibc", target_env = "ohos")))] 45 | UNAME26; 46 | /// No effects. 47 | WHOLE_SECONDS; 48 | } 49 | } 50 | 51 | /// Retrieve the current process personality. 52 | /// 53 | /// Returns a Result containing a Persona instance. 54 | /// 55 | /// Example: 56 | /// 57 | /// ``` 58 | /// # use nix::sys::personality::{self, Persona}; 59 | /// let pers = personality::get().unwrap(); 60 | /// assert!(!pers.contains(Persona::WHOLE_SECONDS)); 61 | /// ``` 62 | pub fn get() -> Result { 63 | let res = unsafe { libc::personality(0xFFFFFFFF) }; 64 | 65 | Errno::result(res).map(Persona::from_bits_truncate) 66 | } 67 | 68 | /// Set the current process personality. 69 | /// 70 | /// Returns a Result containing the *previous* personality for the 71 | /// process, as a Persona. 72 | /// 73 | /// For more information, see [personality(2)](https://man7.org/linux/man-pages/man2/personality.2.html) 74 | /// 75 | /// **NOTE**: This call **replaces** the current personality entirely. 76 | /// To **update** the personality, first call `get()` and then `set()` 77 | /// with the modified persona. 78 | /// 79 | /// Example: 80 | /// 81 | // Disable test on aarch64 until we know why it fails. 82 | // https://github.com/nix-rust/nix/issues/2060 83 | #[cfg_attr(target_arch = "aarch64", doc = " ```no_run")] 84 | #[cfg_attr(not(target_arch = "aarch64"), doc = " ```")] 85 | /// # use nix::sys::personality::{self, Persona}; 86 | /// let mut pers = personality::get().unwrap(); 87 | /// assert!(!pers.contains(Persona::ADDR_NO_RANDOMIZE)); 88 | /// personality::set(pers | Persona::ADDR_NO_RANDOMIZE).unwrap(); 89 | /// ``` 90 | pub fn set(persona: Persona) -> Result { 91 | let res = unsafe { libc::personality(persona.bits() as c_ulong) }; 92 | 93 | Errno::result(res).map(Persona::from_bits_truncate) 94 | } 95 | -------------------------------------------------------------------------------- /src/sys/pthread.rs: -------------------------------------------------------------------------------- 1 | //! Low level threading primitives 2 | 3 | #[cfg(not(target_os = "redox"))] 4 | use crate::errno::Errno; 5 | #[cfg(not(target_os = "redox"))] 6 | use crate::Result; 7 | use libc::{self, pthread_t}; 8 | 9 | /// Identifies an individual thread. 10 | pub type Pthread = pthread_t; 11 | 12 | /// Obtain ID of the calling thread (see 13 | /// [`pthread_self(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html) 14 | /// 15 | /// The thread ID returned by `pthread_self()` is not the same thing as 16 | /// the kernel thread ID returned by a call to `gettid(2)`. 17 | #[inline] 18 | pub fn pthread_self() -> Pthread { 19 | unsafe { libc::pthread_self() } 20 | } 21 | 22 | feature! { 23 | #![feature = "signal"] 24 | 25 | /// Send a signal to a thread (see [`pthread_kill(3)`]). 26 | /// 27 | /// If `signal` is `None`, `pthread_kill` will only preform error checking and 28 | /// won't send any signal. 29 | /// 30 | /// [`pthread_kill(3)`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html 31 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 32 | #[cfg(not(target_os = "redox"))] 33 | pub fn pthread_kill(thread: Pthread, signal: T) -> Result<()> 34 | where T: Into> 35 | { 36 | let sig = match signal.into() { 37 | Some(s) => s as libc::c_int, 38 | None => 0, 39 | }; 40 | let res = unsafe { libc::pthread_kill(thread, sig) }; 41 | Errno::result(res).map(drop) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/sys/ptrace/bsd.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::sys::signal::Signal; 3 | use crate::unistd::Pid; 4 | use crate::Result; 5 | use cfg_if::cfg_if; 6 | use libc::{self, c_int}; 7 | use std::ptr; 8 | 9 | pub type RequestType = c_int; 10 | 11 | cfg_if! { 12 | if #[cfg(any(freebsdlike, apple_targets, target_os = "openbsd"))] { 13 | #[doc(hidden)] 14 | pub type AddressType = *mut ::libc::c_char; 15 | } else { 16 | #[doc(hidden)] 17 | pub type AddressType = *mut ::libc::c_void; 18 | } 19 | } 20 | 21 | libc_enum! { 22 | #[repr(i32)] 23 | /// Ptrace Request enum defining the action to be taken. 24 | #[non_exhaustive] 25 | pub enum Request { 26 | PT_TRACE_ME, 27 | PT_READ_I, 28 | PT_READ_D, 29 | #[cfg(apple_targets)] 30 | PT_READ_U, 31 | PT_WRITE_I, 32 | PT_WRITE_D, 33 | #[cfg(apple_targets)] 34 | PT_WRITE_U, 35 | PT_CONTINUE, 36 | PT_KILL, 37 | #[cfg(any(any(freebsdlike, apple_targets), 38 | all(target_os = "openbsd", target_arch = "x86_64"), 39 | all(target_os = "netbsd", any(target_arch = "x86_64", 40 | target_arch = "powerpc"))))] 41 | PT_STEP, 42 | PT_ATTACH, 43 | PT_DETACH, 44 | #[cfg(apple_targets)] 45 | PT_SIGEXC, 46 | #[cfg(apple_targets)] 47 | PT_THUPDATE, 48 | #[cfg(apple_targets)] 49 | PT_ATTACHEXC 50 | } 51 | } 52 | 53 | unsafe fn ptrace_other( 54 | request: Request, 55 | pid: Pid, 56 | addr: AddressType, 57 | data: c_int, 58 | ) -> Result { 59 | unsafe { 60 | Errno::result(libc::ptrace( 61 | request as RequestType, 62 | libc::pid_t::from(pid), 63 | addr, 64 | data, 65 | )) 66 | .map(|_| 0) 67 | } 68 | } 69 | 70 | /// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)` 71 | /// 72 | /// Indicates that this process is to be traced by its parent. 73 | /// This is the only ptrace request to be issued by the tracee. 74 | pub fn traceme() -> Result<()> { 75 | unsafe { 76 | ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0) 77 | .map(drop) 78 | } 79 | } 80 | 81 | /// Attach to a running process, as with `ptrace(PT_ATTACH, ...)` 82 | /// 83 | /// Attaches to the process specified by `pid`, making it a tracee of the calling process. 84 | pub fn attach(pid: Pid) -> Result<()> { 85 | unsafe { 86 | ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) 87 | } 88 | } 89 | 90 | /// Detaches the current running process, as with `ptrace(PT_DETACH, ...)` 91 | /// 92 | /// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a 93 | /// signal specified by `sig`. 94 | pub fn detach>>(pid: Pid, sig: T) -> Result<()> { 95 | let data = match sig.into() { 96 | Some(s) => s as c_int, 97 | None => 0, 98 | }; 99 | unsafe { 100 | ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop) 101 | } 102 | } 103 | 104 | /// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` 105 | /// 106 | /// Continues the execution of the process with PID `pid`, optionally 107 | /// delivering a signal specified by `sig`. 108 | pub fn cont>>(pid: Pid, sig: T) -> Result<()> { 109 | let data = match sig.into() { 110 | Some(s) => s as c_int, 111 | None => 0, 112 | }; 113 | unsafe { 114 | // Ignore the useless return value 115 | ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data) 116 | .map(drop) 117 | } 118 | } 119 | 120 | /// Issues a kill request as with `ptrace(PT_KILL, ...)` 121 | /// 122 | /// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);` 123 | pub fn kill(pid: Pid) -> Result<()> { 124 | unsafe { 125 | ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(drop) 126 | } 127 | } 128 | 129 | /// Move the stopped tracee process forward by a single step as with 130 | /// `ptrace(PT_STEP, ...)` 131 | /// 132 | /// Advances the execution of the process with PID `pid` by a single step optionally delivering a 133 | /// signal specified by `sig`. 134 | /// 135 | /// # Example 136 | /// ```rust 137 | /// use nix::sys::ptrace::step; 138 | /// use nix::unistd::Pid; 139 | /// use nix::sys::signal::Signal; 140 | /// use nix::sys::wait::*; 141 | /// // If a process changes state to the stopped state because of a SIGUSR1 142 | /// // signal, this will step the process forward and forward the user 143 | /// // signal to the stopped process 144 | /// match waitpid(Pid::from_raw(-1), None) { 145 | /// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { 146 | /// let _ = step(pid, Signal::SIGUSR1); 147 | /// } 148 | /// _ => {}, 149 | /// } 150 | /// ``` 151 | #[cfg(any( 152 | any(freebsdlike, apple_targets), 153 | all(target_os = "openbsd", target_arch = "x86_64"), 154 | all( 155 | target_os = "netbsd", 156 | any(target_arch = "x86_64", target_arch = "powerpc") 157 | ) 158 | ))] 159 | pub fn step>>(pid: Pid, sig: T) -> Result<()> { 160 | let data = match sig.into() { 161 | Some(s) => s as c_int, 162 | None => 0, 163 | }; 164 | unsafe { 165 | ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(drop) 166 | } 167 | } 168 | 169 | /// Reads a word from a processes memory at the given address 170 | // Technically, ptrace doesn't dereference the pointer. It passes it directly 171 | // to the kernel. 172 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 173 | pub fn read(pid: Pid, addr: AddressType) -> Result { 174 | unsafe { 175 | // Traditionally there was a difference between reading data or 176 | // instruction memory but not in modern systems. 177 | ptrace_other(Request::PT_READ_D, pid, addr, 0) 178 | } 179 | } 180 | 181 | /// Writes a word into the processes memory at the given address 182 | // Technically, ptrace doesn't dereference the pointer. It passes it directly 183 | // to the kernel. 184 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 185 | pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> { 186 | unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(drop) } 187 | } 188 | -------------------------------------------------------------------------------- /src/sys/ptrace/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides helpers for making ptrace system calls 2 | 3 | #[cfg(linux_android)] 4 | mod linux; 5 | 6 | #[cfg(linux_android)] 7 | pub use self::linux::*; 8 | 9 | #[cfg(bsd)] 10 | mod bsd; 11 | 12 | #[cfg(bsd)] 13 | pub use self::bsd::*; 14 | -------------------------------------------------------------------------------- /src/sys/reboot.rs: -------------------------------------------------------------------------------- 1 | //! Reboot/shutdown 2 | //! 3 | //! On Linux, This can also be used to enable/disable Ctrl-Alt-Delete. 4 | 5 | use crate::errno::Errno; 6 | use crate::Result; 7 | use cfg_if::cfg_if; 8 | use std::convert::Infallible; 9 | 10 | cfg_if! { 11 | if #[cfg(target_os = "linux")] { 12 | use std::mem::drop; 13 | 14 | libc_enum! { 15 | /// How exactly should the system be rebooted. 16 | /// 17 | /// See [`set_cad_enabled()`](fn.set_cad_enabled.html) for 18 | /// enabling/disabling Ctrl-Alt-Delete. 19 | #[repr(i32)] 20 | #[non_exhaustive] 21 | pub enum RebootMode { 22 | /// Halt the system. 23 | RB_HALT_SYSTEM, 24 | /// Execute a kernel that has been loaded earlier with 25 | /// [`kexec_load(2)`](https://man7.org/linux/man-pages/man2/kexec_load.2.html). 26 | RB_KEXEC, 27 | /// Stop the system and switch off power, if possible. 28 | RB_POWER_OFF, 29 | /// Restart the system. 30 | RB_AUTOBOOT, 31 | // we do not support Restart2. 32 | /// Suspend the system using software suspend. 33 | RB_SW_SUSPEND, 34 | } 35 | } 36 | 37 | /// Reboots or shuts down the system. 38 | pub fn reboot(how: RebootMode) -> Result { 39 | unsafe { libc::reboot(how as libc::c_int) }; 40 | Err(Errno::last()) 41 | } 42 | 43 | /// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). 44 | /// 45 | /// Corresponds to calling `reboot(RB_ENABLE_CAD)` or `reboot(RB_DISABLE_CAD)` in C. 46 | pub fn set_cad_enabled(enable: bool) -> Result<()> { 47 | let cmd = if enable { 48 | libc::RB_ENABLE_CAD 49 | } else { 50 | libc::RB_DISABLE_CAD 51 | }; 52 | let res = unsafe { libc::reboot(cmd) }; 53 | Errno::result(res).map(drop) 54 | } 55 | } else if #[cfg(netbsdlike)] { 56 | use libc::c_int; 57 | 58 | libc_bitflags! { 59 | /// How exactly should the system be rebooted. 60 | pub struct RebootMode: c_int { 61 | /// The default, causing the system to reboot in its usual fashion. 62 | RB_AUTOBOOT; 63 | /// Interpreted by the bootstrap program itself, causing it to 64 | /// prompt on the console as to what file should be booted. 65 | /// Normally, the system is booted from the file “xx(0,0)bsd”, 66 | /// where xx is the default disk name, without prompting for 67 | /// the file name. 68 | RB_ASKNAME; 69 | /// Dump kernel memory before rebooting; see `savecore(8)` for 70 | /// more information. 71 | RB_DUMP; 72 | /// The processor is simply halted; no reboot takes place. 73 | RB_HALT; 74 | /// Power off the system if the system hardware supports the 75 | /// function, otherwise it has no effect. 76 | /// 77 | /// Should be used in conjunction with `RB_HALT`. 78 | RB_POWERDOWN; 79 | /// By default, the system will halt if `reboot()` is called during 80 | /// startup (before the system has finished autoconfiguration), even 81 | /// if `RB_HALT` is not specified. This is because `panic(9)`s 82 | /// during startup will probably just repeat on the next boot. 83 | /// Use of this option implies that the user has requested the 84 | /// action specified (for example, using the `ddb(4)` boot reboot 85 | /// command), so the system will reboot if a halt is not explicitly 86 | /// requested. 87 | #[cfg(target_os = "openbsd")] 88 | RB_USERREQ; 89 | /// Load the symbol table and enable a built-in debugger in the 90 | /// system. This option will have no useful function if the kernel 91 | /// is not configured for debugging. Several other options have 92 | /// different meaning if combined with this option, although their 93 | /// use may not be possible via the `reboot()` call. See `ddb(4)` for 94 | /// more information. 95 | RB_KDB; 96 | /// Normally, the disks are sync'd (see `sync(8)`) before the 97 | /// processor is halted or rebooted. This option may be useful 98 | /// if file system changes have been made manually or if the 99 | /// processor is on fire. 100 | RB_NOSYNC; 101 | /// Normally, the reboot procedure involves an automatic disk 102 | /// consistency check and then multi-user operations. `RB_SINGLE` 103 | /// prevents this, booting the system with a single-user shell on 104 | /// the console. `RB_SINGLE` is actually interpreted by the `init(8)` 105 | /// program in the newly booted system. 106 | /// 107 | /// When no options are given (i.e., `RB_AUTOBOOT` is used), the 108 | /// system is rebooted from file /bsd in the root file system of 109 | /// unit 0 of a disk chosen in a processor specific way. An automatic 110 | /// consistency check of the disks is normally performed (see `fsck(8)`). 111 | RB_SINGLE; 112 | /// Initially invoke the `userconf(4)` facility when the system 113 | /// starts up again, if it has been compiled into the kernel 114 | /// that is loaded. 115 | #[cfg(target_os = "netbsd")] 116 | RB_USERCONF; 117 | /// Don't update the hardware clock from the system clock, presumably 118 | /// because the system clock is suspect. 119 | #[cfg(target_os = "openbsd")] 120 | RB_TIMEBAD; 121 | } 122 | } 123 | 124 | /// Reboot system or halt processor 125 | /// 126 | /// For more information, see the man pages: 127 | /// 128 | /// * [NetBSD](https://man.netbsd.org/reboot.2) 129 | /// * [OpenBSD](https://man.openbsd.org/reboot.2) 130 | #[cfg(netbsdlike)] 131 | pub fn reboot(how: RebootMode) -> Result { 132 | #[cfg(target_os = "openbsd")] 133 | unsafe { libc::reboot(how.bits()) }; 134 | #[cfg(target_os = "netbsd")] 135 | unsafe { libc::reboot(how.bits(), std::ptr::null_mut()) }; 136 | 137 | Err(Errno::last()) 138 | } 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /src/sys/signalfd.rs: -------------------------------------------------------------------------------- 1 | //! Interface for the `signalfd` syscall. 2 | //! 3 | //! # Signal discarding 4 | //! When a signal can't be delivered to a process (or thread), it will become a pending signal. 5 | //! Failure to deliver could happen if the signal is blocked by every thread in the process or if 6 | //! the signal handler is still handling a previous signal. 7 | //! 8 | //! If a signal is sent to a process (or thread) that already has a pending signal of the same 9 | //! type, it will be discarded. This means that if signals of the same type are received faster than 10 | //! they are processed, some of those signals will be dropped. Because of this limitation, 11 | //! `signalfd` in itself cannot be used for reliable communication between processes or threads. 12 | //! 13 | //! Once the signal is unblocked, or the signal handler is finished, and a signal is still pending 14 | //! (ie. not consumed from a signalfd) it will be delivered to the signal handler. 15 | //! 16 | //! Please note that signal discarding is not specific to `signalfd`, but also happens with regular 17 | //! signal handlers. 18 | use crate::errno::Errno; 19 | pub use crate::sys::signal::{self, SigSet}; 20 | use crate::Result; 21 | 22 | /// Information of a received signal, the return type of [`SignalFd::read_signal()`]. 23 | pub use libc::signalfd_siginfo as siginfo; 24 | 25 | use std::mem; 26 | use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; 27 | 28 | libc_bitflags! { 29 | pub struct SfdFlags: libc::c_int { 30 | SFD_NONBLOCK; 31 | SFD_CLOEXEC; 32 | } 33 | } 34 | 35 | #[deprecated(since = "0.23.0", note = "use mem::size_of::() instead")] 36 | pub const SIGNALFD_SIGINFO_SIZE: usize = mem::size_of::(); 37 | 38 | /// Creates a new file descriptor for reading signals. 39 | /// 40 | /// **Important:** please read the module level documentation about signal discarding before using 41 | /// this function! 42 | /// 43 | /// The `mask` parameter specifies the set of signals that can be accepted via this file descriptor. 44 | /// 45 | /// A signal must be blocked on every thread in a process, otherwise it won't be visible from 46 | /// signalfd (the default handler will be invoked instead). 47 | /// 48 | /// See [the signalfd man page for more information](https://man7.org/linux/man-pages/man2/signalfd.2.html) 49 | #[deprecated(since = "0.27.0", note = "Use SignalFd instead")] 50 | pub fn signalfd( 51 | fd: Option, 52 | mask: &SigSet, 53 | flags: SfdFlags, 54 | ) -> Result { 55 | _signalfd(fd, mask, flags) 56 | } 57 | 58 | fn _signalfd( 59 | fd: Option, 60 | mask: &SigSet, 61 | flags: SfdFlags, 62 | ) -> Result { 63 | let raw_fd = fd.map_or(-1, |x| x.as_fd().as_raw_fd()); 64 | unsafe { 65 | Errno::result(libc::signalfd(raw_fd, mask.as_ref(), flags.bits())) 66 | .map(|raw_fd| FromRawFd::from_raw_fd(raw_fd)) 67 | } 68 | } 69 | 70 | /// A helper struct for creating, reading and closing a `signalfd` instance. 71 | /// 72 | /// **Important:** please read the module level documentation about signal discarding before using 73 | /// this struct! 74 | /// 75 | /// # Examples 76 | /// 77 | /// ``` 78 | /// # use nix::sys::signalfd::*; 79 | /// // Set the thread to block the SIGUSR1 signal, otherwise the default handler will be used 80 | /// let mut mask = SigSet::empty(); 81 | /// mask.add(signal::SIGUSR1); 82 | /// mask.thread_block().unwrap(); 83 | /// 84 | /// // Signals are queued up on the file descriptor 85 | /// let mut sfd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); 86 | /// 87 | /// match sfd.read_signal() { 88 | /// // we caught a signal 89 | /// Ok(Some(sig)) => (), 90 | /// // there were no signals waiting (only happens when the SFD_NONBLOCK flag is set, 91 | /// // otherwise the read_signal call blocks) 92 | /// Ok(None) => (), 93 | /// Err(err) => (), // some error happend 94 | /// } 95 | /// ``` 96 | #[derive(Debug)] 97 | pub struct SignalFd(OwnedFd); 98 | 99 | impl SignalFd { 100 | pub fn new(mask: &SigSet) -> Result { 101 | Self::with_flags(mask, SfdFlags::empty()) 102 | } 103 | 104 | pub fn with_flags(mask: &SigSet, flags: SfdFlags) -> Result { 105 | let fd = _signalfd(None::, mask, flags)?; 106 | 107 | Ok(SignalFd(fd)) 108 | } 109 | 110 | pub fn set_mask(&self, mask: &SigSet) -> Result<()> { 111 | self.update(mask, SfdFlags::empty()) 112 | } 113 | 114 | pub fn read_signal(&self) -> Result> { 115 | let mut buffer = mem::MaybeUninit::::uninit(); 116 | 117 | let size = mem::size_of_val(&buffer); 118 | let res = Errno::result(unsafe { 119 | libc::read(self.0.as_raw_fd(), buffer.as_mut_ptr().cast(), size) 120 | }) 121 | .map(|r| r as usize); 122 | match res { 123 | Ok(x) if x == size => Ok(Some(unsafe { buffer.assume_init() })), 124 | Ok(_) => unreachable!("partial read on signalfd"), 125 | Err(Errno::EAGAIN) => Ok(None), 126 | Err(error) => Err(error), 127 | } 128 | } 129 | 130 | /// Constructs a `SignalFd` wrapping an existing `OwnedFd`. 131 | /// 132 | /// # Safety 133 | /// 134 | /// `OwnedFd` is a valid `SignalFd`. 135 | pub unsafe fn from_owned_fd(fd: OwnedFd) -> Self { 136 | Self(fd) 137 | } 138 | 139 | fn update(&self, mask: &SigSet, flags: SfdFlags) -> Result<()> { 140 | let raw_fd = self.0.as_raw_fd(); 141 | unsafe { 142 | Errno::result(libc::signalfd(raw_fd, mask.as_ref(), flags.bits())) 143 | .map(drop) 144 | } 145 | } 146 | } 147 | 148 | impl AsFd for SignalFd { 149 | fn as_fd(&self) -> BorrowedFd { 150 | self.0.as_fd() 151 | } 152 | } 153 | impl AsRawFd for SignalFd { 154 | fn as_raw_fd(&self) -> RawFd { 155 | self.0.as_raw_fd() 156 | } 157 | } 158 | 159 | impl From for OwnedFd { 160 | fn from(value: SignalFd) -> Self { 161 | value.0 162 | } 163 | } 164 | 165 | impl Iterator for SignalFd { 166 | type Item = siginfo; 167 | 168 | fn next(&mut self) -> Option { 169 | match self.read_signal() { 170 | Ok(Some(sig)) => Some(sig), 171 | Ok(None) | Err(_) => None, 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/sys/statvfs.rs: -------------------------------------------------------------------------------- 1 | //! Get filesystem statistics 2 | //! 3 | //! See [the man pages](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html) 4 | //! for more details. 5 | use std::mem; 6 | use std::os::unix::io::{AsFd, AsRawFd}; 7 | 8 | use libc::{self, c_ulong}; 9 | 10 | use crate::{errno::Errno, NixPath, Result}; 11 | 12 | #[cfg(not(target_os = "redox"))] 13 | libc_bitflags!( 14 | /// File system mount Flags 15 | #[derive(Default)] 16 | pub struct FsFlags: c_ulong { 17 | /// Read Only 18 | #[cfg(not(target_os = "haiku"))] 19 | ST_RDONLY; 20 | /// Do not allow the set-uid bits to have an effect 21 | #[cfg(not(target_os = "haiku"))] 22 | ST_NOSUID; 23 | /// Do not interpret character or block-special devices 24 | #[cfg(linux_android)] 25 | ST_NODEV; 26 | /// Do not allow execution of binaries on the filesystem 27 | #[cfg(linux_android)] 28 | ST_NOEXEC; 29 | /// All IO should be done synchronously 30 | #[cfg(linux_android)] 31 | ST_SYNCHRONOUS; 32 | /// Allow mandatory locks on the filesystem 33 | #[cfg(linux_android)] 34 | ST_MANDLOCK; 35 | /// Write on file/directory/symlink 36 | #[cfg(target_os = "linux")] 37 | ST_WRITE; 38 | /// Append-only file 39 | #[cfg(target_os = "linux")] 40 | ST_APPEND; 41 | /// Immutable file 42 | #[cfg(target_os = "linux")] 43 | ST_IMMUTABLE; 44 | /// Do not update access times on files 45 | #[cfg(linux_android)] 46 | ST_NOATIME; 47 | /// Do not update access times on files 48 | #[cfg(linux_android)] 49 | ST_NODIRATIME; 50 | /// Update access time relative to modify/change time 51 | #[cfg(any(target_os = "android", all(target_os = "linux", not(target_env = "musl"), not(target_env = "ohos"))))] 52 | ST_RELATIME; 53 | } 54 | ); 55 | 56 | /// Wrapper around the POSIX `statvfs` struct 57 | /// 58 | /// For more information see the [`statvfs(3)` man pages](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_statvfs.h.html). 59 | #[repr(transparent)] 60 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 61 | pub struct Statvfs(libc::statvfs); 62 | 63 | impl Statvfs { 64 | /// get the file system block size 65 | pub fn block_size(&self) -> c_ulong { 66 | self.0.f_bsize 67 | } 68 | 69 | /// Get the fundamental file system block size 70 | pub fn fragment_size(&self) -> c_ulong { 71 | self.0.f_frsize 72 | } 73 | 74 | /// Get the number of blocks. 75 | /// 76 | /// Units are in units of `fragment_size()` 77 | pub fn blocks(&self) -> libc::fsblkcnt_t { 78 | self.0.f_blocks 79 | } 80 | 81 | /// Get the number of free blocks in the file system 82 | pub fn blocks_free(&self) -> libc::fsblkcnt_t { 83 | self.0.f_bfree 84 | } 85 | 86 | /// Get the number of free blocks for unprivileged users 87 | pub fn blocks_available(&self) -> libc::fsblkcnt_t { 88 | self.0.f_bavail 89 | } 90 | 91 | /// Get the total number of file inodes 92 | pub fn files(&self) -> libc::fsfilcnt_t { 93 | self.0.f_files 94 | } 95 | 96 | /// Get the number of free file inodes 97 | pub fn files_free(&self) -> libc::fsfilcnt_t { 98 | self.0.f_ffree 99 | } 100 | 101 | /// Get the number of free file inodes for unprivileged users 102 | pub fn files_available(&self) -> libc::fsfilcnt_t { 103 | self.0.f_favail 104 | } 105 | 106 | /// Get the file system id 107 | #[cfg(not(target_os = "hurd"))] 108 | pub fn filesystem_id(&self) -> c_ulong { 109 | self.0.f_fsid 110 | } 111 | /// Get the file system id 112 | #[cfg(target_os = "hurd")] 113 | pub fn filesystem_id(&self) -> u64 { 114 | self.0.f_fsid 115 | } 116 | 117 | /// Get the mount flags 118 | #[cfg(not(target_os = "redox"))] 119 | pub fn flags(&self) -> FsFlags { 120 | FsFlags::from_bits_truncate(self.0.f_flag) 121 | } 122 | 123 | /// Get the maximum filename length 124 | pub fn name_max(&self) -> c_ulong { 125 | self.0.f_namemax 126 | } 127 | } 128 | 129 | /// Return a `Statvfs` object with information about the `path` 130 | pub fn statvfs(path: &P) -> Result { 131 | unsafe { 132 | Errno::clear(); 133 | let mut stat = mem::MaybeUninit::::uninit(); 134 | let res = path.with_nix_path(|path| { 135 | libc::statvfs(path.as_ptr(), stat.as_mut_ptr()) 136 | })?; 137 | 138 | Errno::result(res).map(|_| Statvfs(stat.assume_init())) 139 | } 140 | } 141 | 142 | /// Return a `Statvfs` object with information about `fd` 143 | pub fn fstatvfs(fd: Fd) -> Result { 144 | unsafe { 145 | Errno::clear(); 146 | let mut stat = mem::MaybeUninit::::uninit(); 147 | Errno::result(libc::fstatvfs(fd.as_fd().as_raw_fd(), stat.as_mut_ptr())) 148 | .map(|_| Statvfs(stat.assume_init())) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/sys/sysinfo.rs: -------------------------------------------------------------------------------- 1 | use libc::SI_LOAD_SHIFT; 2 | use std::time::Duration; 3 | use std::{cmp, mem}; 4 | 5 | use crate::errno::Errno; 6 | use crate::Result; 7 | 8 | /// System info structure returned by `sysinfo`. 9 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 10 | #[repr(transparent)] 11 | pub struct SysInfo(libc::sysinfo); 12 | 13 | // The fields are c_ulong on 32-bit linux, u64 on 64-bit linux; x32's ulong is u32 14 | #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] 15 | type mem_blocks_t = u64; 16 | #[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))] 17 | type mem_blocks_t = libc::c_ulong; 18 | 19 | impl SysInfo { 20 | /// Returns the load average tuple. 21 | /// 22 | /// The returned values represent the load average over time intervals of 23 | /// 1, 5, and 15 minutes, respectively. 24 | pub fn load_average(&self) -> (f64, f64, f64) { 25 | ( 26 | self.0.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, 27 | self.0.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, 28 | self.0.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, 29 | ) 30 | } 31 | 32 | /// Returns the time since system boot. 33 | // The cast is not unnecessary on all platforms. 34 | #[allow(clippy::unnecessary_cast)] 35 | pub fn uptime(&self) -> Duration { 36 | // Truncate negative values to 0 37 | Duration::from_secs(cmp::max(self.0.uptime, 0) as u64) 38 | } 39 | 40 | /// Current number of processes. 41 | pub fn process_count(&self) -> u16 { 42 | self.0.procs 43 | } 44 | 45 | /// Returns the amount of swap memory in Bytes. 46 | pub fn swap_total(&self) -> u64 { 47 | self.scale_mem(self.0.totalswap) 48 | } 49 | 50 | /// Returns the amount of unused swap memory in Bytes. 51 | pub fn swap_free(&self) -> u64 { 52 | self.scale_mem(self.0.freeswap) 53 | } 54 | 55 | /// Returns the total amount of installed RAM in Bytes. 56 | pub fn ram_total(&self) -> u64 { 57 | self.scale_mem(self.0.totalram) 58 | } 59 | 60 | /// Returns the amount of completely unused RAM in Bytes. 61 | /// 62 | /// "Unused" in this context means that the RAM in neither actively used by 63 | /// programs, nor by the operating system as disk cache or buffer. It is 64 | /// "wasted" RAM since it currently serves no purpose. 65 | pub fn ram_unused(&self) -> u64 { 66 | self.scale_mem(self.0.freeram) 67 | } 68 | 69 | // The cast is not unnecessary on all platforms. 70 | #[allow(clippy::unnecessary_cast)] 71 | fn scale_mem(&self, units: mem_blocks_t) -> u64 { 72 | units as u64 * self.0.mem_unit as u64 73 | } 74 | } 75 | 76 | /// Returns system information. 77 | /// 78 | /// [See `sysinfo(2)`](https://man7.org/linux/man-pages/man2/sysinfo.2.html). 79 | pub fn sysinfo() -> Result { 80 | let mut info = mem::MaybeUninit::uninit(); 81 | let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; 82 | Errno::result(res).map(|_| unsafe { SysInfo(info.assume_init()) }) 83 | } 84 | -------------------------------------------------------------------------------- /src/sys/timer.rs: -------------------------------------------------------------------------------- 1 | //! Timer API via signals. 2 | //! 3 | //! Timer is a POSIX API to create timers and get expiration notifications 4 | //! through queued Unix signals, for the current process. This is similar to 5 | //! Linux's timerfd mechanism, except that API is specific to Linux and makes 6 | //! use of file polling. 7 | //! 8 | //! For more documentation, please read [timer_create](https://pubs.opengroup.org/onlinepubs/9699919799/functions/timer_create.html). 9 | //! 10 | //! # Examples 11 | //! 12 | //! Create an interval timer that signals SIGALARM every 250 milliseconds. 13 | //! 14 | //! ```no_run 15 | //! use nix::sys::signal::{self, SigEvent, SigHandler, SigevNotify, Signal}; 16 | //! use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; 17 | //! use nix::time::ClockId; 18 | //! use std::convert::TryFrom; 19 | //! use std::sync::atomic::{AtomicU64, Ordering}; 20 | //! use std::thread::yield_now; 21 | //! use std::time::Duration; 22 | //! 23 | //! const SIG: Signal = Signal::SIGALRM; 24 | //! static ALARMS: AtomicU64 = AtomicU64::new(0); 25 | //! 26 | //! extern "C" fn handle_alarm(signal: libc::c_int) { 27 | //! let signal = Signal::try_from(signal).unwrap(); 28 | //! if signal == SIG { 29 | //! ALARMS.fetch_add(1, Ordering::Relaxed); 30 | //! } 31 | //! } 32 | //! 33 | //! fn main() { 34 | //! let clockid = ClockId::CLOCK_MONOTONIC; 35 | //! let sigevent = SigEvent::new(SigevNotify::SigevSignal { 36 | //! signal: SIG, 37 | //! si_value: 0, 38 | //! }); 39 | //! 40 | //! let mut timer = Timer::new(clockid, sigevent).unwrap(); 41 | //! let expiration = Expiration::Interval(Duration::from_millis(250).into()); 42 | //! let flags = TimerSetTimeFlags::empty(); 43 | //! timer.set(expiration, flags).expect("could not set timer"); 44 | //! 45 | //! let handler = SigHandler::Handler(handle_alarm); 46 | //! unsafe { signal::signal(SIG, handler) }.unwrap(); 47 | //! 48 | //! loop { 49 | //! let alarms = ALARMS.load(Ordering::Relaxed); 50 | //! if alarms >= 10 { 51 | //! println!("total alarms handled: {}", alarms); 52 | //! break; 53 | //! } 54 | //! yield_now() 55 | //! } 56 | //! } 57 | //! ``` 58 | use crate::sys::signal::SigEvent; 59 | use crate::sys::time::timer::TimerSpec; 60 | pub use crate::sys::time::timer::{Expiration, TimerSetTimeFlags}; 61 | use crate::time::ClockId; 62 | use crate::{errno::Errno, Result}; 63 | use core::mem; 64 | 65 | /// A Unix signal per-process timer. 66 | #[derive(Debug)] 67 | #[repr(transparent)] 68 | pub struct Timer(libc::timer_t); 69 | 70 | impl Timer { 71 | /// Creates a new timer based on the clock defined by `clockid`. The details 72 | /// of the signal and its handler are defined by the passed `sigevent`. 73 | #[doc(alias("timer_create"))] 74 | pub fn new(clockid: ClockId, mut sigevent: SigEvent) -> Result { 75 | let mut timer_id: mem::MaybeUninit = 76 | mem::MaybeUninit::uninit(); 77 | Errno::result(unsafe { 78 | libc::timer_create( 79 | clockid.as_raw(), 80 | sigevent.as_mut_ptr(), 81 | timer_id.as_mut_ptr(), 82 | ) 83 | }) 84 | .map(|_| { 85 | // SAFETY: libc::timer_create is responsible for initializing 86 | // timer_id. 87 | unsafe { Self(timer_id.assume_init()) } 88 | }) 89 | } 90 | 91 | /// Set a new alarm on the timer. 92 | /// 93 | /// # Types of alarm 94 | /// 95 | /// There are 3 types of alarms you can set: 96 | /// 97 | /// - one shot: the alarm will trigger once after the specified amount of 98 | /// time. 99 | /// Example: I want an alarm to go off in 60s and then disable itself. 100 | /// 101 | /// - interval: the alarm will trigger every specified interval of time. 102 | /// Example: I want an alarm to go off every 60s. The alarm will first 103 | /// go off 60s after I set it and every 60s after that. The alarm will 104 | /// not disable itself. 105 | /// 106 | /// - interval delayed: the alarm will trigger after a certain amount of 107 | /// time and then trigger at a specified interval. 108 | /// Example: I want an alarm to go off every 60s but only start in 1h. 109 | /// The alarm will first trigger 1h after I set it and then every 60s 110 | /// after that. The alarm will not disable itself. 111 | /// 112 | /// # Relative vs absolute alarm 113 | /// 114 | /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass 115 | /// to the `Expiration` you want is relative. If however you want an alarm 116 | /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`. 117 | /// Then the one shot TimeSpec and the delay TimeSpec of the delayed 118 | /// interval are going to be interpreted as absolute. 119 | /// 120 | /// # Disabling alarms 121 | /// 122 | /// Note: Only one alarm can be set for any given timer. Setting a new alarm 123 | /// actually removes the previous one. 124 | /// 125 | /// Note: Setting a one shot alarm with a 0s TimeSpec disable the alarm 126 | /// altogether. 127 | #[doc(alias("timer_settime"))] 128 | pub fn set( 129 | &mut self, 130 | expiration: Expiration, 131 | flags: TimerSetTimeFlags, 132 | ) -> Result<()> { 133 | let timerspec: TimerSpec = expiration.into(); 134 | Errno::result(unsafe { 135 | libc::timer_settime( 136 | self.0, 137 | flags.bits(), 138 | timerspec.as_ref(), 139 | core::ptr::null_mut(), 140 | ) 141 | }) 142 | .map(drop) 143 | } 144 | 145 | /// Get the parameters for the alarm currently set, if any. 146 | #[doc(alias("timer_gettime"))] 147 | pub fn get(&self) -> Result> { 148 | let mut timerspec = TimerSpec::none(); 149 | Errno::result(unsafe { 150 | libc::timer_gettime(self.0, timerspec.as_mut()) 151 | }) 152 | .map(|_| { 153 | if timerspec.as_ref().it_interval.tv_sec == 0 154 | && timerspec.as_ref().it_interval.tv_nsec == 0 155 | && timerspec.as_ref().it_value.tv_sec == 0 156 | && timerspec.as_ref().it_value.tv_nsec == 0 157 | { 158 | None 159 | } else { 160 | Some(timerspec.into()) 161 | } 162 | }) 163 | } 164 | 165 | /// Return the number of timers that have overrun 166 | /// 167 | /// Each timer is able to queue one signal to the process at a time, meaning 168 | /// if the signal is not handled before the next expiration the timer has 169 | /// 'overrun'. This function returns how many times that has happened to 170 | /// this timer, up to `libc::DELAYTIMER_MAX`. If more than the maximum 171 | /// number of overruns have happened the return is capped to the maximum. 172 | #[doc(alias("timer_getoverrun"))] 173 | pub fn overruns(&self) -> i32 { 174 | unsafe { libc::timer_getoverrun(self.0) } 175 | } 176 | } 177 | 178 | impl Drop for Timer { 179 | fn drop(&mut self) { 180 | if !std::thread::panicking() { 181 | let result = Errno::result(unsafe { libc::timer_delete(self.0) }); 182 | if let Err(Errno::EINVAL) = result { 183 | panic!("close of Timer encountered EINVAL"); 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/sys/utsname.rs: -------------------------------------------------------------------------------- 1 | //! Get system identification 2 | use crate::{Errno, Result}; 3 | use libc::c_char; 4 | use std::ffi::OsStr; 5 | use std::mem; 6 | use std::os::unix::ffi::OsStrExt; 7 | 8 | /// Describes the running system. Return type of [`uname`]. 9 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 10 | #[repr(transparent)] 11 | pub struct UtsName(libc::utsname); 12 | 13 | impl UtsName { 14 | /// Name of the operating system implementation. 15 | pub fn sysname(&self) -> &OsStr { 16 | cast_and_trim(&self.0.sysname) 17 | } 18 | 19 | /// Network name of this machine. 20 | pub fn nodename(&self) -> &OsStr { 21 | cast_and_trim(&self.0.nodename) 22 | } 23 | 24 | /// Release level of the operating system. 25 | pub fn release(&self) -> &OsStr { 26 | cast_and_trim(&self.0.release) 27 | } 28 | 29 | /// Version level of the operating system. 30 | pub fn version(&self) -> &OsStr { 31 | cast_and_trim(&self.0.version) 32 | } 33 | 34 | /// Machine hardware platform. 35 | pub fn machine(&self) -> &OsStr { 36 | cast_and_trim(&self.0.machine) 37 | } 38 | 39 | /// NIS or YP domain name of this machine. 40 | #[cfg(linux_android)] 41 | pub fn domainname(&self) -> &OsStr { 42 | cast_and_trim(&self.0.domainname) 43 | } 44 | } 45 | 46 | /// Get system identification 47 | pub fn uname() -> Result { 48 | unsafe { 49 | let mut ret = mem::MaybeUninit::zeroed(); 50 | Errno::result(libc::uname(ret.as_mut_ptr()))?; 51 | Ok(UtsName(ret.assume_init())) 52 | } 53 | } 54 | 55 | fn cast_and_trim(slice: &[c_char]) -> &OsStr { 56 | let length = slice 57 | .iter() 58 | .position(|&byte| byte == 0) 59 | .unwrap_or(slice.len()); 60 | let bytes = 61 | unsafe { std::slice::from_raw_parts(slice.as_ptr().cast(), length) }; 62 | 63 | OsStr::from_bytes(bytes) 64 | } 65 | -------------------------------------------------------------------------------- /src/ucontext.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(target_env = "musl", target_env = "ohos")))] 2 | use crate::errno::Errno; 3 | use crate::sys::signal::SigSet; 4 | #[cfg(not(any(target_env = "musl", target_env = "ohos")))] 5 | use crate::Result; 6 | #[cfg(not(any(target_env = "musl", target_env = "ohos")))] 7 | use std::mem; 8 | 9 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 10 | pub struct UContext { 11 | context: libc::ucontext_t, 12 | } 13 | 14 | impl UContext { 15 | #[cfg(not(any(target_env = "musl", target_env = "ohos")))] 16 | pub fn get() -> Result { 17 | let mut context = mem::MaybeUninit::::uninit(); 18 | let res = unsafe { libc::getcontext(context.as_mut_ptr()) }; 19 | Errno::result(res).map(|_| unsafe { 20 | UContext { 21 | context: context.assume_init(), 22 | } 23 | }) 24 | } 25 | 26 | #[cfg(not(any(target_env = "musl", target_env = "ohos")))] 27 | pub fn set(&self) -> Result<()> { 28 | let res = unsafe { 29 | libc::setcontext(&self.context as *const libc::ucontext_t) 30 | }; 31 | Errno::result(res).map(drop) 32 | } 33 | 34 | pub fn sigmask_mut(&mut self) -> &mut SigSet { 35 | unsafe { 36 | &mut *(&mut self.context.uc_sigmask as *mut libc::sigset_t 37 | as *mut SigSet) 38 | } 39 | } 40 | 41 | pub fn sigmask(&self) -> &SigSet { 42 | unsafe { 43 | &*(&self.context.uc_sigmask as *const libc::sigset_t 44 | as *const SigSet) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/common/mod.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | #[macro_export] 4 | macro_rules! skip { 5 | ($($reason: expr),+) => {{ 6 | use ::std::io::{self, Write}; 7 | 8 | let stderr = io::stderr(); 9 | let mut handle = stderr.lock(); 10 | writeln!(handle, $($reason),+).unwrap(); 11 | return; 12 | }} 13 | } 14 | 15 | cfg_if! { 16 | if #[cfg(linux_android)] { 17 | #[macro_export] macro_rules! require_capability { 18 | ($name:expr, $capname:ident) => { 19 | use ::caps::{Capability, CapSet, has_cap}; 20 | 21 | if !has_cap(None, CapSet::Effective, Capability::$capname) 22 | .unwrap() 23 | { 24 | skip!("{} requires capability {}. Skipping test.", $name, Capability::$capname); 25 | } 26 | } 27 | } 28 | } else if #[cfg(not(target_os = "redox"))] { 29 | #[macro_export] macro_rules! require_capability { 30 | ($name:expr, $capname:ident) => {} 31 | } 32 | } 33 | } 34 | 35 | /// Skip the test if we don't have the ability to mount file systems. 36 | #[cfg(target_os = "freebsd")] 37 | #[macro_export] 38 | macro_rules! require_mount { 39 | ($name:expr) => { 40 | use nix::unistd::Uid; 41 | use sysctl::{CtlValue, Sysctl}; 42 | 43 | let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); 44 | if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() 45 | { 46 | skip!( 47 | "{} requires the ability to mount file systems. Skipping test.", 48 | $name 49 | ); 50 | } 51 | }; 52 | } 53 | 54 | #[cfg(linux_android)] 55 | #[macro_export] 56 | macro_rules! skip_if_cirrus { 57 | ($reason:expr) => { 58 | if std::env::var_os("CIRRUS_CI").is_some() { 59 | skip!("{}", $reason); 60 | } 61 | }; 62 | } 63 | 64 | #[cfg(target_os = "freebsd")] 65 | #[macro_export] 66 | macro_rules! skip_if_jailed { 67 | ($name:expr) => { 68 | use sysctl::{CtlValue, Sysctl}; 69 | 70 | let ctl = ::sysctl::Ctl::new("security.jail.jailed").unwrap(); 71 | if let CtlValue::Int(1) = ctl.value().unwrap() { 72 | skip!("{} cannot run in a jail. Skipping test.", $name); 73 | } 74 | }; 75 | } 76 | 77 | #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] 78 | #[macro_export] 79 | macro_rules! skip_if_not_root { 80 | ($name:expr) => { 81 | use nix::unistd::Uid; 82 | 83 | if !Uid::current().is_root() { 84 | skip!("{} requires root privileges. Skipping test.", $name); 85 | } 86 | }; 87 | } 88 | 89 | cfg_if! { 90 | if #[cfg(linux_android)] { 91 | #[macro_export] macro_rules! skip_if_seccomp { 92 | ($name:expr) => { 93 | if let Ok(s) = std::fs::read_to_string("/proc/self/status") { 94 | for l in s.lines() { 95 | let mut fields = l.split_whitespace(); 96 | if fields.next() == Some("Seccomp:") && 97 | fields.next() != Some("0") 98 | { 99 | skip!("{} cannot be run in Seccomp mode. Skipping test.", 100 | stringify!($name)); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } else if #[cfg(not(target_os = "redox"))] { 107 | #[macro_export] macro_rules! skip_if_seccomp { 108 | ($name:expr) => {} 109 | } 110 | } 111 | } 112 | 113 | cfg_if! { 114 | if #[cfg(target_os = "linux")] { 115 | #[macro_export] macro_rules! require_kernel_version { 116 | ($name:expr, $version_requirement:expr) => { 117 | use semver::{Version, VersionReq}; 118 | 119 | let version_requirement = VersionReq::parse($version_requirement) 120 | .expect("Bad match_version provided"); 121 | 122 | let uname = nix::sys::utsname::uname().unwrap(); 123 | println!("{}", uname.sysname().to_str().unwrap()); 124 | println!("{}", uname.nodename().to_str().unwrap()); 125 | println!("{}", uname.release().to_str().unwrap()); 126 | println!("{}", uname.version().to_str().unwrap()); 127 | println!("{}", uname.machine().to_str().unwrap()); 128 | 129 | // Fix stuff that the semver parser can't handle 130 | let fixed_release = &uname.release().to_str().unwrap().to_string() 131 | // Fedora 33 reports version as 4.18.el8_2.x86_64 or 132 | // 5.18.200-fc33.x86_64. Remove the underscore. 133 | .replace("_", "-") 134 | // Cirrus-CI reports version as 4.19.112+ . Remove the + 135 | .replace("+", ""); 136 | let mut version = Version::parse(fixed_release).unwrap(); 137 | 138 | //Keep only numeric parts 139 | version.pre = semver::Prerelease::EMPTY; 140 | version.build = semver::BuildMetadata::EMPTY; 141 | 142 | if !version_requirement.matches(&version) { 143 | skip!("Skip {} because kernel version `{}` doesn't match the requirement `{}`", 144 | stringify!($name), version, version_requirement); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /test/mount/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | mod test_mount; 3 | #[cfg(apple_targets)] 4 | mod test_mount_apple; 5 | #[cfg(target_os = "freebsd")] 6 | mod test_nmount; 7 | -------------------------------------------------------------------------------- /test/mount/test_mount.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self, File}; 2 | use std::io::{Read, Write}; 3 | use std::os::unix::fs::OpenOptionsExt; 4 | use std::os::unix::fs::PermissionsExt; 5 | use std::process::Command; 6 | 7 | use libc::{EACCES, EROFS}; 8 | 9 | use nix::mount::{mount, umount, MsFlags}; 10 | use nix::sys::stat::{self, Mode}; 11 | 12 | use crate::*; 13 | 14 | static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh 15 | exit 23"; 16 | 17 | const EXPECTED_STATUS: i32 = 23; 18 | 19 | const NONE: Option<&'static [u8]> = None; 20 | 21 | #[test] 22 | fn test_mount_tmpfs_without_flags_allows_rwx() { 23 | require_capability!( 24 | "test_mount_tmpfs_without_flags_allows_rwx", 25 | CAP_SYS_ADMIN 26 | ); 27 | let tempdir = tempfile::tempdir().unwrap(); 28 | 29 | mount( 30 | NONE, 31 | tempdir.path(), 32 | Some(b"tmpfs".as_ref()), 33 | MsFlags::empty(), 34 | NONE, 35 | ) 36 | .unwrap_or_else(|e| panic!("mount failed: {e}")); 37 | 38 | let test_path = tempdir.path().join("test"); 39 | 40 | // Verify write. 41 | fs::OpenOptions::new() 42 | .create(true) 43 | .write(true) 44 | .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) 45 | .open(&test_path) 46 | .and_then(|mut f| f.write(SCRIPT_CONTENTS)) 47 | .unwrap_or_else(|e| panic!("write failed: {e}")); 48 | 49 | // Verify read. 50 | let mut buf = Vec::new(); 51 | File::open(&test_path) 52 | .and_then(|mut f| f.read_to_end(&mut buf)) 53 | .unwrap_or_else(|e| panic!("read failed: {e}")); 54 | assert_eq!(buf, SCRIPT_CONTENTS); 55 | 56 | // while forking and unmounting prevent other child processes 57 | let _m = FORK_MTX.lock(); 58 | // Verify execute. 59 | assert_eq!( 60 | EXPECTED_STATUS, 61 | Command::new(&test_path) 62 | .status() 63 | .unwrap_or_else(|e| panic!("exec failed: {e}")) 64 | .code() 65 | .unwrap_or_else(|| panic!("child killed by signal")) 66 | ); 67 | 68 | umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}")); 69 | } 70 | 71 | #[test] 72 | fn test_mount_rdonly_disallows_write() { 73 | require_capability!("test_mount_rdonly_disallows_write", CAP_SYS_ADMIN); 74 | let tempdir = tempfile::tempdir().unwrap(); 75 | 76 | mount( 77 | NONE, 78 | tempdir.path(), 79 | Some(b"tmpfs".as_ref()), 80 | MsFlags::MS_RDONLY, 81 | NONE, 82 | ) 83 | .unwrap_or_else(|e| panic!("mount failed: {e}")); 84 | 85 | // EROFS: Read-only file system 86 | assert_eq!( 87 | EROFS, 88 | File::create(tempdir.path().join("test")) 89 | .unwrap_err() 90 | .raw_os_error() 91 | .unwrap() 92 | ); 93 | 94 | umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}")); 95 | } 96 | 97 | #[test] 98 | fn test_mount_noexec_disallows_exec() { 99 | require_capability!("test_mount_noexec_disallows_exec", CAP_SYS_ADMIN); 100 | let tempdir = tempfile::tempdir().unwrap(); 101 | 102 | mount( 103 | NONE, 104 | tempdir.path(), 105 | Some(b"tmpfs".as_ref()), 106 | MsFlags::MS_NOEXEC, 107 | NONE, 108 | ) 109 | .unwrap_or_else(|e| panic!("mount failed: {e}")); 110 | 111 | let test_path = tempdir.path().join("test"); 112 | 113 | fs::OpenOptions::new() 114 | .create(true) 115 | .write(true) 116 | .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) 117 | .open(&test_path) 118 | .and_then(|mut f| f.write(SCRIPT_CONTENTS)) 119 | .unwrap_or_else(|e| panic!("write failed: {e}")); 120 | 121 | // Verify that we cannot execute despite a+x permissions being set. 122 | let mode = stat::Mode::from_bits_truncate( 123 | fs::metadata(&test_path) 124 | .map(|md| md.permissions().mode()) 125 | .unwrap_or_else(|e| panic!("metadata failed: {e}")), 126 | ); 127 | 128 | assert!( 129 | mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH), 130 | "{:?} did not have execute permissions", 131 | &test_path 132 | ); 133 | 134 | // while forking and unmounting prevent other child processes 135 | let _m = FORK_MTX.lock(); 136 | // EACCES: Permission denied 137 | assert_eq!( 138 | EACCES, 139 | Command::new(&test_path) 140 | .status() 141 | .unwrap_err() 142 | .raw_os_error() 143 | .unwrap() 144 | ); 145 | 146 | umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}")); 147 | } 148 | 149 | #[test] 150 | fn test_mount_bind() { 151 | require_capability!("test_mount_bind", CAP_SYS_ADMIN); 152 | let tempdir = tempfile::tempdir().unwrap(); 153 | let file_name = "test"; 154 | 155 | { 156 | let mount_point = tempfile::tempdir().unwrap(); 157 | 158 | mount( 159 | Some(tempdir.path()), 160 | mount_point.path(), 161 | NONE, 162 | MsFlags::MS_BIND, 163 | NONE, 164 | ) 165 | .unwrap_or_else(|e| panic!("mount failed: {e}")); 166 | 167 | fs::OpenOptions::new() 168 | .create(true) 169 | .write(true) 170 | .mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits()) 171 | .open(mount_point.path().join(file_name)) 172 | .and_then(|mut f| f.write(SCRIPT_CONTENTS)) 173 | .unwrap_or_else(|e| panic!("write failed: {e}")); 174 | 175 | // wait for child processes to prevent EBUSY 176 | let _m = FORK_MTX.lock(); 177 | umount(mount_point.path()) 178 | .unwrap_or_else(|e| panic!("umount failed: {e}")); 179 | } 180 | 181 | // Verify the file written in the mount shows up in source directory, even 182 | // after unmounting. 183 | 184 | let mut buf = Vec::new(); 185 | File::open(tempdir.path().join(file_name)) 186 | .and_then(|mut f| f.read_to_end(&mut buf)) 187 | .unwrap_or_else(|e| panic!("read failed: {e}")); 188 | assert_eq!(buf, SCRIPT_CONTENTS); 189 | } 190 | -------------------------------------------------------------------------------- /test/mount/test_mount_apple.rs: -------------------------------------------------------------------------------- 1 | use nix::errno::Errno; 2 | use nix::mount::{mount, MntFlags}; 3 | 4 | #[test] 5 | fn test_mount() { 6 | let res = mount::("", "", MntFlags::empty(), None); 7 | assert_eq!(res, Err(Errno::ENOENT)); 8 | } 9 | -------------------------------------------------------------------------------- /test/mount/test_nmount.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use nix::{ 3 | errno::Errno, 4 | mount::{unmount, MntFlags, Nmount}, 5 | }; 6 | use std::{ffi::CString, fs::File, path::Path}; 7 | use tempfile::tempdir; 8 | 9 | #[test] 10 | fn ok() { 11 | require_mount!("nullfs"); 12 | 13 | let mountpoint = tempdir().unwrap(); 14 | let target = tempdir().unwrap(); 15 | let _sentry = File::create(target.path().join("sentry")).unwrap(); 16 | 17 | let fstype = CString::new("fstype").unwrap(); 18 | let nullfs = CString::new("nullfs").unwrap(); 19 | Nmount::new() 20 | .str_opt(&fstype, &nullfs) 21 | .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) 22 | .str_opt_owned("target", target.path().to_str().unwrap()) 23 | .nmount(MntFlags::empty()) 24 | .unwrap(); 25 | 26 | // Now check that the sentry is visible through the mountpoint 27 | let exists = Path::exists(&mountpoint.path().join("sentry")); 28 | 29 | // Cleanup the mountpoint before asserting 30 | unmount(mountpoint.path(), MntFlags::empty()).unwrap(); 31 | 32 | assert!(exists); 33 | } 34 | 35 | #[test] 36 | fn bad_fstype() { 37 | let mountpoint = tempdir().unwrap(); 38 | let target = tempdir().unwrap(); 39 | let _sentry = File::create(target.path().join("sentry")).unwrap(); 40 | 41 | let e = Nmount::new() 42 | .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) 43 | .str_opt_owned("target", target.path().to_str().unwrap()) 44 | .nmount(MntFlags::empty()) 45 | .unwrap_err(); 46 | 47 | assert_eq!(e.error(), Errno::EINVAL); 48 | assert_eq!(e.errmsg(), Some("Invalid fstype")); 49 | } 50 | -------------------------------------------------------------------------------- /test/sys/mod.rs: -------------------------------------------------------------------------------- 1 | mod test_signal; 2 | 3 | // NOTE: DragonFly lacks a kernel-level implementation of Posix AIO as of 4 | // this writing. There is an user-level implementation, but whether aio 5 | // works or not heavily depends on which pthread implementation is chosen 6 | // by the user at link time. For this reason we do not want to run aio test 7 | // cases on DragonFly. 8 | #[cfg(any( 9 | target_os = "freebsd", 10 | apple_targets, 11 | all( 12 | target_os = "linux", 13 | not(any(target_env = "uclibc", target_env = "ohos")) 14 | ), 15 | target_os = "netbsd" 16 | ))] 17 | mod test_aio; 18 | #[cfg(not(any( 19 | target_os = "redox", 20 | target_os = "fuchsia", 21 | target_os = "haiku", 22 | target_os = "hurd", 23 | target_os = "cygwin" 24 | )))] 25 | mod test_ioctl; 26 | #[cfg(not(target_os = "redox"))] 27 | mod test_mman; 28 | #[cfg(not(target_os = "redox"))] 29 | mod test_select; 30 | #[cfg(target_os = "linux")] 31 | mod test_signalfd; 32 | #[cfg(not(any(target_os = "redox", target_os = "haiku")))] 33 | mod test_socket; 34 | #[cfg(not(any(target_os = "redox")))] 35 | mod test_sockopt; 36 | mod test_stat; 37 | #[cfg(linux_android)] 38 | mod test_sysinfo; 39 | #[cfg(not(any( 40 | target_os = "redox", 41 | target_os = "fuchsia", 42 | target_os = "haiku" 43 | )))] 44 | mod test_termios; 45 | mod test_uio; 46 | mod test_wait; 47 | 48 | #[cfg(linux_android)] 49 | mod test_epoll; 50 | #[cfg(target_os = "linux")] 51 | mod test_fanotify; 52 | #[cfg(target_os = "linux")] 53 | mod test_inotify; 54 | mod test_pthread; 55 | 56 | #[cfg(any(linux_android, freebsdlike, netbsdlike, apple_targets))] 57 | mod test_ptrace; 58 | #[cfg(linux_android)] 59 | mod test_timerfd; 60 | 61 | #[cfg(all( 62 | any( 63 | target_os = "freebsd", 64 | solarish, 65 | target_os = "linux", 66 | target_os = "netbsd" 67 | ), 68 | feature = "time", 69 | feature = "signal" 70 | ))] 71 | mod test_timer; 72 | 73 | #[cfg(bsd)] 74 | mod test_event; 75 | mod test_statvfs; 76 | mod test_time; 77 | mod test_utsname; 78 | 79 | #[cfg(any(linux_android, freebsdlike, apple_targets, target_os = "openbsd"))] 80 | mod test_statfs; 81 | 82 | #[cfg(not(any( 83 | target_os = "redox", 84 | target_os = "fuchsia", 85 | solarish, 86 | target_os = "haiku" 87 | )))] 88 | mod test_resource; 89 | 90 | // This test module should be enabled for both linux_android and freebsd, but 91 | // the `memfd_create(2)` symbol is not available under Linux QEMU, 92 | // 93 | // https://github.com/nix-rust/nix/actions/runs/9427112650/job/25970870477 94 | // 95 | // and I haven't found a way to stop the linker from linking that symbol, so 96 | // only enable this for FreeBSD for now. 97 | #[cfg(target_os = "freebsd")] 98 | mod test_memfd; 99 | -------------------------------------------------------------------------------- /test/sys/test_aio_drop.rs: -------------------------------------------------------------------------------- 1 | // Test dropping an AioCb that hasn't yet finished. 2 | // This must happen in its own process, because on OSX this test seems to hose 3 | // the AIO subsystem and causes subsequent tests to fail 4 | #[test] 5 | #[should_panic(expected = "Dropped an in-progress AioCb")] 6 | #[cfg(all( 7 | not(target_env = "musl"), 8 | not(target_env = "uclibc"), 9 | not(target_env = "ohos"), 10 | any( 11 | target_os = "linux", 12 | apple_targets, 13 | target_os = "freebsd", 14 | target_os = "netbsd" 15 | ) 16 | ))] 17 | fn test_drop() { 18 | use nix::sys::aio::*; 19 | use nix::sys::signal::*; 20 | use std::os::unix::io::AsFd; 21 | use tempfile::tempfile; 22 | 23 | const WBUF: &[u8] = b"CDEF"; 24 | 25 | let f = tempfile().unwrap(); 26 | f.set_len(6).unwrap(); 27 | let mut aiocb = Box::pin(AioWrite::new( 28 | f.as_fd(), 29 | 2, //offset 30 | WBUF, 31 | 0, //priority 32 | SigevNotify::SigevNone, 33 | )); 34 | aiocb.as_mut().submit().unwrap(); 35 | } 36 | -------------------------------------------------------------------------------- /test/sys/test_epoll.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | 3 | use nix::errno::Errno; 4 | use nix::sys::epoll::{epoll_create1, epoll_ctl}; 5 | use nix::sys::epoll::{EpollCreateFlags, EpollEvent, EpollFlags, EpollOp}; 6 | 7 | #[test] 8 | pub fn test_epoll_errno() { 9 | let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); 10 | let result = epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None); 11 | result.expect_err("assertion failed"); 12 | assert_eq!(result.unwrap_err(), Errno::ENOENT); 13 | 14 | let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None); 15 | result.expect_err("assertion failed"); 16 | assert_eq!(result.unwrap_err(), Errno::EINVAL); 17 | } 18 | 19 | #[test] 20 | pub fn test_epoll_ctl() { 21 | let efd = epoll_create1(EpollCreateFlags::empty()).unwrap(); 22 | let mut event = 23 | EpollEvent::new(EpollFlags::EPOLLIN | EpollFlags::EPOLLERR, 1); 24 | epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, &mut event).unwrap(); 25 | epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None).unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /test/sys/test_event.rs: -------------------------------------------------------------------------------- 1 | use libc::intptr_t; 2 | use nix::sys::event::{EvFlags, EventFilter, FilterFlag, KEvent}; 3 | 4 | #[test] 5 | fn test_struct_kevent() { 6 | use std::mem; 7 | 8 | let udata: intptr_t = 12345; 9 | let data: intptr_t = 0x1337; 10 | 11 | let actual = KEvent::new( 12 | 0xdead_beef, 13 | EventFilter::EVFILT_READ, 14 | EvFlags::EV_ONESHOT | EvFlags::EV_ADD, 15 | FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, 16 | data, 17 | udata, 18 | ); 19 | assert_eq!(0xdead_beef, actual.ident()); 20 | assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); 21 | assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits()); 22 | assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits()); 23 | assert_eq!(data, actual.data()); 24 | assert_eq!(udata, actual.udata()); 25 | assert_eq!(mem::size_of::(), mem::size_of::()); 26 | } 27 | 28 | #[test] 29 | fn test_kevent_filter() { 30 | let udata: intptr_t = 12345; 31 | 32 | let actual = KEvent::new( 33 | 0xdead_beef, 34 | EventFilter::EVFILT_READ, 35 | EvFlags::EV_ONESHOT | EvFlags::EV_ADD, 36 | FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT, 37 | 0x1337, 38 | udata, 39 | ); 40 | assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap()); 41 | } 42 | -------------------------------------------------------------------------------- /test/sys/test_fanotify.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use nix::errno::Errno; 3 | use nix::fcntl::AT_FDCWD; 4 | use nix::sys::fanotify::{ 5 | EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, 6 | Response, 7 | }; 8 | use std::fs::{read_link, read_to_string, File, OpenOptions}; 9 | use std::io::ErrorKind; 10 | use std::io::{Read, Write}; 11 | use std::os::fd::AsRawFd; 12 | use std::thread; 13 | 14 | #[test] 15 | /// Run fanotify tests sequentially to avoid tmp files races 16 | pub fn test_fanotify() { 17 | require_capability!("test_fanotify", CAP_SYS_ADMIN); 18 | 19 | test_fanotify_notifications(); 20 | test_fanotify_responses(); 21 | test_fanotify_overflow(); 22 | } 23 | 24 | fn test_fanotify_notifications() { 25 | let group = 26 | Fanotify::init(InitFlags::FAN_CLASS_NOTIF, EventFFlags::O_RDONLY) 27 | .unwrap(); 28 | let tempdir = tempfile::tempdir().unwrap(); 29 | let tempfile = tempdir.path().join("test"); 30 | OpenOptions::new() 31 | .write(true) 32 | .create_new(true) 33 | .open(&tempfile) 34 | .unwrap(); 35 | 36 | group 37 | .mark( 38 | MarkFlags::FAN_MARK_ADD, 39 | MaskFlags::FAN_OPEN | MaskFlags::FAN_MODIFY | MaskFlags::FAN_CLOSE, 40 | AT_FDCWD, 41 | Some(&tempfile), 42 | ) 43 | .unwrap(); 44 | 45 | // modify test file 46 | { 47 | let mut f = OpenOptions::new().write(true).open(&tempfile).unwrap(); 48 | f.write_all(b"hello").unwrap(); 49 | } 50 | 51 | let mut events = group.read_events().unwrap(); 52 | assert_eq!(events.len(), 1, "should have read exactly one event"); 53 | let event = events.pop().unwrap(); 54 | assert!(event.check_version()); 55 | assert_eq!( 56 | event.mask(), 57 | MaskFlags::FAN_OPEN 58 | | MaskFlags::FAN_MODIFY 59 | | MaskFlags::FAN_CLOSE_WRITE 60 | ); 61 | let fd_opt = event.fd(); 62 | let fd = fd_opt.as_ref().unwrap(); 63 | let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); 64 | assert_eq!(path, tempfile); 65 | 66 | // read test file 67 | { 68 | let mut f = File::open(&tempfile).unwrap(); 69 | let mut s = String::new(); 70 | f.read_to_string(&mut s).unwrap(); 71 | } 72 | 73 | let mut events = group.read_events().unwrap(); 74 | assert_eq!(events.len(), 1, "should have read exactly one event"); 75 | let event = events.pop().unwrap(); 76 | assert!(event.check_version()); 77 | assert_eq!( 78 | event.mask(), 79 | MaskFlags::FAN_OPEN | MaskFlags::FAN_CLOSE_NOWRITE 80 | ); 81 | let fd_opt = event.fd(); 82 | let fd = fd_opt.as_ref().unwrap(); 83 | let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); 84 | assert_eq!(path, tempfile); 85 | } 86 | 87 | fn test_fanotify_responses() { 88 | let group = 89 | Fanotify::init(InitFlags::FAN_CLASS_CONTENT, EventFFlags::O_RDONLY) 90 | .unwrap(); 91 | let tempdir = tempfile::tempdir().unwrap(); 92 | let tempfile = tempdir.path().join("test"); 93 | OpenOptions::new() 94 | .write(true) 95 | .create_new(true) 96 | .open(&tempfile) 97 | .unwrap(); 98 | 99 | group 100 | .mark( 101 | MarkFlags::FAN_MARK_ADD, 102 | MaskFlags::FAN_OPEN_PERM, 103 | AT_FDCWD, 104 | Some(&tempfile), 105 | ) 106 | .unwrap(); 107 | 108 | let file_thread = thread::spawn({ 109 | let tempfile = tempfile.clone(); 110 | 111 | move || { 112 | // first open, should fail 113 | let Err(e) = File::open(&tempfile) else { 114 | panic!("The first open should fail"); 115 | }; 116 | assert_eq!(e.kind(), ErrorKind::PermissionDenied); 117 | 118 | // second open, should succeed 119 | File::open(&tempfile).unwrap(); 120 | } 121 | }); 122 | 123 | // Deny the first open try 124 | let mut events = group.read_events().unwrap(); 125 | assert_eq!(events.len(), 1, "should have read exactly one event"); 126 | let event = events.pop().unwrap(); 127 | assert!(event.check_version()); 128 | assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); 129 | let fd_opt = event.fd(); 130 | let fd = fd_opt.as_ref().unwrap(); 131 | let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); 132 | assert_eq!(path, tempfile); 133 | group 134 | .write_response(FanotifyResponse::new(*fd, Response::FAN_DENY)) 135 | .unwrap(); 136 | 137 | // Allow the second open try 138 | let mut events = group.read_events().unwrap(); 139 | assert_eq!(events.len(), 1, "should have read exactly one event"); 140 | let event = events.pop().unwrap(); 141 | assert!(event.check_version()); 142 | assert_eq!(event.mask(), MaskFlags::FAN_OPEN_PERM); 143 | let fd_opt = event.fd(); 144 | let fd = fd_opt.as_ref().unwrap(); 145 | let path = read_link(format!("/proc/self/fd/{}", fd.as_raw_fd())).unwrap(); 146 | assert_eq!(path, tempfile); 147 | group 148 | .write_response(FanotifyResponse::new(*fd, Response::FAN_ALLOW)) 149 | .unwrap(); 150 | 151 | file_thread.join().unwrap(); 152 | } 153 | 154 | fn test_fanotify_overflow() { 155 | let max_events: usize = 156 | read_to_string("/proc/sys/fs/fanotify/max_queued_events") 157 | .unwrap() 158 | .trim() 159 | .parse() 160 | .unwrap(); 161 | 162 | // make sure the kernel is configured with the default value, 163 | // just so this test doesn't run forever 164 | assert_eq!(max_events, 16384); 165 | 166 | let group = Fanotify::init( 167 | InitFlags::FAN_CLASS_NOTIF 168 | | InitFlags::FAN_REPORT_TID 169 | | InitFlags::FAN_NONBLOCK, 170 | EventFFlags::O_RDONLY, 171 | ) 172 | .unwrap(); 173 | let tempdir = tempfile::tempdir().unwrap(); 174 | let tempfile = tempdir.path().join("test"); 175 | 176 | OpenOptions::new() 177 | .write(true) 178 | .create_new(true) 179 | .open(&tempfile) 180 | .unwrap(); 181 | 182 | group 183 | .mark( 184 | MarkFlags::FAN_MARK_ADD, 185 | MaskFlags::FAN_OPEN, 186 | AT_FDCWD, 187 | Some(&tempfile), 188 | ) 189 | .unwrap(); 190 | 191 | thread::scope(|s| { 192 | // perform 10 more events to demonstrate some will be dropped 193 | for _ in 0..(max_events + 10) { 194 | s.spawn(|| { 195 | File::open(&tempfile).unwrap(); 196 | }); 197 | } 198 | }); 199 | 200 | // flush the queue until it's empty 201 | let mut n = 0; 202 | let mut last_event = None; 203 | loop { 204 | match group.read_events() { 205 | Ok(events) => { 206 | n += events.len(); 207 | if let Some(event) = events.last() { 208 | last_event = Some(event.mask()); 209 | } 210 | } 211 | Err(e) if e == Errno::EWOULDBLOCK => break, 212 | Err(e) => panic!("{e:?}"), 213 | } 214 | } 215 | 216 | // make sure we read all we expected. 217 | // the +1 is for the overflow event. 218 | assert_eq!(n, max_events + 1); 219 | assert_eq!(last_event, Some(MaskFlags::FAN_Q_OVERFLOW)); 220 | } 221 | -------------------------------------------------------------------------------- /test/sys/test_inotify.rs: -------------------------------------------------------------------------------- 1 | use nix::errno::Errno; 2 | use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify}; 3 | use std::ffi::OsString; 4 | use std::fs::{rename, File}; 5 | 6 | #[test] 7 | pub fn test_inotify() { 8 | let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); 9 | let tempdir = tempfile::tempdir().unwrap(); 10 | 11 | instance 12 | .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS) 13 | .unwrap(); 14 | 15 | let events = instance.read_events(); 16 | assert_eq!(events.unwrap_err(), Errno::EAGAIN); 17 | 18 | File::create(tempdir.path().join("test")).unwrap(); 19 | 20 | let events = instance.read_events().unwrap(); 21 | assert_eq!(events[0].name, Some(OsString::from("test"))); 22 | } 23 | 24 | #[test] 25 | pub fn test_inotify_multi_events() { 26 | let instance = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); 27 | let tempdir = tempfile::tempdir().unwrap(); 28 | 29 | instance 30 | .add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS) 31 | .unwrap(); 32 | 33 | let events = instance.read_events(); 34 | assert_eq!(events.unwrap_err(), Errno::EAGAIN); 35 | 36 | File::create(tempdir.path().join("test")).unwrap(); 37 | rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap(); 38 | 39 | // Now there should be 5 events in queue: 40 | // - IN_CREATE on test 41 | // - IN_OPEN on test 42 | // - IN_CLOSE_WRITE on test 43 | // - IN_MOVED_FROM on test with a cookie 44 | // - IN_MOVED_TO on test2 with the same cookie 45 | 46 | let events = instance.read_events().unwrap(); 47 | assert_eq!(events.len(), 5); 48 | 49 | assert_eq!(events[0].mask, AddWatchFlags::IN_CREATE); 50 | assert_eq!(events[0].name, Some(OsString::from("test"))); 51 | 52 | assert_eq!(events[1].mask, AddWatchFlags::IN_OPEN); 53 | assert_eq!(events[1].name, Some(OsString::from("test"))); 54 | 55 | assert_eq!(events[2].mask, AddWatchFlags::IN_CLOSE_WRITE); 56 | assert_eq!(events[2].name, Some(OsString::from("test"))); 57 | 58 | assert_eq!(events[3].mask, AddWatchFlags::IN_MOVED_FROM); 59 | assert_eq!(events[3].name, Some(OsString::from("test"))); 60 | 61 | assert_eq!(events[4].mask, AddWatchFlags::IN_MOVED_TO); 62 | assert_eq!(events[4].name, Some(OsString::from("test2"))); 63 | 64 | assert_eq!(events[3].cookie, events[4].cookie); 65 | } 66 | -------------------------------------------------------------------------------- /test/sys/test_memfd.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_memfd_create() { 3 | use nix::sys::memfd::memfd_create; 4 | use nix::sys::memfd::MFdFlags; 5 | use nix::unistd::lseek; 6 | use nix::unistd::read; 7 | use nix::unistd::{write, Whence}; 8 | 9 | let fd = 10 | memfd_create("test_memfd_create_name", MFdFlags::MFD_CLOEXEC).unwrap(); 11 | let contents = b"hello"; 12 | assert_eq!(write(&fd, contents).unwrap(), 5); 13 | 14 | lseek(&fd, 0, Whence::SeekSet).unwrap(); 15 | 16 | let mut buf = vec![0_u8; contents.len()]; 17 | assert_eq!(read(&fd, &mut buf).unwrap(), 5); 18 | 19 | assert_eq!(contents, buf.as_slice()); 20 | } 21 | -------------------------------------------------------------------------------- /test/sys/test_mman.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::redundant_slicing)] 2 | 3 | use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags}; 4 | use std::num::NonZeroUsize; 5 | 6 | #[test] 7 | fn test_mmap_anonymous() { 8 | unsafe { 9 | let mut ptr = mmap_anonymous( 10 | None, 11 | NonZeroUsize::new(1).unwrap(), 12 | ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, 13 | MapFlags::MAP_PRIVATE, 14 | ) 15 | .unwrap() 16 | .cast::(); 17 | assert_eq!(*ptr.as_ref(), 0x00u8); 18 | *ptr.as_mut() = 0xffu8; 19 | assert_eq!(*ptr.as_ref(), 0xffu8); 20 | } 21 | } 22 | 23 | #[test] 24 | #[cfg(any(target_os = "linux", target_os = "netbsd"))] 25 | fn test_mremap_grow() { 26 | use nix::libc::size_t; 27 | use nix::sys::mman::{mremap, MRemapFlags}; 28 | use std::ptr::NonNull; 29 | 30 | const ONE_K: size_t = 1024; 31 | let one_k_non_zero = NonZeroUsize::new(ONE_K).unwrap(); 32 | 33 | let slice: &mut [u8] = unsafe { 34 | let mem = mmap_anonymous( 35 | None, 36 | one_k_non_zero, 37 | ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, 38 | MapFlags::MAP_PRIVATE, 39 | ) 40 | .unwrap(); 41 | std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) 42 | }; 43 | assert_eq!(slice[ONE_K - 1], 0x00); 44 | slice[ONE_K - 1] = 0xFF; 45 | assert_eq!(slice[ONE_K - 1], 0xFF); 46 | 47 | let slice: &mut [u8] = unsafe { 48 | #[cfg(target_os = "linux")] 49 | let mem = mremap( 50 | NonNull::from(&mut slice[..]).cast(), 51 | ONE_K, 52 | 10 * ONE_K, 53 | MRemapFlags::MREMAP_MAYMOVE, 54 | None, 55 | ) 56 | .unwrap(); 57 | #[cfg(target_os = "netbsd")] 58 | let mem = mremap( 59 | NonNull::from(&mut slice[..]).cast(), 60 | ONE_K, 61 | 10 * ONE_K, 62 | MRemapFlags::MAP_REMAPDUP, 63 | None, 64 | ) 65 | .unwrap(); 66 | std::slice::from_raw_parts_mut(mem.cast().as_ptr(), 10 * ONE_K) 67 | }; 68 | 69 | // The first KB should still have the old data in it. 70 | assert_eq!(slice[ONE_K - 1], 0xFF); 71 | 72 | // The additional range should be zero-init'd and accessible. 73 | assert_eq!(slice[10 * ONE_K - 1], 0x00); 74 | slice[10 * ONE_K - 1] = 0xFF; 75 | assert_eq!(slice[10 * ONE_K - 1], 0xFF); 76 | } 77 | 78 | #[test] 79 | #[cfg(any(target_os = "linux", target_os = "netbsd"))] 80 | // Segfaults for unknown reasons under QEMU for 32-bit targets 81 | #[cfg_attr(all(target_pointer_width = "32", qemu), ignore)] 82 | fn test_mremap_shrink() { 83 | use nix::libc::size_t; 84 | use nix::sys::mman::{mremap, MRemapFlags}; 85 | use std::num::NonZeroUsize; 86 | use std::ptr::NonNull; 87 | 88 | const ONE_K: size_t = 1024; 89 | let ten_one_k = NonZeroUsize::new(10 * ONE_K).unwrap(); 90 | let slice: &mut [u8] = unsafe { 91 | let mem = mmap_anonymous( 92 | None, 93 | ten_one_k, 94 | ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, 95 | MapFlags::MAP_PRIVATE, 96 | ) 97 | .unwrap(); 98 | std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) 99 | }; 100 | assert_eq!(slice[ONE_K - 1], 0x00); 101 | slice[ONE_K - 1] = 0xFF; 102 | assert_eq!(slice[ONE_K - 1], 0xFF); 103 | 104 | let slice: &mut [u8] = unsafe { 105 | let mem = mremap( 106 | NonNull::from(&mut slice[..]).cast(), 107 | ten_one_k.into(), 108 | ONE_K, 109 | MRemapFlags::empty(), 110 | None, 111 | ) 112 | .unwrap(); 113 | // Since we didn't supply MREMAP_MAYMOVE, the address should be the 114 | // same. 115 | assert_eq!(mem.as_ptr(), NonNull::from(&mut slice[..]).cast().as_ptr()); 116 | std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) 117 | }; 118 | 119 | // The first KB should still be accessible and have the old data in it. 120 | assert_eq!(slice[ONE_K - 1], 0xFF); 121 | } 122 | 123 | #[test] 124 | #[cfg(target_os = "linux")] 125 | fn test_mremap_dontunmap() { 126 | use nix::libc::size_t; 127 | use nix::sys::mman::{mremap, MRemapFlags}; 128 | use std::num::NonZeroUsize; 129 | use std::ptr::NonNull; 130 | 131 | const ONE_K: size_t = 1024; 132 | let one_k_non_zero = NonZeroUsize::new(ONE_K).unwrap(); 133 | 134 | let slice: &mut [u8] = unsafe { 135 | let mem = mmap_anonymous( 136 | None, 137 | one_k_non_zero, 138 | ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, 139 | MapFlags::MAP_PRIVATE, 140 | ) 141 | .unwrap(); 142 | std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) 143 | }; 144 | 145 | // because we do not unmap `slice`, `old_size` and `new_size` 146 | // need to be equal or `EINVAL` is set. 147 | let _new_slice: &mut [u8] = unsafe { 148 | let mem = mremap( 149 | NonNull::from(&mut slice[..]).cast(), 150 | ONE_K, 151 | ONE_K, 152 | MRemapFlags::MREMAP_MAYMOVE | MRemapFlags::MREMAP_DONTUNMAP, 153 | None, 154 | ) 155 | .unwrap(); 156 | std::slice::from_raw_parts_mut(mem.cast().as_ptr(), 10 * ONE_K) 157 | }; 158 | } 159 | 160 | #[test] 161 | #[cfg(target_os = "linux")] 162 | fn test_madv_wipeonfork() { 163 | use nix::libc::size_t; 164 | use nix::sys::mman::{madvise, MmapAdvise}; 165 | use nix::unistd::{fork, ForkResult}; 166 | use std::num::NonZeroUsize; 167 | 168 | const ONE_K: size_t = 1024; 169 | let ten_one_k = NonZeroUsize::new(10 * ONE_K).unwrap(); 170 | let slice: &mut [u8] = unsafe { 171 | let mem = mmap_anonymous( 172 | None, 173 | ten_one_k, 174 | ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, 175 | MapFlags::MAP_PRIVATE, 176 | ) 177 | .unwrap(); 178 | madvise(mem, ONE_K, MmapAdvise::MADV_WIPEONFORK) 179 | .expect("madvise failed"); 180 | std::slice::from_raw_parts_mut(mem.as_ptr().cast(), ONE_K) 181 | }; 182 | slice[ONE_K - 1] = 0xFF; 183 | let _m = crate::FORK_MTX.lock(); 184 | 185 | unsafe { 186 | let res = fork().expect("fork failed"); 187 | match res { 188 | ForkResult::Child => { 189 | // that s the whole point of MADV_WIPEONFORK 190 | assert_eq!(slice[ONE_K - 1], 0x00); 191 | libc::_exit(0); 192 | } 193 | ForkResult::Parent { child } => { 194 | nix::sys::signal::kill(child, nix::sys::signal::SIGTERM) 195 | .unwrap(); 196 | let _ = nix::sys::wait::wait().unwrap(); 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /test/sys/test_prctl.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | #[cfg(feature = "process")] 3 | mod test_prctl { 4 | use std::ffi::CStr; 5 | 6 | use nix::sys::prctl; 7 | 8 | #[cfg_attr(qemu, ignore)] 9 | #[test] 10 | fn test_get_set_subreaper() { 11 | let original = prctl::get_child_subreaper().unwrap(); 12 | 13 | prctl::set_child_subreaper(true).unwrap(); 14 | let subreaper = prctl::get_child_subreaper().unwrap(); 15 | assert!(subreaper); 16 | 17 | prctl::set_child_subreaper(original).unwrap(); 18 | } 19 | 20 | #[test] 21 | fn test_get_set_dumpable() { 22 | let original = prctl::get_dumpable().unwrap(); 23 | 24 | prctl::set_dumpable(false).unwrap(); 25 | let dumpable = prctl::get_dumpable().unwrap(); 26 | assert!(!dumpable); 27 | 28 | prctl::set_dumpable(original).unwrap(); 29 | } 30 | 31 | #[test] 32 | fn test_get_set_keepcaps() { 33 | let original = prctl::get_keepcaps().unwrap(); 34 | 35 | prctl::set_keepcaps(true).unwrap(); 36 | let keepcaps = prctl::get_keepcaps().unwrap(); 37 | assert!(keepcaps); 38 | 39 | prctl::set_keepcaps(original).unwrap(); 40 | } 41 | 42 | #[test] 43 | fn test_get_set_clear_mce_kill() { 44 | use prctl::PrctlMCEKillPolicy::*; 45 | 46 | prctl::set_mce_kill(PR_MCE_KILL_LATE).unwrap(); 47 | let mce = prctl::get_mce_kill().unwrap(); 48 | assert_eq!(mce, PR_MCE_KILL_LATE); 49 | 50 | prctl::clear_mce_kill().unwrap(); 51 | let mce = prctl::get_mce_kill().unwrap(); 52 | assert_eq!(mce, PR_MCE_KILL_DEFAULT); 53 | } 54 | 55 | #[cfg_attr(qemu, ignore)] 56 | #[test] 57 | fn test_get_set_pdeathsig() { 58 | use nix::sys::signal::Signal; 59 | 60 | let original = prctl::get_pdeathsig().unwrap(); 61 | 62 | prctl::set_pdeathsig(Signal::SIGUSR1).unwrap(); 63 | let sig = prctl::get_pdeathsig().unwrap(); 64 | assert_eq!(sig, Some(Signal::SIGUSR1)); 65 | 66 | prctl::set_pdeathsig(original).unwrap(); 67 | } 68 | 69 | #[test] 70 | fn test_get_set_name() { 71 | let original = prctl::get_name().unwrap(); 72 | 73 | let long_name = 74 | CStr::from_bytes_with_nul(b"0123456789abcdefghijklmn\0").unwrap(); 75 | prctl::set_name(long_name).unwrap(); 76 | let res = prctl::get_name().unwrap(); 77 | 78 | // name truncated by kernel to TASK_COMM_LEN 79 | assert_eq!(&long_name.to_str().unwrap()[..15], res.to_str().unwrap()); 80 | 81 | let short_name = CStr::from_bytes_with_nul(b"01234567\0").unwrap(); 82 | prctl::set_name(short_name).unwrap(); 83 | let res = prctl::get_name().unwrap(); 84 | assert_eq!(short_name.to_str().unwrap(), res.to_str().unwrap()); 85 | 86 | prctl::set_name(&original).unwrap(); 87 | } 88 | 89 | #[cfg_attr(qemu, ignore)] 90 | #[test] 91 | fn test_get_set_timerslack() { 92 | let original = prctl::get_timerslack().unwrap() as libc::c_ulong; 93 | 94 | let slack = 60_000; 95 | prctl::set_timerslack(slack).unwrap(); 96 | let res = prctl::get_timerslack().unwrap() as libc::c_ulong; 97 | assert_eq!(slack, res); 98 | 99 | prctl::set_timerslack(original).unwrap(); 100 | } 101 | 102 | // Loongarch need to use a newer QEMU that disabled these PRCTL subcodes/methods. 103 | // https://github.com/qemu/qemu/commit/220717a6f46a99031a5b1af964bbf4dec1310440 104 | // So we should ignore them when testing in QEMU environments. 105 | #[cfg_attr(all(qemu, target_arch = "loongarch64"), ignore)] 106 | #[test] 107 | fn test_disable_enable_perf_events() { 108 | prctl::task_perf_events_disable().unwrap(); 109 | prctl::task_perf_events_enable().unwrap(); 110 | } 111 | 112 | #[test] 113 | fn test_get_set_no_new_privs() { 114 | prctl::set_no_new_privs().unwrap(); 115 | let no_new_privs = prctl::get_no_new_privs().unwrap(); 116 | assert!(no_new_privs); 117 | } 118 | 119 | // Loongarch need to use a newer QEMU that disabled these PRCTL subcodes/methods 120 | // https://github.com/qemu/qemu/commit/220717a6f46a99031a5b1af964bbf4dec1310440 121 | // So we should ignore them when testing in QEMU environments. 122 | #[cfg_attr(all(qemu, target_arch = "loongarch64"), ignore)] 123 | #[test] 124 | fn test_get_set_thp_disable() { 125 | let original = prctl::get_thp_disable().unwrap(); 126 | 127 | prctl::set_thp_disable(true).unwrap(); 128 | let thp_disable = prctl::get_thp_disable().unwrap(); 129 | assert!(thp_disable); 130 | 131 | prctl::set_thp_disable(original).unwrap(); 132 | } 133 | 134 | // Ignore this test under QEMU, as it started failing after updating the Linux CI 135 | // runner image, for reasons unknown. 136 | // 137 | // See: https://github.com/nix-rust/nix/issues/2418 138 | #[test] 139 | #[cfg_attr(qemu, ignore)] 140 | fn test_set_vma_anon_name() { 141 | use nix::errno::Errno; 142 | use nix::sys::mman; 143 | use std::num::NonZeroUsize; 144 | 145 | const ONE_K: libc::size_t = 1024; 146 | let sz = NonZeroUsize::new(ONE_K).unwrap(); 147 | let ptr = unsafe { 148 | mman::mmap_anonymous( 149 | None, 150 | sz, 151 | mman::ProtFlags::PROT_READ, 152 | mman::MapFlags::MAP_SHARED, 153 | ) 154 | .unwrap() 155 | }; 156 | let err = prctl::set_vma_anon_name( 157 | ptr, 158 | sz, 159 | Some(CStr::from_bytes_with_nul(b"[,$\0").unwrap()), 160 | ) 161 | .unwrap_err(); 162 | assert_eq!(err, Errno::EINVAL); 163 | // `CONFIG_ANON_VMA_NAME` kernel config might not be set 164 | prctl::set_vma_anon_name( 165 | ptr, 166 | sz, 167 | Some(CStr::from_bytes_with_nul(b"Nix\0").unwrap()), 168 | ) 169 | .unwrap_or_default(); 170 | prctl::set_vma_anon_name(ptr, sz, None).unwrap_or_default(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /test/sys/test_pthread.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::pthread::*; 2 | 3 | #[cfg(any( 4 | target_env = "musl", 5 | target_os = "redox", 6 | target_env = "ohos", 7 | target_os = "cygwin" 8 | ))] 9 | #[test] 10 | fn test_pthread_self() { 11 | let tid = pthread_self(); 12 | assert!(!tid.is_null()); 13 | } 14 | 15 | #[cfg(not(any( 16 | target_env = "musl", 17 | target_os = "redox", 18 | target_env = "ohos", 19 | target_os = "cygwin" 20 | )))] 21 | #[test] 22 | fn test_pthread_self() { 23 | let tid = pthread_self(); 24 | assert_ne!(tid, 0); 25 | } 26 | 27 | #[test] 28 | #[cfg(not(target_os = "redox"))] 29 | fn test_pthread_kill_none() { 30 | pthread_kill(pthread_self(), None) 31 | .expect("Should be able to send signal to my thread."); 32 | } 33 | -------------------------------------------------------------------------------- /test/sys/test_resource.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::resource::{getrlimit, setrlimit, Resource}; 2 | use nix::sys::resource::{getrusage, UsageWho}; 3 | 4 | /// Tests the RLIMIT_NOFILE functionality of getrlimit(), where the resource RLIMIT_NOFILE refers 5 | /// to the maximum file descriptor number that can be opened by the process (aka the maximum number 6 | /// of file descriptors that the process can open, since Linux 4.5). 7 | /// 8 | /// We first fetch the existing file descriptor maximum values using getrlimit(), then edit the 9 | /// soft limit to make sure it has a new and distinct value to the hard limit. We then setrlimit() 10 | /// to put the new soft limit in effect, and then getrlimit() once more to ensure the limits have 11 | /// been updated. 12 | #[test] 13 | #[cfg_attr(target_os = "cygwin", ignore)] 14 | pub fn test_resource_limits_nofile() { 15 | let (mut soft_limit, hard_limit) = 16 | getrlimit(Resource::RLIMIT_NOFILE).unwrap(); 17 | 18 | soft_limit -= 1; 19 | assert_ne!(soft_limit, hard_limit); 20 | setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); 21 | 22 | let (new_soft_limit, _) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); 23 | assert_eq!(new_soft_limit, soft_limit); 24 | } 25 | 26 | #[test] 27 | pub fn test_self_cpu_time() { 28 | // Make sure some CPU time is used. 29 | let mut numbers: Vec = (1..1_000_000).collect(); 30 | numbers.iter_mut().for_each(|item| *item *= 2); 31 | 32 | // FIXME: this is here to help ensure the compiler does not optimize the whole 33 | // thing away. Replace the assert with test::black_box once stabilized. 34 | assert_eq!(numbers[100..200].iter().sum::(), 30_100); 35 | 36 | let usage = getrusage(UsageWho::RUSAGE_SELF) 37 | .expect("Failed to call getrusage for SELF"); 38 | let rusage = usage.as_ref(); 39 | 40 | let user = usage.user_time(); 41 | assert!(user.tv_sec() > 0 || user.tv_usec() > 0); 42 | assert_eq!(user.tv_sec(), rusage.ru_utime.tv_sec); 43 | assert_eq!(user.tv_usec(), rusage.ru_utime.tv_usec); 44 | } 45 | -------------------------------------------------------------------------------- /test/sys/test_signalfd.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | #[test] 4 | fn create_signalfd() { 5 | use nix::sys::{signal::SigSet, signalfd::SignalFd}; 6 | 7 | let mask = SigSet::empty(); 8 | SignalFd::new(&mask).unwrap(); 9 | } 10 | 11 | #[test] 12 | fn create_signalfd_with_opts() { 13 | use nix::sys::{ 14 | signal::SigSet, 15 | signalfd::{SfdFlags, SignalFd}, 16 | }; 17 | 18 | let mask = SigSet::empty(); 19 | SignalFd::with_flags(&mask, SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK) 20 | .unwrap(); 21 | } 22 | 23 | #[test] 24 | fn read_empty_signalfd() { 25 | use nix::sys::{ 26 | signal::SigSet, 27 | signalfd::{SfdFlags, SignalFd}, 28 | }; 29 | 30 | let mask = SigSet::empty(); 31 | let fd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); 32 | 33 | let res = fd.read_signal(); 34 | assert!(res.unwrap().is_none()); 35 | } 36 | 37 | #[test] 38 | fn test_signalfd() { 39 | use nix::sys::signal::{self, raise, SigSet, Signal}; 40 | use nix::sys::signalfd::SignalFd; 41 | 42 | // Grab the mutex for altering signals so we don't interfere with other tests. 43 | let _m = crate::SIGNAL_MTX.lock(); 44 | 45 | // Block the SIGUSR1 signal from automatic processing for this thread 46 | let mut mask = SigSet::empty(); 47 | mask.add(signal::SIGUSR1); 48 | mask.thread_block().unwrap(); 49 | 50 | let fd = SignalFd::new(&mask).unwrap(); 51 | 52 | // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill` 53 | // because `kill` with `getpid` isn't correct during multi-threaded execution like during a 54 | // cargo test session. Instead use `raise` which does the correct thing by default. 55 | raise(signal::SIGUSR1).expect("Error: raise(SIGUSR1) failed"); 56 | 57 | // And now catch that same signal. 58 | let res = fd.read_signal().unwrap().unwrap(); 59 | let signo = Signal::try_from(res.ssi_signo as i32).unwrap(); 60 | assert_eq!(signo, signal::SIGUSR1); 61 | } 62 | 63 | /// Update the signal mask of an already existing signalfd. 64 | #[test] 65 | fn test_signalfd_setmask() { 66 | use nix::sys::signal::{self, raise, SigSet, Signal}; 67 | use nix::sys::signalfd::SignalFd; 68 | 69 | // Grab the mutex for altering signals so we don't interfere with other tests. 70 | let _m = crate::SIGNAL_MTX.lock(); 71 | 72 | // Block the SIGUSR1 signal from automatic processing for this thread 73 | let mut mask = SigSet::empty(); 74 | 75 | let fd = SignalFd::new(&mask).unwrap(); 76 | 77 | mask.add(signal::SIGUSR1); 78 | mask.thread_block().unwrap(); 79 | fd.set_mask(&mask).unwrap(); 80 | 81 | // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill` 82 | // because `kill` with `getpid` isn't correct during multi-threaded execution like during a 83 | // cargo test session. Instead use `raise` which does the correct thing by default. 84 | raise(signal::SIGUSR1).expect("Error: raise(SIGUSR1) failed"); 85 | 86 | // And now catch that same signal. 87 | let res = fd.read_signal().unwrap().unwrap(); 88 | let signo = Signal::try_from(res.ssi_signo as i32).unwrap(); 89 | assert_eq!(signo, signal::SIGUSR1); 90 | } 91 | -------------------------------------------------------------------------------- /test/sys/test_statfs.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::statfs::*; 2 | use nix::sys::statvfs::*; 3 | use std::fs::File; 4 | use std::path::Path; 5 | 6 | fn check_fstatfs(path: &str) { 7 | if !Path::new(path).exists() { 8 | return; 9 | } 10 | let vfs = statvfs(path.as_bytes()).unwrap(); 11 | let file = File::open(path).unwrap(); 12 | let fs = fstatfs(&file).unwrap(); 13 | assert_fs_equals(fs, vfs); 14 | } 15 | 16 | fn check_statfs(path: &str) { 17 | if !Path::new(path).exists() { 18 | return; 19 | } 20 | let vfs = statvfs(path.as_bytes()).unwrap(); 21 | let fs = statfs(path.as_bytes()).unwrap(); 22 | assert_fs_equals(fs, vfs); 23 | } 24 | 25 | fn check_fstatfs_strict(path: &str) { 26 | if !Path::new(path).exists() { 27 | return; 28 | } 29 | let vfs = statvfs(path.as_bytes()); 30 | let file = File::open(path).unwrap(); 31 | let fs = fstatfs(&file); 32 | assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) 33 | } 34 | 35 | fn check_statfs_strict(path: &str) { 36 | if !Path::new(path).exists() { 37 | return; 38 | } 39 | let vfs = statvfs(path.as_bytes()); 40 | let fs = statfs(path.as_bytes()); 41 | assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) 42 | } 43 | 44 | // The cast is not unnecessary on all platforms. 45 | #[allow(clippy::unnecessary_cast)] 46 | fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { 47 | assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); 48 | assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); 49 | } 50 | 51 | #[test] 52 | fn statfs_call() { 53 | check_statfs("/tmp"); 54 | check_statfs("/dev"); 55 | check_statfs("/run"); 56 | check_statfs("/"); 57 | } 58 | 59 | #[test] 60 | fn fstatfs_call() { 61 | check_fstatfs("/tmp"); 62 | check_fstatfs("/dev"); 63 | check_fstatfs("/run"); 64 | check_fstatfs("/"); 65 | } 66 | 67 | // This test is ignored because files_free/blocks_free can change after statvfs call and before 68 | // statfs call. 69 | #[test] 70 | #[ignore] 71 | fn statfs_call_strict() { 72 | check_statfs_strict("/tmp"); 73 | check_statfs_strict("/dev"); 74 | check_statfs_strict("/run"); 75 | check_statfs_strict("/"); 76 | } 77 | 78 | // This test is ignored because files_free/blocks_free can change after statvfs call and before 79 | // fstatfs call. 80 | #[test] 81 | #[ignore] 82 | fn fstatfs_call_strict() { 83 | check_fstatfs_strict("/tmp"); 84 | check_fstatfs_strict("/dev"); 85 | check_fstatfs_strict("/run"); 86 | check_fstatfs_strict("/"); 87 | } 88 | 89 | // The cast is not unnecessary on all platforms. 90 | #[allow(clippy::unnecessary_cast)] 91 | fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) { 92 | assert_eq!(fs.files_free() as u64, vfs.files_free() as u64); 93 | assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64); 94 | assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64); 95 | assert_eq!(fs.files() as u64, vfs.files() as u64); 96 | assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); 97 | assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); 98 | } 99 | -------------------------------------------------------------------------------- /test/sys/test_statvfs.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::statvfs::*; 2 | use std::fs::File; 3 | 4 | #[test] 5 | fn statvfs_call() { 6 | statvfs(&b"/"[..]).unwrap(); 7 | } 8 | 9 | #[test] 10 | fn fstatvfs_call() { 11 | let root = File::open("/").unwrap(); 12 | fstatvfs(&root).unwrap(); 13 | } 14 | -------------------------------------------------------------------------------- /test/sys/test_sysinfo.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::sysinfo::*; 2 | 3 | #[test] 4 | fn sysinfo_works() { 5 | let info = sysinfo().unwrap(); 6 | 7 | let (l1, l5, l15) = info.load_average(); 8 | assert!(l1 >= 0.0); 9 | assert!(l5 >= 0.0); 10 | assert!(l15 >= 0.0); 11 | 12 | info.uptime(); // just test Duration construction 13 | 14 | assert!( 15 | info.swap_free() <= info.swap_total(), 16 | "more swap available than installed (free: {}, total: {})", 17 | info.swap_free(), 18 | info.swap_total() 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /test/sys/test_termios.rs: -------------------------------------------------------------------------------- 1 | use std::os::unix::io::AsFd; 2 | use tempfile::tempfile; 3 | 4 | use nix::errno::Errno; 5 | use nix::fcntl; 6 | use nix::pty::openpty; 7 | use nix::sys::termios::{self, tcgetattr, BaudRate, LocalFlags, OutputFlags}; 8 | use nix::unistd::{read, write}; 9 | 10 | /// Helper function analogous to `std::io::Write::write_all`, but for `Fd`s 11 | fn write_all(f: Fd, buf: &[u8]) { 12 | let mut len = 0; 13 | while len < buf.len() { 14 | len += write(f.as_fd(), &buf[len..]).unwrap(); 15 | } 16 | } 17 | 18 | #[test] 19 | fn test_baudrate_try_from() { 20 | assert_eq!(Ok(BaudRate::B0), BaudRate::try_from(libc::B0)); 21 | #[cfg(not(target_os = "haiku"))] 22 | BaudRate::try_from(999999999).expect_err("assertion failed"); 23 | #[cfg(target_os = "haiku")] 24 | BaudRate::try_from(99).expect_err("assertion failed"); 25 | } 26 | 27 | // Test tcgetattr on a terminal 28 | #[test] 29 | fn test_tcgetattr_pty() { 30 | // openpty uses ptname(3) internally 31 | let _m = crate::PTSNAME_MTX.lock(); 32 | 33 | let pty = openpty(None, None).expect("openpty failed"); 34 | termios::tcgetattr(&pty.slave).unwrap(); 35 | } 36 | 37 | // Test tcgetattr on something that isn't a terminal 38 | #[test] 39 | fn test_tcgetattr_enotty() { 40 | let file = tempfile().unwrap(); 41 | assert_eq!(termios::tcgetattr(&file).err(), Some(Errno::ENOTTY)); 42 | } 43 | 44 | // Test modifying output flags 45 | #[test] 46 | fn test_output_flags() { 47 | // openpty uses ptname(3) internally 48 | let _m = crate::PTSNAME_MTX.lock(); 49 | 50 | // Open one pty to get attributes for the second one 51 | let mut termios = { 52 | let pty = openpty(None, None).expect("openpty failed"); 53 | tcgetattr(&pty.slave).expect("tcgetattr failed") 54 | }; 55 | 56 | // Make sure postprocessing '\r' isn't specified by default or this test is useless. 57 | assert!(!termios 58 | .output_flags 59 | .contains(OutputFlags::OPOST | OutputFlags::OCRNL)); 60 | 61 | // Specify that '\r' characters should be transformed to '\n' 62 | // OPOST is specified to enable post-processing 63 | termios 64 | .output_flags 65 | .insert(OutputFlags::OPOST | OutputFlags::OCRNL); 66 | 67 | // Open a pty 68 | let pty = openpty(None, &termios).unwrap(); 69 | 70 | // Write into the master 71 | let string = "foofoofoo\r"; 72 | write_all(&pty.master, string.as_bytes()); 73 | 74 | // Read from the slave verifying that the output has been properly transformed 75 | let mut buf = [0u8; 10]; 76 | crate::read_exact(&pty.slave, &mut buf); 77 | let transformed_string = "foofoofoo\n"; 78 | assert_eq!(&buf, transformed_string.as_bytes()); 79 | } 80 | 81 | // Test modifying local flags 82 | #[test] 83 | #[cfg(not(target_os = "solaris"))] 84 | fn test_local_flags() { 85 | // openpty uses ptname(3) internally 86 | let _m = crate::PTSNAME_MTX.lock(); 87 | 88 | // Open one pty to get attributes for the second one 89 | let mut termios = { 90 | let pty = openpty(None, None).unwrap(); 91 | tcgetattr(&pty.slave).unwrap() 92 | }; 93 | 94 | // Make sure echo is specified by default or this test is useless. 95 | assert!(termios.local_flags.contains(LocalFlags::ECHO)); 96 | 97 | // Disable local echo 98 | termios.local_flags.remove(LocalFlags::ECHO); 99 | 100 | // Open a new pty with our modified termios settings 101 | let pty = openpty(None, &termios).unwrap(); 102 | 103 | // Set the master is in nonblocking mode or reading will never return. 104 | let flags = fcntl::fcntl(&pty.master, fcntl::F_GETFL).unwrap(); 105 | let new_flags = 106 | fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK; 107 | fcntl::fcntl(pty.master.as_fd(), fcntl::F_SETFL(new_flags)).unwrap(); 108 | 109 | // Write into the master 110 | let string = "foofoofoo\r"; 111 | write_all(&pty.master, string.as_bytes()); 112 | 113 | // Try to read from the master, which should not have anything as echoing was disabled. 114 | let mut buf = [0u8; 10]; 115 | let read = read(&pty.master, &mut buf).unwrap_err(); 116 | assert_eq!(read, Errno::EAGAIN); 117 | } 118 | -------------------------------------------------------------------------------- /test/sys/test_time.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::time::{TimeSpec, TimeVal, TimeValLike}; 2 | use std::time::Duration; 3 | 4 | #[test] 5 | pub fn test_timespec() { 6 | assert_ne!(TimeSpec::seconds(1), TimeSpec::zero()); 7 | assert_eq!( 8 | TimeSpec::seconds(1) + TimeSpec::seconds(2), 9 | TimeSpec::seconds(3) 10 | ); 11 | assert_eq!( 12 | TimeSpec::minutes(3) + TimeSpec::seconds(2), 13 | TimeSpec::seconds(182) 14 | ); 15 | } 16 | 17 | #[test] 18 | pub fn test_timespec_from() { 19 | let duration = Duration::new(123, 123_456_789); 20 | let timespec = TimeSpec::nanoseconds(123_123_456_789); 21 | 22 | assert_eq!(TimeSpec::from(duration), timespec); 23 | assert_eq!(Duration::from(timespec), duration); 24 | } 25 | 26 | #[test] 27 | pub fn test_timespec_neg() { 28 | let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); 29 | let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); 30 | 31 | assert_eq!(a, -b); 32 | } 33 | 34 | #[test] 35 | pub fn test_timespec_ord() { 36 | assert_eq!(TimeSpec::seconds(1), TimeSpec::nanoseconds(1_000_000_000)); 37 | assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001)); 38 | assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999)); 39 | assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999)); 40 | assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001)); 41 | } 42 | 43 | #[test] 44 | pub fn test_timespec_fmt() { 45 | assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); 46 | assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); 47 | assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); 48 | assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); 49 | assert_eq!(TimeSpec::nanoseconds(42).to_string(), "0.000000042 seconds"); 50 | assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); 51 | } 52 | 53 | #[test] 54 | pub fn test_timeval() { 55 | assert_ne!(TimeVal::seconds(1), TimeVal::zero()); 56 | assert_eq!( 57 | TimeVal::seconds(1) + TimeVal::seconds(2), 58 | TimeVal::seconds(3) 59 | ); 60 | assert_eq!( 61 | TimeVal::minutes(3) + TimeVal::seconds(2), 62 | TimeVal::seconds(182) 63 | ); 64 | } 65 | 66 | #[test] 67 | pub fn test_timeval_ord() { 68 | assert_eq!(TimeVal::seconds(1), TimeVal::microseconds(1_000_000)); 69 | assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001)); 70 | assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999)); 71 | assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999)); 72 | assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001)); 73 | } 74 | 75 | #[test] 76 | pub fn test_timeval_neg() { 77 | let a = TimeVal::seconds(1) + TimeVal::microseconds(123); 78 | let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); 79 | 80 | assert_eq!(a, -b); 81 | } 82 | 83 | #[test] 84 | pub fn test_timeval_fmt() { 85 | assert_eq!(TimeVal::zero().to_string(), "0 seconds"); 86 | assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); 87 | assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); 88 | assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); 89 | assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); 90 | assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); 91 | } 92 | -------------------------------------------------------------------------------- /test/sys/test_timer.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::signal::{ 2 | sigaction, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify, 3 | Signal, 4 | }; 5 | use nix::sys::timer::{Expiration, Timer, TimerSetTimeFlags}; 6 | use nix::time::ClockId; 7 | use std::convert::TryFrom; 8 | use std::sync::atomic::{AtomicBool, Ordering}; 9 | use std::thread; 10 | use std::time::{Duration, Instant}; 11 | 12 | const SIG: Signal = Signal::SIGALRM; 13 | static ALARM_CALLED: AtomicBool = AtomicBool::new(false); 14 | 15 | pub extern "C" fn handle_sigalarm(raw_signal: libc::c_int) { 16 | let signal = Signal::try_from(raw_signal).unwrap(); 17 | if signal == SIG { 18 | ALARM_CALLED.store(true, Ordering::Release); 19 | } 20 | } 21 | 22 | #[test] 23 | fn alarm_fires() { 24 | // Avoid interfering with other signal using tests by taking a mutex shared 25 | // among other tests in this crate. 26 | let _m = crate::SIGNAL_MTX.lock(); 27 | const TIMER_PERIOD: Duration = Duration::from_millis(100); 28 | 29 | // 30 | // Setup 31 | // 32 | 33 | // Create a handler for the test signal, `SIG`. The handler is responsible 34 | // for flipping `ALARM_CALLED`. 35 | let handler = SigHandler::Handler(handle_sigalarm); 36 | let signal_action = 37 | SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty()); 38 | let old_handler = unsafe { 39 | sigaction(SIG, &signal_action) 40 | .expect("unable to set signal handler for alarm") 41 | }; 42 | 43 | // Create the timer. We use the monotonic clock here, though any would do 44 | // really. The timer is set to fire every 250 milliseconds with no delay for 45 | // the initial firing. 46 | let clockid = ClockId::CLOCK_MONOTONIC; 47 | let sigevent = SigEvent::new(SigevNotify::SigevSignal { 48 | signal: SIG, 49 | si_value: 0, 50 | }); 51 | let mut timer = 52 | Timer::new(clockid, sigevent).expect("failed to create timer"); 53 | let expiration = Expiration::Interval(TIMER_PERIOD.into()); 54 | let flags = TimerSetTimeFlags::empty(); 55 | timer.set(expiration, flags).expect("could not set timer"); 56 | 57 | // 58 | // Test 59 | // 60 | 61 | // Determine that there's still an expiration tracked by the 62 | // timer. Depending on when this runs either an `Expiration::Interval` or 63 | // `Expiration::IntervalDelayed` will be present. That is, if the timer has 64 | // not fired yet we'll get our original `expiration`, else the one that 65 | // represents a delay to the next expiration. We're only interested in the 66 | // timer still being extant. 67 | match timer.get() { 68 | Ok(Some(exp)) => assert!(matches!( 69 | exp, 70 | Expiration::Interval(..) | Expiration::IntervalDelayed(..) 71 | )), 72 | _ => panic!("timer lost its expiration"), 73 | } 74 | 75 | // Wait for 2 firings of the alarm before checking that it has fired and 76 | // been handled at least the once. If we wait for 3 seconds and the handler 77 | // is never called something has gone sideways and the test fails. 78 | let starttime = Instant::now(); 79 | loop { 80 | thread::sleep(2 * TIMER_PERIOD); 81 | if ALARM_CALLED.load(Ordering::Acquire) { 82 | break; 83 | } 84 | if starttime.elapsed() > Duration::from_secs(3) { 85 | panic!("Timeout waiting for SIGALRM"); 86 | } 87 | } 88 | 89 | // Cleanup: 90 | // 1) deregister the OS's timer. 91 | // 2) Wait for a full timer period, since POSIX does not require that 92 | // disabling the timer will clear pending signals, and on NetBSD at least 93 | // it does not. 94 | // 2) Replace the old signal handler now that we've completed the test. If 95 | // the test fails this process panics, so the fact we might not get here 96 | // is okay. 97 | drop(timer); 98 | thread::sleep(TIMER_PERIOD); 99 | unsafe { 100 | sigaction(SIG, &old_handler).expect("unable to reset signal handler"); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/sys/test_timerfd.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::time::{TimeSpec, TimeValLike}; 2 | use nix::sys::timerfd::{ 3 | ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags, 4 | }; 5 | use std::time::Instant; 6 | 7 | #[test] 8 | pub fn test_timerfd_oneshot() { 9 | let timer = 10 | TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); 11 | 12 | let before = Instant::now(); 13 | 14 | timer 15 | .set( 16 | Expiration::OneShot(TimeSpec::seconds(1)), 17 | TimerSetTimeFlags::empty(), 18 | ) 19 | .unwrap(); 20 | 21 | timer.wait().unwrap(); 22 | 23 | let millis = before.elapsed().as_millis(); 24 | assert!(millis > 900); 25 | } 26 | 27 | #[test] 28 | pub fn test_timerfd_interval() { 29 | let timer = 30 | TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); 31 | 32 | let before = Instant::now(); 33 | timer 34 | .set( 35 | Expiration::IntervalDelayed( 36 | TimeSpec::seconds(1), 37 | TimeSpec::seconds(2), 38 | ), 39 | TimerSetTimeFlags::empty(), 40 | ) 41 | .unwrap(); 42 | 43 | timer.wait().unwrap(); 44 | 45 | let start_delay = before.elapsed().as_millis(); 46 | assert!(start_delay > 900); 47 | 48 | timer.wait().unwrap(); 49 | 50 | let interval_delay = before.elapsed().as_millis(); 51 | assert!(interval_delay > 2900); 52 | } 53 | 54 | #[test] 55 | pub fn test_timerfd_unset() { 56 | let timer = 57 | TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap(); 58 | 59 | timer 60 | .set( 61 | Expiration::OneShot(TimeSpec::seconds(1)), 62 | TimerSetTimeFlags::empty(), 63 | ) 64 | .unwrap(); 65 | 66 | timer.unset().unwrap(); 67 | 68 | assert!(timer.get().unwrap().is_none()); 69 | } 70 | -------------------------------------------------------------------------------- /test/sys/test_utsname.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | #[test] 3 | pub fn test_uname_linux() { 4 | assert_eq!(nix::sys::utsname::uname().unwrap().sysname(), "Linux"); 5 | } 6 | 7 | #[cfg(apple_targets)] 8 | #[test] 9 | pub fn test_uname_darwin() { 10 | assert_eq!(nix::sys::utsname::uname().unwrap().sysname(), "Darwin"); 11 | } 12 | 13 | #[cfg(target_os = "freebsd")] 14 | #[test] 15 | pub fn test_uname_freebsd() { 16 | assert_eq!(nix::sys::utsname::uname().unwrap().sysname(), "FreeBSD"); 17 | } 18 | -------------------------------------------------------------------------------- /test/test.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cfg_if; 3 | #[cfg_attr(not(any(target_os = "redox")), macro_use)] 4 | extern crate nix; 5 | 6 | #[macro_use] 7 | mod common; 8 | mod mount; 9 | mod sys; 10 | #[cfg(not(target_os = "redox"))] 11 | mod test_dir; 12 | mod test_errno; 13 | mod test_fcntl; 14 | #[cfg(linux_android)] 15 | mod test_kmod; 16 | #[cfg(any( 17 | freebsdlike, 18 | all(target_os = "linux", not(target_env = "ohos")), 19 | target_os = "netbsd" 20 | ))] 21 | mod test_mq; 22 | #[cfg(not(target_os = "redox"))] 23 | mod test_net; 24 | mod test_nix_path; 25 | mod test_poll; 26 | #[cfg(not(any( 27 | target_os = "redox", 28 | target_os = "fuchsia", 29 | target_os = "haiku" 30 | )))] 31 | mod test_pty; 32 | #[cfg(any( 33 | linux_android, 34 | target_os = "dragonfly", 35 | all(target_os = "freebsd", fbsd14), 36 | ))] 37 | mod test_sched; 38 | #[cfg(any(linux_android, freebsdlike, apple_targets, solarish))] 39 | mod test_sendfile; 40 | #[cfg(any( 41 | target_os = "freebsd", 42 | target_os = "haiku", 43 | target_os = "linux", 44 | target_os = "netbsd", 45 | apple_targets 46 | ))] 47 | mod test_spawn; 48 | 49 | mod test_syslog; 50 | 51 | mod test_time; 52 | mod test_unistd; 53 | 54 | use nix::unistd::{chdir, getcwd, read}; 55 | use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; 56 | use std::os::unix::io::AsFd; 57 | use std::path::PathBuf; 58 | 59 | /// Helper function analogous to `std::io::Read::read_exact`, but for `Fd`s 60 | fn read_exact(f: Fd, buf: &mut [u8]) { 61 | let mut len = 0; 62 | while len < buf.len() { 63 | // get_mut would be better than split_at_mut, but it requires nightly 64 | let (_, remaining) = buf.split_at_mut(len); 65 | len += read(&f, remaining).unwrap(); 66 | } 67 | } 68 | 69 | /// Any test that creates child processes or can be affected by child processes 70 | /// must grab this mutex, regardless of what it does with those children. 71 | /// 72 | /// It must hold the mutex until the child processes are waited upon. 73 | pub static FORK_MTX: Mutex<()> = Mutex::new(()); 74 | /// Any test that changes the process's current working directory must grab 75 | /// the RwLock exclusively. Any process that cares about the current 76 | /// working directory must grab it shared. 77 | pub static CWD_LOCK: RwLock<()> = RwLock::new(()); 78 | /// Any test that changes the process's supplementary groups must grab this 79 | /// mutex 80 | pub static GROUPS_MTX: Mutex<()> = Mutex::new(()); 81 | /// Any tests that loads or unloads kernel modules must grab this mutex 82 | pub static KMOD_MTX: Mutex<()> = Mutex::new(()); 83 | /// Any test that calls ptsname(3) must grab this mutex. 84 | pub static PTSNAME_MTX: Mutex<()> = Mutex::new(()); 85 | /// Any test that alters signal handling must grab this mutex. 86 | pub static SIGNAL_MTX: Mutex<()> = Mutex::new(()); 87 | 88 | /// RAII object that restores a test's original directory on drop 89 | struct DirRestore<'a> { 90 | d: PathBuf, 91 | _g: RwLockWriteGuard<'a, ()>, 92 | } 93 | 94 | impl DirRestore<'_> { 95 | fn new() -> Self { 96 | let guard = crate::CWD_LOCK.write(); 97 | DirRestore { 98 | _g: guard, 99 | d: getcwd().unwrap(), 100 | } 101 | } 102 | } 103 | 104 | impl Drop for DirRestore<'_> { 105 | fn drop(&mut self) { 106 | let r = chdir(&self.d); 107 | if std::thread::panicking() { 108 | r.unwrap(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/test_clearenv.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | #[test] 4 | fn clearenv() { 5 | env::set_var("FOO", "BAR"); 6 | unsafe { nix::env::clearenv() }.unwrap(); 7 | assert_eq!(env::var("FOO").unwrap_err(), env::VarError::NotPresent); 8 | assert_eq!(env::vars().count(), 0); 9 | } 10 | -------------------------------------------------------------------------------- /test/test_dir.rs: -------------------------------------------------------------------------------- 1 | use nix::dir::{Dir, Type}; 2 | use nix::fcntl::OFlag; 3 | use nix::sys::stat::Mode; 4 | use std::fs::File; 5 | use tempfile::tempdir; 6 | 7 | #[cfg(test)] 8 | fn flags() -> OFlag { 9 | #[cfg(solarish)] 10 | let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC; 11 | 12 | #[cfg(not(solarish))] 13 | let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_DIRECTORY; 14 | 15 | f 16 | } 17 | 18 | #[test] 19 | fn read() { 20 | let tmp = tempdir().unwrap(); 21 | File::create(tmp.path().join("foo")).unwrap(); 22 | std::os::unix::fs::symlink("foo", tmp.path().join("bar")).unwrap(); 23 | let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap(); 24 | let mut entries: Vec<_> = dir.iter().map(|e| e.unwrap()).collect(); 25 | entries.sort_by(|a, b| a.file_name().cmp(b.file_name())); 26 | let entry_names: Vec<_> = entries 27 | .iter() 28 | .map(|e| e.file_name().to_str().unwrap().to_owned()) 29 | .collect(); 30 | assert_eq!(&entry_names[..], &[".", "..", "bar", "foo"]); 31 | 32 | // Check file types. The system is allowed to return DT_UNKNOWN (aka None here) but if it does 33 | // return a type, ensure it's correct. 34 | assert!(&[Some(Type::Directory), None].contains(&entries[0].file_type())); // .: dir 35 | assert!(&[Some(Type::Directory), None].contains(&entries[1].file_type())); // ..: dir 36 | assert!(&[Some(Type::Symlink), None].contains(&entries[2].file_type())); // bar: symlink 37 | assert!(&[Some(Type::File), None].contains(&entries[3].file_type())); // foo: regular file 38 | } 39 | 40 | #[test] 41 | fn rewind() { 42 | let tmp = tempdir().unwrap(); 43 | let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap(); 44 | let entries1: Vec<_> = dir 45 | .iter() 46 | .map(|e| e.unwrap().file_name().to_owned()) 47 | .collect(); 48 | let entries2: Vec<_> = dir 49 | .iter() 50 | .map(|e| e.unwrap().file_name().to_owned()) 51 | .collect(); 52 | let entries3: Vec<_> = dir 53 | .into_iter() 54 | .map(|e| e.unwrap().file_name().to_owned()) 55 | .collect(); 56 | assert_eq!(entries1, entries2); 57 | assert_eq!(entries2, entries3); 58 | } 59 | -------------------------------------------------------------------------------- /test/test_errno.rs: -------------------------------------------------------------------------------- 1 | use nix::errno::Errno; 2 | 3 | #[test] 4 | fn errno_set_and_read() { 5 | Errno::ENFILE.set(); 6 | assert_eq!(Errno::last(), Errno::ENFILE); 7 | } 8 | 9 | #[test] 10 | fn errno_set_and_clear() { 11 | Errno::ENFILE.set(); 12 | assert_eq!(Errno::last(), Errno::ENFILE); 13 | 14 | Errno::clear(); 15 | assert_eq!(Errno::last(), Errno::from_raw(0)); 16 | } 17 | -------------------------------------------------------------------------------- /test/test_kmod/hello_mod/Makefile: -------------------------------------------------------------------------------- 1 | obj-m += hello.o 2 | 3 | all: 4 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules 5 | 6 | clean: 7 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean 8 | -------------------------------------------------------------------------------- /test/test_kmod/hello_mod/hello.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-2.0+ or MIT 3 | */ 4 | #include 5 | #include 6 | 7 | static int number= 1; 8 | static char *who = "World"; 9 | 10 | module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 11 | MODULE_PARM_DESC(myint, "Just some number"); 12 | module_param(who, charp, 0000); 13 | MODULE_PARM_DESC(who, "Whot to greet"); 14 | 15 | int init_module(void) 16 | { 17 | printk(KERN_INFO "Hello %s (%d)!\n", who, number); 18 | return 0; 19 | } 20 | 21 | void cleanup_module(void) 22 | { 23 | printk(KERN_INFO "Goodbye %s (%d)!\n", who, number); 24 | } 25 | 26 | MODULE_LICENSE("Dual MIT/GPL"); 27 | -------------------------------------------------------------------------------- /test/test_kmod/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::fs::copy; 3 | use std::path::PathBuf; 4 | use std::process::Command; 5 | use tempfile::{tempdir, TempDir}; 6 | 7 | fn compile_kernel_module() -> (PathBuf, String, TempDir) { 8 | let _m = crate::FORK_MTX.lock(); 9 | 10 | let tmp_dir = 11 | tempdir().expect("unable to create temporary build directory"); 12 | 13 | copy( 14 | "test/test_kmod/hello_mod/hello.c", 15 | tmp_dir.path().join("hello.c"), 16 | ) 17 | .expect("unable to copy hello.c to temporary build directory"); 18 | copy( 19 | "test/test_kmod/hello_mod/Makefile", 20 | tmp_dir.path().join("Makefile"), 21 | ) 22 | .expect("unable to copy Makefile to temporary build directory"); 23 | 24 | let status = Command::new("make") 25 | .current_dir(tmp_dir.path()) 26 | .status() 27 | .expect("failed to run make"); 28 | 29 | assert!(status.success()); 30 | 31 | // Return the relative path of the build kernel module 32 | (tmp_dir.path().join("hello.ko"), "hello".to_owned(), tmp_dir) 33 | } 34 | 35 | use nix::errno::Errno; 36 | use nix::kmod::{delete_module, DeleteModuleFlags}; 37 | use nix::kmod::{finit_module, init_module, ModuleInitFlags}; 38 | use std::ffi::CString; 39 | use std::fs::File; 40 | use std::io::Read; 41 | 42 | #[test] 43 | fn test_finit_and_delete_module() { 44 | require_capability!("test_finit_and_delete_module", CAP_SYS_MODULE); 45 | let _m0 = crate::KMOD_MTX.lock(); 46 | let _m1 = crate::CWD_LOCK.read(); 47 | 48 | let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); 49 | 50 | let f = File::open(kmod_path).expect("unable to open kernel module"); 51 | finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()) 52 | .expect("unable to load kernel module"); 53 | 54 | delete_module( 55 | &CString::new(kmod_name).unwrap(), 56 | DeleteModuleFlags::empty(), 57 | ) 58 | .expect("unable to unload kernel module"); 59 | } 60 | 61 | #[test] 62 | fn test_finit_and_delete_module_with_params() { 63 | require_capability!( 64 | "test_finit_and_delete_module_with_params", 65 | CAP_SYS_MODULE 66 | ); 67 | let _m0 = crate::KMOD_MTX.lock(); 68 | let _m1 = crate::CWD_LOCK.read(); 69 | 70 | let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); 71 | 72 | let f = File::open(kmod_path).expect("unable to open kernel module"); 73 | finit_module( 74 | &f, 75 | &CString::new("who=Rust number=2018").unwrap(), 76 | ModuleInitFlags::empty(), 77 | ) 78 | .expect("unable to load kernel module"); 79 | 80 | delete_module( 81 | &CString::new(kmod_name).unwrap(), 82 | DeleteModuleFlags::empty(), 83 | ) 84 | .expect("unable to unload kernel module"); 85 | } 86 | 87 | #[test] 88 | fn test_init_and_delete_module() { 89 | require_capability!("test_init_and_delete_module", CAP_SYS_MODULE); 90 | let _m0 = crate::KMOD_MTX.lock(); 91 | let _m1 = crate::CWD_LOCK.read(); 92 | 93 | let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); 94 | 95 | let mut f = File::open(kmod_path).expect("unable to open kernel module"); 96 | let mut contents: Vec = Vec::new(); 97 | f.read_to_end(&mut contents) 98 | .expect("unable to read kernel module content to buffer"); 99 | init_module(&contents, &CString::new("").unwrap()) 100 | .expect("unable to load kernel module"); 101 | 102 | delete_module( 103 | &CString::new(kmod_name).unwrap(), 104 | DeleteModuleFlags::empty(), 105 | ) 106 | .expect("unable to unload kernel module"); 107 | } 108 | 109 | #[test] 110 | fn test_init_and_delete_module_with_params() { 111 | require_capability!( 112 | "test_init_and_delete_module_with_params", 113 | CAP_SYS_MODULE 114 | ); 115 | let _m0 = crate::KMOD_MTX.lock(); 116 | let _m1 = crate::CWD_LOCK.read(); 117 | 118 | let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); 119 | 120 | let mut f = File::open(kmod_path).expect("unable to open kernel module"); 121 | let mut contents: Vec = Vec::new(); 122 | f.read_to_end(&mut contents) 123 | .expect("unable to read kernel module content to buffer"); 124 | init_module(&contents, &CString::new("who=Nix number=2015").unwrap()) 125 | .expect("unable to load kernel module"); 126 | 127 | delete_module( 128 | &CString::new(kmod_name).unwrap(), 129 | DeleteModuleFlags::empty(), 130 | ) 131 | .expect("unable to unload kernel module"); 132 | } 133 | 134 | #[test] 135 | fn test_finit_module_invalid() { 136 | require_capability!("test_finit_module_invalid", CAP_SYS_MODULE); 137 | let _m0 = crate::KMOD_MTX.lock(); 138 | let _m1 = crate::CWD_LOCK.read(); 139 | 140 | let kmod_path = "/dev/zero"; 141 | 142 | let f = File::open(kmod_path).expect("unable to open kernel module"); 143 | let result = 144 | finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); 145 | 146 | assert_eq!(result.unwrap_err(), Errno::EINVAL); 147 | } 148 | 149 | #[test] 150 | fn test_finit_module_twice_and_delete_module() { 151 | require_capability!( 152 | "test_finit_module_twice_and_delete_module", 153 | CAP_SYS_MODULE 154 | ); 155 | let _m0 = crate::KMOD_MTX.lock(); 156 | let _m1 = crate::CWD_LOCK.read(); 157 | 158 | let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); 159 | 160 | let f = File::open(kmod_path).expect("unable to open kernel module"); 161 | finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()) 162 | .expect("unable to load kernel module"); 163 | 164 | let result = 165 | finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); 166 | 167 | assert_eq!(result.unwrap_err(), Errno::EEXIST); 168 | 169 | delete_module( 170 | &CString::new(kmod_name).unwrap(), 171 | DeleteModuleFlags::empty(), 172 | ) 173 | .expect("unable to unload kernel module"); 174 | } 175 | 176 | #[test] 177 | fn test_delete_module_not_loaded() { 178 | require_capability!("test_delete_module_not_loaded", CAP_SYS_MODULE); 179 | let _m0 = crate::KMOD_MTX.lock(); 180 | let _m1 = crate::CWD_LOCK.read(); 181 | 182 | let result = delete_module( 183 | &CString::new("hello").unwrap(), 184 | DeleteModuleFlags::empty(), 185 | ); 186 | 187 | assert_eq!(result.unwrap_err(), Errno::ENOENT); 188 | } 189 | -------------------------------------------------------------------------------- /test/test_net.rs: -------------------------------------------------------------------------------- 1 | use nix::net::if_::*; 2 | 3 | #[cfg(linux_android)] 4 | const LOOPBACK: &[u8] = b"lo"; 5 | 6 | #[cfg(not(any(linux_android, target_os = "haiku")))] 7 | const LOOPBACK: &[u8] = b"lo0"; 8 | 9 | #[cfg(target_os = "haiku")] 10 | const LOOPBACK: &[u8] = b"loop"; 11 | 12 | #[test] 13 | #[cfg_attr(target_os = "cygwin", ignore)] 14 | fn test_if_nametoindex() { 15 | if_nametoindex(LOOPBACK).expect("assertion failed"); 16 | } 17 | 18 | #[test] 19 | #[cfg_attr(target_os = "cygwin", ignore)] 20 | fn test_if_indextoname() { 21 | let loopback_index = if_nametoindex(LOOPBACK).expect("assertion failed"); 22 | assert_eq!( 23 | if_indextoname(loopback_index) 24 | .expect("assertion failed") 25 | .as_bytes(), 26 | LOOPBACK 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /test/test_nix_path.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test_poll.rs: -------------------------------------------------------------------------------- 1 | use nix::{ 2 | errno::Errno, 3 | poll::{poll, PollFd, PollFlags, PollTimeout}, 4 | unistd::{pipe, write}, 5 | }; 6 | use std::os::unix::io::{AsFd, BorrowedFd}; 7 | 8 | macro_rules! loop_while_eintr { 9 | ($poll_expr: expr) => { 10 | loop { 11 | match $poll_expr { 12 | Ok(nfds) => break nfds, 13 | Err(Errno::EINTR) => (), 14 | Err(e) => panic!("{}", e), 15 | } 16 | } 17 | }; 18 | } 19 | 20 | #[test] 21 | fn test_poll() { 22 | let (r, w) = pipe().unwrap(); 23 | let mut fds = [PollFd::new(r.as_fd(), PollFlags::POLLIN)]; 24 | 25 | // Poll an idle pipe. Should timeout 26 | let nfds = loop_while_eintr!(poll(&mut fds, PollTimeout::from(100u8))); 27 | assert_eq!(nfds, 0); 28 | assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN)); 29 | 30 | write(&w, b".").unwrap(); 31 | 32 | // Poll a readable pipe. Should return an event. 33 | let nfds = poll(&mut fds, PollTimeout::from(100u8)).unwrap(); 34 | assert_eq!(nfds, 1); 35 | assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); 36 | } 37 | 38 | // ppoll(2) is the same as poll except for how it handles timeouts and signals. 39 | // Repeating the test for poll(2) should be sufficient to check that our 40 | // bindings are correct. 41 | #[cfg(any(linux_android, freebsdlike))] 42 | #[test] 43 | fn test_ppoll() { 44 | use nix::poll::ppoll; 45 | use nix::sys::signal::SigSet; 46 | use nix::sys::time::{TimeSpec, TimeValLike}; 47 | 48 | let timeout = TimeSpec::milliseconds(1); 49 | let (r, w) = pipe().unwrap(); 50 | let mut fds = [PollFd::new(r.as_fd(), PollFlags::POLLIN)]; 51 | 52 | // Poll an idle pipe. Should timeout 53 | let sigset = SigSet::empty(); 54 | let nfds = loop_while_eintr!(ppoll(&mut fds, Some(timeout), Some(sigset))); 55 | assert_eq!(nfds, 0); 56 | assert!(!fds[0].revents().unwrap().contains(PollFlags::POLLIN)); 57 | 58 | write(&w, b".").unwrap(); 59 | 60 | // Poll a readable pipe. Should return an event. 61 | let nfds = ppoll(&mut fds, Some(timeout), None).unwrap(); 62 | assert_eq!(nfds, 1); 63 | assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); 64 | } 65 | 66 | #[test] 67 | fn test_pollfd_events() { 68 | let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; 69 | let mut pfd = PollFd::new(fd_zero.as_fd(), PollFlags::POLLIN); 70 | assert_eq!(pfd.events(), PollFlags::POLLIN); 71 | pfd.set_events(PollFlags::POLLOUT); 72 | assert_eq!(pfd.events(), PollFlags::POLLOUT); 73 | } 74 | -------------------------------------------------------------------------------- /test/test_sched.rs: -------------------------------------------------------------------------------- 1 | use nix::sched::{sched_getaffinity, sched_getcpu, sched_setaffinity, CpuSet}; 2 | use nix::unistd::Pid; 3 | 4 | #[test] 5 | fn test_sched_affinity() { 6 | // If pid is zero, then the mask of the calling process is returned. 7 | let initial_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap(); 8 | let mut at_least_one_cpu = false; 9 | let mut last_valid_cpu = 0; 10 | for field in 0..CpuSet::count() { 11 | if initial_affinity.is_set(field).unwrap() { 12 | at_least_one_cpu = true; 13 | last_valid_cpu = field; 14 | } 15 | } 16 | assert!(at_least_one_cpu); 17 | 18 | // Now restrict the running CPU 19 | let mut new_affinity = CpuSet::new(); 20 | new_affinity.set(last_valid_cpu).unwrap(); 21 | sched_setaffinity(Pid::from_raw(0), &new_affinity).unwrap(); 22 | 23 | // And now re-check the affinity which should be only the one we set. 24 | let updated_affinity = sched_getaffinity(Pid::from_raw(0)).unwrap(); 25 | for field in 0..CpuSet::count() { 26 | // Should be set only for the CPU we set previously 27 | assert_eq!( 28 | updated_affinity.is_set(field).unwrap(), 29 | field == last_valid_cpu 30 | ) 31 | } 32 | 33 | // Now check that we're also currently running on the CPU in question. 34 | let cur_cpu = sched_getcpu().unwrap(); 35 | assert_eq!(cur_cpu, last_valid_cpu); 36 | 37 | // Finally, reset the initial CPU set 38 | sched_setaffinity(Pid::from_raw(0), &initial_affinity).unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /test/test_spawn.rs: -------------------------------------------------------------------------------- 1 | use super::FORK_MTX; 2 | use nix::errno::Errno; 3 | use nix::spawn::{self, PosixSpawnAttr, PosixSpawnFileActions}; 4 | use nix::sys::signal; 5 | use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; 6 | use std::ffi::{CStr, CString}; 7 | 8 | /// Helper function to find a binary in the $PATH 9 | fn which(exe_name: &str) -> Option { 10 | std::env::var_os("PATH").and_then(|paths| { 11 | std::env::split_paths(&paths) 12 | .filter_map(|dir| { 13 | let full_path = dir.join(exe_name); 14 | if full_path.is_file() { 15 | Some(full_path) 16 | } else { 17 | None 18 | } 19 | }) 20 | .next() 21 | }) 22 | } 23 | 24 | #[test] 25 | fn spawn_true() { 26 | let _guard = FORK_MTX.lock(); 27 | 28 | let bin = which("true").unwrap(); 29 | let args = &[ 30 | CString::new("true").unwrap(), 31 | CString::new("story").unwrap(), 32 | ]; 33 | let vars: &[CString] = &[]; 34 | let actions = PosixSpawnFileActions::init().unwrap(); 35 | let attr = PosixSpawnAttr::init().unwrap(); 36 | 37 | let pid = 38 | spawn::posix_spawn(bin.as_path(), &actions, &attr, args, vars).unwrap(); 39 | 40 | let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); 41 | 42 | match status { 43 | WaitStatus::Exited(wpid, ret) => { 44 | assert_eq!(pid, wpid); 45 | assert_eq!(ret, 0); 46 | } 47 | _ => { 48 | panic!("Invalid WaitStatus"); 49 | } 50 | }; 51 | } 52 | 53 | #[test] 54 | fn spawn_sleep() { 55 | let _guard = FORK_MTX.lock(); 56 | 57 | let bin = which("sleep").unwrap(); 58 | let args = &[CString::new("sleep").unwrap(), CString::new("30").unwrap()]; 59 | let vars: &[CString] = &[]; 60 | let actions = PosixSpawnFileActions::init().unwrap(); 61 | let attr = PosixSpawnAttr::init().unwrap(); 62 | 63 | let pid = 64 | spawn::posix_spawn(bin.as_path(), &actions, &attr, args, vars).unwrap(); 65 | 66 | let status = 67 | waitpid(pid, WaitPidFlag::from_bits(WaitPidFlag::WNOHANG.bits())) 68 | .unwrap(); 69 | match status { 70 | WaitStatus::StillAlive => {} 71 | _ => { 72 | panic!("Invalid WaitStatus"); 73 | } 74 | }; 75 | 76 | signal::kill(pid, signal::SIGTERM).unwrap(); 77 | 78 | let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); 79 | match status { 80 | WaitStatus::Signaled(wpid, wsignal, _) => { 81 | assert_eq!(pid, wpid); 82 | assert_eq!(wsignal, signal::SIGTERM); 83 | } 84 | _ => { 85 | panic!("Invalid WaitStatus"); 86 | } 87 | }; 88 | } 89 | 90 | #[test] 91 | // `posix_spawn(path_not_exist)` succeeds under QEMU, so ignore the test. No need 92 | // to investigate the root cause, this test still works in native environments, which 93 | // is sufficient to test the binding. 94 | #[cfg_attr(qemu, ignore)] 95 | fn spawn_cmd_does_not_exist() { 96 | let _guard = FORK_MTX.lock(); 97 | 98 | let args = &[CString::new("buzz").unwrap()]; 99 | let envs: &[CString] = &[]; 100 | let actions = PosixSpawnFileActions::init().unwrap(); 101 | let attr = PosixSpawnAttr::init().unwrap(); 102 | 103 | let bin = "2b7433c4-523b-470c-abb5-d7ee9fd295d5-fdasf"; 104 | let errno = 105 | spawn::posix_spawn(bin, &actions, &attr, args, envs).unwrap_err(); 106 | assert_eq!(errno, Errno::ENOENT); 107 | } 108 | 109 | #[test] 110 | fn spawnp_true() { 111 | let _guard = FORK_MTX.lock(); 112 | 113 | let bin = &CString::new("true").unwrap(); 114 | let args = &[ 115 | CString::new("true").unwrap(), 116 | CString::new("story").unwrap(), 117 | ]; 118 | let vars: &[CString] = &[]; 119 | let actions = PosixSpawnFileActions::init().unwrap(); 120 | let attr = PosixSpawnAttr::init().unwrap(); 121 | 122 | let pid = spawn::posix_spawnp(bin, &actions, &attr, args, vars).unwrap(); 123 | 124 | let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); 125 | 126 | match status { 127 | WaitStatus::Exited(wpid, ret) => { 128 | assert_eq!(pid, wpid); 129 | assert_eq!(ret, 0); 130 | } 131 | _ => { 132 | panic!("Invalid WaitStatus"); 133 | } 134 | }; 135 | } 136 | 137 | #[test] 138 | fn spawnp_sleep() { 139 | let _guard = FORK_MTX.lock(); 140 | 141 | let bin = &CString::new("sleep").unwrap(); 142 | let args = &[CString::new("sleep").unwrap(), CString::new("30").unwrap()]; 143 | let vars: &[CString] = &[]; 144 | let actions = PosixSpawnFileActions::init().unwrap(); 145 | let attr = PosixSpawnAttr::init().unwrap(); 146 | 147 | let pid = spawn::posix_spawnp(bin, &actions, &attr, args, vars).unwrap(); 148 | 149 | let status = 150 | waitpid(pid, WaitPidFlag::from_bits(WaitPidFlag::WNOHANG.bits())) 151 | .unwrap(); 152 | match status { 153 | WaitStatus::StillAlive => {} 154 | _ => { 155 | panic!("Invalid WaitStatus"); 156 | } 157 | }; 158 | 159 | signal::kill(pid, signal::SIGTERM).unwrap(); 160 | 161 | let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); 162 | match status { 163 | WaitStatus::Signaled(wpid, wsignal, _) => { 164 | assert_eq!(pid, wpid); 165 | assert_eq!(wsignal, signal::SIGTERM); 166 | } 167 | _ => { 168 | panic!("Invalid WaitStatus"); 169 | } 170 | }; 171 | } 172 | 173 | #[test] 174 | // `posix_spawnp(bin_not_exist)` succeeds under QEMU, so ignore the test. No need 175 | // to investigate the root cause, this test still works in native environments, which 176 | // is sufficient to test the binding. 177 | #[cfg_attr(qemu, ignore)] 178 | fn spawnp_cmd_does_not_exist() { 179 | let _guard = FORK_MTX.lock(); 180 | 181 | let args = &[CString::new("buzz").unwrap()]; 182 | let envs: &[CString] = &[]; 183 | let actions = PosixSpawnFileActions::init().unwrap(); 184 | let attr = PosixSpawnAttr::init().unwrap(); 185 | 186 | let bin = CStr::from_bytes_with_nul( 187 | "2b7433c4-523b-470c-abb5-d7ee9fd295d5-fdasf\0".as_bytes(), 188 | ) 189 | .unwrap(); 190 | let errno = 191 | spawn::posix_spawnp(bin, &actions, &attr, args, envs).unwrap_err(); 192 | assert_eq!(errno, Errno::ENOENT); 193 | } 194 | -------------------------------------------------------------------------------- /test/test_syslog.rs: -------------------------------------------------------------------------------- 1 | use nix::syslog::{openlog, syslog, Facility, LogFlags, Severity}; 2 | 3 | #[test] 4 | fn test_syslog_hello_world() { 5 | let flags = LogFlags::LOG_PID; 6 | 7 | #[cfg(not(target_os = "linux"))] 8 | openlog(None::<&str>, flags, Facility::LOG_USER).unwrap(); 9 | #[cfg(target_os = "linux")] 10 | openlog(None, flags, Facility::LOG_USER).unwrap(); 11 | 12 | syslog(Severity::LOG_EMERG, "Hello, nix!").unwrap(); 13 | let name = "syslog"; 14 | syslog(Severity::LOG_NOTICE, &format!("Hello, {name}!")).unwrap(); 15 | } 16 | 17 | #[test] 18 | #[cfg(target_os = "linux")] 19 | fn test_openlog_with_ident() { 20 | use std::ffi::CStr; 21 | 22 | const IDENT: &CStr = unsafe { 23 | CStr::from_bytes_with_nul_unchecked(b"test_openlog_with_ident\0") 24 | }; 25 | 26 | let flags = LogFlags::LOG_PID; 27 | openlog(Some(IDENT), flags, Facility::LOG_USER).unwrap(); 28 | syslog(Severity::LOG_EMERG, "Hello, ident!").unwrap(); 29 | } 30 | 31 | #[test] 32 | #[cfg(not(target_os = "linux"))] 33 | fn test_openlog_with_ident() { 34 | let flags = LogFlags::LOG_PID; 35 | openlog(Some("test_openlog_with_ident"), flags, Facility::LOG_USER) 36 | .unwrap(); 37 | syslog(Severity::LOG_EMERG, "Hello, ident!").unwrap(); 38 | } 39 | -------------------------------------------------------------------------------- /test/test_time.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] 2 | use nix::time::clock_getcpuclockid; 3 | use nix::time::{clock_gettime, ClockId}; 4 | 5 | #[cfg(not(target_os = "redox"))] 6 | #[test] 7 | pub fn test_clock_getres() { 8 | nix::time::clock_getres(ClockId::CLOCK_REALTIME).expect("assertion failed"); 9 | } 10 | 11 | #[test] 12 | pub fn test_clock_gettime() { 13 | clock_gettime(ClockId::CLOCK_REALTIME).expect("assertion failed"); 14 | } 15 | 16 | #[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] 17 | #[test] 18 | pub fn test_clock_getcpuclockid() { 19 | let clock_id = clock_getcpuclockid(nix::unistd::Pid::this()).unwrap(); 20 | clock_gettime(clock_id).unwrap(); 21 | } 22 | 23 | #[cfg(not(target_os = "redox"))] 24 | #[test] 25 | pub fn test_clock_id_res() { 26 | ClockId::CLOCK_REALTIME.res().unwrap(); 27 | } 28 | 29 | #[test] 30 | pub fn test_clock_id_now() { 31 | ClockId::CLOCK_REALTIME.now().unwrap(); 32 | } 33 | 34 | #[cfg(any(freebsdlike, linux_android, target_os = "emscripten"))] 35 | #[test] 36 | pub fn test_clock_id_pid_cpu_clock_id() { 37 | ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) 38 | .map(ClockId::now) 39 | .unwrap() 40 | .unwrap(); 41 | } 42 | 43 | #[cfg(any( 44 | linux_android, 45 | solarish, 46 | freebsdlike, 47 | target_os = "netbsd", 48 | target_os = "hurd", 49 | target_os = "aix" 50 | ))] 51 | #[test] 52 | pub fn test_clock_nanosleep() { 53 | use nix::{ 54 | sys::time::{TimeSpec, TimeValLike}, 55 | time::{clock_nanosleep, ClockNanosleepFlags}, 56 | }; 57 | 58 | let sleep_time = TimeSpec::microseconds(1); 59 | let res = clock_nanosleep( 60 | ClockId::CLOCK_MONOTONIC, 61 | ClockNanosleepFlags::empty(), 62 | &sleep_time, 63 | ); 64 | let expected = TimeSpec::microseconds(0); 65 | assert_eq!(res, Ok(expected)); 66 | } 67 | -------------------------------------------------------------------------------- /towncrier.toml: -------------------------------------------------------------------------------- 1 | # towncrier configuration document: 2 | # https://towncrier.readthedocs.io/en/stable/configuration.html 3 | 4 | [tool.towncrier] 5 | # Read news fragments from this directory 6 | directory = "changelog" 7 | # Concatenate fragments, and prepend to this file 8 | filename = "CHANGELOG.md" 9 | title_format = "## [{version}] - {project_date}" 10 | # Used to disable towncrier's "=====" title header 11 | underlines = ["", "", ""] 12 | # Wrap news fragments to a line length of 79 13 | wrap = true 14 | # Every news fragement under the `changelog` directory is named "..md", 15 | # this `id` field, is called issue/ticket number in towncrier's term 16 | # `issue_format` controls how this will be rendered in the final CHANGELOG 17 | # We use this for Pull Request even though it is called "issue" 18 | issue_format = "[#{issue}](https://github.com/nix-rust/nix/pull/{issue})" 19 | # Ask towncrier to add new notes after this 20 | start_string = "# Change Log\n" 21 | 22 | # nix's change log typs (in alphabetical order) 23 | # These types will be capitalized by default. 24 | [tool.towncrier.fragment.added] 25 | [tool.towncrier.fragment.changed] 26 | [tool.towncrier.fragment.fixed] 27 | [tool.towncrier.fragment.removed] 28 | --------------------------------------------------------------------------------