change ~ use patched/vendor'ed 'nix' crate until fixed at source

- a PR has been submitted to 'nix'; ref: <https://github.com/nix-rust/nix/pull/1590>
This commit is contained in:
Roy Ivy III 2021-11-14 16:29:23 -06:00
parent 39a6e6c75b
commit 8b7f2b44f6
113 changed files with 34881 additions and 3 deletions

View file

@ -381,12 +381,15 @@ atty = "0.2"
rlimit = "0.4.0"
[target.'cfg(unix)'.dev-dependencies]
nix = "0.20.0"
nix = "=0.23.1"
rust-users = { version="0.10", package="users" }
unix_socket = "0.5.0"
[[bin]]
name = "coreutils"
path = "src/bin/coreutils.rs"
[patch.crates-io]
# FixME: [2021-11-16; rivy] remove 'nix' patch when MacOS compatibility is restored; ref: <https://github.com/nix-rust/nix/pull/1590>
# nix = { git = "https://github.com/rivy-t/nix" }
nix = { path = "vendor/nix-v0.23.1-patched" }

274
vendor/nix-v0.23.1-patched/.cirrus.yml vendored Normal file
View file

@ -0,0 +1,274 @@
cargo_cache:
folder: $CARGO_HOME/registry
fingerprint_script: cat Cargo.lock || echo ""
env:
# Build by default; don't just check
BUILD: build
CLIPPYFLAGS: -D warnings
RUSTFLAGS: -D warnings
RUSTDOCFLAGS: -D warnings
TOOL: cargo
# The MSRV
TOOLCHAIN: 1.46.0
ZFLAGS:
# Tests that don't require executing the build binaries
build: &BUILD
build_script:
- . $HOME/.cargo/env || true
- $TOOL +$TOOLCHAIN $BUILD $ZFLAGS --target $TARGET --all-targets
- $TOOL +$TOOLCHAIN doc $ZFLAGS --no-deps --target $TARGET
- $TOOL +$TOOLCHAIN clippy $ZFLAGS --target $TARGET -- $CLIPPYFLAGS
# Tests that do require executing the binaries
test: &TEST
<< : *BUILD
test_script:
- . $HOME/.cargo/env || true
- $TOOL +$TOOLCHAIN test --target $TARGET
# Test FreeBSD in a full VM. Test the i686 target too, in the
# same VM. The binary will be built in 32-bit mode, but will execute on a
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
# the system's binaries, so the environment shouldn't matter.
task:
name: FreeBSD amd64 & i686
env:
TARGET: x86_64-unknown-freebsd
freebsd_instance:
image: freebsd-12-2-release-amd64
setup_script:
- fetch https://sh.rustup.rs -o rustup.sh
- sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN
- . $HOME/.cargo/env
- rustup target add i686-unknown-freebsd
- rustup component add --toolchain $TOOLCHAIN clippy
<< : *TEST
i386_test_script:
- . $HOME/.cargo/env
- cargo build --target i686-unknown-freebsd
- cargo doc --no-deps --target i686-unknown-freebsd
- cargo test --target i686-unknown-freebsd
before_cache_script: rm -rf $CARGO_HOME/registry/index
# Test OSX in a full VM
task:
matrix:
- name: OSX x86_64
env:
TARGET: x86_64-apple-darwin
osx_instance:
image: catalina-xcode
setup_script:
- curl --proto '=https' --tlsv1.2 -sSf -o rustup.sh https://sh.rustup.rs
- sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN
- . $HOME/.cargo/env
- rustup component add --toolchain $TOOLCHAIN clippy
<< : *TEST
before_cache_script: rm -rf $CARGO_HOME/registry/index
# Use cross for QEMU-based testing
# cross needs to execute Docker, so we must use Cirrus's Docker Builder task.
task:
env:
RUST_TEST_THREADS: 1 # QEMU works best with 1 thread
HOME: /tmp/home
PATH: $HOME/.cargo/bin:$PATH
RUSTFLAGS: --cfg qemu -D warnings
TOOL: cross
matrix:
- name: Linux arm gnueabi
env:
TARGET: arm-unknown-linux-gnueabi
- name: Linux armv7 gnueabihf
env:
TARGET: armv7-unknown-linux-gnueabihf
- name: Linux i686
env:
TARGET: i686-unknown-linux-gnu
- name: Linux i686 musl
env:
TARGET: i686-unknown-linux-musl
- name: Linux MIPS
env:
TARGET: mips-unknown-linux-gnu
- name: Linux MIPS64
env:
TARGET: mips64-unknown-linux-gnuabi64
- name: Linux MIPS64 el
env:
TARGET: mips64el-unknown-linux-gnuabi64
- name: Linux mipsel
env:
TARGET: mipsel-unknown-linux-gnu
- name: Linux powerpc64le
env:
TARGET: powerpc64le-unknown-linux-gnu
compute_engine_instance:
image_project: cirrus-images
image: family/docker-builder
platform: linux
cpu: 1 # Since QEMU will only use 1 thread
memory: 4G
setup_script:
- mkdir /tmp/home
- curl --proto '=https' --tlsv1.2 -sSf -o rustup.sh https://sh.rustup.rs
- sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN
- . $HOME/.cargo/env
- cargo install cross
<< : *TEST
before_cache_script: rm -rf $CARGO_HOME/registry/index
# Tasks for Linux native builds
task:
matrix:
- name: Rust Stable
container:
image: rust:latest
env:
TARGET: x86_64-unknown-linux-gnu
TOOLCHAIN:
- name: Linux aarch64
arm_container:
image: rust:1.46
env:
RUSTFLAGS: --cfg graviton -D warnings
TARGET: aarch64-unknown-linux-gnu
- name: Linux x86_64
container:
image: rust:1.46
env:
TARGET: x86_64-unknown-linux-gnu
- name: Linux x86_64 musl
container:
image: rust:1.46
env:
TARGET: x86_64-unknown-linux-musl
setup_script:
- rustup target add $TARGET
- rustup component add clippy
<< : *TEST
before_cache_script: rm -rf $CARGO_HOME/registry/index
# Tasks for cross-compiling, but no testing
task:
container:
image: rust:1.46
env:
BUILD: check
matrix:
# Cross claims to support Android, but when it tries to run Nix's tests it
# reports undefined symbol references.
- name: Android aarch64
env:
TARGET: aarch64-linux-android
- name: Android arm
env:
TARGET: arm-linux-androideabi
- name: Android armv7
env:
TARGET: armv7-linux-androideabi
- name: Android i686
env:
TARGET: i686-linux-android
- name: Android x86_64
env:
TARGET: x86_64-linux-android
- name: Linux arm-musleabi
env:
TARGET: arm-unknown-linux-musleabi
- name: Fuchsia x86_64
env:
TARGET: x86_64-fuchsia
- name: Illumos
env:
TARGET: x86_64-unknown-illumos
# illumos toolchain isn't available via rustup until 1.50
TOOLCHAIN: 1.50.0
container:
image: rust:1.50
# Cross claims to support running tests on iOS, but it actually doesn't.
# https://github.com/rust-embedded/cross/issues/535
- name: iOS aarch64
env:
TARGET: aarch64-apple-ios
# Rustup only supports cross-building from arbitrary hosts for iOS at
# 1.49.0 and above. Below that it's possible to cross-build from an OSX
# host, but OSX VMs are more expensive than Linux VMs.
TOOLCHAIN: 1.49.0
- name: iOS x86_64
env:
TARGET: x86_64-apple-ios
TOOLCHAIN: 1.49.0
# Cross testing on powerpc fails with "undefined reference to renameat2".
# Perhaps cross is using too-old a version?
- name: Linux powerpc
env:
TARGET: powerpc-unknown-linux-gnu
# Cross claims to support Linux powerpc64, but it really doesn't.
# https://github.com/rust-embedded/cross/issues/441
- name: Linux powerpc64
env:
TARGET: powerpc64-unknown-linux-gnu
- name: Linux s390x
env:
TARGET: s390x-unknown-linux-gnu
- name: Linux x32
env:
TARGET: x86_64-unknown-linux-gnux32
- name: NetBSD x86_64
env:
TARGET: x86_64-unknown-netbsd
- name: Redox x86_64
env:
TARGET: x86_64-unknown-redox
# Redox requires a nightly compiler.
# If stuff breaks, change nightly to the date in the toolchain_*
# directory at https://static.redox-os.org
TOOLCHAIN: nightly-2020-08-04
setup_script:
- rustup target add $TARGET
- rustup toolchain install $TOOLCHAIN --profile minimal --target $TARGET
- rustup component add --toolchain $TOOLCHAIN clippy
<< : *BUILD
before_cache_script: rm -rf $CARGO_HOME/registry/index
# Rust Tier 3 targets can't use Rustup
task:
container:
image: rustlang/rust:nightly
env:
BUILD: check
# Must allow here rather than in lib.rs because this lint doesn't exist
# prior to Rust 1.57.0
# https://github.com/rust-lang/rust-clippy/issues/7718
CLIPPYFLAGS: -D warnings -A clippy::if_then_panic
TOOLCHAIN: nightly
ZFLAGS: -Zbuild-std
matrix:
- name: DragonFly BSD x86_64
env:
TARGET: x86_64-unknown-dragonfly
- name: OpenBSD x86_64
env:
TARGET: x86_64-unknown-openbsd
setup_script:
- rustup component add rust-src
<< : *BUILD
before_cache_script: rm -rf $CARGO_HOME/registry/index
# Test that we can build with the lowest version of all dependencies.
# "cargo test" doesn't work because some of our dev-dependencies, like
# rand, can't build with their own minimal dependencies.
task:
name: Minver
env:
TOOLCHAIN: nightly
container:
image: rustlang/rust:nightly
setup_script:
- cargo update -Zminimal-versions
script:
- cargo check
before_cache_script: rm -rf $CARGO_HOME/registry/index

View file

@ -0,0 +1 @@
/CHANGELOG.md merge=union

10
vendor/nix-v0.23.1-patched/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
syntax: glob
Cargo.lock
target
*.diff
*.rej
*.orig
.*.swn
.*.swo
.*.swp
*.a

1227
vendor/nix-v0.23.1-patched/CHANGELOG.md vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
# Contributing to nix
We're really glad you're interested in contributing to nix! This
document has a few pointers and guidelines to help get you started.
To have a welcoming and inclusive project, nix uses the Rust project's
[Code of Conduct][conduct]. All contributors are expected to follow it.
[conduct]: https://www.rust-lang.org/conduct.html
# Issues
We use GitHub's [issue tracker][issues].
[issues]: https://github.com/nix-rust/nix/issues
## Bug reports
Before submitting a new bug report, please [search existing
issues][issue-search] to see if there's something related. If not, just
[open a new issue][new-issue]!
As a reminder, the more information you can give in your issue, the
easier it is to figure out how to fix it. For nix, this will likely
include the OS and version, and the architecture.
[issue-search]: https://github.com/nix-rust/nix/search?utf8=%E2%9C%93&q=is%3Aissue&type=Issues
[new-issue]: https://github.com/nix-rust/nix/issues/new
## Feature / API requests
If you'd like a new API or feature added, please [open a new
issue][new-issue] requesting it. As with reporting a bug, the more
information you can provide, the better.
## Labels
We use labels to help manage issues. The structure is modeled after
[Rust's issue labeling scheme][rust-labels]:
- **A-** prefixed labels state which area of the project the issue
relates to
- **E-** prefixed labels explain the level of experience necessary to fix the
issue
- **O-** prefixed labels specify the OS for issues that are OS-specific
- **R-** prefixed labels specify the architecture for issues that are
architecture-specific
[rust-labels]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#issue-triage
# Pull requests
GitHub pull requests are the primary mechanism we use to change nix. GitHub itself has
some [great documentation][pr-docs] on using the Pull Request feature. We use the 'fork and
pull' model described there.
Please make pull requests against the `master` branch.
If you change the API by way of adding, removing or changing something or if
you fix a bug, please add an appropriate note to the [change log][cl]. We
follow the conventions of [Keep A CHANGELOG][kacl].
[cl]: https://github.com/nix-rust/nix/blob/master/CHANGELOG.md
[kacl]: https://github.com/olivierlacan/keep-a-changelog/tree/18adb5f5be7a898d046f6a4acb93e39dcf40c4ad
[pr-docs]: https://help.github.com/articles/using-pull-requests/
## Testing
nix has a test suite that you can run with `cargo test`. Ideally, we'd like pull
requests to include tests where they make sense. For example, when fixing a bug,
add a test that would have failed without the fix.
After you've made your change, make sure the tests pass in your development
environment. We also have [continuous integration set up on
Cirrus-CI][cirrus-ci], which might find some issues on other platforms. The CI
will run once you open a pull request.
There is also infrastructure for running tests for other targets
locally. More information is available in the [CI Readme][ci-readme].
[cirrus-ci]: https://cirrus-ci.com/github/nix-rust/nix
[ci-readme]: ci/README.md
### Disabling a test in the CI environment
Sometimes there are features that cannot be tested in the CI environment.
To stop a test from running under CI, add `skip_if_cirrus!()` to it. Please
describe the reason it shouldn't run under CI, and a link to an issue if
possible!
## bors, the bot who merges all the PRs
All pull requests are merged via [bors], an integration bot. After the
pull request has been reviewed, the reviewer will leave a comment like
> bors r+
to let bors know that it was approved. Then bors will check that it passes
tests when merged with the latest changes in the `master` branch, and
merge if the tests succeed.
[bors]: https://bors-ng.github.io/
## API conventions
If you're adding a new API, we have a [document with
conventions][conventions] to use throughout the nix project.
[conventions]: https://github.com/nix-rust/nix/blob/master/CONVENTIONS.md

View file

@ -0,0 +1,86 @@
# Conventions
In order to achieve our goal of wrapping [libc][libc] code in idiomatic rust
constructs with minimal performance overhead, we follow the following
conventions.
Note that, thus far, not all the code follows these conventions and not all
conventions we try to follow have been documented here. If you find an instance
of either, feel free to remedy the flaw by opening a pull request with
appropriate changes or additions.
## Change Log
We follow the conventions laid out in [Keep A CHANGELOG][kacl].
[kacl]: https://github.com/olivierlacan/keep-a-changelog/tree/18adb5f5be7a898d046f6a4acb93e39dcf40c4ad
## libc constants, functions and structs
We do not define integer constants ourselves, but use or reexport them from the
[libc crate][libc].
We use the functions exported from [libc][libc] instead of writing our own
`extern` declarations.
We use the `struct` definitions from [libc][libc] internally instead of writing
our own. If we want to add methods to a libc type, we use the newtype pattern.
For example,
```rust
pub struct SigSet(libc::sigset_t);
impl SigSet {
...
}
```
When creating newtypes, we use Rust's `CamelCase` type naming convention.
## Bitflags
Many C functions have flags parameters that are combined from constants using
bitwise operations. We represent the types of these parameters by types defined
using our `libc_bitflags!` macro, which is a convenience wrapper around the
`bitflags!` macro from the [bitflags crate][bitflags] that brings in the
constant value from `libc`.
We name the type for a set of constants whose element's names start with `FOO_`
`FooFlags`.
For example,
```rust
libc_bitflags!{
pub struct ProtFlags: libc::c_int {
PROT_NONE;
PROT_READ;
PROT_WRITE;
PROT_EXEC;
#[cfg(any(target_os = "linux", target_os = "android"))]
PROT_GROWSDOWN;
#[cfg(any(target_os = "linux", target_os = "android"))]
PROT_GROWSUP;
}
}
```
## Enumerations
We represent sets of constants that are intended as mutually exclusive arguments
to parameters of functions by [enumerations][enum].
## Structures Initialized by libc Functions
Whenever we need to use a [libc][libc] function to properly initialize a
variable and said function allows us to use uninitialized memory, we use
[`std::mem::MaybeUninit`][std_MaybeUninit] when defining the variable. This
allows us to avoid the overhead incurred by zeroing or otherwise initializing
the variable.
[bitflags]: https://crates.io/crates/bitflags/
[enum]: https://doc.rust-lang.org/reference.html#enumerations
[libc]: https://crates.io/crates/libc/
[std_MaybeUninit]: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html

74
vendor/nix-v0.23.1-patched/Cargo.toml vendored Normal file
View file

@ -0,0 +1,74 @@
[package]
name = "nix"
description = "Rust friendly bindings to *nix APIs"
edition = "2018"
version = "0.23.1"
authors = ["The nix-rust Project Developers"]
repository = "https://github.com/nix-rust/nix"
license = "MIT"
categories = ["os::unix-apis"]
include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"]
[package.metadata.docs.rs]
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-linux-android",
"x86_64-apple-darwin",
"aarch64-apple-ios",
"x86_64-unknown-freebsd",
"x86_64-unknown-openbsd",
"x86_64-unknown-netbsd",
"x86_64-unknown-dragonfly",
"x86_64-fuchsia",
"x86_64-unknown-redox",
"x86_64-unknown-illumos"
]
[dependencies]
libc = { version = "0.2.102", features = [ "extra_traits" ] }
bitflags = "1.3.1"
cfg-if = "1.0"
[target.'cfg(not(target_os = "redox"))'.dependencies]
memoffset = "0.6.3"
[target.'cfg(target_os = "dragonfly")'.build-dependencies]
cc = "1"
[dev-dependencies]
assert-impl = "0.1"
lazy_static = "1.2"
rand = "0.8"
tempfile = "3.2.0"
semver = "1.0.0"
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies]
caps = "0.5.1"
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
sysctl = "0.1"
[[test]]
name = "test"
path = "test/test.rs"
[[test]]
name = "test-aio-drop"
path = "test/sys/test_aio_drop.rs"
[[test]]
name = "test-clearenv"
path = "test/test_clearenv.rs"
[[test]]
name = "test-lio-listio-resubmit"
path = "test/sys/test_lio_listio_resubmit.rs"
[[test]]
name = "test-mount"
path = "test/test_mount.rs"
harness = false
[[test]]
name = "test-ptymaster-drop"
path = "test/test_ptymaster_drop.rs"

5
vendor/nix-v0.23.1-patched/Cross.toml vendored Normal file
View file

@ -0,0 +1,5 @@
[build.env]
passthrough = [
"RUSTFLAGS",
"RUST_TEST_THREADS"
]

21
vendor/nix-v0.23.1-patched/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Carl Lerche + nix-rust Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

102
vendor/nix-v0.23.1-patched/README.md vendored Normal file
View file

@ -0,0 +1,102 @@
# Rust bindings to *nix APIs
[![Cirrus Build Status](https://api.cirrus-ci.com/github/nix-rust/nix.svg)](https://cirrus-ci.com/github/nix-rust/nix)
[![crates.io](https://img.shields.io/crates/v/nix.svg)](https://crates.io/crates/nix)
[Documentation (Releases)](https://docs.rs/nix/)
Nix seeks to provide friendly bindings to various *nix platform APIs (Linux, Darwin,
...). The goal is to not provide a 100% unified interface, but to unify
what can be while still providing platform specific APIs.
For many system APIs, Nix provides a safe alternative to the unsafe APIs
exposed by the [libc crate](https://github.com/rust-lang/libc). This is done by
wrapping the libc functionality with types/abstractions that enforce legal/safe
usage.
As an example of what Nix provides, examine the differences between what is
exposed by libc and nix for the
[gethostname](https://man7.org/linux/man-pages/man2/gethostname.2.html) system
call:
```rust,ignore
// libc api (unsafe, requires handling return code/errno)
pub unsafe extern fn gethostname(name: *mut c_char, len: size_t) -> c_int;
// nix api (returns a nix::Result<CStr>)
pub fn gethostname<'a>(buffer: &'a mut [u8]) -> Result<&'a CStr>;
```
## Supported Platforms
nix target support consists of two tiers. While nix attempts to support all
platforms supported by [libc](https://github.com/rust-lang/libc), only some
platforms are actively supported due to either technical or manpower
limitations. Support for platforms is split into three tiers:
* Tier 1 - Builds and tests for this target are run in CI. Failures of either
block the inclusion of new code.
* Tier 2 - Builds for this target are run in CI. Failures during the build
blocks the inclusion of new code. Tests may be run, but failures
in tests don't block the inclusion of new code.
* Tier 3 - Builds for this target are run in CI. Failures during the build
*do not* block the inclusion of new code. Testing may be run, but
failures in tests don't block the inclusion of new code.
The following targets are supported by `nix`:
Tier 1:
* aarch64-unknown-linux-gnu
* arm-unknown-linux-gnueabi
* armv7-unknown-linux-gnueabihf
* i686-unknown-freebsd
* i686-unknown-linux-gnu
* i686-unknown-linux-musl
* mips-unknown-linux-gnu
* mips64-unknown-linux-gnuabi64
* mips64el-unknown-linux-gnuabi64
* mipsel-unknown-linux-gnu
* powerpc64le-unknown-linux-gnu
* x86_64-apple-darwin
* x86_64-unknown-freebsd
* x86_64-unknown-linux-gnu
* x86_64-unknown-linux-musl
Tier 2:
* aarch64-apple-ios
* aarch64-linux-android
* arm-linux-androideabi
* arm-unknown-linux-musleabi
* armv7-linux-androideabi
* i686-linux-android
* powerpc-unknown-linux-gnu
* s390x-unknown-linux-gnu
* x86_64-apple-ios
* x86_64-linux-android
* x86_64-unknown-illumos
* x86_64-unknown-netbsd
Tier 3:
* x86_64-fuchsia
* x86_64-unknown-dragonfly
* x86_64-unknown-linux-gnux32
* x86_64-unknown-openbsd
* x86_64-unknown-redox
## Minimum Supported Rust Version (MSRV)
nix is supported on Rust 1.46.0 and higher. It's MSRV will not be
changed in the future without bumping the major or minor version.
## Contributing
Contributions are very welcome. Please See [CONTRIBUTING](CONTRIBUTING.md) for
additional details.
Feel free to join us in [the nix-rust/nix](https://gitter.im/nix-rust/nix) channel on Gitter to
discuss `nix` development.
## License
Nix is licensed under the MIT license. See [LICENSE](LICENSE) for more details.

View file

@ -0,0 +1,18 @@
This document lists the steps that lead to a successful release of the Nix
library.
# Before Release
Nix uses [cargo release](https://github.com/crate-ci/cargo-release) to automate
the release process. Based on changes since the last release, pick a new
version number following semver conventions. For nix, a change that drops
support for some Rust versions counts as a breaking change, and requires a
major bump.
The release is prepared as follows:
- Ask for a new libc version if, necessary. It usually is. Then update the
dependency in Cargo.toml accordingly.
- Confirm that everything's ready for a release by running
`cargo release --dry-run <patch|minor|major>`
- Create the release with `cargo release <patch|minor|major>`

49
vendor/nix-v0.23.1-patched/bors.toml vendored Normal file
View file

@ -0,0 +1,49 @@
status = [
"Android aarch64",
"Android arm",
"Android armv7",
"Android i686",
"Android x86_64",
"DragonFly BSD x86_64",
"FreeBSD amd64 & i686",
"Fuchsia x86_64",
"Linux MIPS",
"Linux MIPS64 el",
"Linux MIPS64",
"Linux aarch64",
"Linux arm gnueabi",
"Linux arm-musleabi",
"Linux armv7 gnueabihf",
"Linux i686 musl",
"Linux i686",
"Linux mipsel",
"Linux powerpc",
"Linux powerpc64",
"Linux powerpc64le",
"Linux s390x",
"Linux x32",
"Linux x86_64 musl",
"Linux x86_64",
"Minver",
"NetBSD x86_64",
"OpenBSD x86_64",
"OSX x86_64",
"Redox x86_64",
"Rust Stable",
"iOS aarch64",
"iOS x86_64",
"Illumos",
]
# Set bors's timeout to 1 hour
#
# bors's timeout should always be at least twice as long as the test suite
# takes. This is to allow the CI provider to fast-fail a test; if one of the
# builders immediately reports a failure, then bors will move on to the next
# batch, leaving the slower builders to work through the already-doomed run and
# the next one.
#
# At the time this was written, nix's test suite took about twenty minutes to
# run. The timeout was raised to one hour to give nix room to grow and time
# for delays on Cirrus's end.
timeout_sec = 3600

View file

@ -0,0 +1,5 @@
no-dev-version = true
pre-release-replacements = [
{ file="CHANGELOG.md", search="Unreleased", replace="{{version}}" },
{ file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}" }
]

246
vendor/nix-v0.23.1-patched/src/dir.rs vendored Normal file
View file

@ -0,0 +1,246 @@
use crate::{Error, NixPath, Result};
use crate::errno::Errno;
use crate::fcntl::{self, OFlag};
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::ptr;
use std::ffi;
use crate::sys;
#[cfg(target_os = "linux")]
use libc::{dirent64 as dirent, readdir64_r as readdir_r};
#[cfg(not(target_os = "linux"))]
use libc::{dirent, readdir_r};
/// An open directory.
///
/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences:
/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing
/// if the path represents a file or directory).
/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc.
/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd`
/// after the `Dir` is dropped.
/// * can be iterated through multiple times without closing and reopening the file
/// descriptor. Each iteration rewinds when finished.
/// * returns entries for `.` (current directory) and `..` (parent directory).
/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
/// does).
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct Dir(
ptr::NonNull<libc::DIR>
);
impl Dir {
/// Opens the given path as with `fcntl::open`.
pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag,
mode: sys::stat::Mode) -> Result<Self> {
let fd = fcntl::open(path, oflag, mode)?;
Dir::from_fd(fd)
}
/// Opens the given path as with `fcntl::openat`.
pub fn openat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P, oflag: OFlag,
mode: sys::stat::Mode) -> Result<Self> {
let fd = fcntl::openat(dirfd, path, oflag, mode)?;
Dir::from_fd(fd)
}
/// Converts from a descriptor-based object, closing the descriptor on success or failure.
#[inline]
pub fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
Dir::from_fd(fd.into_raw_fd())
}
/// Converts from a file descriptor, closing it on success or failure.
pub fn from_fd(fd: RawFd) -> Result<Self> {
let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else(|| {
let e = Error::last();
unsafe { libc::close(fd) };
e
})?;
Ok(Dir(d))
}
/// Returns an iterator of `Result<Entry>` which rewinds when finished.
pub fn iter(&mut self) -> Iter {
Iter(self)
}
}
// `Dir` is not `Sync`. With the current implementation, it could be, but according to
// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
// call `readdir` simultaneously from multiple threads.
//
// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
unsafe impl Send for Dir {}
impl AsRawFd for Dir {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.0.as_ptr()) }
}
}
impl Drop for Dir {
fn drop(&mut self) {
let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
if !std::thread::panicking() && e == Err(Errno::EBADF) {
panic!("Closing an invalid file descriptor!");
};
}
}
fn next(dir: &mut Dir) -> Option<Result<Entry>> {
unsafe {
// Note: POSIX specifies that portable applications should dynamically allocate a
// buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
// for the NUL byte. It doesn't look like the std library does this; it just uses
// fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
// Probably fine here too then.
let mut ent = std::mem::MaybeUninit::<dirent>::uninit();
let mut result = ptr::null_mut();
if let Err(e) = Errno::result(
readdir_r(dir.0.as_ptr(), ent.as_mut_ptr(), &mut result))
{
return Some(Err(e));
}
if result.is_null() {
return None;
}
assert_eq!(result, ent.as_mut_ptr());
Some(Ok(Entry(ent.assume_init())))
}
}
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct Iter<'d>(&'d mut Dir);
impl<'d> Iterator for Iter<'d> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
next(self.0)
}
}
impl<'d> Drop for Iter<'d> {
fn drop(&mut self) {
unsafe { libc::rewinddir((self.0).0.as_ptr()) }
}
}
/// The return type of [Dir::into_iter]
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct OwningIter(Dir);
impl Iterator for OwningIter {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
next(&mut self.0)
}
}
impl IntoIterator for Dir {
type Item = Result<Entry>;
type IntoIter = OwningIter;
/// Creates a owning iterator, that is, one that takes ownership of the
/// `Dir`. The `Dir` cannot be used after calling this. This can be useful
/// when you have a function that both creates a `Dir` instance and returns
/// an `Iterator`.
///
/// Example:
///
/// ```
/// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
/// use std::{iter::Iterator, string::String};
///
/// fn ls_upper(dirname: &str) -> impl Iterator<Item=String> {
/// let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap();
/// d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase())
/// }
/// ```
fn into_iter(self) -> Self::IntoIter {
OwningIter(self)
}
}
/// A directory entry, similar to `std::fs::DirEntry`.
///
/// Note that unlike the std version, this may represent the `.` or `..` entries.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct Entry(dirent);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Type {
Fifo,
CharacterDevice,
Directory,
BlockDevice,
File,
Symlink,
Socket,
}
impl Entry {
/// Returns the inode number (`d_ino`) of the underlying `dirent`.
#[cfg(any(target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "illumos",
target_os = "ios",
target_os = "l4re",
target_os = "linux",
target_os = "macos",
target_os = "solaris"))]
pub fn ino(&self) -> u64 {
self.0.d_ino as u64
}
/// Returns the inode number (`d_fileno`) of the underlying `dirent`.
#[cfg(not(any(target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "illumos",
target_os = "ios",
target_os = "l4re",
target_os = "linux",
target_os = "macos",
target_os = "solaris")))]
#[allow(clippy::useless_conversion)] // Not useless on all OSes
pub fn ino(&self) -> u64 {
u64::from(self.0.d_fileno)
}
/// Returns the bare file name of this directory entry without any other leading path component.
pub fn file_name(&self) -> &ffi::CStr {
unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) }
}
/// Returns the type of this directory entry, if known.
///
/// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
/// notably, some Linux filesystems don't implement this. The caller should use `stat` or
/// `fstat` if this returns `None`.
pub fn file_type(&self) -> Option<Type> {
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
match self.0.d_type {
libc::DT_FIFO => Some(Type::Fifo),
libc::DT_CHR => Some(Type::CharacterDevice),
libc::DT_DIR => Some(Type::Directory),
libc::DT_BLK => Some(Type::BlockDevice),
libc::DT_REG => Some(Type::File),
libc::DT_LNK => Some(Type::Symlink),
libc::DT_SOCK => Some(Type::Socket),
/* libc::DT_UNKNOWN | */ _ => None,
}
// illumos and Solaris systems do not have the d_type member at all:
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
None
}
}

66
vendor/nix-v0.23.1-patched/src/env.rs vendored Normal file
View file

@ -0,0 +1,66 @@
//! Environment variables
use cfg_if::cfg_if;
use std::fmt;
/// Indicates that [`clearenv`] failed for some unknown reason
#[derive(Clone, Copy, Debug)]
pub struct ClearEnvError;
impl fmt::Display for ClearEnvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "clearenv failed")
}
}
impl std::error::Error for ClearEnvError {}
/// Clear the environment of all name-value pairs.
///
/// On platforms where libc provides `clearenv()`, it will be used. libc's
/// `clearenv()` is documented to return an error code but not set errno; if the
/// return value indicates a failure, this function will return
/// [`ClearEnvError`].
///
/// On platforms where libc does not provide `clearenv()`, a fallback
/// implementation will be used that iterates over all environment variables and
/// removes them one-by-one.
///
/// # Safety
///
/// This function is not threadsafe and can cause undefined behavior in
/// combination with `std::env` or other program components that access the
/// environment. See, for example, the discussion on `std::env::remove_var`; this
/// function is a case of an "inherently unsafe non-threadsafe API" dealing with
/// the environment.
///
/// The caller must ensure no other threads access the process environment while
/// this function executes and that no raw pointers to an element of libc's
/// `environ` is currently held. The latter is not an issue if the only other
/// environment access in the program is via `std::env`, but the requirement on
/// thread safety must still be upheld.
pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> {
let ret;
cfg_if! {
if #[cfg(any(target_os = "fuchsia",
target_os = "wasi",
target_env = "wasi",
target_env = "uclibc",
target_os = "linux",
target_os = "android",
target_os = "emscripten"))] {
ret = libc::clearenv();
} else {
use std::env;
for (name, _) in env::vars_os() {
env::remove_var(name);
}
ret = 0;
}
}
if ret == 0 {
Ok(())
} else {
Err(ClearEnvError)
}
}

2723
vendor/nix-v0.23.1-patched/src/errno.rs vendored Normal file

File diff suppressed because it is too large Load diff

696
vendor/nix-v0.23.1-patched/src/fcntl.rs vendored Normal file
View file

@ -0,0 +1,696 @@
use crate::errno::Errno;
use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
use std::ffi::OsString;
#[cfg(not(target_os = "redox"))]
use std::os::raw;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::RawFd;
use crate::sys::stat::Mode;
use crate::{NixPath, Result};
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::ptr; // For splice and copy_file_range
#[cfg(any(target_os = "android", target_os = "linux"))]
use crate::sys::uio::IoVec; // For vmsplice
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_os = "freebsd"
))]
pub use self::posix_fadvise::*;
#[cfg(not(target_os = "redox"))]
libc_bitflags! {
pub struct AtFlags: c_int {
AT_REMOVEDIR;
AT_SYMLINK_FOLLOW;
AT_SYMLINK_NOFOLLOW;
#[cfg(any(target_os = "android", target_os = "linux"))]
AT_NO_AUTOMOUNT;
#[cfg(any(target_os = "android", target_os = "linux"))]
AT_EMPTY_PATH;
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
AT_EACCESS;
}
}
libc_bitflags!(
/// Configuration options for opened files.
pub struct OFlag: c_int {
/// Mask for the access mode of the file.
O_ACCMODE;
/// Use alternate I/O semantics.
#[cfg(target_os = "netbsd")]
O_ALT_IO;
/// Open the file in append-only mode.
O_APPEND;
/// Generate a signal when input or output becomes possible.
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
O_ASYNC;
/// Closes the file descriptor once an `execve` call is made.
///
/// Also sets the file offset to the beginning of the file.
O_CLOEXEC;
/// Create the file if it does not exist.
O_CREAT;
/// Try to minimize cache effects of the I/O for this file.
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd"))]
O_DIRECT;
/// If the specified path isn't a directory, fail.
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
O_DIRECTORY;
/// Implicitly follow each `write()` with an `fdatasync()`.
#[cfg(any(target_os = "android",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
O_DSYNC;
/// Error out if a file was not created.
O_EXCL;
/// Open for execute only.
#[cfg(target_os = "freebsd")]
O_EXEC;
/// Open with an exclusive file lock.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"))]
O_EXLOCK;
/// Same as `O_SYNC`.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
all(target_os = "linux", not(target_env = "musl")),
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"))]
O_FSYNC;
/// Allow files whose sizes can't be represented in an `off_t` to be opened.
#[cfg(any(target_os = "android", target_os = "linux"))]
O_LARGEFILE;
/// Do not update the file last access time during `read(2)`s.
#[cfg(any(target_os = "android", target_os = "linux"))]
O_NOATIME;
/// Don't attach the device as the process' controlling terminal.
#[cfg(not(target_os = "redox"))]
O_NOCTTY;
/// Same as `O_NONBLOCK`.
#[cfg(not(target_os = "redox"))]
O_NDELAY;
/// `open()` will fail if the given path is a symbolic link.
O_NOFOLLOW;
/// When possible, open the file in nonblocking mode.
O_NONBLOCK;
/// Don't deliver `SIGPIPE`.
#[cfg(target_os = "netbsd")]
O_NOSIGPIPE;
/// Obtain a file descriptor for low-level access.
///
/// The file itself is not opened and other file operations will fail.
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
O_PATH;
/// Only allow reading.
///
/// This should not be combined with `O_WRONLY` or `O_RDWR`.
O_RDONLY;
/// Allow both reading and writing.
///
/// This should not be combined with `O_WRONLY` or `O_RDONLY`.
O_RDWR;
/// Similar to `O_DSYNC` but applies to `read`s instead.
#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))]
O_RSYNC;
/// Skip search permission checks.
#[cfg(target_os = "netbsd")]
O_SEARCH;
/// Open with a shared file lock.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"))]
O_SHLOCK;
/// Implicitly follow each `write()` with an `fsync()`.
#[cfg(not(target_os = "redox"))]
O_SYNC;
/// Create an unnamed temporary file.
#[cfg(any(target_os = "android", target_os = "linux"))]
O_TMPFILE;
/// Truncate an existing regular file to 0 length if it allows writing.
O_TRUNC;
/// Restore default TTY attributes.
#[cfg(target_os = "freebsd")]
O_TTY_INIT;
/// Only allow writing.
///
/// This should not be combined with `O_RDONLY` or `O_RDWR`.
O_WRONLY;
}
);
// The conversion is not identical on all operating systems.
#[allow(clippy::useless_conversion)]
pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: Mode) -> Result<RawFd> {
let fd = path.with_nix_path(|cstr| {
unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
})?;
Errno::result(fd)
}
// The conversion is not identical on all operating systems.
#[allow(clippy::useless_conversion)]
#[cfg(not(target_os = "redox"))]
pub fn openat<P: ?Sized + NixPath>(
dirfd: RawFd,
path: &P,
oflag: OFlag,
mode: Mode,
) -> Result<RawFd> {
let fd = path.with_nix_path(|cstr| {
unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
})?;
Errno::result(fd)
}
#[cfg(not(target_os = "redox"))]
pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
old_dirfd: Option<RawFd>,
old_path: &P1,
new_dirfd: Option<RawFd>,
new_path: &P2,
) -> Result<()> {
let res = old_path.with_nix_path(|old_cstr| {
new_path.with_nix_path(|new_cstr| unsafe {
libc::renameat(
at_rawfd(old_dirfd),
old_cstr.as_ptr(),
at_rawfd(new_dirfd),
new_cstr.as_ptr(),
)
})
})??;
Errno::result(res).map(drop)
}
#[cfg(all(
target_os = "linux",
target_env = "gnu",
))]
libc_bitflags! {
pub struct RenameFlags: u32 {
RENAME_EXCHANGE;
RENAME_NOREPLACE;
RENAME_WHITEOUT;
}
}
#[cfg(all(
target_os = "linux",
target_env = "gnu",
))]
pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
old_dirfd: Option<RawFd>,
old_path: &P1,
new_dirfd: Option<RawFd>,
new_path: &P2,
flags: RenameFlags,
) -> Result<()> {
let res = old_path.with_nix_path(|old_cstr| {
new_path.with_nix_path(|new_cstr| unsafe {
libc::renameat2(
at_rawfd(old_dirfd),
old_cstr.as_ptr(),
at_rawfd(new_dirfd),
new_cstr.as_ptr(),
flags.bits(),
)
})
})??;
Errno::result(res).map(drop)
}
fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
unsafe { v.set_len(len as usize) }
v.shrink_to_fit();
Ok(OsString::from_vec(v.to_vec()))
}
fn readlink_maybe_at<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
path: &P,
v: &mut Vec<u8>,
) -> Result<libc::ssize_t> {
path.with_nix_path(|cstr| unsafe {
match dirfd {
#[cfg(target_os = "redox")]
Some(_) => unreachable!(),
#[cfg(not(target_os = "redox"))]
Some(dirfd) => libc::readlinkat(
dirfd,
cstr.as_ptr(),
v.as_mut_ptr() as *mut c_char,
v.capacity() as size_t,
),
None => libc::readlink(
cstr.as_ptr(),
v.as_mut_ptr() as *mut c_char,
v.capacity() as size_t,
),
}
})
}
fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result<OsString> {
let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
// simple case: result is strictly less than `PATH_MAX`
let res = readlink_maybe_at(dirfd, path, &mut v)?;
let len = Errno::result(res)?;
debug_assert!(len >= 0);
if (len as usize) < v.capacity() {
return wrap_readlink_result(v, res);
}
// Uh oh, the result is too long...
// Let's try to ask lstat how many bytes to allocate.
let reported_size = match dirfd {
#[cfg(target_os = "redox")]
Some(_) => unreachable!(),
#[cfg(any(target_os = "android", target_os = "linux"))]
Some(dirfd) => {
let flags = if path.is_empty() { AtFlags::AT_EMPTY_PATH } else { AtFlags::empty() };
super::sys::stat::fstatat(dirfd, path, flags | AtFlags::AT_SYMLINK_NOFOLLOW)
},
#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "redox")))]
Some(dirfd) => super::sys::stat::fstatat(dirfd, path, AtFlags::AT_SYMLINK_NOFOLLOW),
None => super::sys::stat::lstat(path)
}
.map(|x| x.st_size)
.unwrap_or(0);
let mut try_size = if reported_size > 0 {
// Note: even if `lstat`'s apparently valid answer turns out to be
// wrong, we will still read the full symlink no matter what.
reported_size as usize + 1
} else {
// If lstat doesn't cooperate, or reports an error, be a little less
// precise.
(libc::PATH_MAX as usize).max(128) << 1
};
loop {
v.reserve_exact(try_size);
let res = readlink_maybe_at(dirfd, path, &mut v)?;
let len = Errno::result(res)?;
debug_assert!(len >= 0);
if (len as usize) < v.capacity() {
break wrap_readlink_result(v, res);
} else {
// Ugh! Still not big enough!
match try_size.checked_shl(1) {
Some(next_size) => try_size = next_size,
// It's absurd that this would happen, but handle it sanely
// anyway.
None => break Err(Errno::ENAMETOOLONG),
}
}
}
}
pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
inner_readlink(None, path)
}
#[cfg(not(target_os = "redox"))]
pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> {
inner_readlink(Some(dirfd), path)
}
/// Computes the raw fd consumed by a function of the form `*at`.
#[cfg(not(target_os = "redox"))]
pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
match fd {
None => libc::AT_FDCWD,
Some(fd) => fd,
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
libc_bitflags!(
/// Additional flags for file sealing, which allows for limiting operations on a file.
pub struct SealFlag: c_int {
/// Prevents further calls to `fcntl()` with `F_ADD_SEALS`.
F_SEAL_SEAL;
/// The file cannot be reduced in size.
F_SEAL_SHRINK;
/// The size of the file cannot be increased.
F_SEAL_GROW;
/// The file contents cannot be modified.
F_SEAL_WRITE;
}
);
libc_bitflags!(
/// Additional configuration flags for `fcntl`'s `F_SETFD`.
pub struct FdFlag: c_int {
/// The file descriptor will automatically be closed during a successful `execve(2)`.
FD_CLOEXEC;
}
);
#[cfg(not(target_os = "redox"))]
#[derive(Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FcntlArg<'a> {
F_DUPFD(RawFd),
F_DUPFD_CLOEXEC(RawFd),
F_GETFD,
F_SETFD(FdFlag), // FD_FLAGS
F_GETFL,
F_SETFL(OFlag), // O_NONBLOCK
F_SETLK(&'a libc::flock),
F_SETLKW(&'a libc::flock),
F_GETLK(&'a mut libc::flock),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_OFD_SETLK(&'a libc::flock),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_OFD_SETLKW(&'a libc::flock),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_OFD_GETLK(&'a mut libc::flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_ADD_SEALS(SealFlag),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_GET_SEALS,
#[cfg(any(target_os = "macos", target_os = "ios"))]
F_FULLFSYNC,
#[cfg(any(target_os = "linux", target_os = "android"))]
F_GETPIPE_SZ,
#[cfg(any(target_os = "linux", target_os = "android"))]
F_SETPIPE_SZ(c_int),
// TODO: Rest of flags
}
#[cfg(target_os = "redox")]
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FcntlArg {
F_DUPFD(RawFd),
F_DUPFD_CLOEXEC(RawFd),
F_GETFD,
F_SETFD(FdFlag), // FD_FLAGS
F_GETFL,
F_SETFL(OFlag), // O_NONBLOCK
}
pub use self::FcntlArg::*;
// TODO: Figure out how to handle value fcntl returns
pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
let res = unsafe {
match arg {
F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd),
F_DUPFD_CLOEXEC(rawfd) => libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd),
F_GETFD => libc::fcntl(fd, libc::F_GETFD),
F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()),
F_GETFL => libc::fcntl(fd, libc::F_GETFL),
F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()),
#[cfg(not(target_os = "redox"))]
F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock),
#[cfg(not(target_os = "redox"))]
F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock),
#[cfg(not(target_os = "redox"))]
F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS),
#[cfg(any(target_os = "macos", target_os = "ios"))]
F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size),
}
};
Errno::result(res)
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FlockArg {
LockShared,
LockExclusive,
Unlock,
LockSharedNonblock,
LockExclusiveNonblock,
UnlockNonblock,
}
#[cfg(not(target_os = "redox"))]
pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
use self::FlockArg::*;
let res = unsafe {
match arg {
LockShared => libc::flock(fd, libc::LOCK_SH),
LockExclusive => libc::flock(fd, libc::LOCK_EX),
Unlock => libc::flock(fd, libc::LOCK_UN),
LockSharedNonblock => libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB),
LockExclusiveNonblock => libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB),
UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB),
}
};
Errno::result(res).map(drop)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
libc_bitflags! {
/// Additional flags to `splice` and friends.
pub struct SpliceFFlags: c_uint {
/// Request that pages be moved instead of copied.
///
/// Not applicable to `vmsplice`.
SPLICE_F_MOVE;
/// Do not block on I/O.
SPLICE_F_NONBLOCK;
/// Hint that more data will be coming in a subsequent splice.
///
/// Not applicable to `vmsplice`.
SPLICE_F_MORE;
/// Gift the user pages to the kernel.
///
/// Not applicable to `splice`.
SPLICE_F_GIFT;
}
}
/// Copy a range of data from one file to another
///
/// The `copy_file_range` system call performs an in-kernel copy between
/// file descriptors `fd_in` and `fd_out` without the additional cost of
/// transferring data from the kernel to user space and then back into the
/// kernel. It copies up to `len` bytes of data from file descriptor `fd_in` to
/// file descriptor `fd_out`, overwriting any data that exists within the
/// requested range of the target file.
///
/// If the `off_in` and/or `off_out` arguments are used, the values
/// will be mutated to reflect the new position within the file after
/// copying. If they are not used, the relevant filedescriptors will be seeked
/// to the new position.
///
/// On successful completion the number of bytes actually copied will be
/// returned.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn copy_file_range(
fd_in: RawFd,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
off_out: Option<&mut libc::loff_t>,
len: usize,
) -> Result<usize> {
let off_in = off_in
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let off_out = off_out
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let ret = unsafe {
libc::syscall(
libc::SYS_copy_file_range,
fd_in,
off_in,
fd_out,
off_out,
len,
0,
)
};
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn splice(
fd_in: RawFd,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
off_out: Option<&mut libc::loff_t>,
len: usize,
flags: SpliceFFlags,
) -> Result<usize> {
let off_in = off_in
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let off_out = off_out
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let ret = unsafe { libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) };
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn tee(fd_in: RawFd, fd_out: RawFd, len: usize, flags: SpliceFFlags) -> Result<usize> {
let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) };
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn vmsplice(fd: RawFd, iov: &[IoVec<&[u8]>], flags: SpliceFFlags) -> Result<usize> {
let ret = unsafe {
libc::vmsplice(
fd,
iov.as_ptr() as *const libc::iovec,
iov.len(),
flags.bits(),
)
};
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux"))]
libc_bitflags!(
/// Mode argument flags for fallocate determining operation performed on a given range.
pub struct FallocateFlags: c_int {
/// File size is not changed.
///
/// offset + len can be greater than file size.
FALLOC_FL_KEEP_SIZE;
/// Deallocates space by creating a hole.
///
/// Must be ORed with FALLOC_FL_KEEP_SIZE. Byte range starts at offset and continues for len bytes.
FALLOC_FL_PUNCH_HOLE;
/// Removes byte range from a file without leaving a hole.
///
/// Byte range to collapse starts at offset and continues for len bytes.
FALLOC_FL_COLLAPSE_RANGE;
/// Zeroes space in specified byte range.
///
/// Byte range starts at offset and continues for len bytes.
FALLOC_FL_ZERO_RANGE;
/// Increases file space by inserting a hole within the file size.
///
/// Does not overwrite existing data. Hole starts at offset and continues for len bytes.
FALLOC_FL_INSERT_RANGE;
/// Shared file data extants are made private to the file.
///
/// Gaurantees that a subsequent write will not fail due to lack of space.
FALLOC_FL_UNSHARE_RANGE;
}
);
/// Manipulates file space.
///
/// Allows the caller to directly manipulate the allocated disk space for the
/// file referred to by fd.
#[cfg(any(target_os = "linux"))]
pub fn fallocate(
fd: RawFd,
mode: FallocateFlags,
offset: libc::off_t,
len: libc::off_t,
) -> Result<()> {
let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) };
Errno::result(res).map(drop)
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_os = "freebsd"
))]
mod posix_fadvise {
use crate::errno::Errno;
use std::os::unix::io::RawFd;
use crate::Result;
libc_enum! {
#[repr(i32)]
#[non_exhaustive]
pub enum PosixFadviseAdvice {
POSIX_FADV_NORMAL,
POSIX_FADV_SEQUENTIAL,
POSIX_FADV_RANDOM,
POSIX_FADV_NOREUSE,
POSIX_FADV_WILLNEED,
POSIX_FADV_DONTNEED,
}
}
pub fn posix_fadvise(
fd: RawFd,
offset: libc::off_t,
len: libc::off_t,
advice: PosixFadviseAdvice,
) -> Result<()> {
let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) };
if res == 0 {
Ok(())
} else {
Err(Errno::from_i32(res))
}
}
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_os = "freebsd"
))]
pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Result<()> {
let res = unsafe { libc::posix_fallocate(fd, offset, len) };
match Errno::result(res) {
Err(err) => Err(err),
Ok(0) => Ok(()),
Ok(errno) => Err(Errno::from_i32(errno)),
}
}

View file

@ -0,0 +1,121 @@
//! Feature tests for OS functionality
pub use self::os::*;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod os {
use crate::sys::utsname::uname;
// Features:
// * atomic cloexec on socket: 2.6.27
// * pipe2: 2.6.27
// * accept4: 2.6.28
static VERS_UNKNOWN: usize = 1;
static VERS_2_6_18: usize = 2;
static VERS_2_6_27: usize = 3;
static VERS_2_6_28: usize = 4;
static VERS_3: usize = 5;
#[inline]
fn digit(dst: &mut usize, b: u8) {
*dst *= 10;
*dst += (b - b'0') as usize;
}
fn parse_kernel_version() -> usize {
let u = uname();
let mut curr: usize = 0;
let mut major: usize = 0;
let mut minor: usize = 0;
let mut patch: usize = 0;
for b in u.release().bytes() {
if curr >= 3 {
break;
}
match b {
b'.' | b'-' => {
curr += 1;
}
b'0'..=b'9' => {
match curr {
0 => digit(&mut major, b),
1 => digit(&mut minor, b),
_ => digit(&mut patch, b),
}
}
_ => break,
}
}
if major >= 3 {
VERS_3
} else if major >= 2 {
if minor >= 7 {
VERS_UNKNOWN
} else if minor >= 6 {
if patch >= 28 {
VERS_2_6_28
} else if patch >= 27 {
VERS_2_6_27
} else {
VERS_2_6_18
}
} else {
VERS_UNKNOWN
}
} else {
VERS_UNKNOWN
}
}
fn kernel_version() -> usize {
static mut KERNEL_VERS: usize = 0;
unsafe {
if KERNEL_VERS == 0 {
KERNEL_VERS = parse_kernel_version();
}
KERNEL_VERS
}
}
/// Check if the OS supports atomic close-on-exec for sockets
pub fn socket_atomic_cloexec() -> bool {
kernel_version() >= VERS_2_6_27
}
#[test]
pub fn test_parsing_kernel_version() {
assert!(kernel_version() > 0);
}
}
#[cfg(any(
target_os = "dragonfly", // Since ???
target_os = "freebsd", // Since 10.0
target_os = "illumos", // Since ???
target_os = "netbsd", // Since 6.0
target_os = "openbsd", // Since 5.7
target_os = "redox", // Since 1-july-2020
))]
mod os {
/// Check if the OS supports atomic close-on-exec for sockets
pub const fn socket_atomic_cloexec() -> bool {
true
}
}
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "fuchsia",
target_os = "solaris"))]
mod os {
/// Check if the OS supports atomic close-on-exec for sockets
pub const fn socket_atomic_cloexec() -> bool {
false
}
}

View file

@ -0,0 +1,147 @@
//! Query network interface addresses
//!
//! Uses the Linux and/or BSD specific function `getifaddrs` to query the list
//! of interfaces and their associated addresses.
use cfg_if::cfg_if;
use std::ffi;
use std::iter::Iterator;
use std::mem;
use std::option::Option;
use crate::{Result, Errno};
use crate::sys::socket::SockAddr;
use crate::net::if_::*;
/// Describes a single address for an interface as returned by `getifaddrs`.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct InterfaceAddress {
/// Name of the network interface
pub interface_name: String,
/// Flags as from `SIOCGIFFLAGS` ioctl
pub flags: InterfaceFlags,
/// Network address of this interface
pub address: Option<SockAddr>,
/// Netmask of this interface
pub netmask: Option<SockAddr>,
/// Broadcast address of this interface, if applicable
pub broadcast: Option<SockAddr>,
/// Point-to-point destination address
pub destination: Option<SockAddr>,
}
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux"))] {
fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr {
info.ifa_ifu
}
} else {
fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr {
info.ifa_dstaddr
}
}
}
impl InterfaceAddress {
/// Create an `InterfaceAddress` from the libc struct.
fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress {
let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) };
let address = unsafe { SockAddr::from_libc_sockaddr(info.ifa_addr) };
let netmask = unsafe { SockAddr::from_libc_sockaddr(info.ifa_netmask) };
let mut addr = InterfaceAddress {
interface_name: ifname.to_string_lossy().to_string(),
flags: InterfaceFlags::from_bits_truncate(info.ifa_flags as i32),
address,
netmask,
broadcast: None,
destination: None,
};
let ifu = get_ifu_from_sockaddr(info);
if addr.flags.contains(InterfaceFlags::IFF_POINTOPOINT) {
addr.destination = unsafe { SockAddr::from_libc_sockaddr(ifu) };
} else if addr.flags.contains(InterfaceFlags::IFF_BROADCAST) {
addr.broadcast = unsafe { SockAddr::from_libc_sockaddr(ifu) };
}
addr
}
}
/// Holds the results of `getifaddrs`.
///
/// Use the function `getifaddrs` to create this Iterator. Note that the
/// actual list of interfaces can be iterated once and will be freed as
/// soon as the Iterator goes out of scope.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct InterfaceAddressIterator {
base: *mut libc::ifaddrs,
next: *mut libc::ifaddrs,
}
impl Drop for InterfaceAddressIterator {
fn drop(&mut self) {
unsafe { libc::freeifaddrs(self.base) };
}
}
impl Iterator for InterfaceAddressIterator {
type Item = InterfaceAddress;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
match unsafe { self.next.as_ref() } {
Some(ifaddr) => {
self.next = ifaddr.ifa_next;
Some(InterfaceAddress::from_libc_ifaddrs(ifaddr))
}
None => None,
}
}
}
/// Get interface addresses using libc's `getifaddrs`
///
/// Note that the underlying implementation differs between OSes. Only the
/// most common address families are supported by the nix crate (due to
/// lack of time and complexity of testing). The address family is encoded
/// in the specific variant of `SockAddr` returned for the fields `address`,
/// `netmask`, `broadcast`, and `destination`. For any entry not supported,
/// the returned list will contain a `None` entry.
///
/// # Example
/// ```
/// let addrs = nix::ifaddrs::getifaddrs().unwrap();
/// for ifaddr in addrs {
/// match ifaddr.address {
/// Some(address) => {
/// println!("interface {} address {}",
/// ifaddr.interface_name, address);
/// },
/// None => {
/// println!("interface {} with unsupported address family",
/// ifaddr.interface_name);
/// }
/// }
/// }
/// ```
pub fn getifaddrs() -> Result<InterfaceAddressIterator> {
let mut addrs = mem::MaybeUninit::<*mut libc::ifaddrs>::uninit();
unsafe {
Errno::result(libc::getifaddrs(addrs.as_mut_ptr())).map(|_| {
InterfaceAddressIterator {
base: addrs.assume_init(),
next: addrs.assume_init(),
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
// Only checks if `getifaddrs` can be invoked without panicking.
#[test]
fn test_getifaddrs() {
let _ = getifaddrs();
}
}

122
vendor/nix-v0.23.1-patched/src/kmod.rs vendored Normal file
View file

@ -0,0 +1,122 @@
//! Load and unload kernel modules.
//!
//! For more details see
use std::ffi::CStr;
use std::os::unix::io::AsRawFd;
use crate::errno::Errno;
use crate::Result;
/// Loads a kernel module from a buffer.
///
/// It loads an ELF image into kernel space,
/// performs any necessary symbol relocations,
/// initializes module parameters to values provided by the caller,
/// and then runs the module's init function.
///
/// This function requires `CAP_SYS_MODULE` privilege.
///
/// The `module_image` argument points to a buffer containing the binary image
/// to be loaded. The buffer should contain a valid ELF image
/// built for the running kernel.
///
/// The `param_values` argument is a string containing space-delimited specifications
/// of the values for module parameters.
/// Each of the parameter specifications has the form:
///
/// `name[=value[,value...]]`
///
/// # Example
///
/// ```no_run
/// use std::fs::File;
/// use std::io::Read;
/// use std::ffi::CString;
/// use nix::kmod::init_module;
///
/// let mut f = File::open("mykernel.ko").unwrap();
/// let mut contents: Vec<u8> = Vec::new();
/// f.read_to_end(&mut contents).unwrap();
/// init_module(&mut contents, &CString::new("who=Rust when=Now,12").unwrap()).unwrap();
/// ```
///
/// See [`man init_module(2)`](https://man7.org/linux/man-pages/man2/init_module.2.html) for more information.
pub fn init_module(module_image: &[u8], param_values: &CStr) -> Result<()> {
let res = unsafe {
libc::syscall(
libc::SYS_init_module,
module_image.as_ptr(),
module_image.len(),
param_values.as_ptr(),
)
};
Errno::result(res).map(drop)
}
libc_bitflags!(
/// Flags used by the `finit_module` function.
pub struct ModuleInitFlags: libc::c_uint {
/// Ignore symbol version hashes.
MODULE_INIT_IGNORE_MODVERSIONS;
/// Ignore kernel version magic.
MODULE_INIT_IGNORE_VERMAGIC;
}
);
/// Loads a kernel module from a given file descriptor.
///
/// # Example
///
/// ```no_run
/// use std::fs::File;
/// use std::ffi::CString;
/// use nix::kmod::{finit_module, ModuleInitFlags};
///
/// let f = File::open("mymod.ko").unwrap();
/// finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()).unwrap();
/// ```
///
/// See [`man init_module(2)`](https://man7.org/linux/man-pages/man2/init_module.2.html) for more information.
pub fn finit_module<T: AsRawFd>(fd: &T, param_values: &CStr, flags: ModuleInitFlags) -> Result<()> {
let res = unsafe {
libc::syscall(
libc::SYS_finit_module,
fd.as_raw_fd(),
param_values.as_ptr(),
flags.bits(),
)
};
Errno::result(res).map(drop)
}
libc_bitflags!(
/// Flags used by `delete_module`.
///
/// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html)
/// for a detailed description how these flags work.
pub struct DeleteModuleFlags: libc::c_int {
O_NONBLOCK;
O_TRUNC;
}
);
/// Unloads the kernel module with the given name.
///
/// # Example
///
/// ```no_run
/// use std::ffi::CString;
/// use nix::kmod::{delete_module, DeleteModuleFlags};
///
/// delete_module(&CString::new("mymod").unwrap(), DeleteModuleFlags::O_NONBLOCK).unwrap();
/// ```
///
/// See [`man delete_module(2)`](https://man7.org/linux/man-pages/man2/delete_module.2.html) for more information.
pub fn delete_module(name: &CStr, flags: DeleteModuleFlags) -> Result<()> {
let res = unsafe { libc::syscall(libc::SYS_delete_module, name.as_ptr(), flags.bits()) };
Errno::result(res).map(drop)
}

227
vendor/nix-v0.23.1-patched/src/lib.rs vendored Normal file
View file

@ -0,0 +1,227 @@
//! Rust friendly bindings to the various *nix system functions.
//!
//! Modules are structured according to the C header file that they would be
//! defined in.
#![crate_name = "nix"]
#![cfg(unix)]
#![allow(non_camel_case_types)]
#![cfg_attr(test, deny(warnings))]
#![recursion_limit = "500"]
#![deny(unused)]
#![deny(unstable_features)]
#![deny(missing_copy_implementations)]
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]
// Re-exported external crates
pub use libc;
// Private internal modules
#[macro_use] mod macros;
// Public crates
#[cfg(not(target_os = "redox"))]
#[allow(missing_docs)]
pub mod dir;
pub mod env;
#[allow(missing_docs)]
pub mod errno;
pub mod features;
#[allow(missing_docs)]
pub mod fcntl;
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "illumos",
target_os = "openbsd"))]
pub mod ifaddrs;
#[cfg(any(target_os = "android",
target_os = "linux"))]
#[allow(missing_docs)]
pub mod kmod;
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "linux"))]
pub mod mount;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "fushsia",
target_os = "linux",
target_os = "netbsd"))]
#[allow(missing_docs)]
pub mod mqueue;
#[cfg(not(target_os = "redox"))]
pub mod net;
pub mod poll;
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
pub mod pty;
pub mod sched;
pub mod sys;
#[allow(missing_docs)]
pub mod time;
// This can be implemented for other platforms as soon as libc
// provides bindings for them.
#[cfg(all(target_os = "linux",
any(target_arch = "x86", target_arch = "x86_64")))]
#[allow(missing_docs)]
pub mod ucontext;
#[allow(missing_docs)]
pub mod unistd;
/*
*
* ===== Result / Error =====
*
*/
use libc::{c_char, PATH_MAX};
use std::{ptr, result};
use std::ffi::{CStr, OsStr};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use errno::Errno;
/// Nix Result Type
pub type Result<T> = result::Result<T, Errno>;
/// Nix's main error type.
///
/// It's a wrapper around Errno. As such, it's very interoperable with
/// [`std::io::Error`], but it has the advantages of:
/// * `Clone`
/// * `Copy`
/// * `Eq`
/// * Small size
/// * Represents all of the system's errnos, instead of just the most common
/// ones.
pub type Error = Errno;
/// Common trait used to represent file system paths by many Nix functions.
pub trait NixPath {
/// Is the path empty?
fn is_empty(&self) -> bool;
/// Length of the path in bytes
fn len(&self) -> usize;
/// Execute a function with this path as a `CStr`.
///
/// Mostly used internally by Nix.
fn with_nix_path<T, F>(&self, f: F) -> Result<T>
where F: FnOnce(&CStr) -> T;
}
impl NixPath for str {
fn is_empty(&self) -> bool {
NixPath::is_empty(OsStr::new(self))
}
fn len(&self) -> usize {
NixPath::len(OsStr::new(self))
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T>
where F: FnOnce(&CStr) -> T {
OsStr::new(self).with_nix_path(f)
}
}
impl NixPath for OsStr {
fn is_empty(&self) -> bool {
self.as_bytes().is_empty()
}
fn len(&self) -> usize {
self.as_bytes().len()
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T>
where F: FnOnce(&CStr) -> T {
self.as_bytes().with_nix_path(f)
}
}
impl NixPath for CStr {
fn is_empty(&self) -> bool {
self.to_bytes().is_empty()
}
fn len(&self) -> usize {
self.to_bytes().len()
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T>
where F: FnOnce(&CStr) -> T {
// Equivalence with the [u8] impl.
if self.len() >= PATH_MAX as usize {
return Err(Errno::ENAMETOOLONG)
}
Ok(f(self))
}
}
impl NixPath for [u8] {
fn is_empty(&self) -> bool {
self.is_empty()
}
fn len(&self) -> usize {
self.len()
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T>
where F: FnOnce(&CStr) -> T {
let mut buf = [0u8; PATH_MAX as usize];
if self.len() >= PATH_MAX as usize {
return Err(Errno::ENAMETOOLONG)
}
match self.iter().position(|b| *b == 0) {
Some(_) => Err(Errno::EINVAL),
None => {
unsafe {
// TODO: Replace with bytes::copy_memory. rust-lang/rust#24028
ptr::copy_nonoverlapping(self.as_ptr(), buf.as_mut_ptr(), self.len());
Ok(f(CStr::from_ptr(buf.as_ptr() as *const c_char)))
}
}
}
}
}
impl NixPath for Path {
fn is_empty(&self) -> bool {
NixPath::is_empty(self.as_os_str())
}
fn len(&self) -> usize {
NixPath::len(self.as_os_str())
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T> where F: FnOnce(&CStr) -> T {
self.as_os_str().with_nix_path(f)
}
}
impl NixPath for PathBuf {
fn is_empty(&self) -> bool {
NixPath::is_empty(self.as_os_str())
}
fn len(&self) -> usize {
NixPath::len(self.as_os_str())
}
fn with_nix_path<T, F>(&self, f: F) -> Result<T> where F: FnOnce(&CStr) -> T {
self.as_os_str().with_nix_path(f)
}
}

311
vendor/nix-v0.23.1-patched/src/macros.rs vendored Normal file
View file

@ -0,0 +1,311 @@
/// The `libc_bitflags!` macro helps with a common use case of defining a public bitflags type
/// with values from the libc crate. It is used the same way as the `bitflags!` macro, except
/// that only the name of the flag value has to be given.
///
/// The `libc` crate must be in scope with the name `libc`.
///
/// # Example
/// ```
/// libc_bitflags!{
/// pub struct ProtFlags: libc::c_int {
/// PROT_NONE;
/// PROT_READ;
/// /// PROT_WRITE enables write protect
/// PROT_WRITE;
/// PROT_EXEC;
/// #[cfg(any(target_os = "linux", target_os = "android"))]
/// PROT_GROWSDOWN;
/// #[cfg(any(target_os = "linux", target_os = "android"))]
/// PROT_GROWSUP;
/// }
/// }
/// ```
///
/// Example with casting, due to a mistake in libc. In this example, the
/// various flags have different types, so we cast the broken ones to the right
/// type.
///
/// ```
/// libc_bitflags!{
/// pub struct SaFlags: libc::c_ulong {
/// SA_NOCLDSTOP as libc::c_ulong;
/// SA_NOCLDWAIT;
/// SA_NODEFER as libc::c_ulong;
/// SA_ONSTACK;
/// SA_RESETHAND as libc::c_ulong;
/// SA_RESTART as libc::c_ulong;
/// SA_SIGINFO;
/// }
/// }
/// ```
macro_rules! libc_bitflags {
(
$(#[$outer:meta])*
pub struct $BitFlags:ident: $T:ty {
$(
$(#[$inner:ident $($args:tt)*])*
$Flag:ident $(as $cast:ty)*;
)+
}
) => {
::bitflags::bitflags! {
$(#[$outer])*
pub struct $BitFlags: $T {
$(
$(#[$inner $($args)*])*
const $Flag = libc::$Flag $(as $cast)*;
)+
}
}
};
}
/// The `libc_enum!` macro helps with a common use case of defining an enum exclusively using
/// values from the `libc` crate. This macro supports both `pub` and private `enum`s.
///
/// The `libc` crate must be in scope with the name `libc`.
///
/// # Example
/// ```
/// libc_enum!{
/// pub enum ProtFlags {
/// PROT_NONE,
/// PROT_READ,
/// PROT_WRITE,
/// PROT_EXEC,
/// #[cfg(any(target_os = "linux", target_os = "android"))]
/// PROT_GROWSDOWN,
/// #[cfg(any(target_os = "linux", target_os = "android"))]
/// PROT_GROWSUP,
/// }
/// }
/// ```
macro_rules! libc_enum {
// Exit rule.
(@make_enum
name: $BitFlags:ident,
{
$v:vis
attrs: [$($attrs:tt)*],
entries: [$($entries:tt)*],
}
) => {
$($attrs)*
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
$v enum $BitFlags {
$($entries)*
}
};
// Exit rule including TryFrom
(@make_enum
name: $BitFlags:ident,
{
$v:vis
attrs: [$($attrs:tt)*],
entries: [$($entries:tt)*],
from_type: $repr:path,
try_froms: [$($try_froms:tt)*]
}
) => {
$($attrs)*
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
$v enum $BitFlags {
$($entries)*
}
impl ::std::convert::TryFrom<$repr> for $BitFlags {
type Error = $crate::Error;
#[allow(unused_doc_comments)]
fn try_from(x: $repr) -> $crate::Result<Self> {
match x {
$($try_froms)*
_ => Err($crate::Error::EINVAL)
}
}
}
};
// Done accumulating.
(@accumulate_entries
name: $BitFlags:ident,
{
$v:vis
attrs: $attrs:tt,
},
$entries:tt,
$try_froms:tt;
) => {
libc_enum! {
@make_enum
name: $BitFlags,
{
$v
attrs: $attrs,
entries: $entries,
}
}
};
// Done accumulating and want TryFrom
(@accumulate_entries
name: $BitFlags:ident,
{
$v:vis
attrs: $attrs:tt,
from_type: $repr:path,
},
$entries:tt,
$try_froms:tt;
) => {
libc_enum! {
@make_enum
name: $BitFlags,
{
$v
attrs: $attrs,
entries: $entries,
from_type: $repr,
try_froms: $try_froms
}
}
};
// Munch an attr.
(@accumulate_entries
name: $BitFlags:ident,
$prefix:tt,
[$($entries:tt)*],
[$($try_froms:tt)*];
#[$attr:meta] $($tail:tt)*
) => {
libc_enum! {
@accumulate_entries
name: $BitFlags,
$prefix,
[
$($entries)*
#[$attr]
],
[
$($try_froms)*
#[$attr]
];
$($tail)*
}
};
// Munch last ident if not followed by a comma.
(@accumulate_entries
name: $BitFlags:ident,
$prefix:tt,
[$($entries:tt)*],
[$($try_froms:tt)*];
$entry:ident
) => {
libc_enum! {
@accumulate_entries
name: $BitFlags,
$prefix,
[
$($entries)*
$entry = libc::$entry,
],
[
$($try_froms)*
libc::$entry => Ok($BitFlags::$entry),
];
}
};
// Munch an ident; covers terminating comma case.
(@accumulate_entries
name: $BitFlags:ident,
$prefix:tt,
[$($entries:tt)*],
[$($try_froms:tt)*];
$entry:ident,
$($tail:tt)*
) => {
libc_enum! {
@accumulate_entries
name: $BitFlags,
$prefix,
[
$($entries)*
$entry = libc::$entry,
],
[
$($try_froms)*
libc::$entry => Ok($BitFlags::$entry),
];
$($tail)*
}
};
// Munch an ident and cast it to the given type; covers terminating comma.
(@accumulate_entries
name: $BitFlags:ident,
$prefix:tt,
[$($entries:tt)*],
[$($try_froms:tt)*];
$entry:ident as $ty:ty,
$($tail:tt)*
) => {
libc_enum! {
@accumulate_entries
name: $BitFlags,
$prefix,
[
$($entries)*
$entry = libc::$entry as $ty,
],
[
$($try_froms)*
libc::$entry as $ty => Ok($BitFlags::$entry),
];
$($tail)*
}
};
// Entry rule.
(
$(#[$attr:meta])*
$v:vis enum $BitFlags:ident {
$($vals:tt)*
}
) => {
libc_enum! {
@accumulate_entries
name: $BitFlags,
{
$v
attrs: [$(#[$attr])*],
},
[],
[];
$($vals)*
}
};
// Entry rule including TryFrom
(
$(#[$attr:meta])*
$v:vis enum $BitFlags:ident {
$($vals:tt)*
}
impl TryFrom<$repr:path>
) => {
libc_enum! {
@accumulate_entries
name: $BitFlags,
{
$v
attrs: [$(#[$attr])*],
from_type: $repr,
},
[],
[];
$($vals)*
}
};
}

View file

@ -0,0 +1,426 @@
use crate::{
Error,
Errno,
NixPath,
Result,
sys::uio::IoVec
};
use libc::{c_char, c_int, c_uint, c_void};
use std::{
borrow::Cow,
ffi::{CString, CStr},
fmt,
io,
ptr
};
libc_bitflags!(
/// Used with [`Nmount::nmount`].
pub struct MntFlags: c_int {
/// ACL support enabled.
#[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
MNT_ACLS;
/// All I/O to the file system should be done asynchronously.
MNT_ASYNC;
/// dir should instead be a file system ID encoded as “FSID:val0:val1”.
#[cfg(target_os = "freebsd")]
MNT_BYFSID;
/// Force a read-write mount even if the file system appears to be
/// unclean.
MNT_FORCE;
/// GEOM journal support enabled.
#[cfg(target_os = "freebsd")]
MNT_GJOURNAL;
/// MAC support for objects.
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
MNT_MULTILABEL;
/// Disable read clustering.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MNT_NOCLUSTERR;
/// Disable write clustering.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MNT_NOCLUSTERW;
/// Enable NFS version 4 ACLs.
#[cfg(target_os = "freebsd")]
MNT_NFS4ACLS;
/// Do not update access times.
MNT_NOATIME;
/// Disallow program execution.
MNT_NOEXEC;
/// Do not honor setuid or setgid bits on files when executing them.
MNT_NOSUID;
/// Do not follow symlinks.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MNT_NOSYMFOLLOW;
/// Mount read-only.
MNT_RDONLY;
/// Causes the vfs subsystem to update its data structures pertaining to
/// the specified already mounted file system.
MNT_RELOAD;
/// Create a snapshot of the file system.
///
/// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs)
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
MNT_SNAPSHOT;
/// Using soft updates.
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
MNT_SOFTDEP;
/// Directories with the SUID bit set chown new files to their own
/// owner.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MNT_SUIDDIR;
/// All I/O to the file system should be done synchronously.
MNT_SYNCHRONOUS;
/// Union with underlying fs.
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd"
))]
MNT_UNION;
/// Indicates that the mount command is being applied to an already
/// mounted file system.
MNT_UPDATE;
/// Check vnode use counts.
#[cfg(target_os = "freebsd")]
MNT_NONBUSY;
}
);
/// The Error type of [`Nmount::nmount`].
///
/// It wraps an [`Errno`], but also may contain an additional message returned
/// by `nmount(2)`.
#[derive(Debug)]
pub struct NmountError {
errno: Error,
errmsg: Option<String>
}
impl NmountError {
/// Returns the additional error string sometimes generated by `nmount(2)`.
pub fn errmsg(&self) -> Option<&str> {
self.errmsg.as_deref()
}
/// Returns the inner [`Error`]
pub const fn error(&self) -> Error {
self.errno
}
fn new(error: Error, errmsg: Option<&CStr>) -> Self {
Self {
errno: error,
errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned)
}
}
}
impl std::error::Error for NmountError {}
impl fmt::Display for NmountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(errmsg) = &self.errmsg {
write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc())
} else {
write!(f, "{:?}: {}", self.errno, self.errno.desc())
}
}
}
impl From<NmountError> for io::Error {
fn from(err: NmountError) -> Self {
err.errno.into()
}
}
/// Result type of [`Nmount::nmount`].
pub type NmountResult = std::result::Result<(), NmountError>;
/// Mount a FreeBSD file system.
///
/// The `nmount(2)` system call works similarly to the `mount(8)` program; it
/// takes its options as a series of name-value pairs. Most of the values are
/// strings, as are all of the names. The `Nmount` structure builds up an
/// argument list and then executes the syscall.
///
/// # Examples
///
/// To mount `target` onto `mountpoint` with `nullfs`:
/// ```
/// # use nix::unistd::Uid;
/// # use ::sysctl::CtlValue;
/// # if !Uid::current().is_root() && CtlValue::Int(0) == ::sysctl::value("vfs.usermount").unwrap() {
/// # return;
/// # };
/// use nix::mount::{MntFlags, Nmount, unmount};
/// use std::ffi::CString;
/// use tempfile::tempdir;
///
/// let mountpoint = tempdir().unwrap();
/// let target = tempdir().unwrap();
///
/// let fstype = CString::new("fstype").unwrap();
/// let nullfs = CString::new("nullfs").unwrap();
/// Nmount::new()
/// .str_opt(&fstype, &nullfs)
/// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
/// .str_opt_owned("target", target.path().to_str().unwrap())
/// .nmount(MntFlags::empty()).unwrap();
///
/// unmount(mountpoint.path(), MntFlags::empty()).unwrap();
/// ```
///
/// # See Also
/// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount)
/// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs)
#[cfg(target_os = "freebsd")]
#[derive(Debug, Default)]
pub struct Nmount<'a>{
iov: Vec<IoVec<&'a [u8]>>,
is_owned: Vec<bool>,
}
#[cfg(target_os = "freebsd")]
impl<'a> Nmount<'a> {
/// Add an opaque mount option.
///
/// Some file systems take binary-valued mount options. They can be set
/// with this method.
///
/// # Safety
///
/// Unsafe because it will cause `Nmount::nmount` to dereference a raw
/// pointer. The user is responsible for ensuring that `val` is valid and
/// its lifetime outlives `self`! An easy way to do that is to give the
/// value a larger scope than `name`
///
/// # Examples
/// ```
/// use libc::c_void;
/// use nix::mount::Nmount;
/// use std::ffi::CString;
/// use std::mem;
///
/// // Note that flags outlives name
/// let mut flags: u32 = 0xdeadbeef;
/// let name = CString::new("flags").unwrap();
/// let p = &mut flags as *mut u32 as *mut c_void;
/// let len = mem::size_of_val(&flags);
/// let mut nmount = Nmount::new();
/// unsafe { nmount.mut_ptr_opt(&name, p, len) };
/// ```
pub unsafe fn mut_ptr_opt(
&mut self,
name: &'a CStr,
val: *mut c_void,
len: usize
) -> &mut Self
{
self.iov.push(IoVec::from_slice(name.to_bytes_with_nul()));
self.is_owned.push(false);
self.iov.push(IoVec::from_raw_parts(val, len));
self.is_owned.push(false);
self
}
/// Add a mount option that does not take a value.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::ffi::CString;
///
/// let read_only = CString::new("ro").unwrap();
/// Nmount::new()
/// .null_opt(&read_only);
/// ```
pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self {
self.iov.push(IoVec::from_slice(name.to_bytes_with_nul()));
self.is_owned.push(false);
self.iov.push(IoVec::from_raw_parts(ptr::null_mut(), 0));
self.is_owned.push(false);
self
}
/// Add a mount option that does not take a value, but whose name must be
/// owned.
///
///
/// This has higher runtime cost than [`Nmount::null_opt`], but is useful
/// when the name's lifetime doesn't outlive the `Nmount`, or it's a
/// different string type than `CStr`.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
///
/// let read_only = "ro";
/// let mut nmount: Nmount<'static> = Nmount::new();
/// nmount.null_opt_owned(read_only);
/// ```
pub fn null_opt_owned<P: ?Sized + NixPath>(&mut self, name: &P) -> &mut Self
{
name.with_nix_path(|s| {
let len = s.to_bytes_with_nul().len();
self.iov.push(IoVec::from_raw_parts(
// Must free it later
s.to_owned().into_raw() as *mut c_void,
len
));
self.is_owned.push(true);
}).unwrap();
self.iov.push(IoVec::from_raw_parts(ptr::null_mut(), 0));
self.is_owned.push(false);
self
}
/// Add a mount option as a [`CStr`].
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::ffi::CString;
///
/// let fstype = CString::new("fstype").unwrap();
/// let nullfs = CString::new("nullfs").unwrap();
/// Nmount::new()
/// .str_opt(&fstype, &nullfs);
/// ```
pub fn str_opt(
&mut self,
name: &'a CStr,
val: &'a CStr
) -> &mut Self
{
self.iov.push(IoVec::from_slice(name.to_bytes_with_nul()));
self.is_owned.push(false);
self.iov.push(IoVec::from_slice(val.to_bytes_with_nul()));
self.is_owned.push(false);
self
}
/// Add a mount option as an owned string.
///
/// This has higher runtime cost than [`Nmount::str_opt`], but is useful
/// when the value's lifetime doesn't outlive the `Nmount`, or it's a
/// different string type than `CStr`.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::path::Path;
///
/// let mountpoint = Path::new("/mnt");
/// Nmount::new()
/// .str_opt_owned("fspath", mountpoint.to_str().unwrap());
/// ```
pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self
where P1: ?Sized + NixPath,
P2: ?Sized + NixPath
{
name.with_nix_path(|s| {
let len = s.to_bytes_with_nul().len();
self.iov.push(IoVec::from_raw_parts(
// Must free it later
s.to_owned().into_raw() as *mut c_void,
len
));
self.is_owned.push(true);
}).unwrap();
val.with_nix_path(|s| {
let len = s.to_bytes_with_nul().len();
self.iov.push(IoVec::from_raw_parts(
// Must free it later
s.to_owned().into_raw() as *mut c_void,
len
));
self.is_owned.push(true);
}).unwrap();
self
}
/// Create a new `Nmount` struct with no options
pub fn new() -> Self {
Self::default()
}
/// Actually mount the file system.
pub fn nmount(&mut self, flags: MntFlags) -> NmountResult {
// nmount can return extra error information via a "errmsg" return
// argument.
const ERRMSG_NAME: &[u8] = b"errmsg\0";
let mut errmsg = vec![0u8; 255];
self.iov.push(IoVec::from_raw_parts(
ERRMSG_NAME.as_ptr() as *mut c_void,
ERRMSG_NAME.len()
));
self.iov.push(IoVec::from_raw_parts(
errmsg.as_mut_ptr() as *mut c_void,
errmsg.len()
));
let niov = self.iov.len() as c_uint;
let iovp = self.iov.as_mut_ptr() as *mut libc::iovec;
let res = unsafe {
libc::nmount(iovp, niov, flags.bits)
};
match Errno::result(res) {
Ok(_) => Ok(()),
Err(error) => {
let errmsg = match errmsg.iter().position(|&x| x == 0) {
None => None,
Some(0) => None,
Some(n) => {
let sl = &errmsg[0..n + 1];
Some(CStr::from_bytes_with_nul(sl).unwrap())
}
};
Err(NmountError::new(error, errmsg))
}
}
}
}
#[cfg(target_os = "freebsd")]
impl<'a> Drop for Nmount<'a> {
fn drop(&mut self) {
for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) {
if *is_owned {
// Free the owned string. Safe because we recorded ownership,
// and Nmount does not implement Clone.
unsafe {
drop(CString::from_raw(iov.0.iov_base as *mut c_char));
}
}
}
}
}
/// Unmount the file system mounted at `mountpoint`.
///
/// Useful flags include
/// * `MNT_FORCE` - Unmount even if still in use.
/// * `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID
/// encoded as `FSID:val0:val1`, where `val0` and `val1`
/// are the contents of the `fsid_t val[]` array in decimal.
/// The file system that has the specified file system ID
/// will be unmounted. See
/// [`statfs`](crate::sys::statfs::statfs) to determine the
/// `fsid`.
pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()>
where P: ?Sized + NixPath
{
let res = mountpoint.with_nix_path(|cstr| {
unsafe { libc::unmount(cstr.as_ptr(), flags.bits) }
})?;
Errno::result(res).map(drop)
}

View file

@ -0,0 +1,111 @@
#![allow(missing_docs)]
use libc::{self, c_ulong, c_int};
use crate::{Result, NixPath};
use crate::errno::Errno;
libc_bitflags!(
pub struct MsFlags: c_ulong {
/// Mount read-only
MS_RDONLY;
/// Ignore suid and sgid bits
MS_NOSUID;
/// Disallow access to device special files
MS_NODEV;
/// Disallow program execution
MS_NOEXEC;
/// Writes are synced at once
MS_SYNCHRONOUS;
/// Alter flags of a mounted FS
MS_REMOUNT;
/// Allow mandatory locks on a FS
MS_MANDLOCK;
/// Directory modifications are synchronous
MS_DIRSYNC;
/// Do not update access times
MS_NOATIME;
/// Do not update directory access times
MS_NODIRATIME;
/// Linux 2.4.0 - Bind directory at different place
MS_BIND;
MS_MOVE;
MS_REC;
MS_SILENT;
MS_POSIXACL;
MS_UNBINDABLE;
MS_PRIVATE;
MS_SLAVE;
MS_SHARED;
MS_RELATIME;
MS_KERNMOUNT;
MS_I_VERSION;
MS_STRICTATIME;
MS_LAZYTIME;
MS_ACTIVE;
MS_NOUSER;
MS_RMT_MASK;
MS_MGC_VAL;
MS_MGC_MSK;
}
);
libc_bitflags!(
pub struct MntFlags: c_int {
MNT_FORCE;
MNT_DETACH;
MNT_EXPIRE;
}
);
pub fn mount<P1: ?Sized + NixPath, P2: ?Sized + NixPath, P3: ?Sized + NixPath, P4: ?Sized + NixPath>(
source: Option<&P1>,
target: &P2,
fstype: Option<&P3>,
flags: MsFlags,
data: Option<&P4>) -> Result<()> {
fn with_opt_nix_path<P, T, F>(p: Option<&P>, f: F) -> Result<T>
where P: ?Sized + NixPath,
F: FnOnce(*const libc::c_char) -> T
{
match p {
Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
None => Ok(f(std::ptr::null()))
}
}
let res = with_opt_nix_path(source, |s| {
target.with_nix_path(|t| {
with_opt_nix_path(fstype, |ty| {
with_opt_nix_path(data, |d| {
unsafe {
libc::mount(
s,
t.as_ptr(),
ty,
flags.bits,
d as *const libc::c_void
)
}
})
})
})
})????;
Errno::result(res).map(drop)
}
pub fn umount<P: ?Sized + NixPath>(target: &P) -> Result<()> {
let res = target.with_nix_path(|cstr| {
unsafe { libc::umount(cstr.as_ptr()) }
})?;
Errno::result(res).map(drop)
}
pub fn umount2<P: ?Sized + NixPath>(target: &P, flags: MntFlags) -> Result<()> {
let res = target.with_nix_path(|cstr| {
unsafe { libc::umount2(cstr.as_ptr(), flags.bits) }
})?;
Errno::result(res).map(drop)
}

View file

@ -0,0 +1,21 @@
//! Mount file systems
#[cfg(any(target_os = "android", target_os = "linux"))]
mod linux;
#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::linux::*;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
mod bsd;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
))]
pub use self::bsd::*;

178
vendor/nix-v0.23.1-patched/src/mqueue.rs vendored Normal file
View file

@ -0,0 +1,178 @@
//! Posix Message Queue functions
//!
//! [Further reading and details on the C API](https://man7.org/linux/man-pages/man7/mq_overview.7.html)
use crate::Result;
use crate::errno::Errno;
use libc::{self, c_char, mqd_t, size_t};
use std::ffi::CString;
use crate::sys::stat::Mode;
use std::mem;
libc_bitflags!{
pub struct MQ_OFlag: libc::c_int {
O_RDONLY;
O_WRONLY;
O_RDWR;
O_CREAT;
O_EXCL;
O_NONBLOCK;
O_CLOEXEC;
}
}
libc_bitflags!{
pub struct FdFlag: libc::c_int {
FD_CLOEXEC;
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct MqAttr {
mq_attr: libc::mq_attr,
}
// x32 compatibility
// See https://sourceware.org/bugzilla/show_bug.cgi?id=21279
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
pub type mq_attr_member_t = i64;
#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
pub type mq_attr_member_t = libc::c_long;
impl MqAttr {
pub fn new(mq_flags: mq_attr_member_t,
mq_maxmsg: mq_attr_member_t,
mq_msgsize: mq_attr_member_t,
mq_curmsgs: mq_attr_member_t)
-> MqAttr
{
let mut attr = mem::MaybeUninit::<libc::mq_attr>::uninit();
unsafe {
let p = attr.as_mut_ptr();
(*p).mq_flags = mq_flags;
(*p).mq_maxmsg = mq_maxmsg;
(*p).mq_msgsize = mq_msgsize;
(*p).mq_curmsgs = mq_curmsgs;
MqAttr { mq_attr: attr.assume_init() }
}
}
pub const fn flags(&self) -> mq_attr_member_t {
self.mq_attr.mq_flags
}
}
/// Open a message queue
///
/// See also [`mq_open(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_open.html)
// The mode.bits cast is only lossless on some OSes
#[allow(clippy::cast_lossless)]
pub fn mq_open(name: &CString,
oflag: MQ_OFlag,
mode: Mode,
attr: Option<&MqAttr>)
-> Result<mqd_t> {
let res = match attr {
Some(mq_attr) => unsafe {
libc::mq_open(name.as_ptr(),
oflag.bits(),
mode.bits() as libc::c_int,
&mq_attr.mq_attr as *const libc::mq_attr)
},
None => unsafe { libc::mq_open(name.as_ptr(), oflag.bits()) },
};
Errno::result(res)
}
/// Remove a message queue
///
/// See also [`mq_unlink(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_unlink.html)
pub fn mq_unlink(name: &CString) -> Result<()> {
let res = unsafe { libc::mq_unlink(name.as_ptr()) };
Errno::result(res).map(drop)
}
/// Close a message queue
///
/// See also [`mq_close(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_close.html)
pub fn mq_close(mqdes: mqd_t) -> Result<()> {
let res = unsafe { libc::mq_close(mqdes) };
Errno::result(res).map(drop)
}
/// Receive a message from a message queue
///
/// See also [`mq_receive(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_receive.html)
pub fn mq_receive(mqdes: mqd_t, message: &mut [u8], msg_prio: &mut u32) -> Result<usize> {
let len = message.len() as size_t;
let res = unsafe {
libc::mq_receive(mqdes,
message.as_mut_ptr() as *mut c_char,
len,
msg_prio as *mut u32)
};
Errno::result(res).map(|r| r as usize)
}
/// Send a message to a message queue
///
/// See also [`mq_send(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_send.html)
pub fn mq_send(mqdes: mqd_t, message: &[u8], msq_prio: u32) -> Result<()> {
let res = unsafe {
libc::mq_send(mqdes,
message.as_ptr() as *const c_char,
message.len(),
msq_prio)
};
Errno::result(res).map(drop)
}
/// Get message queue attributes
///
/// See also [`mq_getattr(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_getattr.html)
pub fn mq_getattr(mqd: mqd_t) -> Result<MqAttr> {
let mut attr = mem::MaybeUninit::<libc::mq_attr>::uninit();
let res = unsafe { libc::mq_getattr(mqd, attr.as_mut_ptr()) };
Errno::result(res).map(|_| unsafe{MqAttr { mq_attr: attr.assume_init() }})
}
/// Set the attributes of the message queue. Only `O_NONBLOCK` can be set, everything else will be ignored
/// Returns the old attributes
/// It is recommend to use the `mq_set_nonblock()` and `mq_remove_nonblock()` convenience functions as they are easier to use
///
/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_setattr.html)
pub fn mq_setattr(mqd: mqd_t, newattr: &MqAttr) -> Result<MqAttr> {
let mut attr = mem::MaybeUninit::<libc::mq_attr>::uninit();
let res = unsafe {
libc::mq_setattr(mqd, &newattr.mq_attr as *const libc::mq_attr, attr.as_mut_ptr())
};
Errno::result(res).map(|_| unsafe{ MqAttr { mq_attr: attr.assume_init() }})
}
/// Convenience function.
/// Sets the `O_NONBLOCK` attribute for a given message queue descriptor
/// Returns the old attributes
#[allow(clippy::useless_conversion)] // Not useless on all OSes
pub fn mq_set_nonblock(mqd: mqd_t) -> Result<MqAttr> {
let oldattr = mq_getattr(mqd)?;
let newattr = MqAttr::new(mq_attr_member_t::from(MQ_OFlag::O_NONBLOCK.bits()),
oldattr.mq_attr.mq_maxmsg,
oldattr.mq_attr.mq_msgsize,
oldattr.mq_attr.mq_curmsgs);
mq_setattr(mqd, &newattr)
}
/// Convenience function.
/// Removes `O_NONBLOCK` attribute for a given message queue descriptor
/// Returns the old attributes
pub fn mq_remove_nonblock(mqd: mqd_t) -> Result<MqAttr> {
let oldattr = mq_getattr(mqd)?;
let newattr = MqAttr::new(0,
oldattr.mq_attr.mq_maxmsg,
oldattr.mq_attr.mq_msgsize,
oldattr.mq_attr.mq_curmsgs);
mq_setattr(mqd, &newattr)
}

View file

@ -0,0 +1,411 @@
//! Network interface name resolution.
//!
//! Uses Linux and/or POSIX functions to resolve interface names like "eth0"
//! or "socan1" into device numbers.
use crate::{Error, NixPath, Result};
use libc::c_uint;
/// Resolve an interface into a interface number.
pub fn if_nametoindex<P: ?Sized + NixPath>(name: &P) -> Result<c_uint> {
let if_index = name.with_nix_path(|name| unsafe { libc::if_nametoindex(name.as_ptr()) })?;
if if_index == 0 {
Err(Error::last())
} else {
Ok(if_index)
}
}
libc_bitflags!(
/// Standard interface flags, used by `getifaddrs`
pub struct InterfaceFlags: libc::c_int {
/// Interface is running. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_UP;
/// Valid broadcast address set. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_BROADCAST;
/// Internal debugging flag. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_DEBUG;
/// Interface is a loopback interface. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_LOOPBACK;
/// Interface is a point-to-point link. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_POINTOPOINT;
/// Avoid use of trailers. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "illumos",
target_os = "solaris"))]
IFF_NOTRAILERS;
/// Interface manages own routes.
#[cfg(any(target_os = "dragonfly"))]
IFF_SMART;
/// Resources allocated. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris"))]
IFF_RUNNING;
/// No arp protocol, L2 destination address not set. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_NOARP;
/// Interface is in promiscuous mode. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_PROMISC;
/// Receive all multicast packets. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_ALLMULTI;
/// Master of a load balancing bundle. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_MASTER;
/// transmission in progress, tx hardware queue is full
#[cfg(any(target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "ios"))]
IFF_OACTIVE;
/// Protocol code on board.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_INTELLIGENT;
/// Slave of a load balancing bundle. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_SLAVE;
/// Can't hear own transmissions.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "osx"))]
IFF_SIMPLEX;
/// Supports multicast. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
IFF_MULTICAST;
/// Per link layer defined bit.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "ios"))]
IFF_LINK0;
/// Multicast using broadcast.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_MULTI_BCAST;
/// Is able to select media type via ifmap. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_PORTSEL;
/// Per link layer defined bit.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "ios"))]
IFF_LINK1;
/// Non-unique address.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_UNNUMBERED;
/// Auto media selection active. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_AUTOMEDIA;
/// Per link layer defined bit.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "ios"))]
IFF_LINK2;
/// Use alternate physical connection.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "ios"))]
IFF_ALTPHYS;
/// DHCP controls interface.
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
IFF_DHCPRUNNING;
/// The addresses are lost when the interface goes down. (see
/// [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html))
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_DYNAMIC;
/// Do not advertise.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_PRIVATE;
/// Driver signals L1 up. Volatile.
#[cfg(any(target_os = "fuchsia", target_os = "linux"))]
IFF_LOWER_UP;
/// Interface is in polling mode.
#[cfg(any(target_os = "dragonfly"))]
IFF_POLLING_COMPAT;
/// Unconfigurable using ioctl(2).
#[cfg(any(target_os = "freebsd"))]
IFF_CANTCONFIG;
/// Do not transmit packets.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_NOXMIT;
/// Driver signals dormant. Volatile.
#[cfg(any(target_os = "fuchsia", target_os = "linux"))]
IFF_DORMANT;
/// User-requested promisc mode.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
IFF_PPROMISC;
/// Just on-link subnet.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_NOLOCAL;
/// Echo sent packets. Volatile.
#[cfg(any(target_os = "fuchsia", target_os = "linux"))]
IFF_ECHO;
/// User-requested monitor mode.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
IFF_MONITOR;
/// Address is deprecated.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_DEPRECATED;
/// Static ARP.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
IFF_STATICARP;
/// Address from stateless addrconf.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_ADDRCONF;
/// Interface is in polling mode.
#[cfg(any(target_os = "dragonfly"))]
IFF_NPOLLING;
/// Router on interface.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_ROUTER;
/// Interface is in polling mode.
#[cfg(any(target_os = "dragonfly"))]
IFF_IDIRECT;
/// Interface is winding down
#[cfg(any(target_os = "freebsd"))]
IFF_DYING;
/// No NUD on interface.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_NONUD;
/// Interface is being renamed
#[cfg(any(target_os = "freebsd"))]
IFF_RENAMING;
/// Anycast address.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_ANYCAST;
/// Don't exchange routing info.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_NORTEXCH;
/// Do not provide packet information
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_NO_PI as libc::c_int;
/// TUN device (no Ethernet headers)
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_TUN as libc::c_int;
/// TAP device
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
IFF_TAP as libc::c_int;
/// IPv4 interface.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_IPV4;
/// IPv6 interface.
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_IPV6;
/// in.mpathd test address
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_NOFAILOVER;
/// Interface has failed
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_FAILED;
/// Interface is a hot-spare
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_STANDBY;
/// Functioning but not used
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_INACTIVE;
/// Interface is offline
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
IFF_OFFLINE;
#[cfg(target_os = "solaris")]
IFF_COS_ENABLED;
/// Prefer as source addr.
#[cfg(target_os = "solaris")]
IFF_PREFERRED;
/// RFC3041
#[cfg(target_os = "solaris")]
IFF_TEMPORARY;
/// MTU set with SIOCSLIFMTU
#[cfg(target_os = "solaris")]
IFF_FIXEDMTU;
/// Cannot send / receive packets
#[cfg(target_os = "solaris")]
IFF_VIRTUAL;
/// Local address in use
#[cfg(target_os = "solaris")]
IFF_DUPLICATE;
/// IPMP IP interface
#[cfg(target_os = "solaris")]
IFF_IPMP;
}
);
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
mod if_nameindex {
use super::*;
use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
use std::ptr::NonNull;
/// A network interface. Has a name like "eth0" or "wlp4s0" or "wlan0", as well as an index
/// (1, 2, 3, etc) that identifies it in the OS's networking stack.
#[allow(missing_copy_implementations)]
#[repr(transparent)]
pub struct Interface(libc::if_nameindex);
impl Interface {
/// Obtain the index of this interface.
pub fn index(&self) -> c_uint {
self.0.if_index
}
/// Obtain the name of this interface.
pub fn name(&self) -> &CStr {
unsafe { CStr::from_ptr(self.0.if_name) }
}
}
impl fmt::Debug for Interface {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Interface")
.field("index", &self.index())
.field("name", &self.name())
.finish()
}
}
/// A list of the network interfaces available on this system. Obtained from [`if_nameindex()`].
pub struct Interfaces {
ptr: NonNull<libc::if_nameindex>,
}
impl Interfaces {
/// Iterate over the interfaces in this list.
#[inline]
pub fn iter(&self) -> InterfacesIter<'_> {
self.into_iter()
}
/// Convert this to a slice of interfaces. Note that the underlying interfaces list is
/// null-terminated, so calling this calculates the length. If random access isn't needed,
/// [`Interfaces::iter()`] should be used instead.
pub fn to_slice(&self) -> &[Interface] {
let ifs = self.ptr.as_ptr() as *const Interface;
let len = self.iter().count();
unsafe { std::slice::from_raw_parts(ifs, len) }
}
}
impl Drop for Interfaces {
fn drop(&mut self) {
unsafe { libc::if_freenameindex(self.ptr.as_ptr()) };
}
}
impl fmt::Debug for Interfaces {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_slice().fmt(f)
}
}
impl<'a> IntoIterator for &'a Interfaces {
type IntoIter = InterfacesIter<'a>;
type Item = &'a Interface;
#[inline]
fn into_iter(self) -> Self::IntoIter {
InterfacesIter {
ptr: self.ptr.as_ptr(),
_marker: PhantomData,
}
}
}
/// An iterator over the interfaces in an [`Interfaces`].
#[derive(Debug)]
pub struct InterfacesIter<'a> {
ptr: *const libc::if_nameindex,
_marker: PhantomData<&'a Interfaces>,
}
impl<'a> Iterator for InterfacesIter<'a> {
type Item = &'a Interface;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if (*self.ptr).if_index == 0 {
None
} else {
let ret = &*(self.ptr as *const Interface);
self.ptr = self.ptr.add(1);
Some(ret)
}
}
}
}
/// Retrieve a list of the network interfaces available on the local system.
///
/// ```
/// let interfaces = nix::net::if_::if_nameindex().unwrap();
/// for iface in &interfaces {
/// println!("Interface #{} is called {}", iface.index(), iface.name().to_string_lossy());
/// }
/// ```
pub fn if_nameindex() -> Result<Interfaces> {
unsafe {
let ifs = libc::if_nameindex();
let ptr = NonNull::new(ifs).ok_or_else(Error::last)?;
Ok(Interfaces { ptr })
}
}
}
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
pub use if_nameindex::*;

View file

@ -0,0 +1,4 @@
//! Functionality involving network interfaces
// To avoid clashing with the keyword "if", we use "if_" as the module name.
// The original header is called "net/if.h".
pub mod if_;

163
vendor/nix-v0.23.1-patched/src/poll.rs vendored Normal file
View file

@ -0,0 +1,163 @@
//! Wait for events to trigger on specific file descriptors
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))]
use crate::sys::time::TimeSpec;
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))]
use crate::sys::signal::SigSet;
use std::os::unix::io::{AsRawFd, RawFd};
use crate::Result;
use crate::errno::Errno;
/// This is a wrapper around `libc::pollfd`.
///
/// It's meant to be used as an argument to the [`poll`](fn.poll.html) and
/// [`ppoll`](fn.ppoll.html) functions to specify the events of interest
/// for a specific file descriptor.
///
/// After a call to `poll` or `ppoll`, the events that occured can be
/// retrieved by calling [`revents()`](#method.revents) on the `PollFd`.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct PollFd {
pollfd: libc::pollfd,
}
impl PollFd {
/// Creates a new `PollFd` specifying the events of interest
/// for a given file descriptor.
pub const fn new(fd: RawFd, events: PollFlags) -> PollFd {
PollFd {
pollfd: libc::pollfd {
fd,
events: events.bits(),
revents: PollFlags::empty().bits(),
},
}
}
/// Returns the events that occured in the last call to `poll` or `ppoll`. Will only return
/// `None` if the kernel provides status flags that Nix does not know about.
pub fn revents(self) -> Option<PollFlags> {
PollFlags::from_bits(self.pollfd.revents)
}
/// The events of interest for this `PollFd`.
pub fn events(self) -> PollFlags {
PollFlags::from_bits(self.pollfd.events).unwrap()
}
/// Modify the events of interest for this `PollFd`.
pub fn set_events(&mut self, events: PollFlags) {
self.pollfd.events = events.bits();
}
}
impl AsRawFd for PollFd {
fn as_raw_fd(&self) -> RawFd {
self.pollfd.fd
}
}
libc_bitflags! {
/// These flags define the different events that can be monitored by `poll` and `ppoll`
pub struct PollFlags: libc::c_short {
/// There is data to read.
POLLIN;
/// There is some exceptional condition on the file descriptor.
///
/// Possibilities include:
///
/// * There is out-of-band data on a TCP socket (see
/// [tcp(7)](https://man7.org/linux/man-pages/man7/tcp.7.html)).
/// * A pseudoterminal master in packet mode has seen a state
/// change on the slave (see
/// [ioctl_tty(2)](https://man7.org/linux/man-pages/man2/ioctl_tty.2.html)).
/// * A cgroup.events file has been modified (see
/// [cgroups(7)](https://man7.org/linux/man-pages/man7/cgroups.7.html)).
POLLPRI;
/// Writing is now possible, though a write larger that the
/// available space in a socket or pipe will still block (unless
/// `O_NONBLOCK` is set).
POLLOUT;
/// Equivalent to [`POLLIN`](constant.POLLIN.html)
#[cfg(not(target_os = "redox"))]
POLLRDNORM;
#[cfg(not(target_os = "redox"))]
/// Equivalent to [`POLLOUT`](constant.POLLOUT.html)
POLLWRNORM;
/// Priority band data can be read (generally unused on Linux).
#[cfg(not(target_os = "redox"))]
POLLRDBAND;
/// Priority data may be written.
#[cfg(not(target_os = "redox"))]
POLLWRBAND;
/// Error condition (only returned in
/// [`PollFd::revents`](struct.PollFd.html#method.revents);
/// ignored in [`PollFd::new`](struct.PollFd.html#method.new)).
/// This bit is also set for a file descriptor referring to the
/// write end of a pipe when the read end has been closed.
POLLERR;
/// Hang up (only returned in [`PollFd::revents`](struct.PollFd.html#method.revents);
/// ignored in [`PollFd::new`](struct.PollFd.html#method.new)).
/// Note that when reading from a channel such as a pipe or a stream
/// socket, this event merely indicates that the peer closed its
/// end of the channel. Subsequent reads from the channel will
/// return 0 (end of file) only after all outstanding data in the
/// channel has been consumed.
POLLHUP;
/// Invalid request: `fd` not open (only returned in
/// [`PollFd::revents`](struct.PollFd.html#method.revents);
/// ignored in [`PollFd::new`](struct.PollFd.html#method.new)).
POLLNVAL;
}
}
/// `poll` waits for one of a set of file descriptors to become ready to perform I/O.
/// ([`poll(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html))
///
/// `fds` contains all [`PollFd`](struct.PollFd.html) to poll.
/// The function will return as soon as any event occur for any of these `PollFd`s.
///
/// The `timeout` argument specifies the number of milliseconds that `poll()`
/// should block waiting for a file descriptor to become ready. The call
/// will block until either:
///
/// * a file descriptor becomes ready;
/// * the call is interrupted by a signal handler; or
/// * the timeout expires.
///
/// Note that the timeout interval will be rounded up to the system clock
/// granularity, and kernel scheduling delays mean that the blocking
/// interval may overrun by a small amount. Specifying a negative value
/// in timeout means an infinite timeout. Specifying a timeout of zero
/// causes `poll()` to return immediately, even if no file descriptors are
/// ready.
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<libc::c_int> {
let res = unsafe {
libc::poll(fds.as_mut_ptr() as *mut libc::pollfd,
fds.len() as libc::nfds_t,
timeout)
};
Errno::result(res)
}
/// `ppoll()` allows an application to safely wait until either a file
/// descriptor becomes ready or until a signal is caught.
/// ([`poll(2)`](https://man7.org/linux/man-pages/man2/poll.2.html))
///
/// `ppoll` behaves like `poll`, but let you specify what signals may interrupt it
/// with the `sigmask` argument. If you want `ppoll` to block indefinitely,
/// specify `None` as `timeout` (it is like `timeout = -1` for `poll`).
///
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))]
pub fn ppoll(fds: &mut [PollFd], timeout: Option<TimeSpec>, sigmask: SigSet) -> Result<libc::c_int> {
let timeout = timeout.as_ref().map_or(core::ptr::null(), |r| r.as_ref());
let res = unsafe {
libc::ppoll(fds.as_mut_ptr() as *mut libc::pollfd,
fds.len() as libc::nfds_t,
timeout,
sigmask.as_ref())
};
Errno::result(res)
}

348
vendor/nix-v0.23.1-patched/src/pty.rs vendored Normal file
View file

@ -0,0 +1,348 @@
//! Create master and slave virtual pseudo-terminals (PTYs)
pub use libc::pid_t as SessionId;
pub use libc::winsize as Winsize;
use std::ffi::CStr;
use std::io;
use std::mem;
use std::os::unix::prelude::*;
use crate::sys::termios::Termios;
use crate::unistd::{self, ForkResult, Pid};
use crate::{Result, fcntl};
use crate::errno::Errno;
/// Representation of a master/slave pty pair
///
/// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user
/// must manually close the file descriptors.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct OpenptyResult {
/// The master port in a virtual pty pair
pub master: RawFd,
/// The slave port in a virtual pty pair
pub slave: RawFd,
}
/// Representation of a master with a forked pty
///
/// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user
/// must manually close the file descriptors.
#[derive(Clone, Copy, Debug)]
pub struct ForkptyResult {
/// The master port in a virtual pty pair
pub master: RawFd,
/// Metadata about forked process
pub fork_result: ForkResult,
}
/// Representation of the Master device in a master/slave pty pair
///
/// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY
/// functions are given the correct file descriptor. Additionally this type implements `Drop`,
/// so that when it's consumed or goes out of scope, it's automatically cleaned-up.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct PtyMaster(RawFd);
impl AsRawFd for PtyMaster {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl IntoRawFd for PtyMaster {
fn into_raw_fd(self) -> RawFd {
let fd = self.0;
mem::forget(self);
fd
}
}
impl Drop for PtyMaster {
fn drop(&mut self) {
// On drop, we ignore errors like EINTR and EIO because there's no clear
// way to handle them, we can't return anything, and (on FreeBSD at
// least) the file descriptor is deallocated in these cases. However,
// we must panic on EBADF, because it is always an error to close an
// invalid file descriptor. That frequently indicates a double-close
// condition, which can cause confusing errors for future I/O
// operations.
let e = unistd::close(self.0);
if e == Err(Errno::EBADF) {
panic!("Closing an invalid file descriptor!");
};
}
}
impl io::Read for PtyMaster {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
unistd::read(self.0, buf).map_err(io::Error::from)
}
}
impl io::Write for PtyMaster {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
unistd::write(self.0, buf).map_err(io::Error::from)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Grant access to a slave pseudoterminal (see
/// [`grantpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html))
///
/// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the
/// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave.
#[inline]
pub fn grantpt(fd: &PtyMaster) -> Result<()> {
if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 {
return Err(Errno::last());
}
Ok(())
}
/// Open a pseudoterminal device (see
/// [`posix_openpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html))
///
/// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device.
///
/// # Examples
///
/// A common use case with this function is to open both a master and slave PTY pair. This can be
/// done as follows:
///
/// ```
/// use std::path::Path;
/// use nix::fcntl::{OFlag, open};
/// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt};
/// use nix::sys::stat::Mode;
///
/// # #[allow(dead_code)]
/// # fn run() -> nix::Result<()> {
/// // Open a new PTY master
/// let master_fd = posix_openpt(OFlag::O_RDWR)?;
///
/// // Allow a slave to be generated for it
/// grantpt(&master_fd)?;
/// unlockpt(&master_fd)?;
///
/// // Get the name of the slave
/// let slave_name = unsafe { ptsname(&master_fd) }?;
///
/// // Try to open the slave
/// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
let fd = unsafe {
libc::posix_openpt(flags.bits())
};
if fd < 0 {
return Err(Errno::last());
}
Ok(PtyMaster(fd))
}
/// Get the name of the slave pseudoterminal (see
/// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html))
///
/// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master
/// referred to by `fd`.
///
/// This value is useful for opening the slave pty once the master has already been opened with
/// `posix_openpt()`.
///
/// # Safety
///
/// `ptsname()` mutates global variables and is *not* threadsafe.
/// Mutating global variables is always considered `unsafe` by Rust and this
/// function is marked as `unsafe` to reflect that.
///
/// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`.
#[inline]
pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> {
let name_ptr = libc::ptsname(fd.as_raw_fd());
if name_ptr.is_null() {
return Err(Errno::last());
}
let name = CStr::from_ptr(name_ptr);
Ok(name.to_string_lossy().into_owned())
}
/// Get the name of the slave pseudoterminal (see
/// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html))
///
/// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master
/// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the
/// POSIX standard and is instead a Linux-specific extension.
///
/// This value is useful for opening the slave ptty once the master has already been opened with
/// `posix_openpt()`.
#[cfg(any(target_os = "android", target_os = "linux"))]
#[inline]
pub fn ptsname_r(fd: &PtyMaster) -> Result<String> {
let mut name_buf = Vec::<libc::c_char>::with_capacity(64);
let name_buf_ptr = name_buf.as_mut_ptr();
let cname = unsafe {
let cap = name_buf.capacity();
if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 {
return Err(crate::Error::last());
}
CStr::from_ptr(name_buf.as_ptr())
};
let name = cname.to_string_lossy().into_owned();
Ok(name)
}
/// Unlock a pseudoterminal master/slave pseudoterminal pair (see
/// [`unlockpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html))
///
/// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal
/// referred to by `fd`. This must be called before trying to open the slave side of a
/// pseuoterminal.
#[inline]
pub fn unlockpt(fd: &PtyMaster) -> Result<()> {
if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 {
return Err(Errno::last());
}
Ok(())
}
/// Create a new pseudoterminal, returning the slave and master file descriptors
/// in `OpenptyResult`
/// (see [`openpty`](https://man7.org/linux/man-pages/man3/openpty.3.html)).
///
/// If `winsize` is not `None`, the window size of the slave will be set to
/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
/// terminal settings of the slave will be set to the values in `termios`.
#[inline]
pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> {
use std::ptr;
let mut slave = mem::MaybeUninit::<libc::c_int>::uninit();
let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
let ret = {
match (termios.into(), winsize.into()) {
(Some(termios), Some(winsize)) => {
let inner_termios = termios.get_libc_termios();
unsafe {
libc::openpty(
master.as_mut_ptr(),
slave.as_mut_ptr(),
ptr::null_mut(),
&*inner_termios as *const libc::termios as *mut _,
winsize as *const Winsize as *mut _,
)
}
}
(None, Some(winsize)) => {
unsafe {
libc::openpty(
master.as_mut_ptr(),
slave.as_mut_ptr(),
ptr::null_mut(),
ptr::null_mut(),
winsize as *const Winsize as *mut _,
)
}
}
(Some(termios), None) => {
let inner_termios = termios.get_libc_termios();
unsafe {
libc::openpty(
master.as_mut_ptr(),
slave.as_mut_ptr(),
ptr::null_mut(),
&*inner_termios as *const libc::termios as *mut _,
ptr::null_mut(),
)
}
}
(None, None) => {
unsafe {
libc::openpty(
master.as_mut_ptr(),
slave.as_mut_ptr(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
}
}
}
};
Errno::result(ret)?;
unsafe {
Ok(OpenptyResult {
master: master.assume_init(),
slave: slave.assume_init(),
})
}
}
/// Create a new pseudoterminal, returning the master file descriptor and forked pid.
/// in `ForkptyResult`
/// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)).
///
/// If `winsize` is not `None`, the window size of the slave will be set to
/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
/// terminal settings of the slave will be set to the values in `termios`.
///
/// # Safety
///
/// In a multithreaded program, only [async-signal-safe] functions like `pause`
/// and `_exit` may be called by the child (the parent isn't restricted). Note
/// that memory allocation may **not** be async-signal-safe and thus must be
/// prevented.
///
/// Those functions are only a small subset of your operating system's API, so
/// special care must be taken to only invoke code you can control and audit.
///
/// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html
pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
winsize: T,
termios: U,
) -> Result<ForkptyResult> {
use std::ptr;
let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
let term = match termios.into() {
Some(termios) => {
let inner_termios = termios.get_libc_termios();
&*inner_termios as *const libc::termios as *mut _
},
None => ptr::null_mut(),
};
let win = winsize
.into()
.map(|ws| ws as *const Winsize as *mut _)
.unwrap_or(ptr::null_mut());
let res = libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win);
let fork_result = Errno::result(res).map(|res| match res {
0 => ForkResult::Child,
res => ForkResult::Parent { child: Pid::from_raw(res) },
})?;
Ok(ForkptyResult {
master: master.assume_init(),
fork_result,
})
}

282
vendor/nix-v0.23.1-patched/src/sched.rs vendored Normal file
View file

@ -0,0 +1,282 @@
//! Execution scheduling
//!
//! See Also
//! [sched.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sched.h.html)
use crate::{Errno, Result};
#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::sched_linux_like::*;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod sched_linux_like {
use crate::errno::Errno;
use libc::{self, c_int, c_void};
use std::mem;
use std::option::Option;
use std::os::unix::io::RawFd;
use crate::unistd::Pid;
use crate::Result;
// For some functions taking with a parameter of type CloneFlags,
// only a subset of these flags have an effect.
libc_bitflags! {
/// Options for use with [`clone`]
pub struct CloneFlags: c_int {
/// The calling process and the child process run in the same
/// memory space.
CLONE_VM;
/// The caller and the child process share the same filesystem
/// information.
CLONE_FS;
/// The calling process and the child process share the same file
/// descriptor table.
CLONE_FILES;
/// The calling process and the child process share the same table
/// of signal handlers.
CLONE_SIGHAND;
/// If the calling process is being traced, then trace the child
/// also.
CLONE_PTRACE;
/// The execution of the calling process is suspended until the
/// child releases its virtual memory resources via a call to
/// execve(2) or _exit(2) (as with vfork(2)).
CLONE_VFORK;
/// The parent of the new child (as returned by getppid(2))
/// will be the same as that of the calling process.
CLONE_PARENT;
/// The child is placed in the same thread group as the calling
/// process.
CLONE_THREAD;
/// The cloned child is started in a new mount namespace.
CLONE_NEWNS;
/// The child and the calling process share a single list of System
/// V semaphore adjustment values
CLONE_SYSVSEM;
// Not supported by Nix due to lack of varargs support in Rust FFI
// CLONE_SETTLS;
// Not supported by Nix due to lack of varargs support in Rust FFI
// CLONE_PARENT_SETTID;
// Not supported by Nix due to lack of varargs support in Rust FFI
// CLONE_CHILD_CLEARTID;
/// Unused since Linux 2.6.2
#[deprecated(since = "0.23.0", note = "Deprecated by Linux 2.6.2")]
CLONE_DETACHED;
/// A tracing process cannot force `CLONE_PTRACE` on this child
/// process.
CLONE_UNTRACED;
// Not supported by Nix due to lack of varargs support in Rust FFI
// CLONE_CHILD_SETTID;
/// Create the process in a new cgroup namespace.
CLONE_NEWCGROUP;
/// Create the process in a new UTS namespace.
CLONE_NEWUTS;
/// Create the process in a new IPC namespace.
CLONE_NEWIPC;
/// Create the process in a new user namespace.
CLONE_NEWUSER;
/// Create the process in a new PID namespace.
CLONE_NEWPID;
/// Create the process in a new network namespace.
CLONE_NEWNET;
/// The new process shares an I/O context with the calling process.
CLONE_IO;
}
}
/// Type for the function executed by [`clone`].
pub type CloneCb<'a> = Box<dyn FnMut() -> isize + 'a>;
/// CpuSet represent a bit-mask of CPUs.
/// CpuSets are used by sched_setaffinity and
/// sched_getaffinity for example.
///
/// This is a wrapper around `libc::cpu_set_t`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CpuSet {
cpu_set: libc::cpu_set_t,
}
impl CpuSet {
/// Create a new and empty CpuSet.
pub fn new() -> CpuSet {
CpuSet {
cpu_set: unsafe { mem::zeroed() },
}
}
/// Test to see if a CPU is in the CpuSet.
/// `field` is the CPU id to test
pub fn is_set(&self, field: usize) -> Result<bool> {
if field >= CpuSet::count() {
Err(Errno::EINVAL)
} else {
Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) })
}
}
/// Add a CPU to CpuSet.
/// `field` is the CPU id to add
pub fn set(&mut self, field: usize) -> Result<()> {
if field >= CpuSet::count() {
Err(Errno::EINVAL)
} else {
unsafe { libc::CPU_SET(field, &mut self.cpu_set); }
Ok(())
}
}
/// Remove a CPU from CpuSet.
/// `field` is the CPU id to remove
pub fn unset(&mut self, field: usize) -> Result<()> {
if field >= CpuSet::count() {
Err(Errno::EINVAL)
} else {
unsafe { libc::CPU_CLR(field, &mut self.cpu_set);}
Ok(())
}
}
/// Return the maximum number of CPU in CpuSet
pub const fn count() -> usize {
8 * mem::size_of::<libc::cpu_set_t>()
}
}
impl Default for CpuSet {
fn default() -> Self {
Self::new()
}
}
/// `sched_setaffinity` set a thread's CPU affinity mask
/// ([`sched_setaffinity(2)`](https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html))
///
/// `pid` is the thread ID to update.
/// If pid is zero, then the calling thread is updated.
///
/// The `cpuset` argument specifies the set of CPUs on which the thread
/// will be eligible to run.
///
/// # Example
///
/// Binding the current thread to CPU 0 can be done as follows:
///
/// ```rust,no_run
/// use nix::sched::{CpuSet, sched_setaffinity};
/// use nix::unistd::Pid;
///
/// let mut cpu_set = CpuSet::new();
/// cpu_set.set(0);
/// sched_setaffinity(Pid::from_raw(0), &cpu_set);
/// ```
pub fn sched_setaffinity(pid: Pid, cpuset: &CpuSet) -> Result<()> {
let res = unsafe {
libc::sched_setaffinity(
pid.into(),
mem::size_of::<CpuSet>() as libc::size_t,
&cpuset.cpu_set,
)
};
Errno::result(res).map(drop)
}
/// `sched_getaffinity` get a thread's CPU affinity mask
/// ([`sched_getaffinity(2)`](https://man7.org/linux/man-pages/man2/sched_getaffinity.2.html))
///
/// `pid` is the thread ID to check.
/// If pid is zero, then the calling thread is checked.
///
/// Returned `cpuset` is the set of CPUs on which the thread
/// is eligible to run.
///
/// # Example
///
/// Checking if the current thread can run on CPU 0 can be done as follows:
///
/// ```rust,no_run
/// use nix::sched::sched_getaffinity;
/// use nix::unistd::Pid;
///
/// let cpu_set = sched_getaffinity(Pid::from_raw(0)).unwrap();
/// if cpu_set.is_set(0).unwrap() {
/// println!("Current thread can run on CPU 0");
/// }
/// ```
pub fn sched_getaffinity(pid: Pid) -> Result<CpuSet> {
let mut cpuset = CpuSet::new();
let res = unsafe {
libc::sched_getaffinity(
pid.into(),
mem::size_of::<CpuSet>() as libc::size_t,
&mut cpuset.cpu_set,
)
};
Errno::result(res).and(Ok(cpuset))
}
/// `clone` create a child process
/// ([`clone(2)`](https://man7.org/linux/man-pages/man2/clone.2.html))
///
/// `stack` is a reference to an array which will hold the stack of the new
/// process. Unlike when calling `clone(2)` from C, the provided stack
/// address need not be the highest address of the region. Nix will take
/// care of that requirement. The user only needs to provide a reference to
/// a normally allocated buffer.
pub fn clone(
mut cb: CloneCb,
stack: &mut [u8],
flags: CloneFlags,
signal: Option<c_int>,
) -> Result<Pid> {
extern "C" fn callback(data: *mut CloneCb) -> c_int {
let cb: &mut CloneCb = unsafe { &mut *data };
(*cb)() as c_int
}
let res = unsafe {
let combined = flags.bits() | signal.unwrap_or(0);
let ptr = stack.as_mut_ptr().add(stack.len());
let ptr_aligned = ptr.sub(ptr as usize % 16);
libc::clone(
mem::transmute(
callback as extern "C" fn(*mut Box<dyn FnMut() -> isize>) -> i32,
),
ptr_aligned as *mut c_void,
combined,
&mut cb as *mut _ as *mut c_void,
)
};
Errno::result(res).map(Pid::from_raw)
}
/// disassociate parts of the process execution context
///
/// See also [unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html)
pub fn unshare(flags: CloneFlags) -> Result<()> {
let res = unsafe { libc::unshare(flags.bits()) };
Errno::result(res).map(drop)
}
/// reassociate thread with a namespace
///
/// See also [setns(2)](https://man7.org/linux/man-pages/man2/setns.2.html)
pub fn setns(fd: RawFd, nstype: CloneFlags) -> Result<()> {
let res = unsafe { libc::setns(fd, nstype.bits()) };
Errno::result(res).map(drop)
}
}
/// Explicitly yield the processor to other threads.
///
/// [Further reading](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html)
pub fn sched_yield() -> Result<()> {
let res = unsafe { libc::sched_yield() };
Errno::result(res).map(drop)
}

1122
vendor/nix-v0.23.1-patched/src/sys/aio.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,109 @@
use crate::Result;
use crate::errno::Errno;
use libc::{self, c_int};
use std::os::unix::io::RawFd;
use std::ptr;
use std::mem;
libc_bitflags!(
pub struct EpollFlags: c_int {
EPOLLIN;
EPOLLPRI;
EPOLLOUT;
EPOLLRDNORM;
EPOLLRDBAND;
EPOLLWRNORM;
EPOLLWRBAND;
EPOLLMSG;
EPOLLERR;
EPOLLHUP;
EPOLLRDHUP;
#[cfg(target_os = "linux")] // Added in 4.5; not in Android.
EPOLLEXCLUSIVE;
#[cfg(not(target_arch = "mips"))]
EPOLLWAKEUP;
EPOLLONESHOT;
EPOLLET;
}
);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
pub enum EpollOp {
EpollCtlAdd = libc::EPOLL_CTL_ADD,
EpollCtlDel = libc::EPOLL_CTL_DEL,
EpollCtlMod = libc::EPOLL_CTL_MOD,
}
libc_bitflags!{
pub struct EpollCreateFlags: c_int {
EPOLL_CLOEXEC;
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct EpollEvent {
event: libc::epoll_event,
}
impl EpollEvent {
pub fn new(events: EpollFlags, data: u64) -> Self {
EpollEvent { event: libc::epoll_event { events: events.bits() as u32, u64: data } }
}
pub fn empty() -> Self {
unsafe { mem::zeroed::<EpollEvent>() }
}
pub fn events(&self) -> EpollFlags {
EpollFlags::from_bits(self.event.events as c_int).unwrap()
}
pub fn data(&self) -> u64 {
self.event.u64
}
}
#[inline]
pub fn epoll_create() -> Result<RawFd> {
let res = unsafe { libc::epoll_create(1024) };
Errno::result(res)
}
#[inline]
pub fn epoll_create1(flags: EpollCreateFlags) -> Result<RawFd> {
let res = unsafe { libc::epoll_create1(flags.bits()) };
Errno::result(res)
}
#[inline]
pub fn epoll_ctl<'a, T>(epfd: RawFd, op: EpollOp, fd: RawFd, event: T) -> Result<()>
where T: Into<Option<&'a mut EpollEvent>>
{
let mut event: Option<&mut EpollEvent> = event.into();
if event.is_none() && op != EpollOp::EpollCtlDel {
Err(Errno::EINVAL)
} else {
let res = unsafe {
if let Some(ref mut event) = event {
libc::epoll_ctl(epfd, op as c_int, fd, &mut event.event)
} else {
libc::epoll_ctl(epfd, op as c_int, fd, ptr::null_mut())
}
};
Errno::result(res).map(drop)
}
}
#[inline]
pub fn epoll_wait(epfd: RawFd, events: &mut [EpollEvent], timeout_ms: isize) -> Result<usize> {
let res = unsafe {
libc::epoll_wait(epfd, events.as_mut_ptr() as *mut libc::epoll_event, events.len() as c_int, timeout_ms as c_int)
};
Errno::result(res).map(|r| r as usize)
}

View file

@ -0,0 +1,348 @@
/* TOOD: Implement for other kqueue based systems
*/
use crate::{Errno, Result};
#[cfg(not(target_os = "netbsd"))]
use libc::{timespec, time_t, c_int, c_long, intptr_t, uintptr_t};
#[cfg(target_os = "netbsd")]
use libc::{timespec, time_t, c_long, intptr_t, uintptr_t, size_t};
use std::convert::TryInto;
use std::os::unix::io::RawFd;
use std::ptr;
// Redefine kevent in terms of programmer-friendly enums and bitfields.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct KEvent {
kevent: libc::kevent,
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd",
target_os = "ios", target_os = "macos",
target_os = "openbsd"))]
type type_of_udata = *mut libc::c_void;
#[cfg(any(target_os = "dragonfly", target_os = "freebsd",
target_os = "ios", target_os = "macos"))]
type type_of_data = intptr_t;
#[cfg(any(target_os = "netbsd"))]
type type_of_udata = intptr_t;
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
type type_of_data = i64;
#[cfg(target_os = "netbsd")]
type type_of_event_filter = u32;
#[cfg(not(target_os = "netbsd"))]
type type_of_event_filter = i16;
libc_enum! {
#[cfg_attr(target_os = "netbsd", repr(u32))]
#[cfg_attr(not(target_os = "netbsd"), repr(i16))]
#[non_exhaustive]
pub enum EventFilter {
EVFILT_AIO,
/// Returns whenever there is no remaining data in the write buffer
#[cfg(target_os = "freebsd")]
EVFILT_EMPTY,
#[cfg(target_os = "dragonfly")]
EVFILT_EXCEPT,
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos"))]
EVFILT_FS,
#[cfg(target_os = "freebsd")]
EVFILT_LIO,
#[cfg(any(target_os = "ios", target_os = "macos"))]
EVFILT_MACHPORT,
EVFILT_PROC,
/// Returns events associated with the process referenced by a given
/// process descriptor, created by `pdfork()`. The events to monitor are:
///
/// - NOTE_EXIT: the process has exited. The exit status will be stored in data.
#[cfg(target_os = "freebsd")]
EVFILT_PROCDESC,
EVFILT_READ,
/// Returns whenever an asynchronous `sendfile()` call completes.
#[cfg(target_os = "freebsd")]
EVFILT_SENDFILE,
EVFILT_SIGNAL,
EVFILT_TIMER,
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos"))]
EVFILT_USER,
#[cfg(any(target_os = "ios", target_os = "macos"))]
EVFILT_VM,
EVFILT_VNODE,
EVFILT_WRITE,
}
impl TryFrom<type_of_event_filter>
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd",
target_os = "ios", target_os = "macos",
target_os = "openbsd"))]
pub type type_of_event_flag = u16;
#[cfg(any(target_os = "netbsd"))]
pub type type_of_event_flag = u32;
libc_bitflags!{
pub struct EventFlag: type_of_event_flag {
EV_ADD;
EV_CLEAR;
EV_DELETE;
EV_DISABLE;
#[cfg(any(target_os = "dragonfly", target_os = "freebsd",
target_os = "ios", target_os = "macos",
target_os = "netbsd", target_os = "openbsd"))]
EV_DISPATCH;
#[cfg(target_os = "freebsd")]
EV_DROP;
EV_ENABLE;
EV_EOF;
EV_ERROR;
#[cfg(any(target_os = "macos", target_os = "ios"))]
EV_FLAG0;
EV_FLAG1;
#[cfg(target_os = "dragonfly")]
EV_NODATA;
EV_ONESHOT;
#[cfg(any(target_os = "macos", target_os = "ios"))]
EV_OOBAND;
#[cfg(any(target_os = "macos", target_os = "ios"))]
EV_POLL;
#[cfg(any(target_os = "dragonfly", target_os = "freebsd",
target_os = "ios", target_os = "macos",
target_os = "netbsd", target_os = "openbsd"))]
EV_RECEIPT;
EV_SYSFLAGS;
}
}
libc_bitflags!(
pub struct FilterFlag: u32 {
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_ABSOLUTE;
NOTE_ATTRIB;
NOTE_CHILD;
NOTE_DELETE;
#[cfg(target_os = "openbsd")]
NOTE_EOF;
NOTE_EXEC;
NOTE_EXIT;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_EXITSTATUS;
NOTE_EXTEND;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_FFAND;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_FFCOPY;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_FFCTRLMASK;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_FFLAGSMASK;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_FFNOP;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_FFOR;
NOTE_FORK;
NOTE_LINK;
NOTE_LOWAT;
#[cfg(target_os = "freebsd")]
NOTE_MSECONDS;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_NONE;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
NOTE_NSECONDS;
#[cfg(target_os = "dragonfly")]
NOTE_OOB;
NOTE_PCTRLMASK;
NOTE_PDATAMASK;
NOTE_RENAME;
NOTE_REVOKE;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
NOTE_SECONDS;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_SIGNAL;
NOTE_TRACK;
NOTE_TRACKERR;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"))]
NOTE_TRIGGER;
#[cfg(target_os = "openbsd")]
NOTE_TRUNCATE;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
NOTE_USECONDS;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_VM_ERROR;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_VM_PRESSURE;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_VM_PRESSURE_SUDDEN_TERMINATE;
#[cfg(any(target_os = "macos", target_os = "ios"))]
NOTE_VM_PRESSURE_TERMINATE;
NOTE_WRITE;
}
);
pub fn kqueue() -> Result<RawFd> {
let res = unsafe { libc::kqueue() };
Errno::result(res)
}
// KEvent can't derive Send because on some operating systems, udata is defined
// as a void*. However, KEvent's public API always treats udata as an intptr_t,
// which is safe to Send.
unsafe impl Send for KEvent {
}
impl KEvent {
pub fn new(ident: uintptr_t, filter: EventFilter, flags: EventFlag,
fflags:FilterFlag, data: intptr_t, udata: intptr_t) -> KEvent {
KEvent { kevent: libc::kevent {
ident,
filter: filter as type_of_event_filter,
flags: flags.bits(),
fflags: fflags.bits(),
data: data as type_of_data,
udata: udata as type_of_udata
} }
}
pub fn ident(&self) -> uintptr_t {
self.kevent.ident
}
pub fn filter(&self) -> Result<EventFilter> {
self.kevent.filter.try_into()
}
pub fn flags(&self) -> EventFlag {
EventFlag::from_bits(self.kevent.flags).unwrap()
}
pub fn fflags(&self) -> FilterFlag {
FilterFlag::from_bits(self.kevent.fflags).unwrap()
}
pub fn data(&self) -> intptr_t {
self.kevent.data as intptr_t
}
pub fn udata(&self) -> intptr_t {
self.kevent.udata as intptr_t
}
}
pub fn kevent(kq: RawFd,
changelist: &[KEvent],
eventlist: &mut [KEvent],
timeout_ms: usize) -> Result<usize> {
// Convert ms to timespec
let timeout = timespec {
tv_sec: (timeout_ms / 1000) as time_t,
tv_nsec: ((timeout_ms % 1000) * 1_000_000) as c_long
};
kevent_ts(kq, changelist, eventlist, Some(timeout))
}
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd"))]
type type_of_nchanges = c_int;
#[cfg(target_os = "netbsd")]
type type_of_nchanges = size_t;
pub fn kevent_ts(kq: RawFd,
changelist: &[KEvent],
eventlist: &mut [KEvent],
timeout_opt: Option<timespec>) -> Result<usize> {
let res = unsafe {
libc::kevent(
kq,
changelist.as_ptr() as *const libc::kevent,
changelist.len() as type_of_nchanges,
eventlist.as_mut_ptr() as *mut libc::kevent,
eventlist.len() as type_of_nchanges,
if let Some(ref timeout) = timeout_opt {timeout as *const timespec} else {ptr::null()})
};
Errno::result(res).map(|r| r as usize)
}
#[inline]
pub fn ev_set(ev: &mut KEvent,
ident: usize,
filter: EventFilter,
flags: EventFlag,
fflags: FilterFlag,
udata: intptr_t) {
ev.kevent.ident = ident as uintptr_t;
ev.kevent.filter = filter as type_of_event_filter;
ev.kevent.flags = flags.bits();
ev.kevent.fflags = fflags.bits();
ev.kevent.data = 0;
ev.kevent.udata = udata as type_of_udata;
}
#[test]
fn test_struct_kevent() {
use std::mem;
let udata : intptr_t = 12345;
let actual = KEvent::new(0xdead_beef,
EventFilter::EVFILT_READ,
EventFlag::EV_ONESHOT | EventFlag::EV_ADD,
FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT,
0x1337,
udata);
assert_eq!(0xdead_beef, actual.ident());
let filter = actual.kevent.filter;
assert_eq!(libc::EVFILT_READ, filter);
assert_eq!(libc::EV_ONESHOT | libc::EV_ADD, actual.flags().bits());
assert_eq!(libc::NOTE_CHILD | libc::NOTE_EXIT, actual.fflags().bits());
assert_eq!(0x1337, actual.data() as type_of_data);
assert_eq!(udata as type_of_udata, actual.udata() as type_of_udata);
assert_eq!(mem::size_of::<libc::kevent>(), mem::size_of::<KEvent>());
}
#[test]
fn test_kevent_filter() {
let udata : intptr_t = 12345;
let actual = KEvent::new(0xdead_beef,
EventFilter::EVFILT_READ,
EventFlag::EV_ONESHOT | EventFlag::EV_ADD,
FilterFlag::NOTE_CHILD | FilterFlag::NOTE_EXIT,
0x1337,
udata);
assert_eq!(EventFilter::EVFILT_READ, actual.filter().unwrap());
}

View file

@ -0,0 +1,17 @@
use std::os::unix::io::RawFd;
use crate::Result;
use crate::errno::Errno;
libc_bitflags! {
pub struct EfdFlags: libc::c_int {
EFD_CLOEXEC; // Since Linux 2.6.27
EFD_NONBLOCK; // Since Linux 2.6.27
EFD_SEMAPHORE; // Since Linux 2.6.30
}
}
pub fn eventfd(initval: libc::c_uint, flags: EfdFlags) -> Result<RawFd> {
let res = unsafe { libc::eventfd(initval, flags.bits()) };
Errno::result(res).map(|r| r as RawFd)
}

View file

@ -0,0 +1,233 @@
//! Monitoring API for filesystem events.
//!
//! Inotify is a Linux-only API to monitor filesystems events.
//!
//! For more documentation, please read [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html).
//!
//! # Examples
//!
//! Monitor all events happening in directory "test":
//! ```no_run
//! # use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify};
//! #
//! // We create a new inotify instance.
//! let instance = Inotify::init(InitFlags::empty()).unwrap();
//!
//! // We add a new watch on directory "test" for all events.
//! let wd = instance.add_watch("test", AddWatchFlags::IN_ALL_EVENTS).unwrap();
//!
//! loop {
//! // We read from our inotify instance for events.
//! let events = instance.read_events().unwrap();
//! println!("Events: {:?}", events);
//! }
//! ```
use libc::{
c_char,
c_int,
};
use std::ffi::{OsString,OsStr,CStr};
use std::os::unix::ffi::OsStrExt;
use std::mem::{MaybeUninit, size_of};
use std::os::unix::io::{RawFd,AsRawFd,FromRawFd};
use std::ptr;
use crate::unistd::read;
use crate::Result;
use crate::NixPath;
use crate::errno::Errno;
libc_bitflags! {
/// Configuration options for [`inotify_add_watch`](fn.inotify_add_watch.html).
pub struct AddWatchFlags: u32 {
IN_ACCESS;
IN_MODIFY;
IN_ATTRIB;
IN_CLOSE_WRITE;
IN_CLOSE_NOWRITE;
IN_OPEN;
IN_MOVED_FROM;
IN_MOVED_TO;
IN_CREATE;
IN_DELETE;
IN_DELETE_SELF;
IN_MOVE_SELF;
IN_UNMOUNT;
IN_Q_OVERFLOW;
IN_IGNORED;
IN_CLOSE;
IN_MOVE;
IN_ONLYDIR;
IN_DONT_FOLLOW;
IN_ISDIR;
IN_ONESHOT;
IN_ALL_EVENTS;
}
}
libc_bitflags! {
/// Configuration options for [`inotify_init1`](fn.inotify_init1.html).
pub struct InitFlags: c_int {
IN_CLOEXEC;
IN_NONBLOCK;
}
}
/// An inotify instance. This is also a file descriptor, you can feed it to
/// other interfaces consuming file descriptors, epoll for example.
#[derive(Debug, Clone, Copy)]
pub struct Inotify {
fd: RawFd
}
/// This object is returned when you create a new watch on an inotify instance.
/// It is then returned as part of an event once triggered. It allows you to
/// know which watch triggered which event.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct WatchDescriptor {
wd: i32
}
/// A single inotify event.
///
/// For more documentation see, [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html).
#[derive(Debug)]
pub struct InotifyEvent {
/// Watch descriptor. This field corresponds to the watch descriptor you
/// were issued when calling add_watch. It allows you to know which watch
/// this event comes from.
pub wd: WatchDescriptor,
/// Event mask. This field is a bitfield describing the exact event that
/// occured.
pub mask: AddWatchFlags,
/// This cookie is a number that allows you to connect related events. For
/// now only IN_MOVED_FROM and IN_MOVED_TO can be connected.
pub cookie: u32,
/// Filename. This field exists only if the event was triggered for a file
/// inside the watched directory.
pub name: Option<OsString>
}
impl Inotify {
/// Initialize a new inotify instance.
///
/// Returns a Result containing an inotify instance.
///
/// For more information see, [inotify_init(2)](https://man7.org/linux/man-pages/man2/inotify_init.2.html).
pub fn init(flags: InitFlags) -> Result<Inotify> {
let res = Errno::result(unsafe {
libc::inotify_init1(flags.bits())
});
res.map(|fd| Inotify { fd })
}
/// Adds a new watch on the target file or directory.
///
/// Returns a watch descriptor. This is not a File Descriptor!
///
/// For more information see, [inotify_add_watch(2)](https://man7.org/linux/man-pages/man2/inotify_add_watch.2.html).
pub fn add_watch<P: ?Sized + NixPath>(self,
path: &P,
mask: AddWatchFlags)
-> Result<WatchDescriptor>
{
let res = path.with_nix_path(|cstr| {
unsafe {
libc::inotify_add_watch(self.fd, cstr.as_ptr(), mask.bits())
}
})?;
Errno::result(res).map(|wd| WatchDescriptor { wd })
}
/// Removes an existing watch using the watch descriptor returned by
/// inotify_add_watch.
///
/// Returns an EINVAL error if the watch descriptor is invalid.
///
/// For more information see, [inotify_rm_watch(2)](https://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html).
#[cfg(target_os = "linux")]
pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> {
let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd) };
Errno::result(res).map(drop)
}
#[cfg(target_os = "android")]
pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> {
let res = unsafe { libc::inotify_rm_watch(self.fd, wd.wd as u32) };
Errno::result(res).map(drop)
}
/// Reads a collection of events from the inotify file descriptor. This call
/// can either be blocking or non blocking depending on whether IN_NONBLOCK
/// was set at initialization.
///
/// Returns as many events as available. If the call was non blocking and no
/// events could be read then the EAGAIN error is returned.
pub fn read_events(self) -> Result<Vec<InotifyEvent>> {
let header_size = size_of::<libc::inotify_event>();
const BUFSIZ: usize = 4096;
let mut buffer = [0u8; BUFSIZ];
let mut events = Vec::new();
let mut offset = 0;
let nread = read(self.fd, &mut buffer)?;
while (nread - offset) >= header_size {
let event = unsafe {
let mut event = MaybeUninit::<libc::inotify_event>::uninit();
ptr::copy_nonoverlapping(
buffer.as_ptr().add(offset),
event.as_mut_ptr() as *mut u8,
(BUFSIZ - offset).min(header_size)
);
event.assume_init()
};
let name = match event.len {
0 => None,
_ => {
let ptr = unsafe {
buffer
.as_ptr()
.add(offset + header_size)
as *const c_char
};
let cstr = unsafe { CStr::from_ptr(ptr) };
Some(OsStr::from_bytes(cstr.to_bytes()).to_owned())
}
};
events.push(InotifyEvent {
wd: WatchDescriptor { wd: event.wd },
mask: AddWatchFlags::from_bits_truncate(event.mask),
cookie: event.cookie,
name
});
offset += header_size + event.len as usize;
}
Ok(events)
}
}
impl AsRawFd for Inotify {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl FromRawFd for Inotify {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Inotify { fd }
}
}

View file

@ -0,0 +1,109 @@
/// The datatype used for the ioctl number
#[doc(hidden)]
#[cfg(not(target_os = "illumos"))]
pub type ioctl_num_type = ::libc::c_ulong;
#[doc(hidden)]
#[cfg(target_os = "illumos")]
pub type ioctl_num_type = ::libc::c_int;
/// The datatype used for the 3rd argument
#[doc(hidden)]
pub type ioctl_param_type = ::libc::c_int;
mod consts {
use crate::sys::ioctl::ioctl_num_type;
#[doc(hidden)]
pub const VOID: ioctl_num_type = 0x2000_0000;
#[doc(hidden)]
pub const OUT: ioctl_num_type = 0x4000_0000;
#[doc(hidden)]
#[allow(overflowing_literals)]
pub const IN: ioctl_num_type = 0x8000_0000;
#[doc(hidden)]
pub const INOUT: ioctl_num_type = IN|OUT;
#[doc(hidden)]
pub const IOCPARM_MASK: ioctl_num_type = 0x1fff;
}
pub use self::consts::*;
#[macro_export]
#[doc(hidden)]
macro_rules! ioc {
($inout:expr, $group:expr, $num:expr, $len:expr) => (
$inout | (($len as $crate::sys::ioctl::ioctl_num_type & $crate::sys::ioctl::IOCPARM_MASK) << 16) | (($group as $crate::sys::ioctl::ioctl_num_type) << 8) | ($num as $crate::sys::ioctl::ioctl_num_type)
)
}
/// Generate an ioctl request code for a command that passes no data.
///
/// This is equivalent to the `_IO()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_none!()` directly.
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// const KVMIO: u8 = 0xAE;
/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03));
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! request_code_none {
($g:expr, $n:expr) => (ioc!($crate::sys::ioctl::VOID, $g, $n, 0))
}
/// Generate an ioctl request code for a command that passes an integer
///
/// This is equivalent to the `_IOWINT()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_write_int!()` directly.
#[macro_export(local_inner_macros)]
macro_rules! request_code_write_int {
($g:expr, $n:expr) => (ioc!($crate::sys::ioctl::VOID, $g, $n, ::std::mem::size_of::<$crate::libc::c_int>()))
}
/// Generate an ioctl request code for a command that reads.
///
/// This is equivalent to the `_IOR()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_read!()` directly.
///
/// The read/write direction is relative to userland, so this
/// command would be userland is reading and the kernel is
/// writing.
#[macro_export(local_inner_macros)]
macro_rules! request_code_read {
($g:expr, $n:expr, $len:expr) => (ioc!($crate::sys::ioctl::OUT, $g, $n, $len))
}
/// Generate an ioctl request code for a command that writes.
///
/// This is equivalent to the `_IOW()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_write!()` directly.
///
/// The read/write direction is relative to userland, so this
/// command would be userland is writing and the kernel is
/// reading.
#[macro_export(local_inner_macros)]
macro_rules! request_code_write {
($g:expr, $n:expr, $len:expr) => (ioc!($crate::sys::ioctl::IN, $g, $n, $len))
}
/// Generate an ioctl request code for a command that reads and writes.
///
/// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_readwrite!()` directly.
#[macro_export(local_inner_macros)]
macro_rules! request_code_readwrite {
($g:expr, $n:expr, $len:expr) => (ioc!($crate::sys::ioctl::INOUT, $g, $n, $len))
}

View file

@ -0,0 +1,141 @@
/// The datatype used for the ioctl number
#[cfg(any(target_os = "android", target_env = "musl"))]
#[doc(hidden)]
pub type ioctl_num_type = ::libc::c_int;
#[cfg(not(any(target_os = "android", target_env = "musl")))]
#[doc(hidden)]
pub type ioctl_num_type = ::libc::c_ulong;
/// The datatype used for the 3rd argument
#[doc(hidden)]
pub type ioctl_param_type = ::libc::c_ulong;
#[doc(hidden)]
pub const NRBITS: ioctl_num_type = 8;
#[doc(hidden)]
pub const TYPEBITS: ioctl_num_type = 8;
#[cfg(any(target_arch = "mips", target_arch = "mips64", target_arch = "powerpc", target_arch = "powerpc64", target_arch = "sparc64"))]
mod consts {
#[doc(hidden)]
pub const NONE: u8 = 1;
#[doc(hidden)]
pub const READ: u8 = 2;
#[doc(hidden)]
pub const WRITE: u8 = 4;
#[doc(hidden)]
pub const SIZEBITS: u8 = 13;
#[doc(hidden)]
pub const DIRBITS: u8 = 3;
}
// "Generic" ioctl protocol
#[cfg(any(target_arch = "x86",
target_arch = "arm",
target_arch = "s390x",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv64"))]
mod consts {
#[doc(hidden)]
pub const NONE: u8 = 0;
#[doc(hidden)]
pub const READ: u8 = 2;
#[doc(hidden)]
pub const WRITE: u8 = 1;
#[doc(hidden)]
pub const SIZEBITS: u8 = 14;
#[doc(hidden)]
pub const DIRBITS: u8 = 2;
}
pub use self::consts::*;
#[doc(hidden)]
pub const NRSHIFT: ioctl_num_type = 0;
#[doc(hidden)]
pub const TYPESHIFT: ioctl_num_type = NRSHIFT + NRBITS as ioctl_num_type;
#[doc(hidden)]
pub const SIZESHIFT: ioctl_num_type = TYPESHIFT + TYPEBITS as ioctl_num_type;
#[doc(hidden)]
pub const DIRSHIFT: ioctl_num_type = SIZESHIFT + SIZEBITS as ioctl_num_type;
#[doc(hidden)]
pub const NRMASK: ioctl_num_type = (1 << NRBITS) - 1;
#[doc(hidden)]
pub const TYPEMASK: ioctl_num_type = (1 << TYPEBITS) - 1;
#[doc(hidden)]
pub const SIZEMASK: ioctl_num_type = (1 << SIZEBITS) - 1;
#[doc(hidden)]
pub const DIRMASK: ioctl_num_type = (1 << DIRBITS) - 1;
/// Encode an ioctl command.
#[macro_export]
#[doc(hidden)]
macro_rules! ioc {
($dir:expr, $ty:expr, $nr:expr, $sz:expr) => (
(($dir as $crate::sys::ioctl::ioctl_num_type & $crate::sys::ioctl::DIRMASK) << $crate::sys::ioctl::DIRSHIFT) |
(($ty as $crate::sys::ioctl::ioctl_num_type & $crate::sys::ioctl::TYPEMASK) << $crate::sys::ioctl::TYPESHIFT) |
(($nr as $crate::sys::ioctl::ioctl_num_type & $crate::sys::ioctl::NRMASK) << $crate::sys::ioctl::NRSHIFT) |
(($sz as $crate::sys::ioctl::ioctl_num_type & $crate::sys::ioctl::SIZEMASK) << $crate::sys::ioctl::SIZESHIFT))
}
/// Generate an ioctl request code for a command that passes no data.
///
/// This is equivalent to the `_IO()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_none!()` directly.
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// const KVMIO: u8 = 0xAE;
/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03));
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! request_code_none {
($ty:expr, $nr:expr) => (ioc!($crate::sys::ioctl::NONE, $ty, $nr, 0))
}
/// Generate an ioctl request code for a command that reads.
///
/// This is equivalent to the `_IOR()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_read!()` directly.
///
/// The read/write direction is relative to userland, so this
/// command would be userland is reading and the kernel is
/// writing.
#[macro_export(local_inner_macros)]
macro_rules! request_code_read {
($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::sys::ioctl::READ, $ty, $nr, $sz))
}
/// Generate an ioctl request code for a command that writes.
///
/// This is equivalent to the `_IOW()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_write!()` directly.
///
/// The read/write direction is relative to userland, so this
/// command would be userland is writing and the kernel is
/// reading.
#[macro_export(local_inner_macros)]
macro_rules! request_code_write {
($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::sys::ioctl::WRITE, $ty, $nr, $sz))
}
/// Generate an ioctl request code for a command that reads and writes.
///
/// This is equivalent to the `_IOWR()` macro exposed by the C ioctl API.
///
/// You should only use this macro directly if the `ioctl` you're working
/// with is "bad" and you cannot use `ioctl_readwrite!()` directly.
#[macro_export(local_inner_macros)]
macro_rules! request_code_readwrite {
($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::sys::ioctl::READ | $crate::sys::ioctl::WRITE, $ty, $nr, $sz))
}

View file

@ -0,0 +1,778 @@
//! Provide helpers for making ioctl system calls.
//!
//! This library is pretty low-level and messy. `ioctl` is not fun.
//!
//! What is an `ioctl`?
//! ===================
//!
//! The `ioctl` syscall is the grab-bag syscall on POSIX systems. Don't want to add a new
//! syscall? Make it an `ioctl`! `ioctl` refers to both the syscall, and the commands that can be
//! sent with it. `ioctl` stands for "IO control", and the commands are always sent to a file
//! descriptor.
//!
//! It is common to see `ioctl`s used for the following purposes:
//!
//! * Provide read/write access to out-of-band data related to a device such as configuration
//! (for instance, setting serial port options)
//! * Provide a mechanism for performing full-duplex data transfers (for instance, xfer on SPI
//! devices).
//! * Provide access to control functions on a device (for example, on Linux you can send
//! commands like pause, resume, and eject to the CDROM device.
//! * Do whatever else the device driver creator thought made most sense.
//!
//! `ioctl`s are synchronous system calls and are similar to read and write calls in that regard.
//! They operate on file descriptors and have an identifier that specifies what the ioctl is.
//! Additionally they may read or write data and therefore need to pass along a data pointer.
//! Besides the semantics of the ioctls being confusing, the generation of this identifer can also
//! be difficult.
//!
//! Historically `ioctl` numbers were arbitrary hard-coded values. In Linux (before 2.6) and some
//! unices this has changed to a more-ordered system where the ioctl numbers are partitioned into
//! subcomponents (For linux this is documented in
//! [`Documentation/ioctl/ioctl-number.rst`](https://elixir.bootlin.com/linux/latest/source/Documentation/userspace-api/ioctl/ioctl-number.rst)):
//!
//! * Number: The actual ioctl ID
//! * Type: A grouping of ioctls for a common purpose or driver
//! * Size: The size in bytes of the data that will be transferred
//! * Direction: Whether there is any data and if it's read, write, or both
//!
//! Newer drivers should not generate complete integer identifiers for their `ioctl`s instead
//! preferring to use the 4 components above to generate the final ioctl identifier. Because of
//! how old `ioctl`s are, however, there are many hard-coded `ioctl` identifiers. These are
//! commonly referred to as "bad" in `ioctl` documentation.
//!
//! Defining `ioctl`s
//! =================
//!
//! This library provides several `ioctl_*!` macros for binding `ioctl`s. These generate public
//! unsafe functions that can then be used for calling the ioctl. This macro has a few different
//! ways it can be used depending on the specific ioctl you're working with.
//!
//! A simple `ioctl` is `SPI_IOC_RD_MODE`. This ioctl works with the SPI interface on Linux. This
//! specific `ioctl` reads the mode of the SPI device as a `u8`. It's declared in
//! `/include/uapi/linux/spi/spidev.h` as `_IOR(SPI_IOC_MAGIC, 1, __u8)`. Since it uses the `_IOR`
//! macro, we know it's a `read` ioctl and can use the `ioctl_read!` macro as follows:
//!
//! ```
//! # #[macro_use] extern crate nix;
//! const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
//! const SPI_IOC_TYPE_MODE: u8 = 1;
//! ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8);
//! # fn main() {}
//! ```
//!
//! This generates the function:
//!
//! ```
//! # #[macro_use] extern crate nix;
//! # use std::mem;
//! # use nix::{libc, Result};
//! # use nix::errno::Errno;
//! # use nix::libc::c_int as c_int;
//! # const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
//! # const SPI_IOC_TYPE_MODE: u8 = 1;
//! pub unsafe fn spi_read_mode(fd: c_int, data: *mut u8) -> Result<c_int> {
//! let res = libc::ioctl(fd, request_code_read!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, mem::size_of::<u8>()), data);
//! Errno::result(res)
//! }
//! # fn main() {}
//! ```
//!
//! The return value for the wrapper functions generated by the `ioctl_*!` macros are `nix::Error`s.
//! These are generated by assuming the return value of the ioctl is `-1` on error and everything
//! else is a valid return value. If this is not the case, `Result::map` can be used to map some
//! of the range of "good" values (-Inf..-2, 0..Inf) into a smaller range in a helper function.
//!
//! Writing `ioctl`s generally use pointers as their data source and these should use the
//! `ioctl_write_ptr!`. But in some cases an `int` is passed directly. For these `ioctl`s use the
//! `ioctl_write_int!` macro. This variant does not take a type as the last argument:
//!
//! ```
//! # #[macro_use] extern crate nix;
//! const HCI_IOC_MAGIC: u8 = b'k';
//! const HCI_IOC_HCIDEVUP: u8 = 1;
//! ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP);
//! # fn main() {}
//! ```
//!
//! Some `ioctl`s don't transfer any data, and those should use `ioctl_none!`. This macro
//! doesn't take a type and so it is declared similar to the `write_int` variant shown above.
//!
//! The mode for a given `ioctl` should be clear from the documentation if it has good
//! documentation. Otherwise it will be clear based on the macro used to generate the `ioctl`
//! number where `_IO`, `_IOR`, `_IOW`, and `_IOWR` map to "none", "read", "write_*", and "readwrite"
//! respectively. To determine the specific `write_` variant to use you'll need to find
//! what the argument type is supposed to be. If it's an `int`, then `write_int` should be used,
//! otherwise it should be a pointer and `write_ptr` should be used. On Linux the
//! [`ioctl_list` man page](https://man7.org/linux/man-pages/man2/ioctl_list.2.html) describes a
//! large number of `ioctl`s and describes their argument data type.
//!
//! Using "bad" `ioctl`s
//! --------------------
//!
//! As mentioned earlier, there are many old `ioctl`s that do not use the newer method of
//! generating `ioctl` numbers and instead use hardcoded values. These can be used with the
//! `ioctl_*_bad!` macros. This naming comes from the Linux kernel which refers to these
//! `ioctl`s as "bad". These are a different variant as they bypass calling the macro that generates
//! the ioctl number and instead use the defined value directly.
//!
//! For example the `TCGETS` `ioctl` reads a `termios` data structure for a given file descriptor.
//! It's defined as `0x5401` in `ioctls.h` on Linux and can be implemented as:
//!
//! ```
//! # #[macro_use] extern crate nix;
//! # #[cfg(any(target_os = "android", target_os = "linux"))]
//! # use nix::libc::TCGETS as TCGETS;
//! # #[cfg(any(target_os = "android", target_os = "linux"))]
//! # use nix::libc::termios as termios;
//! # #[cfg(any(target_os = "android", target_os = "linux"))]
//! ioctl_read_bad!(tcgets, TCGETS, termios);
//! # fn main() {}
//! ```
//!
//! The generated function has the same form as that generated by `ioctl_read!`:
//!
//! ```text
//! pub unsafe fn tcgets(fd: c_int, data: *mut termios) -> Result<c_int>;
//! ```
//!
//! Working with Arrays
//! -------------------
//!
//! Some `ioctl`s work with entire arrays of elements. These are supported by the `ioctl_*_buf`
//! family of macros: `ioctl_read_buf`, `ioctl_write_buf`, and `ioctl_readwrite_buf`. Note that
//! there are no "bad" versions for working with buffers. The generated functions include a `len`
//! argument to specify the number of elements (where the type of each element is specified in the
//! macro).
//!
//! Again looking to the SPI `ioctl`s on Linux for an example, there is a `SPI_IOC_MESSAGE` `ioctl`
//! that queues up multiple SPI messages by writing an entire array of `spi_ioc_transfer` structs.
//! `linux/spi/spidev.h` defines a macro to calculate the `ioctl` number like:
//!
//! ```C
//! #define SPI_IOC_MAGIC 'k'
//! #define SPI_MSGSIZE(N) ...
//! #define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])
//! ```
//!
//! The `SPI_MSGSIZE(N)` calculation is already handled by the `ioctl_*!` macros, so all that's
//! needed to define this `ioctl` is:
//!
//! ```
//! # #[macro_use] extern crate nix;
//! const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
//! const SPI_IOC_TYPE_MESSAGE: u8 = 0;
//! # pub struct spi_ioc_transfer(u64);
//! ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer);
//! # fn main() {}
//! ```
//!
//! This generates a function like:
//!
//! ```
//! # #[macro_use] extern crate nix;
//! # use std::mem;
//! # use nix::{libc, Result};
//! # use nix::errno::Errno;
//! # use nix::libc::c_int as c_int;
//! # const SPI_IOC_MAGIC: u8 = b'k';
//! # const SPI_IOC_TYPE_MESSAGE: u8 = 0;
//! # pub struct spi_ioc_transfer(u64);
//! pub unsafe fn spi_message(fd: c_int, data: &mut [spi_ioc_transfer]) -> Result<c_int> {
//! let res = libc::ioctl(fd,
//! request_code_write!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, data.len() * mem::size_of::<spi_ioc_transfer>()),
//! data);
//! Errno::result(res)
//! }
//! # fn main() {}
//! ```
//!
//! Finding `ioctl` Documentation
//! -----------------------------
//!
//! For Linux, look at your system's headers. For example, `/usr/include/linux/input.h` has a lot
//! of lines defining macros which use `_IO`, `_IOR`, `_IOW`, `_IOC`, and `_IOWR`. Some `ioctl`s are
//! documented directly in the headers defining their constants, but others have more extensive
//! documentation in man pages (like termios' `ioctl`s which are in `tty_ioctl(4)`).
//!
//! Documenting the Generated Functions
//! ===================================
//!
//! In many cases, users will wish for the functions generated by the `ioctl`
//! macro to be public and documented. For this reason, the generated functions
//! are public by default. If you wish to hide the ioctl, you will need to put
//! them in a private module.
//!
//! For documentation, it is possible to use doc comments inside the `ioctl_*!` macros. Here is an
//! example :
//!
//! ```
//! # #[macro_use] extern crate nix;
//! # use nix::libc::c_int;
//! ioctl_read! {
//! /// Make the given terminal the controlling terminal of the calling process. The calling
//! /// process must be a session leader and not have a controlling terminal already. If the
//! /// terminal is already the controlling terminal of a different session group then the
//! /// ioctl will fail with **EPERM**, unless the caller is root (more precisely: has the
//! /// **CAP_SYS_ADMIN** capability) and arg equals 1, in which case the terminal is stolen
//! /// and all processes that had it as controlling terminal lose it.
//! tiocsctty, b't', 19, c_int
//! }
//!
//! # fn main() {}
//! ```
use cfg_if::cfg_if;
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
#[macro_use]
mod linux;
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
pub use self::linux::*;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "illumos",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
#[macro_use]
mod bsd;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "illumos",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
pub use self::bsd::*;
/// Convert raw ioctl return value to a Nix result
#[macro_export]
#[doc(hidden)]
macro_rules! convert_ioctl_res {
($w:expr) => (
{
$crate::errno::Errno::result($w)
}
);
}
/// Generates a wrapper function for an ioctl that passes no data to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// The `videodev2` driver on Linux defines the `log_status` `ioctl` as:
///
/// ```C
/// #define VIDIOC_LOG_STATUS _IO('V', 70)
/// ```
///
/// This can be implemented in Rust like:
///
/// ```no_run
/// # #[macro_use] extern crate nix;
/// ioctl_none!(log_status, b'V', 70);
/// fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_none {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_none!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type))
}
)
}
/// Generates a wrapper function for a "bad" ioctl that passes no data to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl request code
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```no_run
/// # #[macro_use] extern crate nix;
/// # use libc::TIOCNXCL;
/// # use std::fs::File;
/// # use std::os::unix::io::AsRawFd;
/// ioctl_none_bad!(tiocnxcl, TIOCNXCL);
/// fn main() {
/// let file = File::open("/dev/ttyUSB0").unwrap();
/// unsafe { tiocnxcl(file.as_raw_fd()) }.unwrap();
/// }
/// ```
// TODO: add an example using request_code_*!()
#[macro_export(local_inner_macros)]
macro_rules! ioctl_none_bad {
($(#[$attr:meta])* $name:ident, $nr:expr) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type))
}
)
}
/// Generates a wrapper function for an ioctl that reads data from the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
/// const SPI_IOC_TYPE_MODE: u8 = 1;
/// ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_read {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: *mut $ty)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for a "bad" ioctl that reads data from the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl request code
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// # #[cfg(any(target_os = "android", target_os = "linux"))]
/// ioctl_read_bad!(tcgets, libc::TCGETS, libc::termios);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_read_bad {
($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: *mut $ty)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for an ioctl that writes data through a pointer to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *const DATA_TYPE) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// # pub struct v4l2_audio {}
/// ioctl_write_ptr!(s_audio, b'V', 34, v4l2_audio);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_write_ptr {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: *const $ty)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for a "bad" ioctl that writes data through a pointer to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl request code
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *const DATA_TYPE) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// # #[cfg(any(target_os = "android", target_os = "linux"))]
/// ioctl_write_ptr_bad!(tcsets, libc::TCSETS, libc::termios);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_write_ptr_bad {
($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: *const $ty)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
cfg_if!{
if #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] {
/// Generates a wrapper function for a ioctl that writes an integer to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: nix::sys::ioctl::ioctl_param_type) -> Result<libc::c_int>
/// ```
///
/// `nix::sys::ioctl::ioctl_param_type` depends on the OS:
/// * BSD - `libc::c_int`
/// * Linux - `libc::c_ulong`
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// ioctl_write_int!(vt_activate, b'v', 4);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_write_int {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: $crate::sys::ioctl::ioctl_param_type)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write_int!($ioty, $nr) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
} else {
/// Generates a wrapper function for a ioctl that writes an integer to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: nix::sys::ioctl::ioctl_param_type) -> Result<libc::c_int>
/// ```
///
/// `nix::sys::ioctl::ioctl_param_type` depends on the OS:
/// * BSD - `libc::c_int`
/// * Linux - `libc::c_ulong`
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// const HCI_IOC_MAGIC: u8 = b'k';
/// const HCI_IOC_HCIDEVUP: u8 = 1;
/// ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_write_int {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: $crate::sys::ioctl::ioctl_param_type)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, ::std::mem::size_of::<$crate::libc::c_int>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
}
}
/// Generates a wrapper function for a "bad" ioctl that writes an integer to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl request code
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: libc::c_int) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate nix;
/// # #[cfg(any(target_os = "android", target_os = "linux"))]
/// ioctl_write_int_bad!(tcsbrk, libc::TCSBRK);
/// # fn main() {}
/// ```
///
/// ```rust
/// # #[macro_use] extern crate nix;
/// const KVMIO: u8 = 0xAE;
/// ioctl_write_int_bad!(kvm_create_vm, request_code_none!(KVMIO, 0x03));
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_write_int_bad {
($(#[$attr:meta])* $name:ident, $nr:expr) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: $crate::libc::c_int)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for an ioctl that reads and writes data to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate nix;
/// # pub struct v4l2_audio {}
/// ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_readwrite {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: *mut $ty)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for a "bad" ioctl that reads and writes data to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl request code
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: *mut DATA_TYPE) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
// TODO: Find an example for ioctl_readwrite_bad
#[macro_export(local_inner_macros)]
macro_rules! ioctl_readwrite_bad {
($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: *mut $ty)
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, $nr as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for an ioctl that reads an array of elements from the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &mut [DATA_TYPE]) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
// TODO: Find an example for ioctl_read_buf
#[macro_export(local_inner_macros)]
macro_rules! ioctl_read_buf {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: &mut [$ty])
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_read!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for an ioctl that writes an array of elements to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &[DATA_TYPE]) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate nix;
/// const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
/// const SPI_IOC_TYPE_MESSAGE: u8 = 0;
/// # pub struct spi_ioc_transfer(u64);
/// ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer);
/// # fn main() {}
/// ```
#[macro_export(local_inner_macros)]
macro_rules! ioctl_write_buf {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: &[$ty])
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_write!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}
/// Generates a wrapper function for an ioctl that reads and writes an array of elements to the kernel.
///
/// The arguments to this macro are:
///
/// * The function name
/// * The ioctl identifier
/// * The ioctl sequence number
/// * The data type passed by this ioctl
///
/// The generated function has the following signature:
///
/// ```rust,ignore
/// pub unsafe fn FUNCTION_NAME(fd: libc::c_int, data: &mut [DATA_TYPE]) -> Result<libc::c_int>
/// ```
///
/// For a more in-depth explanation of ioctls, see [`::sys::ioctl`](sys/ioctl/index.html).
// TODO: Find an example for readwrite_buf
#[macro_export(local_inner_macros)]
macro_rules! ioctl_readwrite_buf {
($(#[$attr:meta])* $name:ident, $ioty:expr, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: $crate::libc::c_int,
data: &mut [$ty])
-> $crate::Result<$crate::libc::c_int> {
convert_ioctl_res!($crate::libc::ioctl(fd, request_code_readwrite!($ioty, $nr, data.len() * ::std::mem::size_of::<$ty>()) as $crate::sys::ioctl::ioctl_num_type, data))
}
)
}

View file

@ -0,0 +1,19 @@
use std::os::unix::io::RawFd;
use crate::Result;
use crate::errno::Errno;
use std::ffi::CStr;
libc_bitflags!(
pub struct MemFdCreateFlag: libc::c_uint {
MFD_CLOEXEC;
MFD_ALLOW_SEALING;
}
);
pub fn memfd_create(name: &CStr, flags: MemFdCreateFlag) -> Result<RawFd> {
let res = unsafe {
libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags.bits())
};
Errno::result(res).map(|r| r as RawFd)
}

View file

@ -0,0 +1,464 @@
use crate::Result;
#[cfg(not(target_os = "android"))]
use crate::NixPath;
use crate::errno::Errno;
#[cfg(not(target_os = "android"))]
use crate::fcntl::OFlag;
use libc::{self, c_int, c_void, size_t, off_t};
#[cfg(not(target_os = "android"))]
use crate::sys::stat::Mode;
use std::os::unix::io::RawFd;
libc_bitflags!{
/// Desired memory protection of a memory mapping.
pub struct ProtFlags: c_int {
/// Pages cannot be accessed.
PROT_NONE;
/// Pages can be read.
PROT_READ;
/// Pages can be written.
PROT_WRITE;
/// Pages can be executed
PROT_EXEC;
/// Apply protection up to the end of a mapping that grows upwards.
#[cfg(any(target_os = "android", target_os = "linux"))]
PROT_GROWSDOWN;
/// Apply protection down to the beginning of a mapping that grows downwards.
#[cfg(any(target_os = "android", target_os = "linux"))]
PROT_GROWSUP;
}
}
libc_bitflags!{
/// Additional parameters for `mmap()`.
pub struct MapFlags: c_int {
/// Compatibility flag. Ignored.
MAP_FILE;
/// Share this mapping. Mutually exclusive with `MAP_PRIVATE`.
MAP_SHARED;
/// Create a private copy-on-write mapping. Mutually exclusive with `MAP_SHARED`.
MAP_PRIVATE;
/// Place the mapping at exactly the address specified in `addr`.
MAP_FIXED;
/// To be used with `MAP_FIXED`, to forbid the system
/// to select a different address than the one specified.
#[cfg(target_os = "freebsd")]
MAP_EXCL;
/// Synonym for `MAP_ANONYMOUS`.
MAP_ANON;
/// The mapping is not backed by any file.
MAP_ANONYMOUS;
/// Put the mapping into the first 2GB of the process address space.
#[cfg(any(all(any(target_os = "android", target_os = "linux"),
any(target_arch = "x86", target_arch = "x86_64")),
all(target_os = "linux", target_env = "musl", any(target_arch = "x86", target_arch = "x86_64")),
all(target_os = "freebsd", target_pointer_width = "64")))]
MAP_32BIT;
/// Used for stacks; indicates to the kernel that the mapping should extend downward in memory.
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_GROWSDOWN;
/// Compatibility flag. Ignored.
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_DENYWRITE;
/// Compatibility flag. Ignored.
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_EXECUTABLE;
/// Mark the mmaped region to be locked in the same way as `mlock(2)`.
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_LOCKED;
/// Do not reserve swap space for this mapping.
///
/// This was removed in FreeBSD 11 and is unused in DragonFlyBSD.
#[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))]
MAP_NORESERVE;
/// Populate page tables for a mapping.
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_POPULATE;
/// Only meaningful when used with `MAP_POPULATE`. Don't perform read-ahead.
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_NONBLOCK;
/// Allocate the mapping using "huge pages."
#[cfg(any(target_os = "android", target_os = "linux"))]
MAP_HUGETLB;
/// Make use of 64KB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_64KB;
/// Make use of 512KB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_512KB;
/// Make use of 1MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_1MB;
/// Make use of 2MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_2MB;
/// Make use of 8MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_8MB;
/// Make use of 16MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_16MB;
/// Make use of 32MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_32MB;
/// Make use of 256MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_256MB;
/// Make use of 512MB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_512MB;
/// Make use of 1GB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_1GB;
/// Make use of 2GB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_2GB;
/// Make use of 16GB huge page (must be supported by the system)
#[cfg(target_os = "linux")]
MAP_HUGE_16GB;
/// Lock the mapped region into memory as with `mlock(2)`.
#[cfg(target_os = "netbsd")]
MAP_WIRED;
/// Causes dirtied data in the specified range to be flushed to disk only when necessary.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MAP_NOSYNC;
/// Rename private pages to a file.
///
/// This was removed in FreeBSD 11 and is unused in DragonFlyBSD.
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
MAP_RENAME;
/// Region may contain semaphores.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
MAP_HASSEMAPHORE;
/// Region grows down, like a stack.
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
MAP_STACK;
/// Pages in this mapping are not retained in the kernel's memory cache.
#[cfg(any(target_os = "ios", target_os = "macos"))]
MAP_NOCACHE;
/// Allows the W/X bit on the page, it's necessary on aarch64 architecture.
#[cfg(any(target_os = "ios", target_os = "macos"))]
MAP_JIT;
/// Allows to use large pages, underlying alignment based on size.
#[cfg(target_os = "freesd")]
MAP_ALIGNED_SUPER;
/// Pages will be discarded in the core dumps.
#[cfg(target_os = "openbsd")]
MAP_CONCEAL;
}
}
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
libc_bitflags!{
/// Options for `mremap()`.
pub struct MRemapFlags: c_int {
/// Permit the kernel to relocate the mapping to a new virtual address, if necessary.
#[cfg(target_os = "linux")]
MREMAP_MAYMOVE;
/// Place the mapping at exactly the address specified in `new_address`.
#[cfg(target_os = "linux")]
MREMAP_FIXED;
/// Permits to use the old and new address as hints to relocate the mapping.
#[cfg(target_os = "netbsd")]
MAP_FIXED;
/// Allows to duplicate the mapping to be able to apply different flags on the copy.
#[cfg(target_os = "netbsd")]
MAP_REMAPDUP;
}
}
libc_enum!{
/// Usage information for a range of memory to allow for performance optimizations by the kernel.
///
/// Used by [`madvise`](./fn.madvise.html).
#[repr(i32)]
#[non_exhaustive]
pub enum MmapAdvise {
/// No further special treatment. This is the default.
MADV_NORMAL,
/// Expect random page references.
MADV_RANDOM,
/// Expect sequential page references.
MADV_SEQUENTIAL,
/// Expect access in the near future.
MADV_WILLNEED,
/// Do not expect access in the near future.
MADV_DONTNEED,
/// Free up a given range of pages and its associated backing store.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_REMOVE,
/// Do not make pages in this range available to the child after a `fork(2)`.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_DONTFORK,
/// Undo the effect of `MADV_DONTFORK`.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_DOFORK,
/// Poison the given pages.
///
/// Subsequent references to those pages are treated like hardware memory corruption.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_HWPOISON,
/// Enable Kernel Samepage Merging (KSM) for the given pages.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_MERGEABLE,
/// Undo the effect of `MADV_MERGEABLE`
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_UNMERGEABLE,
/// Preserve the memory of each page but offline the original page.
#[cfg(any(target_os = "android",
all(target_os = "linux", any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "sparc64"))))]
MADV_SOFT_OFFLINE,
/// Enable Transparent Huge Pages (THP) for pages in the given range.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_HUGEPAGE,
/// Undo the effect of `MADV_HUGEPAGE`.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_NOHUGEPAGE,
/// Exclude the given range from a core dump.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_DONTDUMP,
/// Undo the effect of an earlier `MADV_DONTDUMP`.
#[cfg(any(target_os = "android", target_os = "linux"))]
MADV_DODUMP,
/// Specify that the application no longer needs the pages in the given range.
MADV_FREE,
/// Request that the system not flush the current range to disk unless it needs to.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MADV_NOSYNC,
/// Undoes the effects of `MADV_NOSYNC` for any future pages dirtied within the given range.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MADV_AUTOSYNC,
/// Region is not included in a core file.
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MADV_NOCORE,
/// Include region in a core file
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
MADV_CORE,
#[cfg(any(target_os = "freebsd"))]
MADV_PROTECT,
/// Invalidate the hardware page table for the given region.
#[cfg(target_os = "dragonfly")]
MADV_INVAL,
/// Set the offset of the page directory page to `value` for the virtual page table.
#[cfg(target_os = "dragonfly")]
MADV_SETMAP,
/// Indicates that the application will not need the data in the given range.
#[cfg(any(target_os = "ios", target_os = "macos"))]
MADV_ZERO_WIRED_PAGES,
#[cfg(any(target_os = "ios", target_os = "macos"))]
MADV_FREE_REUSABLE,
#[cfg(any(target_os = "ios", target_os = "macos"))]
MADV_FREE_REUSE,
#[cfg(any(target_os = "ios", target_os = "macos"))]
MADV_CAN_REUSE,
}
}
libc_bitflags!{
/// Configuration flags for `msync`.
pub struct MsFlags: c_int {
/// Schedule an update but return immediately.
MS_ASYNC;
/// Invalidate all cached data.
MS_INVALIDATE;
/// Invalidate pages, but leave them mapped.
#[cfg(any(target_os = "ios", target_os = "macos"))]
MS_KILLPAGES;
/// Deactivate pages, but leave them mapped.
#[cfg(any(target_os = "ios", target_os = "macos"))]
MS_DEACTIVATE;
/// Perform an update and wait for it to complete.
MS_SYNC;
}
}
libc_bitflags!{
/// Flags for `mlockall`.
pub struct MlockAllFlags: c_int {
/// Lock pages that are currently mapped into the address space of the process.
MCL_CURRENT;
/// Lock pages which will become mapped into the address space of the process in the future.
MCL_FUTURE;
}
}
/// Locks all memory pages that contain part of the address range with `length`
/// bytes starting at `addr`.
///
/// Locked pages never move to the swap area.
///
/// # Safety
///
/// `addr` must meet all the requirements described in the `mlock(2)` man page.
pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> {
Errno::result(libc::mlock(addr, length)).map(drop)
}
/// Unlocks all memory pages that contain part of the address range with
/// `length` bytes starting at `addr`.
///
/// # Safety
///
/// `addr` must meet all the requirements described in the `munlock(2)` man
/// page.
pub unsafe fn munlock(addr: *const c_void, length: size_t) -> Result<()> {
Errno::result(libc::munlock(addr, length)).map(drop)
}
/// Locks all memory pages mapped into this process' address space.
///
/// Locked pages never move to the swap area.
///
/// # Safety
///
/// `addr` must meet all the requirements described in the `mlockall(2)` man
/// page.
pub fn mlockall(flags: MlockAllFlags) -> Result<()> {
unsafe { Errno::result(libc::mlockall(flags.bits())) }.map(drop)
}
/// Unlocks all memory pages mapped into this process' address space.
pub fn munlockall() -> Result<()> {
unsafe { Errno::result(libc::munlockall()) }.map(drop)
}
/// allocate memory, or map files or devices into memory
///
/// # Safety
///
/// See the `mmap(2)` man page for detailed requirements.
pub unsafe fn mmap(addr: *mut c_void, length: size_t, prot: ProtFlags, flags: MapFlags, fd: RawFd, offset: off_t) -> Result<*mut c_void> {
let ret = libc::mmap(addr, length, prot.bits(), flags.bits(), fd, offset);
if ret == libc::MAP_FAILED {
Err(Errno::last())
} else {
Ok(ret)
}
}
/// Expands (or shrinks) an existing memory mapping, potentially moving it at
/// the same time.
///
/// # Safety
///
/// See the `mremap(2)` [man page](https://man7.org/linux/man-pages/man2/mremap.2.html) for
/// detailed requirements.
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
pub unsafe fn mremap(
addr: *mut c_void,
old_size: size_t,
new_size: size_t,
flags: MRemapFlags,
new_address: Option<* mut c_void>,
) -> Result<*mut c_void> {
#[cfg(target_os = "linux")]
let ret = libc::mremap(addr, old_size, new_size, flags.bits(), new_address.unwrap_or(std::ptr::null_mut()));
#[cfg(target_os = "netbsd")]
let ret = libc::mremap(
addr,
old_size,
new_address.unwrap_or(std::ptr::null_mut()),
new_size,
flags.bits(),
);
if ret == libc::MAP_FAILED {
Err(Errno::last())
} else {
Ok(ret)
}
}
/// remove a mapping
///
/// # Safety
///
/// `addr` must meet all the requirements described in the `munmap(2)` man
/// page.
pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> {
Errno::result(libc::munmap(addr, len)).map(drop)
}
/// give advice about use of memory
///
/// # Safety
///
/// See the `madvise(2)` man page. Take special care when using
/// `MmapAdvise::MADV_FREE`.
pub unsafe fn madvise(addr: *mut c_void, length: size_t, advise: MmapAdvise) -> Result<()> {
Errno::result(libc::madvise(addr, length, advise as i32)).map(drop)
}
/// Set protection of memory mapping.
///
/// See [`mprotect(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html) for
/// details.
///
/// # Safety
///
/// Calls to `mprotect` are inherently unsafe, as changes to memory protections can lead to
/// SIGSEGVs.
///
/// ```
/// # use nix::libc::size_t;
/// # use nix::sys::mman::{mmap, mprotect, MapFlags, ProtFlags};
/// # use std::ptr;
/// const ONE_K: size_t = 1024;
/// let mut slice: &mut [u8] = unsafe {
/// let mem = mmap(ptr::null_mut(), ONE_K, ProtFlags::PROT_NONE,
/// MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, -1, 0).unwrap();
/// mprotect(mem, ONE_K, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE).unwrap();
/// std::slice::from_raw_parts_mut(mem as *mut u8, ONE_K)
/// };
/// assert_eq!(slice[0], 0x00);
/// slice[0] = 0xFF;
/// assert_eq!(slice[0], 0xFF);
/// ```
pub unsafe fn mprotect(addr: *mut c_void, length: size_t, prot: ProtFlags) -> Result<()> {
Errno::result(libc::mprotect(addr, length, prot.bits())).map(drop)
}
/// synchronize a mapped region
///
/// # Safety
///
/// `addr` must meet all the requirements described in the `msync(2)` man
/// page.
pub unsafe fn msync(addr: *mut c_void, length: size_t, flags: MsFlags) -> Result<()> {
Errno::result(libc::msync(addr, length, flags.bits())).map(drop)
}
#[cfg(not(target_os = "android"))]
pub fn shm_open<P: ?Sized + NixPath>(name: &P, flag: OFlag, mode: Mode) -> Result<RawFd> {
let ret = name.with_nix_path(|cstr| {
#[cfg(any(target_os = "macos", target_os = "ios"))]
unsafe {
libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::c_uint)
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
unsafe {
libc::shm_open(cstr.as_ptr(), flag.bits(), mode.bits() as libc::mode_t)
}
})?;
Errno::result(ret)
}
#[cfg(not(target_os = "android"))]
pub fn shm_unlink<P: ?Sized + NixPath>(name: &P) -> Result<()> {
let ret = name.with_nix_path(|cstr| {
unsafe { libc::shm_unlink(cstr.as_ptr()) }
})?;
Errno::result(ret).map(drop)
}

View file

@ -0,0 +1,131 @@
//! Mostly platform-specific functionality
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd"))]
pub mod aio;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(missing_docs)]
pub mod epoll;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
#[allow(missing_docs)]
pub mod event;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(missing_docs)]
pub mod eventfd;
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "redox",
target_os = "macos",
target_os = "netbsd",
target_os = "illumos",
target_os = "openbsd"))]
#[macro_use]
pub mod ioctl;
#[cfg(target_os = "linux")]
#[allow(missing_docs)]
pub mod memfd;
#[cfg(not(target_os = "redox"))]
#[allow(missing_docs)]
pub mod mman;
#[cfg(target_os = "linux")]
#[allow(missing_docs)]
pub mod personality;
pub mod pthread;
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
#[allow(missing_docs)]
pub mod ptrace;
#[cfg(target_os = "linux")]
pub mod quota;
#[cfg(any(target_os = "linux"))]
#[allow(missing_docs)]
pub mod reboot;
#[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "illumos")))]
pub mod resource;
#[cfg(not(target_os = "redox"))]
pub mod select;
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos"))]
pub mod sendfile;
pub mod signal;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(missing_docs)]
pub mod signalfd;
#[cfg(not(target_os = "redox"))]
#[allow(missing_docs)]
pub mod socket;
#[allow(missing_docs)]
pub mod stat;
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "openbsd"
))]
pub mod statfs;
pub mod statvfs;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(missing_docs)]
pub mod sysinfo;
#[allow(missing_docs)]
pub mod termios;
#[allow(missing_docs)]
pub mod time;
pub mod uio;
pub mod utsname;
pub mod wait;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(missing_docs)]
pub mod inotify;
#[cfg(any(target_os = "android", target_os = "linux"))]
#[allow(missing_docs)]
pub mod timerfd;

View file

@ -0,0 +1,70 @@
use crate::Result;
use crate::errno::Errno;
use libc::{self, c_int, c_ulong};
libc_bitflags! {
/// Flags used and returned by [`get()`](fn.get.html) and
/// [`set()`](fn.set.html).
pub struct Persona: c_int {
ADDR_COMPAT_LAYOUT;
ADDR_NO_RANDOMIZE;
ADDR_LIMIT_32BIT;
ADDR_LIMIT_3GB;
#[cfg(not(target_env = "musl"))]
FDPIC_FUNCPTRS;
MMAP_PAGE_ZERO;
READ_IMPLIES_EXEC;
SHORT_INODE;
STICKY_TIMEOUTS;
#[cfg(not(target_env = "musl"))]
UNAME26;
WHOLE_SECONDS;
}
}
/// Retrieve the current process personality.
///
/// Returns a Result containing a Persona instance.
///
/// Example:
///
/// ```
/// # use nix::sys::personality::{self, Persona};
/// let pers = personality::get().unwrap();
/// assert!(!pers.contains(Persona::WHOLE_SECONDS));
/// ```
pub fn get() -> Result<Persona> {
let res = unsafe {
libc::personality(0xFFFFFFFF)
};
Errno::result(res).map(Persona::from_bits_truncate)
}
/// Set the current process personality.
///
/// Returns a Result containing the *previous* personality for the
/// process, as a Persona.
///
/// For more information, see [personality(2)](https://man7.org/linux/man-pages/man2/personality.2.html)
///
/// **NOTE**: This call **replaces** the current personality entirely.
/// To **update** the personality, first call `get()` and then `set()`
/// with the modified persona.
///
/// Example:
///
/// ```
/// # use nix::sys::personality::{self, Persona};
/// let mut pers = personality::get().unwrap();
/// assert!(!pers.contains(Persona::ADDR_NO_RANDOMIZE));
/// personality::set(pers | Persona::ADDR_NO_RANDOMIZE);
/// ```
pub fn set(persona: Persona) -> Result<Persona> {
let res = unsafe {
libc::personality(persona.bits() as c_ulong)
};
Errno::result(res).map(Persona::from_bits_truncate)
}

View file

@ -0,0 +1,38 @@
//! Low level threading primitives
#[cfg(not(target_os = "redox"))]
use crate::errno::Errno;
#[cfg(not(target_os = "redox"))]
use crate::Result;
#[cfg(not(target_os = "redox"))]
use crate::sys::signal::Signal;
use libc::{self, pthread_t};
/// Identifies an individual thread.
pub type Pthread = pthread_t;
/// Obtain ID of the calling thread (see
/// [`pthread_self(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html)
///
/// The thread ID returned by `pthread_self()` is not the same thing as
/// the kernel thread ID returned by a call to `gettid(2)`.
#[inline]
pub fn pthread_self() -> Pthread {
unsafe { libc::pthread_self() }
}
/// Send a signal to a thread (see [`pthread_kill(3)`]).
///
/// If `signal` is `None`, `pthread_kill` will only preform error checking and
/// won't send any signal.
///
/// [`pthread_kill(3)`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html
#[cfg(not(target_os = "redox"))]
pub fn pthread_kill<T: Into<Option<Signal>>>(thread: Pthread, signal: T) -> Result<()> {
let sig = match signal.into() {
Some(s) => s as libc::c_int,
None => 0,
};
let res = unsafe { libc::pthread_kill(thread, sig) };
Errno::result(res).map(drop)
}

View file

@ -0,0 +1,176 @@
use cfg_if::cfg_if;
use crate::errno::Errno;
use libc::{self, c_int};
use std::ptr;
use crate::sys::signal::Signal;
use crate::unistd::Pid;
use crate::Result;
pub type RequestType = c_int;
cfg_if! {
if #[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "openbsd"))] {
#[doc(hidden)]
pub type AddressType = *mut ::libc::c_char;
} else {
#[doc(hidden)]
pub type AddressType = *mut ::libc::c_void;
}
}
libc_enum! {
#[repr(i32)]
/// Ptrace Request enum defining the action to be taken.
#[non_exhaustive]
pub enum Request {
PT_TRACE_ME,
PT_READ_I,
PT_READ_D,
#[cfg(target_os = "macos")]
PT_READ_U,
PT_WRITE_I,
PT_WRITE_D,
#[cfg(target_os = "macos")]
PT_WRITE_U,
PT_CONTINUE,
PT_KILL,
#[cfg(any(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos"),
all(target_os = "openbsd", target_arch = "x86_64"),
all(target_os = "netbsd", any(target_arch = "x86_64",
target_arch = "powerpc"))))]
PT_STEP,
PT_ATTACH,
PT_DETACH,
#[cfg(target_os = "macos")]
PT_SIGEXC,
#[cfg(target_os = "macos")]
PT_THUPDATE,
#[cfg(target_os = "macos")]
PT_ATTACHEXC
}
}
unsafe fn ptrace_other(
request: Request,
pid: Pid,
addr: AddressType,
data: c_int,
) -> Result<c_int> {
Errno::result(libc::ptrace(
request as RequestType,
libc::pid_t::from(pid),
addr,
data,
)).map(|_| 0)
}
/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)`
///
/// Indicates that this process is to be traced by its parent.
/// This is the only ptrace request to be issued by the tracee.
pub fn traceme() -> Result<()> {
unsafe { ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0).map(drop) }
}
/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)`
///
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
unsafe { ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) }
}
/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)`
///
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
/// signal specified by `sig`.
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as c_int,
None => 0,
};
unsafe {
ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop)
}
}
/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
///
/// Continues the execution of the process with PID `pid`, optionally
/// delivering a signal specified by `sig`.
pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as c_int,
None => 0,
};
unsafe {
// Ignore the useless return value
ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(drop)
}
}
/// Issues a kill request as with `ptrace(PT_KILL, ...)`
///
/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);`
pub fn kill(pid: Pid) -> Result<()> {
unsafe {
ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(drop)
}
}
/// Move the stopped tracee process forward by a single step as with
/// `ptrace(PT_STEP, ...)`
///
/// Advances the execution of the process with PID `pid` by a single step optionally delivering a
/// signal specified by `sig`.
///
/// # Example
/// ```rust
/// use nix::sys::ptrace::step;
/// use nix::unistd::Pid;
/// use nix::sys::signal::Signal;
/// use nix::sys::wait::*;
/// // If a process changes state to the stopped state because of a SIGUSR1
/// // signal, this will step the process forward and forward the user
/// // signal to the stopped process
/// match waitpid(Pid::from_raw(-1), None) {
/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => {
/// let _ = step(pid, Signal::SIGUSR1);
/// }
/// _ => {},
/// }
/// ```
#[cfg(
any(
any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"),
all(target_os = "openbsd", target_arch = "x86_64"),
all(target_os = "netbsd",
any(target_arch = "x86_64", target_arch = "powerpc")
)
)
)]
pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as c_int,
None => 0,
};
unsafe { ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(drop) }
}
/// Reads a word from a processes memory at the given address
pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> {
unsafe {
// Traditionally there was a difference between reading data or
// instruction memory but not in modern systems.
ptrace_other(Request::PT_READ_D, pid, addr, 0)
}
}
/// Writes a word into the processes memory at the given address
pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> {
unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(drop) }
}

View file

@ -0,0 +1,479 @@
//! For detailed description of the ptrace requests, consult `man ptrace`.
use cfg_if::cfg_if;
use std::{mem, ptr};
use crate::Result;
use crate::errno::Errno;
use libc::{self, c_void, c_long, siginfo_t};
use crate::unistd::Pid;
use crate::sys::signal::Signal;
pub type AddressType = *mut ::libc::c_void;
#[cfg(all(
target_os = "linux",
any(all(target_arch = "x86_64",
any(target_env = "gnu", target_env = "musl")),
all(target_arch = "x86", target_env = "gnu"))
))]
use libc::user_regs_struct;
cfg_if! {
if #[cfg(any(all(target_os = "linux", target_arch = "s390x"),
all(target_os = "linux", target_env = "gnu")))] {
#[doc(hidden)]
pub type RequestType = ::libc::c_uint;
} else {
#[doc(hidden)]
pub type RequestType = ::libc::c_int;
}
}
libc_enum!{
#[cfg_attr(not(any(target_env = "musl", target_os = "android")), repr(u32))]
#[cfg_attr(any(target_env = "musl", target_os = "android"), repr(i32))]
/// Ptrace Request enum defining the action to be taken.
#[non_exhaustive]
pub enum Request {
PTRACE_TRACEME,
PTRACE_PEEKTEXT,
PTRACE_PEEKDATA,
PTRACE_PEEKUSER,
PTRACE_POKETEXT,
PTRACE_POKEDATA,
PTRACE_POKEUSER,
PTRACE_CONT,
PTRACE_KILL,
PTRACE_SINGLESTEP,
#[cfg(any(all(target_os = "android", target_pointer_width = "32"),
all(target_os = "linux", any(target_env = "musl",
target_arch = "mips",
target_arch = "mips64",
target_arch = "x86_64",
target_pointer_width = "32"))))]
PTRACE_GETREGS,
#[cfg(any(all(target_os = "android", target_pointer_width = "32"),
all(target_os = "linux", any(target_env = "musl",
target_arch = "mips",
target_arch = "mips64",
target_arch = "x86_64",
target_pointer_width = "32"))))]
PTRACE_SETREGS,
#[cfg(any(all(target_os = "android", target_pointer_width = "32"),
all(target_os = "linux", any(target_env = "musl",
target_arch = "mips",
target_arch = "mips64",
target_arch = "x86_64",
target_pointer_width = "32"))))]
PTRACE_GETFPREGS,
#[cfg(any(all(target_os = "android", target_pointer_width = "32"),
all(target_os = "linux", any(target_env = "musl",
target_arch = "mips",
target_arch = "mips64",
target_arch = "x86_64",
target_pointer_width = "32"))))]
PTRACE_SETFPREGS,
PTRACE_ATTACH,
PTRACE_DETACH,
#[cfg(all(target_os = "linux", any(target_env = "musl",
target_arch = "mips",
target_arch = "mips64",
target_arch = "x86",
target_arch = "x86_64")))]
PTRACE_GETFPXREGS,
#[cfg(all(target_os = "linux", any(target_env = "musl",
target_arch = "mips",
target_arch = "mips64",
target_arch = "x86",
target_arch = "x86_64")))]
PTRACE_SETFPXREGS,
PTRACE_SYSCALL,
PTRACE_SETOPTIONS,
PTRACE_GETEVENTMSG,
PTRACE_GETSIGINFO,
PTRACE_SETSIGINFO,
#[cfg(all(target_os = "linux", not(any(target_arch = "mips",
target_arch = "mips64"))))]
PTRACE_GETREGSET,
#[cfg(all(target_os = "linux", not(any(target_arch = "mips",
target_arch = "mips64"))))]
PTRACE_SETREGSET,
#[cfg(target_os = "linux")]
PTRACE_SEIZE,
#[cfg(target_os = "linux")]
PTRACE_INTERRUPT,
#[cfg(all(target_os = "linux", not(any(target_arch = "mips",
target_arch = "mips64"))))]
PTRACE_LISTEN,
#[cfg(all(target_os = "linux", not(any(target_arch = "mips",
target_arch = "mips64"))))]
PTRACE_PEEKSIGINFO,
#[cfg(all(target_os = "linux", target_env = "gnu",
any(target_arch = "x86", target_arch = "x86_64")))]
PTRACE_SYSEMU,
#[cfg(all(target_os = "linux", target_env = "gnu",
any(target_arch = "x86", target_arch = "x86_64")))]
PTRACE_SYSEMU_SINGLESTEP,
}
}
libc_enum!{
#[repr(i32)]
/// Using the ptrace options the tracer can configure the tracee to stop
/// at certain events. This enum is used to define those events as defined
/// in `man ptrace`.
#[non_exhaustive]
pub enum Event {
/// Event that stops before a return from fork or clone.
PTRACE_EVENT_FORK,
/// Event that stops before a return from vfork or clone.
PTRACE_EVENT_VFORK,
/// Event that stops before a return from clone.
PTRACE_EVENT_CLONE,
/// Event that stops before a return from execve.
PTRACE_EVENT_EXEC,
/// Event for a return from vfork.
PTRACE_EVENT_VFORK_DONE,
/// Event for a stop before an exit. Unlike the waitpid Exit status program.
/// registers can still be examined
PTRACE_EVENT_EXIT,
/// Stop triggered by a seccomp rule on a tracee.
PTRACE_EVENT_SECCOMP,
/// Stop triggered by the `INTERRUPT` syscall, or a group stop,
/// or when a new child is attached.
PTRACE_EVENT_STOP,
}
}
libc_bitflags! {
/// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request.
/// See `man ptrace` for more details.
pub struct Options: libc::c_int {
/// When delivering system call traps set a bit to allow tracer to
/// distinguish between normal stops or syscall stops. May not work on
/// all systems.
PTRACE_O_TRACESYSGOOD;
/// Stop tracee at next fork and start tracing the forked process.
PTRACE_O_TRACEFORK;
/// Stop tracee at next vfork call and trace the vforked process.
PTRACE_O_TRACEVFORK;
/// Stop tracee at next clone call and trace the cloned process.
PTRACE_O_TRACECLONE;
/// Stop tracee at next execve call.
PTRACE_O_TRACEEXEC;
/// Stop tracee at vfork completion.
PTRACE_O_TRACEVFORKDONE;
/// Stop tracee at next exit call. Stops before exit commences allowing
/// tracer to see location of exit and register states.
PTRACE_O_TRACEEXIT;
/// Stop tracee when a SECCOMP_RET_TRACE rule is triggered. See `man seccomp` for more
/// details.
PTRACE_O_TRACESECCOMP;
/// Send a SIGKILL to the tracee if the tracer exits. This is useful
/// for ptrace jailers to prevent tracees from escaping their control.
#[cfg(any(target_os = "android", target_os = "linux"))]
PTRACE_O_EXITKILL;
}
}
fn ptrace_peek(request: Request, pid: Pid, addr: AddressType, data: *mut c_void) -> Result<c_long> {
let ret = unsafe {
Errno::clear();
libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data)
};
match Errno::result(ret) {
Ok(..) | Err(Errno::UnknownErrno) => Ok(ret),
err @ Err(..) => err,
}
}
/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)`
#[cfg(all(
target_os = "linux",
any(all(target_arch = "x86_64",
any(target_env = "gnu", target_env = "musl")),
all(target_arch = "x86", target_env = "gnu"))
))]
pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
ptrace_get_data::<user_regs_struct>(Request::PTRACE_GETREGS, pid)
}
/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
#[cfg(all(
target_os = "linux",
any(all(target_arch = "x86_64",
any(target_env = "gnu", target_env = "musl")),
all(target_arch = "x86", target_env = "gnu"))
))]
pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
let res = unsafe {
libc::ptrace(Request::PTRACE_SETREGS as RequestType,
libc::pid_t::from(pid),
ptr::null_mut::<c_void>(),
&regs as *const _ as *const c_void)
};
Errno::result(res).map(drop)
}
/// Function for ptrace requests that return values from the data field.
/// Some ptrace get requests populate structs or larger elements than `c_long`
/// and therefore use the data field to return values. This function handles these
/// requests.
fn ptrace_get_data<T>(request: Request, pid: Pid) -> Result<T> {
let mut data = mem::MaybeUninit::uninit();
let res = unsafe {
libc::ptrace(request as RequestType,
libc::pid_t::from(pid),
ptr::null_mut::<T>(),
data.as_mut_ptr() as *const _ as *const c_void)
};
Errno::result(res)?;
Ok(unsafe{ data.assume_init() })
}
unsafe fn ptrace_other(request: Request, pid: Pid, addr: AddressType, data: *mut c_void) -> Result<c_long> {
Errno::result(libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data)).map(|_| 0)
}
/// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`.
pub fn setoptions(pid: Pid, options: Options) -> Result<()> {
let res = unsafe {
libc::ptrace(Request::PTRACE_SETOPTIONS as RequestType,
libc::pid_t::from(pid),
ptr::null_mut::<c_void>(),
options.bits() as *mut c_void)
};
Errno::result(res).map(drop)
}
/// Gets a ptrace event as described by `ptrace(PTRACE_GETEVENTMSG,...)`
pub fn getevent(pid: Pid) -> Result<c_long> {
ptrace_get_data::<c_long>(Request::PTRACE_GETEVENTMSG, pid)
}
/// Get siginfo as with `ptrace(PTRACE_GETSIGINFO,...)`
pub fn getsiginfo(pid: Pid) -> Result<siginfo_t> {
ptrace_get_data::<siginfo_t>(Request::PTRACE_GETSIGINFO, pid)
}
/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)`
pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> {
let ret = unsafe{
Errno::clear();
libc::ptrace(Request::PTRACE_SETSIGINFO as RequestType,
libc::pid_t::from(pid),
ptr::null_mut::<c_void>(),
sig as *const _ as *const c_void)
};
match Errno::result(ret) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
/// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)`
///
/// Indicates that this process is to be traced by its parent.
/// This is the only ptrace request to be issued by the tracee.
pub fn traceme() -> Result<()> {
unsafe {
ptrace_other(
Request::PTRACE_TRACEME,
Pid::from_raw(0),
ptr::null_mut(),
ptr::null_mut(),
).map(drop) // ignore the useless return value
}
}
/// Continue execution until the next syscall, as with `ptrace(PTRACE_SYSCALL, ...)`
///
/// Arranges for the tracee to be stopped at the next entry to or exit from a system call,
/// optionally delivering a signal specified by `sig`.
pub fn syscall<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(
Request::PTRACE_SYSCALL,
pid,
ptr::null_mut(),
data,
).map(drop) // ignore the useless return value
}
}
/// Continue execution until the next syscall, as with `ptrace(PTRACE_SYSEMU, ...)`
///
/// In contrast to the `syscall` function, the syscall stopped at will not be executed.
/// Thus the the tracee will only be stopped once per syscall,
/// optionally delivering a signal specified by `sig`.
#[cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86", target_arch = "x86_64")))]
pub fn sysemu<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(Request::PTRACE_SYSEMU, pid, ptr::null_mut(), data).map(drop)
// ignore the useless return value
}
}
/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)`
///
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
unsafe {
ptrace_other(
Request::PTRACE_ATTACH,
pid,
ptr::null_mut(),
ptr::null_mut(),
).map(drop) // ignore the useless return value
}
}
/// Attach to a running process, as with `ptrace(PTRACE_SEIZE, ...)`
///
/// Attaches to the process specified in pid, making it a tracee of the calling process.
#[cfg(target_os = "linux")]
pub fn seize(pid: Pid, options: Options) -> Result<()> {
unsafe {
ptrace_other(
Request::PTRACE_SEIZE,
pid,
ptr::null_mut(),
options.bits() as *mut c_void,
).map(drop) // ignore the useless return value
}
}
/// Detaches the current running process, as with `ptrace(PTRACE_DETACH, ...)`
///
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
/// signal specified by `sig`.
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(
Request::PTRACE_DETACH,
pid,
ptr::null_mut(),
data
).map(drop)
}
}
/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
///
/// Continues the execution of the process with PID `pid`, optionally
/// delivering a signal specified by `sig`.
pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(Request::PTRACE_CONT, pid, ptr::null_mut(), data).map(drop) // ignore the useless return value
}
}
/// Stop a tracee, as with `ptrace(PTRACE_INTERRUPT, ...)`
///
/// This request is equivalent to `ptrace(PTRACE_INTERRUPT, ...)`
#[cfg(target_os = "linux")]
pub fn interrupt(pid: Pid) -> Result<()> {
unsafe {
ptrace_other(Request::PTRACE_INTERRUPT, pid, ptr::null_mut(), ptr::null_mut()).map(drop)
}
}
/// Issues a kill request as with `ptrace(PTRACE_KILL, ...)`
///
/// This request is equivalent to `ptrace(PTRACE_CONT, ..., SIGKILL);`
pub fn kill(pid: Pid) -> Result<()> {
unsafe {
ptrace_other(Request::PTRACE_KILL, pid, ptr::null_mut(), ptr::null_mut()).map(drop)
}
}
/// Move the stopped tracee process forward by a single step as with
/// `ptrace(PTRACE_SINGLESTEP, ...)`
///
/// Advances the execution of the process with PID `pid` by a single step optionally delivering a
/// signal specified by `sig`.
///
/// # Example
/// ```rust
/// use nix::sys::ptrace::step;
/// use nix::unistd::Pid;
/// use nix::sys::signal::Signal;
/// use nix::sys::wait::*;
///
/// // If a process changes state to the stopped state because of a SIGUSR1
/// // signal, this will step the process forward and forward the user
/// // signal to the stopped process
/// match waitpid(Pid::from_raw(-1), None) {
/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => {
/// let _ = step(pid, Signal::SIGUSR1);
/// }
/// _ => {},
/// }
/// ```
pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data).map(drop)
}
}
/// Move the stopped tracee process forward by a single step or stop at the next syscall
/// as with `ptrace(PTRACE_SYSEMU_SINGLESTEP, ...)`
///
/// Advances the execution by a single step or until the next syscall.
/// In case the tracee is stopped at a syscall, the syscall will not be executed.
/// Optionally, the signal specified by `sig` is delivered to the tracee upon continuation.
#[cfg(all(target_os = "linux", target_env = "gnu", any(target_arch = "x86", target_arch = "x86_64")))]
pub fn sysemu_step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(
Request::PTRACE_SYSEMU_SINGLESTEP,
pid,
ptr::null_mut(),
data,
)
.map(drop) // ignore the useless return value
}
}
/// Reads a word from a processes memory at the given address
pub fn read(pid: Pid, addr: AddressType) -> Result<c_long> {
ptrace_peek(Request::PTRACE_PEEKDATA, pid, addr, ptr::null_mut())
}
/// Writes a word into the processes memory at the given address
///
/// # Safety
///
/// The `data` argument is passed directly to `ptrace(2)`. Read that man page
/// for guidance.
pub unsafe fn write(
pid: Pid,
addr: AddressType,
data: *mut c_void) -> Result<()>
{
ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(drop)
}

View file

@ -0,0 +1,22 @@
///! Provides helpers for making ptrace system calls
#[cfg(any(target_os = "android", target_os = "linux"))]
mod linux;
#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::linux::*;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
mod bsd;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
))]
pub use self::bsd::*;

View file

@ -0,0 +1,277 @@
//! Set and configure disk quotas for users, groups, or projects.
//!
//! # Examples
//!
//! Enabling and setting a quota:
//!
//! ```rust,no_run
//! # use nix::sys::quota::{Dqblk, quotactl_on, quotactl_set, QuotaFmt, QuotaType, QuotaValidFlags};
//! quotactl_on(QuotaType::USRQUOTA, "/dev/sda1", QuotaFmt::QFMT_VFS_V1, "aquota.user");
//! let mut dqblk: Dqblk = Default::default();
//! dqblk.set_blocks_hard_limit(10000);
//! dqblk.set_blocks_soft_limit(8000);
//! quotactl_set(QuotaType::USRQUOTA, "/dev/sda1", 50, &dqblk, QuotaValidFlags::QIF_BLIMITS);
//! ```
use std::default::Default;
use std::{mem, ptr};
use libc::{self, c_int, c_char};
use crate::{Result, NixPath};
use crate::errno::Errno;
struct QuotaCmd(QuotaSubCmd, QuotaType);
impl QuotaCmd {
#[allow(unused_unsafe)]
fn as_int(&self) -> c_int {
unsafe { libc::QCMD(self.0 as i32, self.1 as i32) }
}
}
// linux quota version >= 2
libc_enum!{
#[repr(i32)]
enum QuotaSubCmd {
Q_SYNC,
Q_QUOTAON,
Q_QUOTAOFF,
Q_GETQUOTA,
Q_SETQUOTA,
}
}
libc_enum!{
/// The scope of the quota.
#[repr(i32)]
#[non_exhaustive]
pub enum QuotaType {
/// Specify a user quota
USRQUOTA,
/// Specify a group quota
GRPQUOTA,
}
}
libc_enum!{
/// The type of quota format to use.
#[repr(i32)]
#[non_exhaustive]
pub enum QuotaFmt {
/// Use the original quota format.
QFMT_VFS_OLD,
/// Use the standard VFS v0 quota format.
///
/// Handles 32-bit UIDs/GIDs and quota limits up to 2<sup>32</sup> bytes/2<sup>32</sup> inodes.
QFMT_VFS_V0,
/// Use the VFS v1 quota format.
///
/// Handles 32-bit UIDs/GIDs and quota limits of 2<sup>64</sup> bytes/2<sup>64</sup> inodes.
QFMT_VFS_V1,
}
}
libc_bitflags!(
/// Indicates the quota fields that are valid to read from.
#[derive(Default)]
pub struct QuotaValidFlags: u32 {
/// The block hard & soft limit fields.
QIF_BLIMITS;
/// The current space field.
QIF_SPACE;
/// The inode hard & soft limit fields.
QIF_ILIMITS;
/// The current inodes field.
QIF_INODES;
/// The disk use time limit field.
QIF_BTIME;
/// The file quote time limit field.
QIF_ITIME;
/// All block & inode limits.
QIF_LIMITS;
/// The space & inodes usage fields.
QIF_USAGE;
/// The time limit fields.
QIF_TIMES;
/// All fields.
QIF_ALL;
}
);
/// Wrapper type for `if_dqblk`
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Dqblk(libc::dqblk);
impl Default for Dqblk {
fn default() -> Dqblk {
Dqblk(libc::dqblk {
dqb_bhardlimit: 0,
dqb_bsoftlimit: 0,
dqb_curspace: 0,
dqb_ihardlimit: 0,
dqb_isoftlimit: 0,
dqb_curinodes: 0,
dqb_btime: 0,
dqb_itime: 0,
dqb_valid: 0,
})
}
}
impl Dqblk {
/// The absolute limit on disk quota blocks allocated.
pub fn blocks_hard_limit(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
Some(self.0.dqb_bhardlimit)
} else {
None
}
}
/// Set the absolute limit on disk quota blocks allocated.
pub fn set_blocks_hard_limit(&mut self, limit: u64) {
self.0.dqb_bhardlimit = limit;
}
/// Preferred limit on disk quota blocks
pub fn blocks_soft_limit(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_BLIMITS) {
Some(self.0.dqb_bsoftlimit)
} else {
None
}
}
/// Set the preferred limit on disk quota blocks allocated.
pub fn set_blocks_soft_limit(&mut self, limit: u64) {
self.0.dqb_bsoftlimit = limit;
}
/// Current occupied space (bytes).
pub fn occupied_space(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_SPACE) {
Some(self.0.dqb_curspace)
} else {
None
}
}
/// Maximum number of allocated inodes.
pub fn inodes_hard_limit(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
Some(self.0.dqb_ihardlimit)
} else {
None
}
}
/// Set the maximum number of allocated inodes.
pub fn set_inodes_hard_limit(&mut self, limit: u64) {
self.0.dqb_ihardlimit = limit;
}
/// Preferred inode limit
pub fn inodes_soft_limit(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_ILIMITS) {
Some(self.0.dqb_isoftlimit)
} else {
None
}
}
/// Set the preferred limit of allocated inodes.
pub fn set_inodes_soft_limit(&mut self, limit: u64) {
self.0.dqb_isoftlimit = limit;
}
/// Current number of allocated inodes.
pub fn allocated_inodes(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_INODES) {
Some(self.0.dqb_curinodes)
} else {
None
}
}
/// Time limit for excessive disk use.
pub fn block_time_limit(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_BTIME) {
Some(self.0.dqb_btime)
} else {
None
}
}
/// Set the time limit for excessive disk use.
pub fn set_block_time_limit(&mut self, limit: u64) {
self.0.dqb_btime = limit;
}
/// Time limit for excessive files.
pub fn inode_time_limit(&self) -> Option<u64> {
let valid_fields = QuotaValidFlags::from_bits_truncate(self.0.dqb_valid);
if valid_fields.contains(QuotaValidFlags::QIF_ITIME) {
Some(self.0.dqb_itime)
} else {
None
}
}
/// Set the time limit for excessive files.
pub fn set_inode_time_limit(&mut self, limit: u64) {
self.0.dqb_itime = limit;
}
}
fn quotactl<P: ?Sized + NixPath>(cmd: QuotaCmd, special: Option<&P>, id: c_int, addr: *mut c_char) -> Result<()> {
unsafe {
Errno::clear();
let res = match special {
Some(dev) => dev.with_nix_path(|path| libc::quotactl(cmd.as_int(), path.as_ptr(), id, addr)),
None => Ok(libc::quotactl(cmd.as_int(), ptr::null(), id, addr)),
}?;
Errno::result(res).map(drop)
}
}
/// Turn on disk quotas for a block device.
pub fn quotactl_on<P: ?Sized + NixPath>(which: QuotaType, special: &P, format: QuotaFmt, quota_file: &P) -> Result<()> {
quota_file.with_nix_path(|path| {
let mut path_copy = path.to_bytes_with_nul().to_owned();
let p: *mut c_char = path_copy.as_mut_ptr() as *mut c_char;
quotactl(QuotaCmd(QuotaSubCmd::Q_QUOTAON, which), Some(special), format as c_int, p)
})?
}
/// Disable disk quotas for a block device.
pub fn quotactl_off<P: ?Sized + NixPath>(which: QuotaType, special: &P) -> Result<()> {
quotactl(QuotaCmd(QuotaSubCmd::Q_QUOTAOFF, which), Some(special), 0, ptr::null_mut())
}
/// Update the on-disk copy of quota usages for a filesystem.
///
/// If `special` is `None`, then all file systems with active quotas are sync'd.
pub fn quotactl_sync<P: ?Sized + NixPath>(which: QuotaType, special: Option<&P>) -> Result<()> {
quotactl(QuotaCmd(QuotaSubCmd::Q_SYNC, which), special, 0, ptr::null_mut())
}
/// Get disk quota limits and current usage for the given user/group id.
pub fn quotactl_get<P: ?Sized + NixPath>(which: QuotaType, special: &P, id: c_int) -> Result<Dqblk> {
let mut dqblk = mem::MaybeUninit::uninit();
quotactl(QuotaCmd(QuotaSubCmd::Q_GETQUOTA, which), Some(special), id, dqblk.as_mut_ptr() as *mut c_char)?;
Ok(unsafe{ Dqblk(dqblk.assume_init())})
}
/// Configure quota values for the specified fields for a given user/group id.
pub fn quotactl_set<P: ?Sized + NixPath>(which: QuotaType, special: &P, id: c_int, dqblk: &Dqblk, fields: QuotaValidFlags) -> Result<()> {
let mut dqblk_copy = *dqblk;
dqblk_copy.0.dqb_valid = fields.bits();
quotactl(QuotaCmd(QuotaSubCmd::Q_SETQUOTA, which), Some(special), id, &mut dqblk_copy as *mut _ as *mut c_char)
}

View file

@ -0,0 +1,45 @@
//! Reboot/shutdown or enable/disable Ctrl-Alt-Delete.
use crate::Result;
use crate::errno::Errno;
use std::convert::Infallible;
use std::mem::drop;
libc_enum! {
/// How exactly should the system be rebooted.
///
/// See [`set_cad_enabled()`](fn.set_cad_enabled.html) for
/// enabling/disabling Ctrl-Alt-Delete.
#[repr(i32)]
#[non_exhaustive]
pub enum RebootMode {
RB_HALT_SYSTEM,
RB_KEXEC,
RB_POWER_OFF,
RB_AUTOBOOT,
// we do not support Restart2,
RB_SW_SUSPEND,
}
}
pub fn reboot(how: RebootMode) -> Result<Infallible> {
unsafe {
libc::reboot(how as libc::c_int)
};
Err(Errno::last())
}
/// Enable or disable the reboot keystroke (Ctrl-Alt-Delete).
///
/// Corresponds to calling `reboot(RB_ENABLE_CAD)` or `reboot(RB_DISABLE_CAD)` in C.
pub fn set_cad_enabled(enable: bool) -> Result<()> {
let cmd = if enable {
libc::RB_ENABLE_CAD
} else {
libc::RB_DISABLE_CAD
};
let res = unsafe {
libc::reboot(cmd)
};
Errno::result(res).map(drop)
}

View file

@ -0,0 +1,233 @@
//! Configure the process resource limits.
use cfg_if::cfg_if;
use crate::errno::Errno;
use crate::Result;
pub use libc::rlim_t;
use std::mem;
cfg_if! {
if #[cfg(all(target_os = "linux", target_env = "gnu"))]{
use libc::{__rlimit_resource_t, rlimit, RLIM_INFINITY};
}else if #[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "dragonfly",
all(target_os = "linux", not(target_env = "gnu"))
))]{
use libc::{c_int, rlimit, RLIM_INFINITY};
}
}
libc_enum! {
/// The Resource enum is platform dependent. Check different platform
/// manuals for more details. Some platform links has been provided for
/// earier reference (non-exhaustive).
///
/// * [Linux](https://man7.org/linux/man-pages/man2/getrlimit.2.html)
/// * [FreeBSD](https://www.freebsd.org/cgi/man.cgi?query=setrlimit)
// linux-gnu uses u_int as resource enum, which is implemented in libc as
// well.
//
// https://gcc.gnu.org/legacy-ml/gcc/2015-08/msg00441.html
// https://github.com/rust-lang/libc/blob/master/src/unix/linux_like/linux/gnu/mod.rs
#[cfg_attr(all(target_os = "linux", target_env = "gnu"), repr(u32))]
#[cfg_attr(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios",
target_os = "android",
target_os = "dragonfly",
all(target_os = "linux", not(target_env = "gnu"))
), repr(i32))]
#[non_exhaustive]
pub enum Resource {
#[cfg(not(any(
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
/// The maximum amount (in bytes) of virtual memory the process is
/// allowed to map.
RLIMIT_AS,
/// The largest size (in bytes) core(5) file that may be created.
RLIMIT_CORE,
/// The maximum amount of cpu time (in seconds) to be used by each
/// process.
RLIMIT_CPU,
/// The maximum size (in bytes) of the data segment for a process
RLIMIT_DATA,
/// The largest size (in bytes) file that may be created.
RLIMIT_FSIZE,
/// The maximum number of open files for this process.
RLIMIT_NOFILE,
/// The maximum size (in bytes) of the stack segment for a process.
RLIMIT_STACK,
#[cfg(target_os = "freebsd")]
/// The maximum number of kqueues this user id is allowed to create.
RLIMIT_KQUEUES,
#[cfg(any(target_os = "android", target_os = "linux"))]
/// A limit on the combined number of flock locks and fcntl leases that
/// this process may establish.
RLIMIT_LOCKS,
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "linux"))]
/// The maximum size (in bytes) which a process may lock into memory
/// using the mlock(2) system call.
RLIMIT_MEMLOCK,
#[cfg(any(target_os = "android", target_os = "linux"))]
/// A limit on the number of bytes that can be allocated for POSIX
/// message queues for the real user ID of the calling process.
RLIMIT_MSGQUEUE,
#[cfg(any(target_os = "android", target_os = "linux"))]
/// A ceiling to which the process's nice value can be raised using
/// setpriority or nice.
RLIMIT_NICE,
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "linux"))]
/// The maximum number of simultaneous processes for this user id.
RLIMIT_NPROC,
#[cfg(target_os = "freebsd")]
/// The maximum number of pseudo-terminals this user id is allowed to
/// create.
RLIMIT_NPTS,
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "linux"))]
/// When there is memory pressure and swap is available, prioritize
/// eviction of a process' resident pages beyond this amount (in bytes).
RLIMIT_RSS,
#[cfg(any(target_os = "android", target_os = "linux"))]
/// A ceiling on the real-time priority that may be set for this process
/// using sched_setscheduler and sched_set param.
RLIMIT_RTPRIO,
#[cfg(any(target_os = "linux"))]
/// A limit (in microseconds) on the amount of CPU time that a process
/// scheduled under a real-time scheduling policy may con sume without
/// making a blocking system call.
RLIMIT_RTTIME,
#[cfg(any(target_os = "android", target_os = "linux"))]
/// A limit on the number of signals that may be queued for the real
/// user ID of the calling process.
RLIMIT_SIGPENDING,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
/// The maximum size (in bytes) of socket buffer usage for this user.
RLIMIT_SBSIZE,
#[cfg(target_os = "freebsd")]
/// The maximum size (in bytes) of the swap space that may be reserved
/// or used by all of this user id's processes.
RLIMIT_SWAP,
#[cfg(target_os = "freebsd")]
/// An alias for RLIMIT_AS.
RLIMIT_VMEM,
}
}
/// Get the current processes resource limits
///
/// A value of `None` indicates the value equals to `RLIM_INFINITY` which means
/// there is no limit.
///
/// # Parameters
///
/// * `resource`: The [`Resource`] that we want to get the limits of.
///
/// # Examples
///
/// ```
/// # use nix::sys::resource::{getrlimit, Resource};
///
/// let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap();
/// println!("current soft_limit: {:?}", soft_limit);
/// println!("current hard_limit: {:?}", hard_limit);
/// ```
///
/// # References
///
/// [getrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215)
///
/// [`Resource`]: enum.Resource.html
pub fn getrlimit(resource: Resource) -> Result<(Option<rlim_t>, Option<rlim_t>)> {
let mut old_rlim = mem::MaybeUninit::<rlimit>::uninit();
cfg_if! {
if #[cfg(all(target_os = "linux", target_env = "gnu"))]{
let res = unsafe { libc::getrlimit(resource as __rlimit_resource_t, old_rlim.as_mut_ptr()) };
}else{
let res = unsafe { libc::getrlimit(resource as c_int, old_rlim.as_mut_ptr()) };
}
}
Errno::result(res).map(|_| {
let rlimit { rlim_cur, rlim_max } = unsafe { old_rlim.assume_init() };
(Some(rlim_cur), Some(rlim_max))
})
}
/// Set the current processes resource limits
///
/// # Parameters
///
/// * `resource`: The [`Resource`] that we want to set the limits of.
/// * `soft_limit`: The value that the kernel enforces for the corresponding
/// resource. Note: `None` input will be replaced by constant `RLIM_INFINITY`.
/// * `hard_limit`: The ceiling for the soft limit. Must be lower or equal to
/// the current hard limit for non-root users. Note: `None` input will be
/// replaced by constant `RLIM_INFINITY`.
///
/// > Note: for some os (linux_gnu), setting hard_limit to `RLIM_INFINITY` can
/// > results `EPERM` Error. So you will need to set the number explicitly.
///
/// # Examples
///
/// ```
/// # use nix::sys::resource::{setrlimit, Resource};
///
/// let soft_limit = Some(512);
/// let hard_limit = Some(1024);
/// setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap();
/// ```
///
/// # References
///
/// [setrlimit(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html#tag_16_215)
///
/// [`Resource`]: enum.Resource.html
///
/// Note: `setrlimit` provides a safe wrapper to libc's `setrlimit`.
pub fn setrlimit(
resource: Resource,
soft_limit: Option<rlim_t>,
hard_limit: Option<rlim_t>,
) -> Result<()> {
let new_rlim = rlimit {
rlim_cur: soft_limit.unwrap_or(RLIM_INFINITY),
rlim_max: hard_limit.unwrap_or(RLIM_INFINITY),
};
cfg_if! {
if #[cfg(all(target_os = "linux", target_env = "gnu"))]{
let res = unsafe { libc::setrlimit(resource as __rlimit_resource_t, &new_rlim as *const rlimit) };
}else{
let res = unsafe { libc::setrlimit(resource as c_int, &new_rlim as *const rlimit) };
}
}
Errno::result(res).map(drop)
}

View file

@ -0,0 +1,430 @@
//! Portably monitor a group of file descriptors for readiness.
use std::convert::TryFrom;
use std::iter::FusedIterator;
use std::mem;
use std::ops::Range;
use std::os::unix::io::RawFd;
use std::ptr::{null, null_mut};
use libc::{self, c_int};
use crate::Result;
use crate::errno::Errno;
use crate::sys::signal::SigSet;
use crate::sys::time::{TimeSpec, TimeVal};
pub use libc::FD_SETSIZE;
/// Contains a set of file descriptors used by [`select`]
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct FdSet(libc::fd_set);
fn assert_fd_valid(fd: RawFd) {
assert!(
usize::try_from(fd).map_or(false, |fd| fd < FD_SETSIZE),
"fd must be in the range 0..FD_SETSIZE",
);
}
impl FdSet {
/// Create an empty `FdSet`
pub fn new() -> FdSet {
let mut fdset = mem::MaybeUninit::uninit();
unsafe {
libc::FD_ZERO(fdset.as_mut_ptr());
FdSet(fdset.assume_init())
}
}
/// Add a file descriptor to an `FdSet`
pub fn insert(&mut self, fd: RawFd) {
assert_fd_valid(fd);
unsafe { libc::FD_SET(fd, &mut self.0) };
}
/// Remove a file descriptor from an `FdSet`
pub fn remove(&mut self, fd: RawFd) {
assert_fd_valid(fd);
unsafe { libc::FD_CLR(fd, &mut self.0) };
}
/// Test an `FdSet` for the presence of a certain file descriptor.
pub fn contains(&self, fd: RawFd) -> bool {
assert_fd_valid(fd);
unsafe { libc::FD_ISSET(fd, &self.0) }
}
/// Remove all file descriptors from this `FdSet`.
pub fn clear(&mut self) {
unsafe { libc::FD_ZERO(&mut self.0) };
}
/// Finds the highest file descriptor in the set.
///
/// Returns `None` if the set is empty.
///
/// This can be used to calculate the `nfds` parameter of the [`select`] function.
///
/// # Example
///
/// ```
/// # use nix::sys::select::FdSet;
/// let mut set = FdSet::new();
/// set.insert(4);
/// set.insert(9);
/// assert_eq!(set.highest(), Some(9));
/// ```
///
/// [`select`]: fn.select.html
pub fn highest(&self) -> Option<RawFd> {
self.fds(None).next_back()
}
/// Returns an iterator over the file descriptors in the set.
///
/// For performance, it takes an optional higher bound: the iterator will
/// not return any elements of the set greater than the given file
/// descriptor.
///
/// # Examples
///
/// ```
/// # use nix::sys::select::FdSet;
/// # use std::os::unix::io::RawFd;
/// let mut set = FdSet::new();
/// set.insert(4);
/// set.insert(9);
/// let fds: Vec<RawFd> = set.fds(None).collect();
/// assert_eq!(fds, vec![4, 9]);
/// ```
#[inline]
pub fn fds(&self, highest: Option<RawFd>) -> Fds {
Fds {
set: self,
range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE),
}
}
}
impl Default for FdSet {
fn default() -> Self {
Self::new()
}
}
/// Iterator over `FdSet`.
#[derive(Debug)]
pub struct Fds<'a> {
set: &'a FdSet,
range: Range<usize>,
}
impl<'a> Iterator for Fds<'a> {
type Item = RawFd;
fn next(&mut self) -> Option<RawFd> {
for i in &mut self.range {
if self.set.contains(i as RawFd) {
return Some(i as RawFd);
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let (_, upper) = self.range.size_hint();
(0, upper)
}
}
impl<'a> DoubleEndedIterator for Fds<'a> {
#[inline]
fn next_back(&mut self) -> Option<RawFd> {
while let Some(i) = self.range.next_back() {
if self.set.contains(i as RawFd) {
return Some(i as RawFd);
}
}
None
}
}
impl<'a> FusedIterator for Fds<'a> {}
/// Monitors file descriptors for readiness
///
/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all
/// file descriptors that are ready for the given operation are set.
///
/// When this function returns, `timeout` has an implementation-defined value.
///
/// # Parameters
///
/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this
/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1
/// to the maximum of that.
/// * `readfds`: File descriptors to check for being ready to read.
/// * `writefds`: File descriptors to check for being ready to write.
/// * `errorfds`: File descriptors to check for pending error conditions.
/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block
/// indefinitely).
///
/// # References
///
/// [select(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html)
///
/// [`FdSet::highest`]: struct.FdSet.html#method.highest
pub fn select<'a, N, R, W, E, T>(nfds: N,
readfds: R,
writefds: W,
errorfds: E,
timeout: T) -> Result<c_int>
where
N: Into<Option<c_int>>,
R: Into<Option<&'a mut FdSet>>,
W: Into<Option<&'a mut FdSet>>,
E: Into<Option<&'a mut FdSet>>,
T: Into<Option<&'a mut TimeVal>>,
{
let mut readfds = readfds.into();
let mut writefds = writefds.into();
let mut errorfds = errorfds.into();
let timeout = timeout.into();
let nfds = nfds.into().unwrap_or_else(|| {
readfds.iter_mut()
.chain(writefds.iter_mut())
.chain(errorfds.iter_mut())
.map(|set| set.highest().unwrap_or(-1))
.max()
.unwrap_or(-1) + 1
});
let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
let timeout = timeout.map(|tv| tv as *mut _ as *mut libc::timeval)
.unwrap_or(null_mut());
let res = unsafe {
libc::select(nfds, readfds, writefds, errorfds, timeout)
};
Errno::result(res)
}
/// Monitors file descriptors for readiness with an altered signal mask.
///
/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all
/// file descriptors that are ready for the given operation are set.
///
/// When this function returns, the original signal mask is restored.
///
/// Unlike [`select`](#fn.select), `pselect` does not mutate the `timeout` value.
///
/// # Parameters
///
/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this
/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1
/// to the maximum of that.
/// * `readfds`: File descriptors to check for read readiness
/// * `writefds`: File descriptors to check for write readiness
/// * `errorfds`: File descriptors to check for pending error conditions.
/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block
/// indefinitely).
/// * `sigmask`: Signal mask to activate while waiting for file descriptors to turn
/// ready (`None` to set no alternative signal mask).
///
/// # References
///
/// [pselect(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html)
///
/// [The new pselect() system call](https://lwn.net/Articles/176911/)
///
/// [`FdSet::highest`]: struct.FdSet.html#method.highest
pub fn pselect<'a, N, R, W, E, T, S>(nfds: N,
readfds: R,
writefds: W,
errorfds: E,
timeout: T,
sigmask: S) -> Result<c_int>
where
N: Into<Option<c_int>>,
R: Into<Option<&'a mut FdSet>>,
W: Into<Option<&'a mut FdSet>>,
E: Into<Option<&'a mut FdSet>>,
T: Into<Option<&'a TimeSpec>>,
S: Into<Option<&'a SigSet>>,
{
let mut readfds = readfds.into();
let mut writefds = writefds.into();
let mut errorfds = errorfds.into();
let sigmask = sigmask.into();
let timeout = timeout.into();
let nfds = nfds.into().unwrap_or_else(|| {
readfds.iter_mut()
.chain(writefds.iter_mut())
.chain(errorfds.iter_mut())
.map(|set| set.highest().unwrap_or(-1))
.max()
.unwrap_or(-1) + 1
});
let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
let timeout = timeout.map(|ts| ts.as_ref() as *const libc::timespec).unwrap_or(null());
let sigmask = sigmask.map(|sm| sm.as_ref() as *const libc::sigset_t).unwrap_or(null());
let res = unsafe {
libc::pselect(nfds, readfds, writefds, errorfds, timeout, sigmask)
};
Errno::result(res)
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::unix::io::RawFd;
use crate::sys::time::{TimeVal, TimeValLike};
use crate::unistd::{write, pipe};
#[test]
fn fdset_insert() {
let mut fd_set = FdSet::new();
for i in 0..FD_SETSIZE {
assert!(!fd_set.contains(i as RawFd));
}
fd_set.insert(7);
assert!(fd_set.contains(7));
}
#[test]
fn fdset_remove() {
let mut fd_set = FdSet::new();
for i in 0..FD_SETSIZE {
assert!(!fd_set.contains(i as RawFd));
}
fd_set.insert(7);
fd_set.remove(7);
for i in 0..FD_SETSIZE {
assert!(!fd_set.contains(i as RawFd));
}
}
#[test]
fn fdset_clear() {
let mut fd_set = FdSet::new();
fd_set.insert(1);
fd_set.insert((FD_SETSIZE / 2) as RawFd);
fd_set.insert((FD_SETSIZE - 1) as RawFd);
fd_set.clear();
for i in 0..FD_SETSIZE {
assert!(!fd_set.contains(i as RawFd));
}
}
#[test]
fn fdset_highest() {
let mut set = FdSet::new();
assert_eq!(set.highest(), None);
set.insert(0);
assert_eq!(set.highest(), Some(0));
set.insert(90);
assert_eq!(set.highest(), Some(90));
set.remove(0);
assert_eq!(set.highest(), Some(90));
set.remove(90);
assert_eq!(set.highest(), None);
set.insert(4);
set.insert(5);
set.insert(7);
assert_eq!(set.highest(), Some(7));
}
#[test]
fn fdset_fds() {
let mut set = FdSet::new();
assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![]);
set.insert(0);
assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0]);
set.insert(90);
assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0, 90]);
// highest limit
assert_eq!(set.fds(Some(89)).collect::<Vec<_>>(), vec![0]);
assert_eq!(set.fds(Some(90)).collect::<Vec<_>>(), vec![0, 90]);
}
#[test]
fn test_select() {
let (r1, w1) = pipe().unwrap();
write(w1, b"hi!").unwrap();
let (r2, _w2) = pipe().unwrap();
let mut fd_set = FdSet::new();
fd_set.insert(r1);
fd_set.insert(r2);
let mut timeout = TimeVal::seconds(10);
assert_eq!(1, select(None,
&mut fd_set,
None,
None,
&mut timeout).unwrap());
assert!(fd_set.contains(r1));
assert!(!fd_set.contains(r2));
}
#[test]
fn test_select_nfds() {
let (r1, w1) = pipe().unwrap();
write(w1, b"hi!").unwrap();
let (r2, _w2) = pipe().unwrap();
let mut fd_set = FdSet::new();
fd_set.insert(r1);
fd_set.insert(r2);
let mut timeout = TimeVal::seconds(10);
assert_eq!(1, select(Some(fd_set.highest().unwrap() + 1),
&mut fd_set,
None,
None,
&mut timeout).unwrap());
assert!(fd_set.contains(r1));
assert!(!fd_set.contains(r2));
}
#[test]
fn test_select_nfds2() {
let (r1, w1) = pipe().unwrap();
write(w1, b"hi!").unwrap();
let (r2, _w2) = pipe().unwrap();
let mut fd_set = FdSet::new();
fd_set.insert(r1);
fd_set.insert(r2);
let mut timeout = TimeVal::seconds(10);
assert_eq!(1, select(::std::cmp::max(r1, r2) + 1,
&mut fd_set,
None,
None,
&mut timeout).unwrap());
assert!(fd_set.contains(r1));
assert!(!fd_set.contains(r2));
}
}

View file

@ -0,0 +1,231 @@
//! Send data from a file to a socket, bypassing userland.
use cfg_if::cfg_if;
use std::os::unix::io::RawFd;
use std::ptr;
use libc::{self, off_t};
use crate::Result;
use crate::errno::Errno;
/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`.
///
/// Returns a `Result` with the number of bytes written.
///
/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will
/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified
/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to
/// the byte after the last byte copied.
///
/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket.
///
/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html)
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn sendfile(
out_fd: RawFd,
in_fd: RawFd,
offset: Option<&mut off_t>,
count: usize,
) -> Result<usize> {
let offset = offset
.map(|offset| offset as *mut _)
.unwrap_or(ptr::null_mut());
let ret = unsafe { libc::sendfile(out_fd, in_fd, offset, count) };
Errno::result(ret).map(|r| r as usize)
}
/// Copy up to `count` bytes to `out_fd` from `in_fd` starting at `offset`.
///
/// Returns a `Result` with the number of bytes written.
///
/// If `offset` is `None`, `sendfile` will begin reading at the current offset of `in_fd`and will
/// update the offset of `in_fd`. If `offset` is `Some`, `sendfile` will begin at the specified
/// offset and will not update the offset of `in_fd`. Instead, it will mutate `offset` to point to
/// the byte after the last byte copied.
///
/// `in_fd` must support `mmap`-like operations and therefore cannot be a socket.
///
/// For more information, see [the sendfile(2) man page.](https://man7.org/linux/man-pages/man2/sendfile.2.html)
#[cfg(target_os = "linux")]
pub fn sendfile64(
out_fd: RawFd,
in_fd: RawFd,
offset: Option<&mut libc::off64_t>,
count: usize,
) -> Result<usize> {
let offset = offset
.map(|offset| offset as *mut _)
.unwrap_or(ptr::null_mut());
let ret = unsafe { libc::sendfile64(out_fd, in_fd, offset, count) };
Errno::result(ret).map(|r| r as usize)
}
cfg_if! {
if #[cfg(any(target_os = "freebsd",
target_os = "ios",
target_os = "macos"))] {
use crate::sys::uio::IoVec;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct SendfileHeaderTrailer<'a>(
libc::sf_hdtr,
Option<Vec<IoVec<&'a [u8]>>>,
Option<Vec<IoVec<&'a [u8]>>>,
);
impl<'a> SendfileHeaderTrailer<'a> {
fn new(
headers: Option<&'a [&'a [u8]]>,
trailers: Option<&'a [&'a [u8]]>
) -> SendfileHeaderTrailer<'a> {
let header_iovecs: Option<Vec<IoVec<&[u8]>>> =
headers.map(|s| s.iter().map(|b| IoVec::from_slice(b)).collect());
let trailer_iovecs: Option<Vec<IoVec<&[u8]>>> =
trailers.map(|s| s.iter().map(|b| IoVec::from_slice(b)).collect());
SendfileHeaderTrailer(
libc::sf_hdtr {
headers: {
header_iovecs
.as_ref()
.map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec
},
hdr_cnt: header_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32,
trailers: {
trailer_iovecs
.as_ref()
.map_or(ptr::null(), |v| v.as_ptr()) as *mut libc::iovec
},
trl_cnt: trailer_iovecs.as_ref().map(|v| v.len()).unwrap_or(0) as i32
},
header_iovecs,
trailer_iovecs,
)
}
}
}
}
cfg_if! {
if #[cfg(target_os = "freebsd")] {
use libc::c_int;
libc_bitflags!{
/// Configuration options for [`sendfile`.](fn.sendfile.html)
pub struct SfFlags: c_int {
/// Causes `sendfile` to return EBUSY instead of blocking when attempting to read a
/// busy page.
SF_NODISKIO;
/// Causes `sendfile` to sleep until the network stack releases its reference to the
/// VM pages read. When `sendfile` returns, the data is not guaranteed to have been
/// sent, but it is safe to modify the file.
SF_SYNC;
/// Causes `sendfile` to cache exactly the number of pages specified in the
/// `readahead` parameter, disabling caching heuristics.
SF_USER_READAHEAD;
/// Causes `sendfile` not to cache the data read.
SF_NOCACHE;
}
}
/// Read up to `count` bytes from `in_fd` starting at `offset` and write to `out_sock`.
///
/// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
/// an error occurs.
///
/// `in_fd` must describe a regular file or shared memory object. `out_sock` must describe a
/// stream socket.
///
/// If `offset` falls past the end of the file, the function returns success and zero bytes
/// written.
///
/// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
/// file (EOF).
///
/// `headers` and `trailers` specify optional slices of byte slices to be sent before and
/// after the data read from `in_fd`, respectively. The length of headers and trailers sent
/// is included in the returned count of bytes written. The values of `offset` and `count`
/// do not apply to headers or trailers.
///
/// `readahead` specifies the minimum number of pages to cache in memory ahead of the page
/// currently being sent.
///
/// For more information, see
/// [the sendfile(2) man page.](https://www.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2)
#[allow(clippy::too_many_arguments)]
pub fn sendfile(
in_fd: RawFd,
out_sock: RawFd,
offset: off_t,
count: Option<usize>,
headers: Option<&[&[u8]]>,
trailers: Option<&[&[u8]]>,
flags: SfFlags,
readahead: u16
) -> (Result<()>, off_t) {
// Readahead goes in upper 16 bits
// Flags goes in lower 16 bits
// see `man 2 sendfile`
let ra32 = u32::from(readahead);
let flags: u32 = (ra32 << 16) | (flags.bits() as u32);
let mut bytes_sent: off_t = 0;
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr);
let return_code = unsafe {
libc::sendfile(in_fd,
out_sock,
offset,
count.unwrap_or(0),
hdtr_ptr as *mut libc::sf_hdtr,
&mut bytes_sent as *mut off_t,
flags as c_int)
};
(Errno::result(return_code).and(Ok(())), bytes_sent)
}
} else if #[cfg(any(target_os = "ios", target_os = "macos"))] {
/// Read bytes from `in_fd` starting at `offset` and write up to `count` bytes to
/// `out_sock`.
///
/// Returns a `Result` and a count of bytes written. Bytes written may be non-zero even if
/// an error occurs.
///
/// `in_fd` must describe a regular file. `out_sock` must describe a stream socket.
///
/// If `offset` falls past the end of the file, the function returns success and zero bytes
/// written.
///
/// If `count` is `None` or 0, bytes will be read from `in_fd` until reaching the end of
/// file (EOF).
///
/// `hdtr` specifies an optional list of headers and trailers to be sent before and after
/// the data read from `in_fd`, respectively. The length of headers and trailers sent is
/// included in the returned count of bytes written. If any headers are specified and
/// `count` is non-zero, the length of the headers will be counted in the limit of total
/// bytes sent. Trailers do not count toward the limit of bytes sent and will always be sent
/// regardless. The value of `offset` does not affect headers or trailers.
///
/// For more information, see
/// [the sendfile(2) man page.](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/sendfile.2.html)
pub fn sendfile(
in_fd: RawFd,
out_sock: RawFd,
offset: off_t,
count: Option<off_t>,
headers: Option<&[&[u8]]>,
trailers: Option<&[&[u8]]>
) -> (Result<()>, off_t) {
let mut len = count.unwrap_or(0);
let hdtr = headers.or(trailers).map(|_| SendfileHeaderTrailer::new(headers, trailers));
let hdtr_ptr = hdtr.as_ref().map_or(ptr::null(), |s| &s.0 as *const libc::sf_hdtr);
let return_code = unsafe {
libc::sendfile(in_fd,
out_sock,
offset,
&mut len as *mut off_t,
hdtr_ptr as *mut libc::sf_hdtr,
0)
};
(Errno::result(return_code).and(Ok(())), len)
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,169 @@
//! Interface for the `signalfd` syscall.
//!
//! # Signal discarding
//! When a signal can't be delivered to a process (or thread), it will become a pending signal.
//! Failure to deliver could happen if the signal is blocked by every thread in the process or if
//! the signal handler is still handling a previous signal.
//!
//! If a signal is sent to a process (or thread) that already has a pending signal of the same
//! type, it will be discarded. This means that if signals of the same type are received faster than
//! they are processed, some of those signals will be dropped. Because of this limitation,
//! `signalfd` in itself cannot be used for reliable communication between processes or threads.
//!
//! Once the signal is unblocked, or the signal handler is finished, and a signal is still pending
//! (ie. not consumed from a signalfd) it will be delivered to the signal handler.
//!
//! Please note that signal discarding is not specific to `signalfd`, but also happens with regular
//! signal handlers.
use crate::unistd;
use crate::Result;
use crate::errno::Errno;
pub use crate::sys::signal::{self, SigSet};
pub use libc::signalfd_siginfo as siginfo;
use std::os::unix::io::{RawFd, AsRawFd};
use std::mem;
libc_bitflags!{
pub struct SfdFlags: libc::c_int {
SFD_NONBLOCK;
SFD_CLOEXEC;
}
}
pub const SIGNALFD_NEW: RawFd = -1;
#[deprecated(since = "0.23.0", note = "use mem::size_of::<siginfo>() instead")]
pub const SIGNALFD_SIGINFO_SIZE: usize = mem::size_of::<siginfo>();
/// Creates a new file descriptor for reading signals.
///
/// **Important:** please read the module level documentation about signal discarding before using
/// this function!
///
/// The `mask` parameter specifies the set of signals that can be accepted via this file descriptor.
///
/// A signal must be blocked on every thread in a process, otherwise it won't be visible from
/// signalfd (the default handler will be invoked instead).
///
/// See [the signalfd man page for more information](https://man7.org/linux/man-pages/man2/signalfd.2.html)
pub fn signalfd(fd: RawFd, mask: &SigSet, flags: SfdFlags) -> Result<RawFd> {
unsafe {
Errno::result(libc::signalfd(fd as libc::c_int, mask.as_ref(), flags.bits()))
}
}
/// A helper struct for creating, reading and closing a `signalfd` instance.
///
/// **Important:** please read the module level documentation about signal discarding before using
/// this struct!
///
/// # Examples
///
/// ```
/// # use nix::sys::signalfd::*;
/// // Set the thread to block the SIGUSR1 signal, otherwise the default handler will be used
/// let mut mask = SigSet::empty();
/// mask.add(signal::SIGUSR1);
/// mask.thread_block().unwrap();
///
/// // Signals are queued up on the file descriptor
/// let mut sfd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap();
///
/// match sfd.read_signal() {
/// // we caught a signal
/// Ok(Some(sig)) => (),
/// // there were no signals waiting (only happens when the SFD_NONBLOCK flag is set,
/// // otherwise the read_signal call blocks)
/// Ok(None) => (),
/// Err(err) => (), // some error happend
/// }
/// ```
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct SignalFd(RawFd);
impl SignalFd {
pub fn new(mask: &SigSet) -> Result<SignalFd> {
Self::with_flags(mask, SfdFlags::empty())
}
pub fn with_flags(mask: &SigSet, flags: SfdFlags) -> Result<SignalFd> {
let fd = signalfd(SIGNALFD_NEW, mask, flags)?;
Ok(SignalFd(fd))
}
pub fn set_mask(&mut self, mask: &SigSet) -> Result<()> {
signalfd(self.0, mask, SfdFlags::empty()).map(drop)
}
pub fn read_signal(&mut self) -> Result<Option<siginfo>> {
let mut buffer = mem::MaybeUninit::<siginfo>::uninit();
let size = mem::size_of_val(&buffer);
let res = Errno::result(unsafe {
libc::read(self.0, buffer.as_mut_ptr() as *mut libc::c_void, size)
}).map(|r| r as usize);
match res {
Ok(x) if x == size => Ok(Some(unsafe { buffer.assume_init() })),
Ok(_) => unreachable!("partial read on signalfd"),
Err(Errno::EAGAIN) => Ok(None),
Err(error) => Err(error)
}
}
}
impl Drop for SignalFd {
fn drop(&mut self) {
let e = unistd::close(self.0);
if !std::thread::panicking() && e == Err(Errno::EBADF) {
panic!("Closing an invalid file descriptor!");
};
}
}
impl AsRawFd for SignalFd {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl Iterator for SignalFd {
type Item = siginfo;
fn next(&mut self) -> Option<Self::Item> {
match self.read_signal() {
Ok(Some(sig)) => Some(sig),
Ok(None) | Err(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_signalfd() {
let mask = SigSet::empty();
let fd = SignalFd::new(&mask);
assert!(fd.is_ok());
}
#[test]
fn create_signalfd_with_opts() {
let mask = SigSet::empty();
let fd = SignalFd::with_flags(&mask, SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK);
assert!(fd.is_ok());
}
#[test]
fn read_empty_signalfd() {
let mask = SigSet::empty();
let mut fd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap();
let res = fd.read_signal();
assert!(res.unwrap().is_none());
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,930 @@
//! Socket options as used by `setsockopt` and `getsockopt`.
use cfg_if::cfg_if;
use super::{GetSockOpt, SetSockOpt};
use crate::Result;
use crate::errno::Errno;
use crate::sys::time::TimeVal;
use libc::{self, c_int, c_void, socklen_t};
use std::mem::{
self,
MaybeUninit
};
use std::os::unix::io::RawFd;
use std::ffi::{OsStr, OsString};
#[cfg(target_family = "unix")]
use std::os::unix::ffi::OsStrExt;
// Constants
// TCP_CA_NAME_MAX isn't defined in user space include files
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
const TCP_CA_NAME_MAX: usize = 16;
/// Helper for implementing `SetSockOpt` for a given socket option. See
/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html).
///
/// This macro aims to help implementing `SetSockOpt` for different socket options that accept
/// different kinds of data to be used with `setsockopt`.
///
/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option
/// you are implementing represents a simple type.
///
/// # Arguments
///
/// * `$name:ident`: name of the type you want to implement `SetSockOpt` for.
/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets*
/// (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
/// and more. Please refer to your system manual for more options. Will be passed as the second
/// argument (`level`) to the `setsockopt` call.
/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`)
/// to the `setsockopt` call.
/// * Type of the value that you are going to set.
/// * Type that implements the `Set` trait for the type from the previous item (like `SetBool` for
/// `bool`, `SetUsize` for `usize`, etc.).
macro_rules! setsockopt_impl {
($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => {
impl SetSockOpt for $name {
type Val = $ty;
fn set(&self, fd: RawFd, val: &$ty) -> Result<()> {
unsafe {
let setter: $setter = Set::new(val);
let res = libc::setsockopt(fd, $level, $flag,
setter.ffi_ptr(),
setter.ffi_len());
Errno::result(res).map(drop)
}
}
}
}
}
/// Helper for implementing `GetSockOpt` for a given socket option. See
/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html).
///
/// This macro aims to help implementing `GetSockOpt` for different socket options that accept
/// different kinds of data to be use with `getsockopt`.
///
/// Instead of using this macro directly consider using `sockopt_impl!`, especially if the option
/// you are implementing represents a simple type.
///
/// # Arguments
///
/// * Name of the type you want to implement `GetSockOpt` for.
/// * Socket layer, or a `protocol level`: could be *raw sockets* (`lic::SOL_SOCKET`), *ip
/// protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`), and more. Please refer
/// to your system manual for more options. Will be passed as the second argument (`level`) to
/// the `getsockopt` call.
/// * A flag to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
/// `libc::SO_ORIGINAL_DST` and others. Will be passed as the third argument (`option_name`) to
/// the `getsockopt` call.
/// * Type of the value that you are going to get.
/// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for
/// `bool`, `GetUsize` for `usize`, etc.).
macro_rules! getsockopt_impl {
($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => {
impl GetSockOpt for $name {
type Val = $ty;
fn get(&self, fd: RawFd) -> Result<$ty> {
unsafe {
let mut getter: $getter = Get::uninit();
let res = libc::getsockopt(fd, $level, $flag,
getter.ffi_ptr(),
getter.ffi_len());
Errno::result(res)?;
Ok(getter.assume_init())
}
}
}
}
}
/// Helper to generate the sockopt accessors. See
/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html) and
/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html).
///
/// This macro aims to help implementing `GetSockOpt` and `SetSockOpt` for different socket options
/// that accept different kinds of data to be use with `getsockopt` and `setsockopt` respectively.
///
/// Basically this macro wraps up the [`getsockopt_impl!`](macro.getsockopt_impl.html) and
/// [`setsockopt_impl!`](macro.setsockopt_impl.html) macros.
///
/// # Arguments
///
/// * `GetOnly`, `SetOnly` or `Both`: whether you want to implement only getter, only setter or
/// both of them.
/// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for.
/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets*
/// (`lic::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
/// and more. Please refer to your system manual for more options. Will be passed as the second
/// argument (`level`) to the `getsockopt`/`setsockopt` call.
/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
/// `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`)
/// to the `setsockopt`/`getsockopt` call.
/// * `$ty:ty`: type of the value that will be get/set.
/// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`.
/// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`.
macro_rules! sockopt_impl {
($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => {
sockopt_impl!($(#[$attr])*
$name, GetOnly, $level, $flag, bool, GetBool);
};
($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => {
sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, GetU8);
};
($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) =>
{
sockopt_impl!($(#[$attr])*
$name, GetOnly, $level, $flag, usize, GetUsize);
};
($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => {
sockopt_impl!($(#[$attr])*
$name, SetOnly, $level, $flag, bool, SetBool);
};
($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => {
sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, SetU8);
};
($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) =>
{
sockopt_impl!($(#[$attr])*
$name, SetOnly, $level, $flag, usize, SetUsize);
};
($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => {
sockopt_impl!($(#[$attr])*
$name, Both, $level, $flag, bool, GetBool, SetBool);
};
($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => {
sockopt_impl!($(#[$attr])*
$name, Both, $level, $flag, u8, GetU8, SetU8);
};
($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => {
sockopt_impl!($(#[$attr])*
$name, Both, $level, $flag, usize, GetUsize, SetUsize);
};
($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path,
OsString<$array:ty>) =>
{
sockopt_impl!($(#[$attr])*
$name, Both, $level, $flag, OsString, GetOsString<$array>,
SetOsString);
};
/*
* Matchers with generic getter types must be placed at the end, so
* they'll only match _after_ specialized matchers fail
*/
($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) =>
{
sockopt_impl!($(#[$attr])*
$name, GetOnly, $level, $flag, $ty, GetStruct<$ty>);
};
($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty,
$getter:ty) =>
{
$(#[$attr])*
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct $name;
getsockopt_impl!($name, $level, $flag, $ty, $getter);
};
($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) =>
{
sockopt_impl!($(#[$attr])*
$name, SetOnly, $level, $flag, $ty, SetStruct<$ty>);
};
($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty,
$setter:ty) =>
{
$(#[$attr])*
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct $name;
setsockopt_impl!($name, $level, $flag, $ty, $setter);
};
($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty,
$getter:ty, $setter:ty) =>
{
$(#[$attr])*
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct $name;
setsockopt_impl!($name, $level, $flag, $ty, $setter);
getsockopt_impl!($name, $level, $flag, $ty, $getter);
};
($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => {
sockopt_impl!($(#[$attr])*
$name, Both, $level, $flag, $ty, GetStruct<$ty>,
SetStruct<$ty>);
};
}
/*
*
* ===== Define sockopts =====
*
*/
sockopt_impl!(
/// Enables local address reuse
ReuseAddr, Both, libc::SOL_SOCKET, libc::SO_REUSEADDR, bool
);
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
sockopt_impl!(
/// Permits multiple AF_INET or AF_INET6 sockets to be bound to an
/// identical socket address.
ReusePort, Both, libc::SOL_SOCKET, libc::SO_REUSEPORT, bool);
sockopt_impl!(
/// Under most circumstances, TCP sends data when it is presented; when
/// outstanding data has not yet been acknowledged, it gathers small amounts
/// of output to be sent in a single packet once an acknowledgement is
/// received. For a small number of clients, such as window systems that
/// send a stream of mouse events which receive no replies, this
/// packetization may cause significant delays. The boolean option
/// TCP_NODELAY defeats this algorithm.
TcpNoDelay, Both, libc::IPPROTO_TCP, libc::TCP_NODELAY, bool);
sockopt_impl!(
/// When enabled, a close(2) or shutdown(2) will not return until all
/// queued messages for the socket have been successfully sent or the
/// linger timeout has been reached.
Linger, Both, libc::SOL_SOCKET, libc::SO_LINGER, libc::linger);
sockopt_impl!(
/// Join a multicast group
IpAddMembership, SetOnly, libc::IPPROTO_IP, libc::IP_ADD_MEMBERSHIP,
super::IpMembershipRequest);
sockopt_impl!(
/// Leave a multicast group.
IpDropMembership, SetOnly, libc::IPPROTO_IP, libc::IP_DROP_MEMBERSHIP,
super::IpMembershipRequest);
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
sockopt_impl!(
/// Join an IPv6 multicast group.
Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_ADD_MEMBERSHIP, super::Ipv6MembershipRequest);
sockopt_impl!(
/// Leave an IPv6 multicast group.
Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, super::Ipv6MembershipRequest);
} else if #[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "illumos",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris"))] {
sockopt_impl!(
/// Join an IPv6 multicast group.
Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6,
libc::IPV6_JOIN_GROUP, super::Ipv6MembershipRequest);
sockopt_impl!(
/// Leave an IPv6 multicast group.
Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6,
libc::IPV6_LEAVE_GROUP, super::Ipv6MembershipRequest);
}
}
sockopt_impl!(
/// Set or read the time-to-live value of outgoing multicast packets for
/// this socket.
IpMulticastTtl, Both, libc::IPPROTO_IP, libc::IP_MULTICAST_TTL, u8);
sockopt_impl!(
/// Set or read a boolean integer argument that determines whether sent
/// multicast packets should be looped back to the local sockets.
IpMulticastLoop, Both, libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP, bool);
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
sockopt_impl!(
/// If enabled, this boolean option allows binding to an IP address that
/// is nonlocal or does not (yet) exist.
IpFreebind, Both, libc::IPPROTO_IP, libc::IP_FREEBIND, bool);
sockopt_impl!(
/// Specify the receiving timeout until reporting an error.
ReceiveTimeout, Both, libc::SOL_SOCKET, libc::SO_RCVTIMEO, TimeVal);
sockopt_impl!(
/// Specify the sending timeout until reporting an error.
SendTimeout, Both, libc::SOL_SOCKET, libc::SO_SNDTIMEO, TimeVal);
sockopt_impl!(
/// Set or get the broadcast flag.
Broadcast, Both, libc::SOL_SOCKET, libc::SO_BROADCAST, bool);
sockopt_impl!(
/// If this option is enabled, out-of-band data is directly placed into
/// the receive data stream.
OobInline, Both, libc::SOL_SOCKET, libc::SO_OOBINLINE, bool);
sockopt_impl!(
/// Get and clear the pending socket error.
SocketError, GetOnly, libc::SOL_SOCKET, libc::SO_ERROR, i32);
sockopt_impl!(
/// Enable sending of keep-alive messages on connection-oriented sockets.
KeepAlive, Both, libc::SOL_SOCKET, libc::SO_KEEPALIVE, bool);
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "ios"
))]
sockopt_impl!(
/// Get the credentials of the peer process of a connected unix domain
/// socket.
LocalPeerCred, GetOnly, 0, libc::LOCAL_PEERCRED, super::XuCred);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Return the credentials of the foreign process connected to this socket.
PeerCredentials, GetOnly, libc::SOL_SOCKET, libc::SO_PEERCRED, super::UnixCredentials);
#[cfg(any(target_os = "ios",
target_os = "macos"))]
sockopt_impl!(
/// Specify the amount of time, in seconds, that the connection must be idle
/// before keepalive probes (if enabled) are sent.
TcpKeepAlive, Both, libc::IPPROTO_TCP, libc::TCP_KEEPALIVE, u32);
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "nacl"))]
sockopt_impl!(
/// The time (in seconds) the connection needs to remain idle before TCP
/// starts sending keepalive probes
TcpKeepIdle, Both, libc::IPPROTO_TCP, libc::TCP_KEEPIDLE, u32);
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
sockopt_impl!(
/// The maximum segment size for outgoing TCP packets.
TcpMaxSeg, Both, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32);
} else {
sockopt_impl!(
/// The maximum segment size for outgoing TCP packets.
TcpMaxSeg, GetOnly, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32);
}
}
#[cfg(not(target_os = "openbsd"))]
sockopt_impl!(
/// The maximum number of keepalive probes TCP should send before
/// dropping the connection.
TcpKeepCount, Both, libc::IPPROTO_TCP, libc::TCP_KEEPCNT, u32);
#[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "linux"))]
sockopt_impl!(
#[allow(missing_docs)]
// Not documented by Linux!
TcpRepair, Both, libc::IPPROTO_TCP, libc::TCP_REPAIR, u32);
#[cfg(not(target_os = "openbsd"))]
sockopt_impl!(
/// The time (in seconds) between individual keepalive probes.
TcpKeepInterval, Both, libc::IPPROTO_TCP, libc::TCP_KEEPINTVL, u32);
#[cfg(any(target_os = "fuchsia", target_os = "linux"))]
sockopt_impl!(
/// Specifies the maximum amount of time in milliseconds that transmitted
/// data may remain unacknowledged before TCP will forcibly close the
/// corresponding connection
TcpUserTimeout, Both, libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT, u32);
sockopt_impl!(
/// Sets or gets the maximum socket receive buffer in bytes.
RcvBuf, Both, libc::SOL_SOCKET, libc::SO_RCVBUF, usize);
sockopt_impl!(
/// Sets or gets the maximum socket send buffer in bytes.
SndBuf, Both, libc::SOL_SOCKET, libc::SO_SNDBUF, usize);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can
/// perform the same task as `SO_RCVBUF`, but the `rmem_max limit` can be
/// overridden.
RcvBufForce, SetOnly, libc::SOL_SOCKET, libc::SO_RCVBUFFORCE, usize);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can
/// perform the same task as `SO_SNDBUF`, but the `wmem_max` limit can be
/// overridden.
SndBufForce, SetOnly, libc::SOL_SOCKET, libc::SO_SNDBUFFORCE, usize);
sockopt_impl!(
/// Gets the socket type as an integer.
SockType, GetOnly, libc::SOL_SOCKET, libc::SO_TYPE, super::SockType);
sockopt_impl!(
/// Returns a value indicating whether or not this socket has been marked to
/// accept connections with `listen(2)`.
AcceptConn, GetOnly, libc::SOL_SOCKET, libc::SO_ACCEPTCONN, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Bind this socket to a particular device like “eth0”.
BindToDevice, Both, libc::SOL_SOCKET, libc::SO_BINDTODEVICE, OsString<[u8; libc::IFNAMSIZ]>);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
#[allow(missing_docs)]
// Not documented by Linux!
OriginalDst, GetOnly, libc::SOL_IP, libc::SO_ORIGINAL_DST, libc::sockaddr_in);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
#[allow(missing_docs)]
// Not documented by Linux!
Ip6tOriginalDst, GetOnly, libc::SOL_IPV6, libc::IP6T_SO_ORIGINAL_DST, libc::sockaddr_in6);
sockopt_impl!(
/// Enable or disable the receiving of the `SO_TIMESTAMP` control message.
ReceiveTimestamp, Both, libc::SOL_SOCKET, libc::SO_TIMESTAMP, bool);
#[cfg(all(target_os = "linux"))]
sockopt_impl!(
/// Enable or disable the receiving of the `SO_TIMESTAMPNS` control message.
ReceiveTimestampns, Both, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Setting this boolean option enables transparent proxying on this socket.
IpTransparent, Both, libc::SOL_IP, libc::IP_TRANSPARENT, bool);
#[cfg(target_os = "openbsd")]
sockopt_impl!(
/// Allows the socket to be bound to addresses which are not local to the
/// machine, so it can be used to make a transparent proxy.
BindAny, Both, libc::SOL_SOCKET, libc::SO_BINDANY, bool);
#[cfg(target_os = "freebsd")]
sockopt_impl!(
/// Can `bind(2)` to any address, even one not bound to any available
/// network interface in the system.
BindAny, Both, libc::IPPROTO_IP, libc::IP_BINDANY, bool);
#[cfg(target_os = "linux")]
sockopt_impl!(
/// Set the mark for each packet sent through this socket (similar to the
/// netfilter MARK target but socket-based).
Mark, Both, libc::SOL_SOCKET, libc::SO_MARK, u32);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Enable or disable the receiving of the `SCM_CREDENTIALS` control
/// message.
PassCred, Both, libc::SOL_SOCKET, libc::SO_PASSCRED, bool);
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
sockopt_impl!(
/// This option allows the caller to set the TCP congestion control
/// algorithm to be used, on a per-socket basis.
TcpCongestion, Both, libc::IPPROTO_TCP, libc::TCP_CONGESTION, OsString<[u8; TCP_CA_NAME_MAX]>);
#[cfg(any(
target_os = "android",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
))]
sockopt_impl!(
/// Pass an `IP_PKTINFO` ancillary message that contains a pktinfo
/// structure that supplies some information about the incoming packet.
Ipv4PacketInfo, Both, libc::IPPROTO_IP, libc::IP_PKTINFO, bool);
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
sockopt_impl!(
/// Set delivery of the `IPV6_PKTINFO` control message on incoming
/// datagrams.
Ipv6RecvPacketInfo, Both, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, bool);
#[cfg(any(
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
sockopt_impl!(
/// The `recvmsg(2)` call returns a `struct sockaddr_dl` corresponding to
/// the interface on which the packet was received.
Ipv4RecvIf, Both, libc::IPPROTO_IP, libc::IP_RECVIF, bool);
#[cfg(any(
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
sockopt_impl!(
/// The `recvmsg(2)` call will return the destination IP address for a UDP
/// datagram.
Ipv4RecvDstAddr, Both, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, bool);
#[cfg(target_os = "linux")]
sockopt_impl!(
#[allow(missing_docs)]
// Not documented by Linux!
UdpGsoSegment, Both, libc::SOL_UDP, libc::UDP_SEGMENT, libc::c_int);
#[cfg(target_os = "linux")]
sockopt_impl!(
#[allow(missing_docs)]
// Not documented by Linux!
UdpGroSegment, Both, libc::IPPROTO_UDP, libc::UDP_GRO, bool);
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
sockopt_impl!(
/// Indicates that an unsigned 32-bit value ancillary message (cmsg) should
/// be attached to received skbs indicating the number of packets dropped by
/// the socket since its creation.
RxqOvfl, Both, libc::SOL_SOCKET, libc::SO_RXQ_OVFL, libc::c_int);
sockopt_impl!(
/// The socket is restricted to sending and receiving IPv6 packets only.
Ipv6V6Only, Both, libc::IPPROTO_IPV6, libc::IPV6_V6ONLY, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Enable extended reliable error message passing.
Ipv4RecvErr, Both, libc::IPPROTO_IP, libc::IP_RECVERR, bool);
#[cfg(any(target_os = "android", target_os = "linux"))]
sockopt_impl!(
/// Control receiving of asynchronous error options.
Ipv6RecvErr, Both, libc::IPPROTO_IPV6, libc::IPV6_RECVERR, bool);
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
sockopt_impl!(
/// Set or retrieve the current time-to-live field that is used in every
/// packet sent from this socket.
Ipv4Ttl, Both, libc::IPPROTO_IP, libc::IP_TTL, libc::c_int);
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
sockopt_impl!(
/// Set the unicast hop limit for the socket.
Ipv6Ttl, Both, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS, libc::c_int);
#[allow(missing_docs)]
// Not documented by Linux!
#[cfg(any(target_os = "android", target_os = "linux"))]
#[derive(Copy, Clone, Debug)]
pub struct AlgSetAeadAuthSize;
// ALG_SET_AEAD_AUTH_SIZE read the length from passed `option_len`
// See https://elixir.bootlin.com/linux/v4.4/source/crypto/af_alg.c#L222
#[cfg(any(target_os = "android", target_os = "linux"))]
impl SetSockOpt for AlgSetAeadAuthSize {
type Val = usize;
fn set(&self, fd: RawFd, val: &usize) -> Result<()> {
unsafe {
let res = libc::setsockopt(fd,
libc::SOL_ALG,
libc::ALG_SET_AEAD_AUTHSIZE,
::std::ptr::null(),
*val as libc::socklen_t);
Errno::result(res).map(drop)
}
}
}
#[allow(missing_docs)]
// Not documented by Linux!
#[cfg(any(target_os = "android", target_os = "linux"))]
#[derive(Clone, Debug)]
pub struct AlgSetKey<T>(::std::marker::PhantomData<T>);
#[cfg(any(target_os = "android", target_os = "linux"))]
impl<T> Default for AlgSetKey<T> {
fn default() -> Self {
AlgSetKey(Default::default())
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
impl<T> SetSockOpt for AlgSetKey<T> where T: AsRef<[u8]> + Clone {
type Val = T;
fn set(&self, fd: RawFd, val: &T) -> Result<()> {
unsafe {
let res = libc::setsockopt(fd,
libc::SOL_ALG,
libc::ALG_SET_KEY,
val.as_ref().as_ptr() as *const _,
val.as_ref().len() as libc::socklen_t);
Errno::result(res).map(drop)
}
}
}
/*
*
* ===== Accessor helpers =====
*
*/
/// Helper trait that describes what is expected from a `GetSockOpt` getter.
trait Get<T> {
/// Returns an uninitialized value.
fn uninit() -> Self;
/// Returns a pointer to the stored value. This pointer will be passed to the system's
/// `getsockopt` call (`man 3p getsockopt`, argument `option_value`).
fn ffi_ptr(&mut self) -> *mut c_void;
/// Returns length of the stored value. This pointer will be passed to the system's
/// `getsockopt` call (`man 3p getsockopt`, argument `option_len`).
fn ffi_len(&mut self) -> *mut socklen_t;
/// Returns the hopefully initialized inner value.
unsafe fn assume_init(self) -> T;
}
/// Helper trait that describes what is expected from a `SetSockOpt` setter.
trait Set<'a, T> {
/// Initialize the setter with a given value.
fn new(val: &'a T) -> Self;
/// Returns a pointer to the stored value. This pointer will be passed to the system's
/// `setsockopt` call (`man 3p setsockopt`, argument `option_value`).
fn ffi_ptr(&self) -> *const c_void;
/// Returns length of the stored value. This pointer will be passed to the system's
/// `setsockopt` call (`man 3p setsockopt`, argument `option_len`).
fn ffi_len(&self) -> socklen_t;
}
/// Getter for an arbitrary `struct`.
struct GetStruct<T> {
len: socklen_t,
val: MaybeUninit<T>,
}
impl<T> Get<T> for GetStruct<T> {
fn uninit() -> Self {
GetStruct {
len: mem::size_of::<T>() as socklen_t,
val: MaybeUninit::uninit(),
}
}
fn ffi_ptr(&mut self) -> *mut c_void {
self.val.as_mut_ptr() as *mut c_void
}
fn ffi_len(&mut self) -> *mut socklen_t {
&mut self.len
}
unsafe fn assume_init(self) -> T {
assert_eq!(self.len as usize, mem::size_of::<T>(), "invalid getsockopt implementation");
self.val.assume_init()
}
}
/// Setter for an arbitrary `struct`.
struct SetStruct<'a, T: 'static> {
ptr: &'a T,
}
impl<'a, T> Set<'a, T> for SetStruct<'a, T> {
fn new(ptr: &'a T) -> SetStruct<'a, T> {
SetStruct { ptr }
}
fn ffi_ptr(&self) -> *const c_void {
self.ptr as *const T as *const c_void
}
fn ffi_len(&self) -> socklen_t {
mem::size_of::<T>() as socklen_t
}
}
/// Getter for a boolean value.
struct GetBool {
len: socklen_t,
val: MaybeUninit<c_int>,
}
impl Get<bool> for GetBool {
fn uninit() -> Self {
GetBool {
len: mem::size_of::<c_int>() as socklen_t,
val: MaybeUninit::uninit(),
}
}
fn ffi_ptr(&mut self) -> *mut c_void {
self.val.as_mut_ptr() as *mut c_void
}
fn ffi_len(&mut self) -> *mut socklen_t {
&mut self.len
}
unsafe fn assume_init(self) -> bool {
assert_eq!(self.len as usize, mem::size_of::<c_int>(), "invalid getsockopt implementation");
self.val.assume_init() != 0
}
}
/// Setter for a boolean value.
struct SetBool {
val: c_int,
}
impl<'a> Set<'a, bool> for SetBool {
fn new(val: &'a bool) -> SetBool {
SetBool { val: if *val { 1 } else { 0 } }
}
fn ffi_ptr(&self) -> *const c_void {
&self.val as *const c_int as *const c_void
}
fn ffi_len(&self) -> socklen_t {
mem::size_of::<c_int>() as socklen_t
}
}
/// Getter for an `u8` value.
struct GetU8 {
len: socklen_t,
val: MaybeUninit<u8>,
}
impl Get<u8> for GetU8 {
fn uninit() -> Self {
GetU8 {
len: mem::size_of::<u8>() as socklen_t,
val: MaybeUninit::uninit(),
}
}
fn ffi_ptr(&mut self) -> *mut c_void {
self.val.as_mut_ptr() as *mut c_void
}
fn ffi_len(&mut self) -> *mut socklen_t {
&mut self.len
}
unsafe fn assume_init(self) -> u8 {
assert_eq!(self.len as usize, mem::size_of::<u8>(), "invalid getsockopt implementation");
self.val.assume_init()
}
}
/// Setter for an `u8` value.
struct SetU8 {
val: u8,
}
impl<'a> Set<'a, u8> for SetU8 {
fn new(val: &'a u8) -> SetU8 {
SetU8 { val: *val as u8 }
}
fn ffi_ptr(&self) -> *const c_void {
&self.val as *const u8 as *const c_void
}
fn ffi_len(&self) -> socklen_t {
mem::size_of::<c_int>() as socklen_t
}
}
/// Getter for an `usize` value.
struct GetUsize {
len: socklen_t,
val: MaybeUninit<c_int>,
}
impl Get<usize> for GetUsize {
fn uninit() -> Self {
GetUsize {
len: mem::size_of::<c_int>() as socklen_t,
val: MaybeUninit::uninit(),
}
}
fn ffi_ptr(&mut self) -> *mut c_void {
self.val.as_mut_ptr() as *mut c_void
}
fn ffi_len(&mut self) -> *mut socklen_t {
&mut self.len
}
unsafe fn assume_init(self) -> usize {
assert_eq!(self.len as usize, mem::size_of::<c_int>(), "invalid getsockopt implementation");
self.val.assume_init() as usize
}
}
/// Setter for an `usize` value.
struct SetUsize {
val: c_int,
}
impl<'a> Set<'a, usize> for SetUsize {
fn new(val: &'a usize) -> SetUsize {
SetUsize { val: *val as c_int }
}
fn ffi_ptr(&self) -> *const c_void {
&self.val as *const c_int as *const c_void
}
fn ffi_len(&self) -> socklen_t {
mem::size_of::<c_int>() as socklen_t
}
}
/// Getter for a `OsString` value.
struct GetOsString<T: AsMut<[u8]>> {
len: socklen_t,
val: MaybeUninit<T>,
}
impl<T: AsMut<[u8]>> Get<OsString> for GetOsString<T> {
fn uninit() -> Self {
GetOsString {
len: mem::size_of::<T>() as socklen_t,
val: MaybeUninit::uninit(),
}
}
fn ffi_ptr(&mut self) -> *mut c_void {
self.val.as_mut_ptr() as *mut c_void
}
fn ffi_len(&mut self) -> *mut socklen_t {
&mut self.len
}
unsafe fn assume_init(self) -> OsString {
let len = self.len as usize;
let mut v = self.val.assume_init();
OsStr::from_bytes(&v.as_mut()[0..len]).to_owned()
}
}
/// Setter for a `OsString` value.
struct SetOsString<'a> {
val: &'a OsStr,
}
impl<'a> Set<'a, OsString> for SetOsString<'a> {
fn new(val: &'a OsString) -> SetOsString {
SetOsString { val: val.as_os_str() }
}
fn ffi_ptr(&self) -> *const c_void {
self.val.as_bytes().as_ptr() as *const c_void
}
fn ffi_len(&self) -> socklen_t {
self.val.len() as socklen_t
}
}
#[cfg(test)]
mod test {
#[cfg(any(target_os = "android", target_os = "linux"))]
#[test]
fn can_get_peercred_on_unix_socket() {
use super::super::*;
let (a, b) = socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::empty()).unwrap();
let a_cred = getsockopt(a, super::PeerCredentials).unwrap();
let b_cred = getsockopt(b, super::PeerCredentials).unwrap();
assert_eq!(a_cred, b_cred);
assert!(a_cred.pid() != 0);
}
#[test]
fn is_socket_type_unix() {
use super::super::*;
use crate::unistd::close;
let (a, b) = socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::empty()).unwrap();
let a_type = getsockopt(a, super::SockType).unwrap();
assert_eq!(a_type, SockType::Stream);
close(a).unwrap();
close(b).unwrap();
}
#[test]
fn is_socket_type_dgram() {
use super::super::*;
use crate::unistd::close;
let s = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None).unwrap();
let s_type = getsockopt(s, super::SockType).unwrap();
assert_eq!(s_type, SockType::Datagram);
close(s).unwrap();
}
#[cfg(any(target_os = "freebsd",
target_os = "linux",
target_os = "nacl"))]
#[test]
fn can_get_listen_on_tcp_socket() {
use super::super::*;
use crate::unistd::close;
let s = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), None).unwrap();
let s_listening = getsockopt(s, super::AcceptConn).unwrap();
assert!(!s_listening);
listen(s, 10).unwrap();
let s_listening2 = getsockopt(s, super::AcceptConn).unwrap();
assert!(s_listening2);
close(s).unwrap();
}
}

View file

@ -0,0 +1,315 @@
pub use libc::{dev_t, mode_t};
pub use libc::stat as FileStat;
use crate::{Result, NixPath, errno::Errno};
#[cfg(not(target_os = "redox"))]
use crate::fcntl::{AtFlags, at_rawfd};
use std::mem;
use std::os::unix::io::RawFd;
use crate::sys::time::{TimeSpec, TimeVal};
libc_bitflags!(
/// "File type" flags for `mknod` and related functions.
pub struct SFlag: mode_t {
S_IFIFO;
S_IFCHR;
S_IFDIR;
S_IFBLK;
S_IFREG;
S_IFLNK;
S_IFSOCK;
S_IFMT;
}
);
libc_bitflags! {
/// "File mode / permissions" flags.
pub struct Mode: mode_t {
S_IRWXU;
S_IRUSR;
S_IWUSR;
S_IXUSR;
S_IRWXG;
S_IRGRP;
S_IWGRP;
S_IXGRP;
S_IRWXO;
S_IROTH;
S_IWOTH;
S_IXOTH;
S_ISUID as mode_t;
S_ISGID as mode_t;
S_ISVTX as mode_t;
}
}
/// Create a special or ordinary file, by pathname.
pub fn mknod<P: ?Sized + NixPath>(path: &P, kind: SFlag, perm: Mode, dev: dev_t) -> Result<()> {
let res = path.with_nix_path(|cstr| unsafe {
libc::mknod(cstr.as_ptr(), kind.bits | perm.bits() as mode_t, dev)
})?;
Errno::result(res).map(drop)
}
/// Create a special or ordinary file, relative to a given directory.
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))]
pub fn mknodat<P: ?Sized + NixPath>(
dirfd: RawFd,
path: &P,
kind: SFlag,
perm: Mode,
dev: dev_t,
) -> Result<()> {
let res = path.with_nix_path(|cstr| unsafe {
libc::mknodat(dirfd, cstr.as_ptr(), kind.bits | perm.bits() as mode_t, dev)
})?;
Errno::result(res).map(drop)
}
#[cfg(target_os = "linux")]
pub const fn major(dev: dev_t) -> u64 {
((dev >> 32) & 0xffff_f000) |
((dev >> 8) & 0x0000_0fff)
}
#[cfg(target_os = "linux")]
pub const fn minor(dev: dev_t) -> u64 {
((dev >> 12) & 0xffff_ff00) |
((dev ) & 0x0000_00ff)
}
#[cfg(target_os = "linux")]
pub const fn makedev(major: u64, minor: u64) -> dev_t {
((major & 0xffff_f000) << 32) |
((major & 0x0000_0fff) << 8) |
((minor & 0xffff_ff00) << 12) |
(minor & 0x0000_00ff)
}
pub fn umask(mode: Mode) -> Mode {
let prev = unsafe { libc::umask(mode.bits() as mode_t) };
Mode::from_bits(prev).expect("[BUG] umask returned invalid Mode")
}
pub fn stat<P: ?Sized + NixPath>(path: &P) -> Result<FileStat> {
let mut dst = mem::MaybeUninit::uninit();
let res = path.with_nix_path(|cstr| {
unsafe {
libc::stat(cstr.as_ptr(), dst.as_mut_ptr())
}
})?;
Errno::result(res)?;
Ok(unsafe{dst.assume_init()})
}
pub fn lstat<P: ?Sized + NixPath>(path: &P) -> Result<FileStat> {
let mut dst = mem::MaybeUninit::uninit();
let res = path.with_nix_path(|cstr| {
unsafe {
libc::lstat(cstr.as_ptr(), dst.as_mut_ptr())
}
})?;
Errno::result(res)?;
Ok(unsafe{dst.assume_init()})
}
pub fn fstat(fd: RawFd) -> Result<FileStat> {
let mut dst = mem::MaybeUninit::uninit();
let res = unsafe { libc::fstat(fd, dst.as_mut_ptr()) };
Errno::result(res)?;
Ok(unsafe{dst.assume_init()})
}
#[cfg(not(target_os = "redox"))]
pub fn fstatat<P: ?Sized + NixPath>(dirfd: RawFd, pathname: &P, f: AtFlags) -> Result<FileStat> {
let mut dst = mem::MaybeUninit::uninit();
let res = pathname.with_nix_path(|cstr| {
unsafe { libc::fstatat(dirfd, cstr.as_ptr(), dst.as_mut_ptr(), f.bits() as libc::c_int) }
})?;
Errno::result(res)?;
Ok(unsafe{dst.assume_init()})
}
/// Change the file permission bits of the file specified by a file descriptor.
///
/// # References
///
/// [fchmod(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html).
pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> {
let res = unsafe { libc::fchmod(fd, mode.bits() as mode_t) };
Errno::result(res).map(drop)
}
/// Flags for `fchmodat` function.
#[derive(Clone, Copy, Debug)]
pub enum FchmodatFlags {
FollowSymlink,
NoFollowSymlink,
}
/// Change the file permission bits.
///
/// The file to be changed is determined relative to the directory associated
/// with the file descriptor `dirfd` or the current working directory
/// if `dirfd` is `None`.
///
/// If `flag` is `FchmodatFlags::NoFollowSymlink` and `path` names a symbolic link,
/// then the mode of the symbolic link is changed.
///
/// `fchmodat(None, path, mode, FchmodatFlags::FollowSymlink)` is identical to
/// a call `libc::chmod(path, mode)`. That's why `chmod` is unimplemented
/// in the `nix` crate.
///
/// # References
///
/// [fchmodat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html).
#[cfg(not(target_os = "redox"))]
pub fn fchmodat<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
path: &P,
mode: Mode,
flag: FchmodatFlags,
) -> Result<()> {
let atflag =
match flag {
FchmodatFlags::FollowSymlink => AtFlags::empty(),
FchmodatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
};
let res = path.with_nix_path(|cstr| unsafe {
libc::fchmodat(
at_rawfd(dirfd),
cstr.as_ptr(),
mode.bits() as mode_t,
atflag.bits() as libc::c_int,
)
})?;
Errno::result(res).map(drop)
}
/// Change the access and modification times of a file.
///
/// `utimes(path, times)` is identical to
/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)`. The former
/// is a deprecated API so prefer using the latter if the platforms you care
/// about support it.
///
/// # References
///
/// [utimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html).
pub fn utimes<P: ?Sized + NixPath>(path: &P, atime: &TimeVal, mtime: &TimeVal) -> Result<()> {
let times: [libc::timeval; 2] = [*atime.as_ref(), *mtime.as_ref()];
let res = path.with_nix_path(|cstr| unsafe {
libc::utimes(cstr.as_ptr(), &times[0])
})?;
Errno::result(res).map(drop)
}
/// Change the access and modification times of a file without following symlinks.
///
/// `lutimes(path, times)` is identical to
/// `utimensat(None, path, times, UtimensatFlags::NoFollowSymlink)`. The former
/// is a deprecated API so prefer using the latter if the platforms you care
/// about support it.
///
/// # References
///
/// [lutimes(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/lutimes.html).
#[cfg(any(target_os = "linux",
target_os = "haiku",
target_os = "ios",
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd"))]
pub fn lutimes<P: ?Sized + NixPath>(path: &P, atime: &TimeVal, mtime: &TimeVal) -> Result<()> {
let times: [libc::timeval; 2] = [*atime.as_ref(), *mtime.as_ref()];
let res = path.with_nix_path(|cstr| unsafe {
libc::lutimes(cstr.as_ptr(), &times[0])
})?;
Errno::result(res).map(drop)
}
/// Change the access and modification times of the file specified by a file descriptor.
///
/// # References
///
/// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html).
#[inline]
pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> {
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
let res = unsafe { libc::futimens(fd, &times[0]) };
Errno::result(res).map(drop)
}
/// Flags for `utimensat` function.
// TODO: replace with fcntl::AtFlags
#[derive(Clone, Copy, Debug)]
pub enum UtimensatFlags {
FollowSymlink,
NoFollowSymlink,
}
/// Change the access and modification times of a file.
///
/// The file to be changed is determined relative to the directory associated
/// with the file descriptor `dirfd` or the current working directory
/// if `dirfd` is `None`.
///
/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link,
/// then the mode of the symbolic link is changed.
///
/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)` is identical to
/// `utimes(path, times)`. The latter is a deprecated API so prefer using the
/// former if the platforms you care about support it.
///
/// # References
///
/// [utimensat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html).
#[cfg(not(target_os = "redox"))]
pub fn utimensat<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
path: &P,
atime: &TimeSpec,
mtime: &TimeSpec,
flag: UtimensatFlags
) -> Result<()> {
let atflag =
match flag {
UtimensatFlags::FollowSymlink => AtFlags::empty(),
UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
};
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
let res = path.with_nix_path(|cstr| unsafe {
libc::utimensat(
at_rawfd(dirfd),
cstr.as_ptr(),
&times[0],
atflag.bits() as libc::c_int,
)
})?;
Errno::result(res).map(drop)
}
#[cfg(not(target_os = "redox"))]
pub fn mkdirat<P: ?Sized + NixPath>(fd: RawFd, path: &P, mode: Mode) -> Result<()> {
let res = path.with_nix_path(|cstr| {
unsafe { libc::mkdirat(fd, cstr.as_ptr(), mode.bits() as mode_t) }
})?;
Errno::result(res).map(drop)
}

View file

@ -0,0 +1,622 @@
//! Get filesystem statistics, non-portably
//!
//! See [`statvfs`](crate::sys::statvfs) for a portable alternative.
use std::fmt::{self, Debug};
use std::mem;
use std::os::unix::io::AsRawFd;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
use std::ffi::CStr;
use crate::{NixPath, Result, errno::Errno};
/// Identifies a mounted file system
#[cfg(target_os = "android")]
pub type fsid_t = libc::__fsid_t;
/// Identifies a mounted file system
#[cfg(not(target_os = "android"))]
pub type fsid_t = libc::fsid_t;
/// Describes a mounted file system
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Statfs(libc::statfs);
#[cfg(target_os = "freebsd")]
type fs_type_t = u32;
#[cfg(target_os = "android")]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_arch = "s390x"))]
type fs_type_t = libc::c_uint;
#[cfg(all(target_os = "linux", target_env = "musl"))]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))]
type fs_type_t = libc::__fsword_t;
/// Describes the file system type as known by the operating system.
#[cfg(any(
target_os = "freebsd",
target_os = "android",
all(target_os = "linux", target_arch = "s390x"),
all(target_os = "linux", target_env = "musl"),
all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))),
))]
#[derive(Eq, Copy, Clone, PartialEq, Debug)]
pub struct FsType(pub fs_type_t);
// These constants are defined without documentation in the Linux headers, so we
// can't very well document them here.
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const ADFS_SUPER_MAGIC: FsType = FsType(libc::ADFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const AFFS_SUPER_MAGIC: FsType = FsType(libc::AFFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const CODA_SUPER_MAGIC: FsType = FsType(libc::CODA_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const CRAMFS_MAGIC: FsType = FsType(libc::CRAMFS_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const EFS_SUPER_MAGIC: FsType = FsType(libc::EFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const EXT2_SUPER_MAGIC: FsType = FsType(libc::EXT2_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const EXT3_SUPER_MAGIC: FsType = FsType(libc::EXT3_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const EXT4_SUPER_MAGIC: FsType = FsType(libc::EXT4_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const HPFS_SUPER_MAGIC: FsType = FsType(libc::HPFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const HUGETLBFS_MAGIC: FsType = FsType(libc::HUGETLBFS_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const ISOFS_SUPER_MAGIC: FsType = FsType(libc::ISOFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const JFFS2_SUPER_MAGIC: FsType = FsType(libc::JFFS2_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const MINIX_SUPER_MAGIC: FsType = FsType(libc::MINIX_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const MINIX_SUPER_MAGIC2: FsType = FsType(libc::MINIX_SUPER_MAGIC2 as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const MINIX2_SUPER_MAGIC: FsType = FsType(libc::MINIX2_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const MINIX2_SUPER_MAGIC2: FsType = FsType(libc::MINIX2_SUPER_MAGIC2 as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const MSDOS_SUPER_MAGIC: FsType = FsType(libc::MSDOS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const NCP_SUPER_MAGIC: FsType = FsType(libc::NCP_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const NFS_SUPER_MAGIC: FsType = FsType(libc::NFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const OPENPROM_SUPER_MAGIC: FsType = FsType(libc::OPENPROM_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const OVERLAYFS_SUPER_MAGIC: FsType = FsType(libc::OVERLAYFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const PROC_SUPER_MAGIC: FsType = FsType(libc::PROC_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const QNX4_SUPER_MAGIC: FsType = FsType(libc::QNX4_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const REISERFS_SUPER_MAGIC: FsType = FsType(libc::REISERFS_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const SMB_SUPER_MAGIC: FsType = FsType(libc::SMB_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const TMPFS_MAGIC: FsType = FsType(libc::TMPFS_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const USBDEVICE_SUPER_MAGIC: FsType = FsType(libc::USBDEVICE_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const CGROUP_SUPER_MAGIC: FsType = FsType(libc::CGROUP_SUPER_MAGIC as fs_type_t);
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[allow(missing_docs)]
pub const CGROUP2_SUPER_MAGIC: FsType = FsType(libc::CGROUP2_SUPER_MAGIC as fs_type_t);
impl Statfs {
/// Magic code defining system type
#[cfg(not(any(
target_os = "openbsd",
target_os = "dragonfly",
target_os = "ios",
target_os = "macos"
)))]
pub fn filesystem_type(&self) -> FsType {
FsType(self.0.f_type)
}
/// Magic code defining system type
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn filesystem_type_name(&self) -> &str {
let c_str = unsafe { CStr::from_ptr(self.0.f_fstypename.as_ptr()) };
c_str.to_str().unwrap()
}
/// Optimal transfer block size
#[cfg(any(target_os = "ios", target_os = "macos"))]
pub fn optimal_transfer_size(&self) -> i32 {
self.0.f_iosize
}
/// Optimal transfer block size
#[cfg(target_os = "openbsd")]
pub fn optimal_transfer_size(&self) -> u32 {
self.0.f_iosize
}
/// Optimal transfer block size
#[cfg(all(target_os = "linux", target_arch = "s390x"))]
pub fn optimal_transfer_size(&self) -> u32 {
self.0.f_bsize
}
/// Optimal transfer block size
#[cfg(any(
target_os = "android",
all(target_os = "linux", target_env = "musl")
))]
pub fn optimal_transfer_size(&self) -> libc::c_ulong {
self.0.f_bsize
}
/// Optimal transfer block size
#[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))]
pub fn optimal_transfer_size(&self) -> libc::__fsword_t {
self.0.f_bsize
}
/// Optimal transfer block size
#[cfg(target_os = "dragonfly")]
pub fn optimal_transfer_size(&self) -> libc::c_long {
self.0.f_iosize
}
/// Optimal transfer block size
#[cfg(target_os = "freebsd")]
pub fn optimal_transfer_size(&self) -> u64 {
self.0.f_iosize
}
/// Size of a block
#[cfg(any(target_os = "ios", target_os = "macos", target_os = "openbsd"))]
pub fn block_size(&self) -> u32 {
self.0.f_bsize
}
/// Size of a block
// f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471
#[cfg(all(target_os = "linux", target_arch = "s390x"))]
pub fn block_size(&self) -> u32 {
self.0.f_bsize
}
/// Size of a block
// f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn block_size(&self) -> libc::c_ulong {
self.0.f_bsize
}
/// Size of a block
// f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471
#[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))]
pub fn block_size(&self) -> libc::__fsword_t {
self.0.f_bsize
}
/// Size of a block
#[cfg(target_os = "freebsd")]
pub fn block_size(&self) -> u64 {
self.0.f_bsize
}
/// Size of a block
#[cfg(target_os = "android")]
pub fn block_size(&self) -> libc::c_ulong {
self.0.f_bsize
}
/// Size of a block
#[cfg(target_os = "dragonfly")]
pub fn block_size(&self) -> libc::c_long {
self.0.f_bsize
}
/// Maximum length of filenames
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
pub fn maximum_name_length(&self) -> u32 {
self.0.f_namemax
}
/// Maximum length of filenames
#[cfg(all(target_os = "linux", target_arch = "s390x"))]
pub fn maximum_name_length(&self) -> u32 {
self.0.f_namelen
}
/// Maximum length of filenames
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub fn maximum_name_length(&self) -> libc::c_ulong {
self.0.f_namelen
}
/// Maximum length of filenames
#[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))]
pub fn maximum_name_length(&self) -> libc::__fsword_t {
self.0.f_namelen
}
/// Maximum length of filenames
#[cfg(target_os = "android")]
pub fn maximum_name_length(&self) -> libc::c_ulong {
self.0.f_namelen
}
/// Total data blocks in filesystem
#[cfg(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
))]
pub fn blocks(&self) -> u64 {
self.0.f_blocks
}
/// Total data blocks in filesystem
#[cfg(target_os = "dragonfly")]
pub fn blocks(&self) -> libc::c_long {
self.0.f_blocks
}
/// Total data blocks in filesystem
#[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))]
pub fn blocks(&self) -> u64 {
self.0.f_blocks
}
/// Total data blocks in filesystem
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32")))
)))]
pub fn blocks(&self) -> libc::c_ulong {
self.0.f_blocks
}
/// Free blocks in filesystem
#[cfg(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
))]
pub fn blocks_free(&self) -> u64 {
self.0.f_bfree
}
/// Free blocks in filesystem
#[cfg(target_os = "dragonfly")]
pub fn blocks_free(&self) -> libc::c_long {
self.0.f_bfree
}
/// Free blocks in filesystem
#[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))]
pub fn blocks_free(&self) -> u64 {
self.0.f_bfree
}
/// Free blocks in filesystem
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32")))
)))]
pub fn blocks_free(&self) -> libc::c_ulong {
self.0.f_bfree
}
/// Free blocks available to unprivileged user
#[cfg(any(target_os = "ios", target_os = "macos", target_os = "android"))]
pub fn blocks_available(&self) -> u64 {
self.0.f_bavail
}
/// Free blocks available to unprivileged user
#[cfg(target_os = "dragonfly")]
pub fn blocks_available(&self) -> libc::c_long {
self.0.f_bavail
}
/// Free blocks available to unprivileged user
#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
pub fn blocks_available(&self) -> i64 {
self.0.f_bavail
}
/// Free blocks available to unprivileged user
#[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))]
pub fn blocks_available(&self) -> u64 {
self.0.f_bavail
}
/// Free blocks available to unprivileged user
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32")))
)))]
pub fn blocks_available(&self) -> libc::c_ulong {
self.0.f_bavail
}
/// Total file nodes in filesystem
#[cfg(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
))]
pub fn files(&self) -> u64 {
self.0.f_files
}
/// Total file nodes in filesystem
#[cfg(target_os = "dragonfly")]
pub fn files(&self) -> libc::c_long {
self.0.f_files
}
/// Total file nodes in filesystem
#[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))]
pub fn files(&self) -> libc::fsfilcnt_t {
self.0.f_files
}
/// Total file nodes in filesystem
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32")))
)))]
pub fn files(&self) -> libc::c_ulong {
self.0.f_files
}
/// Free file nodes in filesystem
#[cfg(any(
target_os = "android",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
pub fn files_free(&self) -> u64 {
self.0.f_ffree
}
/// Free file nodes in filesystem
#[cfg(target_os = "dragonfly")]
pub fn files_free(&self) -> libc::c_long {
self.0.f_ffree
}
/// Free file nodes in filesystem
#[cfg(target_os = "freebsd")]
pub fn files_free(&self) -> i64 {
self.0.f_ffree
}
/// Free file nodes in filesystem
#[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))]
pub fn files_free(&self) -> libc::fsfilcnt_t {
self.0.f_ffree
}
/// Free file nodes in filesystem
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "android",
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32")))
)))]
pub fn files_free(&self) -> libc::c_ulong {
self.0.f_ffree
}
/// Filesystem ID
pub fn filesystem_id(&self) -> fsid_t {
self.0.f_fsid
}
}
impl Debug for Statfs {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Statfs")
.field("optimal_transfer_size", &self.optimal_transfer_size())
.field("block_size", &self.block_size())
.field("blocks", &self.blocks())
.field("blocks_free", &self.blocks_free())
.field("blocks_available", &self.blocks_available())
.field("files", &self.files())
.field("files_free", &self.files_free())
.field("filesystem_id", &self.filesystem_id())
.finish()
}
}
/// Describes a mounted file system.
///
/// The result is OS-dependent. For a portabable alternative, see
/// [`statvfs`](crate::sys::statvfs::statvfs).
///
/// # Arguments
///
/// `path` - Path to any file within the file system to describe
pub fn statfs<P: ?Sized + NixPath>(path: &P) -> Result<Statfs> {
unsafe {
let mut stat = mem::MaybeUninit::<libc::statfs>::uninit();
let res = path.with_nix_path(|path| libc::statfs(path.as_ptr(), stat.as_mut_ptr()))?;
Errno::result(res).map(|_| Statfs(stat.assume_init()))
}
}
/// Describes a mounted file system.
///
/// The result is OS-dependent. For a portabable alternative, see
/// [`fstatvfs`](crate::sys::statvfs::fstatvfs).
///
/// # Arguments
///
/// `fd` - File descriptor of any open file within the file system to describe
pub fn fstatfs<T: AsRawFd>(fd: &T) -> Result<Statfs> {
unsafe {
let mut stat = mem::MaybeUninit::<libc::statfs>::uninit();
Errno::result(libc::fstatfs(fd.as_raw_fd(), stat.as_mut_ptr()))
.map(|_| Statfs(stat.assume_init()))
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use crate::sys::statfs::*;
use crate::sys::statvfs::*;
use std::path::Path;
#[test]
fn statfs_call() {
check_statfs("/tmp");
check_statfs("/dev");
check_statfs("/run");
check_statfs("/");
}
#[test]
fn fstatfs_call() {
check_fstatfs("/tmp");
check_fstatfs("/dev");
check_fstatfs("/run");
check_fstatfs("/");
}
fn check_fstatfs(path: &str) {
if !Path::new(path).exists() {
return;
}
let vfs = statvfs(path.as_bytes()).unwrap();
let file = File::open(path).unwrap();
let fs = fstatfs(&file).unwrap();
assert_fs_equals(fs, vfs);
}
fn check_statfs(path: &str) {
if !Path::new(path).exists() {
return;
}
let vfs = statvfs(path.as_bytes()).unwrap();
let fs = statfs(path.as_bytes()).unwrap();
assert_fs_equals(fs, vfs);
}
fn assert_fs_equals(fs: Statfs, vfs: Statvfs) {
assert_eq!(fs.files() as u64, vfs.files() as u64);
assert_eq!(fs.blocks() as u64, vfs.blocks() as u64);
assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64);
}
// This test is ignored because files_free/blocks_free can change after statvfs call and before
// statfs call.
#[test]
#[ignore]
fn statfs_call_strict() {
check_statfs_strict("/tmp");
check_statfs_strict("/dev");
check_statfs_strict("/run");
check_statfs_strict("/");
}
// This test is ignored because files_free/blocks_free can change after statvfs call and before
// fstatfs call.
#[test]
#[ignore]
fn fstatfs_call_strict() {
check_fstatfs_strict("/tmp");
check_fstatfs_strict("/dev");
check_fstatfs_strict("/run");
check_fstatfs_strict("/");
}
fn check_fstatfs_strict(path: &str) {
if !Path::new(path).exists() {
return;
}
let vfs = statvfs(path.as_bytes());
let file = File::open(path).unwrap();
let fs = fstatfs(&file);
assert_fs_equals_strict(fs.unwrap(), vfs.unwrap())
}
fn check_statfs_strict(path: &str) {
if !Path::new(path).exists() {
return;
}
let vfs = statvfs(path.as_bytes());
let fs = statfs(path.as_bytes());
assert_fs_equals_strict(fs.unwrap(), vfs.unwrap())
}
fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) {
assert_eq!(fs.files_free() as u64, vfs.files_free() as u64);
assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64);
assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64);
assert_eq!(fs.files() as u64, vfs.files() as u64);
assert_eq!(fs.blocks() as u64, vfs.blocks() as u64);
assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64);
}
}

View file

@ -0,0 +1,161 @@
//! Get filesystem statistics
//!
//! See [the man pages](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html)
//! for more details.
use std::mem;
use std::os::unix::io::AsRawFd;
use libc::{self, c_ulong};
use crate::{Result, NixPath, errno::Errno};
#[cfg(not(target_os = "redox"))]
libc_bitflags!(
/// File system mount Flags
#[repr(C)]
#[derive(Default)]
pub struct FsFlags: c_ulong {
/// Read Only
ST_RDONLY;
/// Do not allow the set-uid bits to have an effect
ST_NOSUID;
/// Do not interpret character or block-special devices
#[cfg(any(target_os = "android", target_os = "linux"))]
ST_NODEV;
/// Do not allow execution of binaries on the filesystem
#[cfg(any(target_os = "android", target_os = "linux"))]
ST_NOEXEC;
/// All IO should be done synchronously
#[cfg(any(target_os = "android", target_os = "linux"))]
ST_SYNCHRONOUS;
/// Allow mandatory locks on the filesystem
#[cfg(any(target_os = "android", target_os = "linux"))]
ST_MANDLOCK;
/// Write on file/directory/symlink
#[cfg(target_os = "linux")]
ST_WRITE;
/// Append-only file
#[cfg(target_os = "linux")]
ST_APPEND;
/// Immutable file
#[cfg(target_os = "linux")]
ST_IMMUTABLE;
/// Do not update access times on files
#[cfg(any(target_os = "android", target_os = "linux"))]
ST_NOATIME;
/// Do not update access times on files
#[cfg(any(target_os = "android", target_os = "linux"))]
ST_NODIRATIME;
/// Update access time relative to modify/change time
#[cfg(any(target_os = "android", all(target_os = "linux", not(target_env = "musl"))))]
ST_RELATIME;
}
);
/// Wrapper around the POSIX `statvfs` struct
///
/// For more information see the [`statvfs(3)` man pages](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_statvfs.h.html).
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Statvfs(libc::statvfs);
impl Statvfs {
/// get the file system block size
pub fn block_size(&self) -> c_ulong {
self.0.f_bsize
}
/// Get the fundamental file system block size
pub fn fragment_size(&self) -> c_ulong {
self.0.f_frsize
}
/// Get the number of blocks.
///
/// Units are in units of `fragment_size()`
pub fn blocks(&self) -> libc::fsblkcnt_t {
self.0.f_blocks
}
/// Get the number of free blocks in the file system
pub fn blocks_free(&self) -> libc::fsblkcnt_t {
self.0.f_bfree
}
/// Get the number of free blocks for unprivileged users
pub fn blocks_available(&self) -> libc::fsblkcnt_t {
self.0.f_bavail
}
/// Get the total number of file inodes
pub fn files(&self) -> libc::fsfilcnt_t {
self.0.f_files
}
/// Get the number of free file inodes
pub fn files_free(&self) -> libc::fsfilcnt_t {
self.0.f_ffree
}
/// Get the number of free file inodes for unprivileged users
pub fn files_available(&self) -> libc::fsfilcnt_t {
self.0.f_favail
}
/// Get the file system id
pub fn filesystem_id(&self) -> c_ulong {
self.0.f_fsid
}
/// Get the mount flags
#[cfg(not(target_os = "redox"))]
pub fn flags(&self) -> FsFlags {
FsFlags::from_bits_truncate(self.0.f_flag)
}
/// Get the maximum filename length
pub fn name_max(&self) -> c_ulong {
self.0.f_namemax
}
}
/// Return a `Statvfs` object with information about the `path`
pub fn statvfs<P: ?Sized + NixPath>(path: &P) -> Result<Statvfs> {
unsafe {
Errno::clear();
let mut stat = mem::MaybeUninit::<libc::statvfs>::uninit();
let res = path.with_nix_path(|path|
libc::statvfs(path.as_ptr(), stat.as_mut_ptr())
)?;
Errno::result(res).map(|_| Statvfs(stat.assume_init()))
}
}
/// Return a `Statvfs` object with information about `fd`
pub fn fstatvfs<T: AsRawFd>(fd: &T) -> Result<Statvfs> {
unsafe {
Errno::clear();
let mut stat = mem::MaybeUninit::<libc::statvfs>::uninit();
Errno::result(libc::fstatvfs(fd.as_raw_fd(), stat.as_mut_ptr()))
.map(|_| Statvfs(stat.assume_init()))
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use crate::sys::statvfs::*;
#[test]
fn statvfs_call() {
statvfs(&b"/"[..]).unwrap();
}
#[test]
fn fstatvfs_call() {
let root = File::open("/").unwrap();
fstatvfs(&root).unwrap();
}
}

View file

@ -0,0 +1,79 @@
use libc::{self, SI_LOAD_SHIFT};
use std::{cmp, mem};
use std::time::Duration;
use crate::Result;
use crate::errno::Errno;
/// System info structure returned by `sysinfo`.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct SysInfo(libc::sysinfo);
// The fields are c_ulong on 32-bit linux, u64 on 64-bit linux; x32's ulong is u32
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
type mem_blocks_t = u64;
#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
type mem_blocks_t = libc::c_ulong;
impl SysInfo {
/// Returns the load average tuple.
///
/// The returned values represent the load average over time intervals of
/// 1, 5, and 15 minutes, respectively.
pub fn load_average(&self) -> (f64, f64, f64) {
(
self.0.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64,
self.0.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64,
self.0.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64,
)
}
/// Returns the time since system boot.
pub fn uptime(&self) -> Duration {
// Truncate negative values to 0
Duration::from_secs(cmp::max(self.0.uptime, 0) as u64)
}
/// Current number of processes.
pub fn process_count(&self) -> u16 {
self.0.procs
}
/// Returns the amount of swap memory in Bytes.
pub fn swap_total(&self) -> u64 {
self.scale_mem(self.0.totalswap)
}
/// Returns the amount of unused swap memory in Bytes.
pub fn swap_free(&self) -> u64 {
self.scale_mem(self.0.freeswap)
}
/// Returns the total amount of installed RAM in Bytes.
pub fn ram_total(&self) -> u64 {
self.scale_mem(self.0.totalram)
}
/// Returns the amount of completely unused RAM in Bytes.
///
/// "Unused" in this context means that the RAM in neither actively used by
/// programs, nor by the operating system as disk cache or buffer. It is
/// "wasted" RAM since it currently serves no purpose.
pub fn ram_unused(&self) -> u64 {
self.scale_mem(self.0.freeram)
}
fn scale_mem(&self, units: mem_blocks_t) -> u64 {
units as u64 * self.0.mem_unit as u64
}
}
/// Returns system information.
///
/// [See `sysinfo(2)`](https://man7.org/linux/man-pages/man2/sysinfo.2.html).
pub fn sysinfo() -> Result<SysInfo> {
let mut info = mem::MaybeUninit::uninit();
let res = unsafe { libc::sysinfo(info.as_mut_ptr()) };
Errno::result(res).map(|_| unsafe{ SysInfo(info.assume_init()) })
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,609 @@
use std::{cmp, fmt, ops};
use std::time::Duration;
use std::convert::From;
use libc::{timespec, timeval};
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
pub use libc::{time_t, suseconds_t};
pub trait TimeValLike: Sized {
#[inline]
fn zero() -> Self {
Self::seconds(0)
}
#[inline]
fn hours(hours: i64) -> Self {
let secs = hours.checked_mul(SECS_PER_HOUR)
.expect("TimeValLike::hours ouf of bounds");
Self::seconds(secs)
}
#[inline]
fn minutes(minutes: i64) -> Self {
let secs = minutes.checked_mul(SECS_PER_MINUTE)
.expect("TimeValLike::minutes out of bounds");
Self::seconds(secs)
}
fn seconds(seconds: i64) -> Self;
fn milliseconds(milliseconds: i64) -> Self;
fn microseconds(microseconds: i64) -> Self;
fn nanoseconds(nanoseconds: i64) -> Self;
#[inline]
fn num_hours(&self) -> i64 {
self.num_seconds() / 3600
}
#[inline]
fn num_minutes(&self) -> i64 {
self.num_seconds() / 60
}
fn num_seconds(&self) -> i64;
fn num_milliseconds(&self) -> i64;
fn num_microseconds(&self) -> i64;
fn num_nanoseconds(&self) -> i64;
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct TimeSpec(timespec);
const NANOS_PER_SEC: i64 = 1_000_000_000;
const SECS_PER_MINUTE: i64 = 60;
const SECS_PER_HOUR: i64 = 3600;
#[cfg(target_pointer_width = "64")]
const TS_MAX_SECONDS: i64 = (::std::i64::MAX / NANOS_PER_SEC) - 1;
#[cfg(target_pointer_width = "32")]
const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64;
const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS;
// x32 compatibility
// See https://sourceware.org/bugzilla/show_bug.cgi?id=16437
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
type timespec_tv_nsec_t = i64;
#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
type timespec_tv_nsec_t = libc::c_long;
impl From<timespec> for TimeSpec {
fn from(ts: timespec) -> Self {
Self(ts)
}
}
impl From<Duration> for TimeSpec {
fn from(duration: Duration) -> Self {
Self::from_duration(duration)
}
}
impl From<TimeSpec> for Duration {
fn from(timespec: TimeSpec) -> Self {
Duration::new(timespec.0.tv_sec as u64, timespec.0.tv_nsec as u32)
}
}
impl AsRef<timespec> for TimeSpec {
fn as_ref(&self) -> &timespec {
&self.0
}
}
impl AsMut<timespec> for TimeSpec {
fn as_mut(&mut self) -> &mut timespec {
&mut self.0
}
}
impl Ord for TimeSpec {
// The implementation of cmp is simplified by assuming that the struct is
// normalized. That is, tv_nsec must always be within [0, 1_000_000_000)
fn cmp(&self, other: &TimeSpec) -> cmp::Ordering {
if self.tv_sec() == other.tv_sec() {
self.tv_nsec().cmp(&other.tv_nsec())
} else {
self.tv_sec().cmp(&other.tv_sec())
}
}
}
impl PartialOrd for TimeSpec {
fn partial_cmp(&self, other: &TimeSpec) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl TimeValLike for TimeSpec {
#[inline]
fn seconds(seconds: i64) -> TimeSpec {
assert!(seconds >= TS_MIN_SECONDS && seconds <= TS_MAX_SECONDS,
"TimeSpec out of bounds; seconds={}", seconds);
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
TimeSpec(timespec {tv_sec: seconds as time_t, tv_nsec: 0 })
}
#[inline]
fn milliseconds(milliseconds: i64) -> TimeSpec {
let nanoseconds = milliseconds.checked_mul(1_000_000)
.expect("TimeSpec::milliseconds out of bounds");
TimeSpec::nanoseconds(nanoseconds)
}
/// Makes a new `TimeSpec` with given number of microseconds.
#[inline]
fn microseconds(microseconds: i64) -> TimeSpec {
let nanoseconds = microseconds.checked_mul(1_000)
.expect("TimeSpec::milliseconds out of bounds");
TimeSpec::nanoseconds(nanoseconds)
}
/// Makes a new `TimeSpec` with given number of nanoseconds.
#[inline]
fn nanoseconds(nanoseconds: i64) -> TimeSpec {
let (secs, nanos) = div_mod_floor_64(nanoseconds, NANOS_PER_SEC);
assert!(secs >= TS_MIN_SECONDS && secs <= TS_MAX_SECONDS,
"TimeSpec out of bounds");
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
TimeSpec(timespec {tv_sec: secs as time_t,
tv_nsec: nanos as timespec_tv_nsec_t })
}
fn num_seconds(&self) -> i64 {
if self.tv_sec() < 0 && self.tv_nsec() > 0 {
(self.tv_sec() + 1) as i64
} else {
self.tv_sec() as i64
}
}
fn num_milliseconds(&self) -> i64 {
self.num_nanoseconds() / 1_000_000
}
fn num_microseconds(&self) -> i64 {
self.num_nanoseconds() / 1_000_000_000
}
fn num_nanoseconds(&self) -> i64 {
let secs = self.num_seconds() * 1_000_000_000;
let nsec = self.nanos_mod_sec();
secs + nsec as i64
}
}
impl TimeSpec {
fn nanos_mod_sec(&self) -> timespec_tv_nsec_t {
if self.tv_sec() < 0 && self.tv_nsec() > 0 {
self.tv_nsec() - NANOS_PER_SEC as timespec_tv_nsec_t
} else {
self.tv_nsec()
}
}
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
pub const fn tv_sec(&self) -> time_t {
self.0.tv_sec
}
pub const fn tv_nsec(&self) -> timespec_tv_nsec_t {
self.0.tv_nsec
}
pub const fn from_duration(duration: Duration) -> Self {
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
TimeSpec(timespec {
tv_sec: duration.as_secs() as time_t,
tv_nsec: duration.subsec_nanos() as timespec_tv_nsec_t
})
}
pub const fn from_timespec(timespec: timespec) -> Self {
Self(timespec)
}
}
impl ops::Neg for TimeSpec {
type Output = TimeSpec;
fn neg(self) -> TimeSpec {
TimeSpec::nanoseconds(-self.num_nanoseconds())
}
}
impl ops::Add for TimeSpec {
type Output = TimeSpec;
fn add(self, rhs: TimeSpec) -> TimeSpec {
TimeSpec::nanoseconds(
self.num_nanoseconds() + rhs.num_nanoseconds())
}
}
impl ops::Sub for TimeSpec {
type Output = TimeSpec;
fn sub(self, rhs: TimeSpec) -> TimeSpec {
TimeSpec::nanoseconds(
self.num_nanoseconds() - rhs.num_nanoseconds())
}
}
impl ops::Mul<i32> for TimeSpec {
type Output = TimeSpec;
fn mul(self, rhs: i32) -> TimeSpec {
let usec = self.num_nanoseconds().checked_mul(i64::from(rhs))
.expect("TimeSpec multiply out of bounds");
TimeSpec::nanoseconds(usec)
}
}
impl ops::Div<i32> for TimeSpec {
type Output = TimeSpec;
fn div(self, rhs: i32) -> TimeSpec {
let usec = self.num_nanoseconds() / i64::from(rhs);
TimeSpec::nanoseconds(usec)
}
}
impl fmt::Display for TimeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (abs, sign) = if self.tv_sec() < 0 {
(-*self, "-")
} else {
(*self, "")
};
let sec = abs.tv_sec();
write!(f, "{}", sign)?;
if abs.tv_nsec() == 0 {
if abs.tv_sec() == 1 {
write!(f, "{} second", sec)?;
} else {
write!(f, "{} seconds", sec)?;
}
} else if abs.tv_nsec() % 1_000_000 == 0 {
write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)?;
} else if abs.tv_nsec() % 1_000 == 0 {
write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)?;
} else {
write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())?;
}
Ok(())
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct TimeVal(timeval);
const MICROS_PER_SEC: i64 = 1_000_000;
#[cfg(target_pointer_width = "64")]
const TV_MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1;
#[cfg(target_pointer_width = "32")]
const TV_MAX_SECONDS: i64 = ::std::isize::MAX as i64;
const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS;
impl AsRef<timeval> for TimeVal {
fn as_ref(&self) -> &timeval {
&self.0
}
}
impl AsMut<timeval> for TimeVal {
fn as_mut(&mut self) -> &mut timeval {
&mut self.0
}
}
impl Ord for TimeVal {
// The implementation of cmp is simplified by assuming that the struct is
// normalized. That is, tv_usec must always be within [0, 1_000_000)
fn cmp(&self, other: &TimeVal) -> cmp::Ordering {
if self.tv_sec() == other.tv_sec() {
self.tv_usec().cmp(&other.tv_usec())
} else {
self.tv_sec().cmp(&other.tv_sec())
}
}
}
impl PartialOrd for TimeVal {
fn partial_cmp(&self, other: &TimeVal) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl TimeValLike for TimeVal {
#[inline]
fn seconds(seconds: i64) -> TimeVal {
assert!(seconds >= TV_MIN_SECONDS && seconds <= TV_MAX_SECONDS,
"TimeVal out of bounds; seconds={}", seconds);
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
TimeVal(timeval {tv_sec: seconds as time_t, tv_usec: 0 })
}
#[inline]
fn milliseconds(milliseconds: i64) -> TimeVal {
let microseconds = milliseconds.checked_mul(1_000)
.expect("TimeVal::milliseconds out of bounds");
TimeVal::microseconds(microseconds)
}
/// Makes a new `TimeVal` with given number of microseconds.
#[inline]
fn microseconds(microseconds: i64) -> TimeVal {
let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC);
assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS,
"TimeVal out of bounds");
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
TimeVal(timeval {tv_sec: secs as time_t,
tv_usec: micros as suseconds_t })
}
/// Makes a new `TimeVal` with given number of nanoseconds. Some precision
/// will be lost
#[inline]
fn nanoseconds(nanoseconds: i64) -> TimeVal {
let microseconds = nanoseconds / 1000;
let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC);
assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS,
"TimeVal out of bounds");
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
TimeVal(timeval {tv_sec: secs as time_t,
tv_usec: micros as suseconds_t })
}
fn num_seconds(&self) -> i64 {
if self.tv_sec() < 0 && self.tv_usec() > 0 {
(self.tv_sec() + 1) as i64
} else {
self.tv_sec() as i64
}
}
fn num_milliseconds(&self) -> i64 {
self.num_microseconds() / 1_000
}
fn num_microseconds(&self) -> i64 {
let secs = self.num_seconds() * 1_000_000;
let usec = self.micros_mod_sec();
secs + usec as i64
}
fn num_nanoseconds(&self) -> i64 {
self.num_microseconds() * 1_000
}
}
impl TimeVal {
fn micros_mod_sec(&self) -> suseconds_t {
if self.tv_sec() < 0 && self.tv_usec() > 0 {
self.tv_usec() - MICROS_PER_SEC as suseconds_t
} else {
self.tv_usec()
}
}
#[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848
pub const fn tv_sec(&self) -> time_t {
self.0.tv_sec
}
pub const fn tv_usec(&self) -> suseconds_t {
self.0.tv_usec
}
}
impl ops::Neg for TimeVal {
type Output = TimeVal;
fn neg(self) -> TimeVal {
TimeVal::microseconds(-self.num_microseconds())
}
}
impl ops::Add for TimeVal {
type Output = TimeVal;
fn add(self, rhs: TimeVal) -> TimeVal {
TimeVal::microseconds(
self.num_microseconds() + rhs.num_microseconds())
}
}
impl ops::Sub for TimeVal {
type Output = TimeVal;
fn sub(self, rhs: TimeVal) -> TimeVal {
TimeVal::microseconds(
self.num_microseconds() - rhs.num_microseconds())
}
}
impl ops::Mul<i32> for TimeVal {
type Output = TimeVal;
fn mul(self, rhs: i32) -> TimeVal {
let usec = self.num_microseconds().checked_mul(i64::from(rhs))
.expect("TimeVal multiply out of bounds");
TimeVal::microseconds(usec)
}
}
impl ops::Div<i32> for TimeVal {
type Output = TimeVal;
fn div(self, rhs: i32) -> TimeVal {
let usec = self.num_microseconds() / i64::from(rhs);
TimeVal::microseconds(usec)
}
}
impl fmt::Display for TimeVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (abs, sign) = if self.tv_sec() < 0 {
(-*self, "-")
} else {
(*self, "")
};
let sec = abs.tv_sec();
write!(f, "{}", sign)?;
if abs.tv_usec() == 0 {
if abs.tv_sec() == 1 {
write!(f, "{} second", sec)?;
} else {
write!(f, "{} seconds", sec)?;
}
} else if abs.tv_usec() % 1000 == 0 {
write!(f, "{}.{:03} seconds", sec, abs.tv_usec() / 1000)?;
} else {
write!(f, "{}.{:06} seconds", sec, abs.tv_usec())?;
}
Ok(())
}
}
impl From<timeval> for TimeVal {
fn from(tv: timeval) -> Self {
TimeVal(tv)
}
}
#[inline]
fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) {
(div_floor_64(this, other), mod_floor_64(this, other))
}
#[inline]
fn div_floor_64(this: i64, other: i64) -> i64 {
match div_rem_64(this, other) {
(d, r) if (r > 0 && other < 0)
|| (r < 0 && other > 0) => d - 1,
(d, _) => d,
}
}
#[inline]
fn mod_floor_64(this: i64, other: i64) -> i64 {
match this % other {
r if (r > 0 && other < 0)
|| (r < 0 && other > 0) => r + other,
r => r,
}
}
#[inline]
fn div_rem_64(this: i64, other: i64) -> (i64, i64) {
(this / other, this % other)
}
#[cfg(test)]
mod test {
use super::{TimeSpec, TimeVal, TimeValLike};
use std::time::Duration;
#[test]
pub fn test_timespec() {
assert!(TimeSpec::seconds(1) != TimeSpec::zero());
assert_eq!(TimeSpec::seconds(1) + TimeSpec::seconds(2),
TimeSpec::seconds(3));
assert_eq!(TimeSpec::minutes(3) + TimeSpec::seconds(2),
TimeSpec::seconds(182));
}
#[test]
pub fn test_timespec_from() {
let duration = Duration::new(123, 123_456_789);
let timespec = TimeSpec::nanoseconds(123_123_456_789);
assert_eq!(TimeSpec::from(duration), timespec);
assert_eq!(Duration::from(timespec), duration);
}
#[test]
pub fn test_timespec_neg() {
let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123);
let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123);
assert_eq!(a, -b);
}
#[test]
pub fn test_timespec_ord() {
assert!(TimeSpec::seconds(1) == TimeSpec::nanoseconds(1_000_000_000));
assert!(TimeSpec::seconds(1) < TimeSpec::nanoseconds(1_000_000_001));
assert!(TimeSpec::seconds(1) > TimeSpec::nanoseconds(999_999_999));
assert!(TimeSpec::seconds(-1) < TimeSpec::nanoseconds(-999_999_999));
assert!(TimeSpec::seconds(-1) > TimeSpec::nanoseconds(-1_000_000_001));
}
#[test]
pub fn test_timespec_fmt() {
assert_eq!(TimeSpec::zero().to_string(), "0 seconds");
assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds");
assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds");
assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds");
assert_eq!(TimeSpec::nanoseconds(42).to_string(), "0.000000042 seconds");
assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds");
}
#[test]
pub fn test_timeval() {
assert!(TimeVal::seconds(1) != TimeVal::zero());
assert_eq!(TimeVal::seconds(1) + TimeVal::seconds(2),
TimeVal::seconds(3));
assert_eq!(TimeVal::minutes(3) + TimeVal::seconds(2),
TimeVal::seconds(182));
}
#[test]
pub fn test_timeval_ord() {
assert!(TimeVal::seconds(1) == TimeVal::microseconds(1_000_000));
assert!(TimeVal::seconds(1) < TimeVal::microseconds(1_000_001));
assert!(TimeVal::seconds(1) > TimeVal::microseconds(999_999));
assert!(TimeVal::seconds(-1) < TimeVal::microseconds(-999_999));
assert!(TimeVal::seconds(-1) > TimeVal::microseconds(-1_000_001));
}
#[test]
pub fn test_timeval_neg() {
let a = TimeVal::seconds(1) + TimeVal::microseconds(123);
let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123);
assert_eq!(a, -b);
}
#[test]
pub fn test_timeval_fmt() {
assert_eq!(TimeVal::zero().to_string(), "0 seconds");
assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds");
assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds");
assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds");
assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds");
assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds");
}
}

View file

@ -0,0 +1,281 @@
//! Timer API via file descriptors.
//!
//! Timer FD is a Linux-only API to create timers and get expiration
//! notifications through file descriptors.
//!
//! For more documentation, please read [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html).
//!
//! # Examples
//!
//! Create a new one-shot timer that expires after 1 second.
//! ```
//! # use std::os::unix::io::AsRawFd;
//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags,
//! # Expiration};
//! # use nix::sys::time::{TimeSpec, TimeValLike};
//! # use nix::unistd::read;
//! #
//! // We create a new monotonic timer.
//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
//! .unwrap();
//!
//! // We set a new one-shot timer in 1 seconds.
//! timer.set(
//! Expiration::OneShot(TimeSpec::seconds(1)),
//! TimerSetTimeFlags::empty()
//! ).unwrap();
//!
//! // We wait for the timer to expire.
//! timer.wait().unwrap();
//! ```
use crate::sys::time::TimeSpec;
use crate::unistd::read;
use crate::{errno::Errno, Result};
use bitflags::bitflags;
use libc::c_int;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
/// A timerfd instance. This is also a file descriptor, you can feed it to
/// other interfaces consuming file descriptors, epoll for example.
#[derive(Debug)]
pub struct TimerFd {
fd: RawFd,
}
impl AsRawFd for TimerFd {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl FromRawFd for TimerFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
TimerFd { fd }
}
}
libc_enum! {
/// The type of the clock used to mark the progress of the timer. For more
/// details on each kind of clock, please refer to [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html).
#[repr(i32)]
#[non_exhaustive]
pub enum ClockId {
CLOCK_REALTIME,
CLOCK_MONOTONIC,
CLOCK_BOOTTIME,
CLOCK_REALTIME_ALARM,
CLOCK_BOOTTIME_ALARM,
}
}
libc_bitflags! {
/// Additional flags to change the behaviour of the file descriptor at the
/// time of creation.
pub struct TimerFlags: c_int {
TFD_NONBLOCK;
TFD_CLOEXEC;
}
}
bitflags! {
/// Flags that are used for arming the timer.
pub struct TimerSetTimeFlags: libc::c_int {
const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
}
}
#[derive(Debug, Clone, Copy)]
struct TimerSpec(libc::itimerspec);
impl TimerSpec {
pub const fn none() -> Self {
Self(libc::itimerspec {
it_interval: libc::timespec {
tv_sec: 0,
tv_nsec: 0,
},
it_value: libc::timespec {
tv_sec: 0,
tv_nsec: 0,
},
})
}
}
impl AsRef<libc::itimerspec> for TimerSpec {
fn as_ref(&self) -> &libc::itimerspec {
&self.0
}
}
impl From<Expiration> for TimerSpec {
fn from(expiration: Expiration) -> TimerSpec {
match expiration {
Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
it_interval: libc::timespec {
tv_sec: 0,
tv_nsec: 0,
},
it_value: *t.as_ref(),
}),
Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
it_interval: *interval.as_ref(),
it_value: *start.as_ref(),
}),
Expiration::Interval(t) => TimerSpec(libc::itimerspec {
it_interval: *t.as_ref(),
it_value: *t.as_ref(),
}),
}
}
}
impl From<TimerSpec> for Expiration {
fn from(timerspec: TimerSpec) -> Expiration {
match timerspec {
TimerSpec(libc::itimerspec {
it_interval:
libc::timespec {
tv_sec: 0,
tv_nsec: 0,
},
it_value: ts,
}) => Expiration::OneShot(ts.into()),
TimerSpec(libc::itimerspec {
it_interval: int_ts,
it_value: val_ts,
}) => {
if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
Expiration::Interval(int_ts.into())
} else {
Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
}
}
}
}
}
/// An enumeration allowing the definition of the expiration time of an alarm,
/// recurring or not.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Expiration {
OneShot(TimeSpec),
IntervalDelayed(TimeSpec, TimeSpec),
Interval(TimeSpec),
}
impl TimerFd {
/// Creates a new timer based on the clock defined by `clockid`. The
/// underlying fd can be assigned specific flags with `flags` (CLOEXEC,
/// NONBLOCK). The underlying fd will be closed on drop.
pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> {
Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) })
.map(|fd| Self { fd })
}
/// Sets a new alarm on the timer.
///
/// # Types of alarm
///
/// There are 3 types of alarms you can set:
///
/// - one shot: the alarm will trigger once after the specified amount of
/// time.
/// Example: I want an alarm to go off in 60s and then disables itself.
///
/// - interval: the alarm will trigger every specified interval of time.
/// Example: I want an alarm to go off every 60s. The alarm will first
/// go off 60s after I set it and every 60s after that. The alarm will
/// not disable itself.
///
/// - interval delayed: the alarm will trigger after a certain amount of
/// time and then trigger at a specified interval.
/// Example: I want an alarm to go off every 60s but only start in 1h.
/// The alarm will first trigger 1h after I set it and then every 60s
/// after that. The alarm will not disable itself.
///
/// # Relative vs absolute alarm
///
/// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
/// to the `Expiration` you want is relative. If however you want an alarm
/// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
/// Then the one shot TimeSpec and the delay TimeSpec of the delayed
/// interval are going to be interpreted as absolute.
///
/// # Disabling alarms
///
/// Note: Only one alarm can be set for any given timer. Setting a new alarm
/// actually removes the previous one.
///
/// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm
/// altogether.
pub fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
let timerspec: TimerSpec = expiration.into();
Errno::result(unsafe {
libc::timerfd_settime(
self.fd,
flags.bits(),
timerspec.as_ref(),
std::ptr::null_mut(),
)
})
.map(drop)
}
/// Get the parameters for the alarm currently set, if any.
pub fn get(&self) -> Result<Option<Expiration>> {
let mut timerspec = TimerSpec::none();
let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0;
Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| {
if timerspec.0.it_interval.tv_sec == 0
&& timerspec.0.it_interval.tv_nsec == 0
&& timerspec.0.it_value.tv_sec == 0
&& timerspec.0.it_value.tv_nsec == 0
{
None
} else {
Some(timerspec.into())
}
})
}
/// Remove the alarm if any is set.
pub fn unset(&self) -> Result<()> {
Errno::result(unsafe {
libc::timerfd_settime(
self.fd,
TimerSetTimeFlags::empty().bits(),
TimerSpec::none().as_ref(),
std::ptr::null_mut(),
)
})
.map(drop)
}
/// Wait for the configured alarm to expire.
///
/// Note: If the alarm is unset, then you will wait forever.
pub fn wait(&self) -> Result<()> {
while let Err(e) = read(self.fd, &mut [0u8; 8]) {
if e != Errno::EINTR {
return Err(e)
}
}
Ok(())
}
}
impl Drop for TimerFd {
fn drop(&mut self) {
if !std::thread::panicking() {
let result = Errno::result(unsafe {
libc::close(self.fd)
});
if let Err(Errno::EBADF) = result {
panic!("close of TimerFd encountered EBADF");
}
}
}
}

View file

@ -0,0 +1,223 @@
//! Vectored I/O
use crate::Result;
use crate::errno::Errno;
use libc::{self, c_int, c_void, size_t, off_t};
use std::marker::PhantomData;
use std::os::unix::io::RawFd;
/// Low-level vectored write to a raw file descriptor
///
/// See also [writev(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/writev.html)
pub fn writev(fd: RawFd, iov: &[IoVec<&[u8]>]) -> Result<usize> {
let res = unsafe { libc::writev(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) };
Errno::result(res).map(|r| r as usize)
}
/// Low-level vectored read from a raw file descriptor
///
/// See also [readv(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html)
pub fn readv(fd: RawFd, iov: &mut [IoVec<&mut [u8]>]) -> Result<usize> {
let res = unsafe { libc::readv(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int) };
Errno::result(res).map(|r| r as usize)
}
/// Write to `fd` at `offset` from buffers in `iov`.
///
/// Buffers in `iov` will be written in order until all buffers have been written
/// or an error occurs. The file offset is not changed.
///
/// See also: [`writev`](fn.writev.html) and [`pwrite`](fn.pwrite.html)
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
pub fn pwritev(fd: RawFd, iov: &[IoVec<&[u8]>],
offset: off_t) -> Result<usize> {
let res = unsafe {
libc::pwritev(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int, offset)
};
Errno::result(res).map(|r| r as usize)
}
/// Read from `fd` at `offset` filling buffers in `iov`.
///
/// Buffers in `iov` will be filled in order until all buffers have been filled,
/// no more bytes are available, or an error occurs. The file offset is not
/// changed.
///
/// See also: [`readv`](fn.readv.html) and [`pread`](fn.pread.html)
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
pub fn preadv(fd: RawFd, iov: &[IoVec<&mut [u8]>],
offset: off_t) -> Result<usize> {
let res = unsafe {
libc::preadv(fd, iov.as_ptr() as *const libc::iovec, iov.len() as c_int, offset)
};
Errno::result(res).map(|r| r as usize)
}
/// Low-level write to a file, with specified offset.
///
/// See also [pwrite(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pwrite.html)
// TODO: move to unistd
pub fn pwrite(fd: RawFd, buf: &[u8], offset: off_t) -> Result<usize> {
let res = unsafe {
libc::pwrite(fd, buf.as_ptr() as *const c_void, buf.len() as size_t,
offset)
};
Errno::result(res).map(|r| r as usize)
}
/// Low-level write to a file, with specified offset.
///
/// See also [pread(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html)
// TODO: move to unistd
pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result<usize>{
let res = unsafe {
libc::pread(fd, buf.as_mut_ptr() as *mut c_void, buf.len() as size_t,
offset)
};
Errno::result(res).map(|r| r as usize)
}
/// A slice of memory in a remote process, starting at address `base`
/// and consisting of `len` bytes.
///
/// This is the same underlying C structure as [`IoVec`](struct.IoVec.html),
/// except that it refers to memory in some other process, and is
/// therefore not represented in Rust by an actual slice as `IoVec` is. It
/// is used with [`process_vm_readv`](fn.process_vm_readv.html)
/// and [`process_vm_writev`](fn.process_vm_writev.html).
#[cfg(target_os = "linux")]
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RemoteIoVec {
/// The starting address of this slice (`iov_base`).
pub base: usize,
/// The number of bytes in this slice (`iov_len`).
pub len: usize,
}
/// Write data directly to another process's virtual memory
/// (see [`process_vm_writev`(2)]).
///
/// `local_iov` is a list of [`IoVec`]s containing the data to be written,
/// and `remote_iov` is a list of [`RemoteIoVec`]s identifying where the
/// data should be written in the target process. On success, returns the
/// number of bytes written, which will always be a whole
/// number of `remote_iov` chunks.
///
/// This requires the same permissions as debugging the process using
/// [ptrace]: you must either be a privileged process (with
/// `CAP_SYS_PTRACE`), or you must be running as the same user as the
/// target process and the OS must have unprivileged debugging enabled.
///
/// This function is only available on Linux.
///
/// [`process_vm_writev`(2)]: https://man7.org/linux/man-pages/man2/process_vm_writev.2.html
/// [ptrace]: ../ptrace/index.html
/// [`IoVec`]: struct.IoVec.html
/// [`RemoteIoVec`]: struct.RemoteIoVec.html
#[cfg(target_os = "linux")]
pub fn process_vm_writev(
pid: crate::unistd::Pid,
local_iov: &[IoVec<&[u8]>],
remote_iov: &[RemoteIoVec]) -> Result<usize>
{
let res = unsafe {
libc::process_vm_writev(pid.into(),
local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong,
remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0)
};
Errno::result(res).map(|r| r as usize)
}
/// Read data directly from another process's virtual memory
/// (see [`process_vm_readv`(2)]).
///
/// `local_iov` is a list of [`IoVec`]s containing the buffer to copy
/// data into, and `remote_iov` is a list of [`RemoteIoVec`]s identifying
/// where the source data is in the target process. On success,
/// returns the number of bytes written, which will always be a whole
/// number of `remote_iov` chunks.
///
/// This requires the same permissions as debugging the process using
/// [`ptrace`]: you must either be a privileged process (with
/// `CAP_SYS_PTRACE`), or you must be running as the same user as the
/// target process and the OS must have unprivileged debugging enabled.
///
/// This function is only available on Linux.
///
/// [`process_vm_readv`(2)]: https://man7.org/linux/man-pages/man2/process_vm_readv.2.html
/// [`ptrace`]: ../ptrace/index.html
/// [`IoVec`]: struct.IoVec.html
/// [`RemoteIoVec`]: struct.RemoteIoVec.html
#[cfg(any(target_os = "linux"))]
pub fn process_vm_readv(
pid: crate::unistd::Pid,
local_iov: &[IoVec<&mut [u8]>],
remote_iov: &[RemoteIoVec]) -> Result<usize>
{
let res = unsafe {
libc::process_vm_readv(pid.into(),
local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong,
remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0)
};
Errno::result(res).map(|r| r as usize)
}
/// A vector of buffers.
///
/// Vectored I/O methods like [`writev`] and [`readv`] use this structure for
/// both reading and writing. Each `IoVec` specifies the base address and
/// length of an area in memory.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct IoVec<T>(pub(crate) libc::iovec, PhantomData<T>);
impl<T> IoVec<T> {
/// View the `IoVec` as a Rust slice.
#[inline]
pub fn as_slice(&self) -> &[u8] {
use std::slice;
unsafe {
slice::from_raw_parts(
self.0.iov_base as *const u8,
self.0.iov_len as usize)
}
}
}
impl<'a> IoVec<&'a [u8]> {
#[cfg(target_os = "freebsd")]
pub(crate) fn from_raw_parts(base: *mut c_void, len: usize) -> Self {
IoVec(libc::iovec {
iov_base: base,
iov_len: len
}, PhantomData)
}
/// Create an `IoVec` from a Rust slice.
pub fn from_slice(buf: &'a [u8]) -> IoVec<&'a [u8]> {
IoVec(libc::iovec {
iov_base: buf.as_ptr() as *mut c_void,
iov_len: buf.len() as size_t,
}, PhantomData)
}
}
impl<'a> IoVec<&'a mut [u8]> {
/// Create an `IoVec` from a mutable Rust slice.
pub fn from_mut_slice(buf: &'a mut [u8]) -> IoVec<&'a mut [u8]> {
IoVec(libc::iovec {
iov_base: buf.as_ptr() as *mut c_void,
iov_len: buf.len() as size_t,
}, PhantomData)
}
}

View file

@ -0,0 +1,75 @@
//! Get system identification
use std::mem;
use libc::{self, c_char};
use std::ffi::CStr;
use std::str::from_utf8_unchecked;
/// Describes the running system. Return type of [`uname`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct UtsName(libc::utsname);
impl UtsName {
/// Name of the operating system implementation
pub fn sysname(&self) -> &str {
to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char)
}
/// Network name of this machine.
pub fn nodename(&self) -> &str {
to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char)
}
/// Release level of the operating system.
pub fn release(&self) -> &str {
to_str(&(&self.0.release as *const c_char ) as *const *const c_char)
}
/// Version level of the operating system.
pub fn version(&self) -> &str {
to_str(&(&self.0.version as *const c_char ) as *const *const c_char)
}
/// Machine hardware platform.
pub fn machine(&self) -> &str {
to_str(&(&self.0.machine as *const c_char ) as *const *const c_char)
}
}
/// Get system identification
pub fn uname() -> UtsName {
unsafe {
let mut ret = mem::MaybeUninit::uninit();
libc::uname(ret.as_mut_ptr());
UtsName(ret.assume_init())
}
}
#[inline]
fn to_str<'a>(s: *const *const c_char) -> &'a str {
unsafe {
let res = CStr::from_ptr(*s).to_bytes();
from_utf8_unchecked(res)
}
}
#[cfg(test)]
mod test {
#[cfg(target_os = "linux")]
#[test]
pub fn test_uname_linux() {
assert_eq!(super::uname().sysname(), "Linux");
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[test]
pub fn test_uname_darwin() {
assert_eq!(super::uname().sysname(), "Darwin");
}
#[cfg(target_os = "freebsd")]
#[test]
pub fn test_uname_freebsd() {
assert_eq!(super::uname().sysname(), "FreeBSD");
}
}

View file

@ -0,0 +1,262 @@
//! Wait for a process to change status
use crate::errno::Errno;
use crate::sys::signal::Signal;
use crate::unistd::Pid;
use crate::Result;
use cfg_if::cfg_if;
use libc::{self, c_int};
use std::convert::TryFrom;
libc_bitflags!(
/// Controls the behavior of [`waitpid`].
pub struct WaitPidFlag: c_int {
/// Do not block when there are no processes wishing to report status.
WNOHANG;
/// Report the status of selected processes which are stopped due to a
/// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN),
/// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU),
/// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or
/// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal.
WUNTRACED;
/// Report the status of selected processes which have terminated.
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "haiku",
target_os = "ios",
target_os = "linux",
target_os = "redox",
target_os = "macos",
target_os = "netbsd"))]
WEXITED;
/// Report the status of selected processes that have continued from a
/// job control stop by receiving a
/// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal.
WCONTINUED;
/// An alias for WUNTRACED.
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "haiku",
target_os = "ios",
target_os = "linux",
target_os = "redox",
target_os = "macos",
target_os = "netbsd"))]
WSTOPPED;
/// Don't reap, just poll status.
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "haiku",
target_os = "ios",
target_os = "linux",
target_os = "redox",
target_os = "macos",
target_os = "netbsd"))]
WNOWAIT;
/// Don't wait on children of other threads in this group
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
__WNOTHREAD;
/// Wait on all children, regardless of type
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
__WALL;
/// Wait for "clone" children only.
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
__WCLONE;
}
);
/// Possible return values from `wait()` or `waitpid()`.
///
/// Each status (other than `StillAlive`) describes a state transition
/// in a child process `Pid`, such as the process exiting or stopping,
/// plus additional data about the transition if any.
///
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
/// and `PtraceSyscall`. Portable code should avoid exhaustively
/// matching on `WaitStatus`.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WaitStatus {
/// The process exited normally (as with `exit()` or returning from
/// `main`) with the given exit code. This case matches the C macro
/// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
Exited(Pid, i32),
/// The process was killed by the given signal. The third field
/// indicates whether the signal generated a core dump. This case
/// matches the C macro `WIFSIGNALED(status)`; the last two fields
/// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
Signaled(Pid, Signal, bool),
/// The process is alive, but was stopped by the given signal. This
/// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
/// case matches the C macro `WIFSTOPPED(status)`; the second field
/// is `WSTOPSIG(status)`.
Stopped(Pid, Signal),
/// The traced process was stopped by a `PTRACE_EVENT_*` event. See
/// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
/// currently-defined events use `SIGTRAP` as the signal; the third
/// field is the `PTRACE_EVENT_*` value of the event.
///
/// [`nix::sys::ptrace`]: ../ptrace/index.html
/// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(any(target_os = "linux", target_os = "android"))]
PtraceEvent(Pid, Signal, c_int),
/// The traced process was stopped by execution of a system call,
/// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
/// more information.
///
/// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(any(target_os = "linux", target_os = "android"))]
PtraceSyscall(Pid),
/// The process was previously stopped but has resumed execution
/// after receiving a `SIGCONT` signal. This is only reported if
/// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
/// macro `WIFCONTINUED(status)`.
Continued(Pid),
/// There are currently no state changes to report in any awaited
/// child process. This is only returned if `WaitPidFlag::WNOHANG`
/// was used (otherwise `wait()` or `waitpid()` would block until
/// there was something to report).
StillAlive,
}
impl WaitStatus {
/// Extracts the PID from the WaitStatus unless it equals StillAlive.
pub fn pid(&self) -> Option<Pid> {
use self::WaitStatus::*;
match *self {
Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => Some(p),
StillAlive => None,
#[cfg(any(target_os = "android", target_os = "linux"))]
PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
}
}
}
fn exited(status: i32) -> bool {
libc::WIFEXITED(status)
}
fn exit_status(status: i32) -> i32 {
libc::WEXITSTATUS(status)
}
fn signaled(status: i32) -> bool {
libc::WIFSIGNALED(status)
}
fn term_signal(status: i32) -> Result<Signal> {
Signal::try_from(libc::WTERMSIG(status))
}
fn dumped_core(status: i32) -> bool {
libc::WCOREDUMP(status)
}
fn stopped(status: i32) -> bool {
libc::WIFSTOPPED(status)
}
fn stop_signal(status: i32) -> Result<Signal> {
Signal::try_from(libc::WSTOPSIG(status))
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn syscall_stop(status: i32) -> bool {
// From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
// of delivering SIGTRAP | 0x80 as the signal number for syscall
// stops. This allows easily distinguishing syscall stops from
// genuine SIGTRAP signals.
libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn stop_additional(status: i32) -> c_int {
(status >> 16) as c_int
}
fn continued(status: i32) -> bool {
libc::WIFCONTINUED(status)
}
impl WaitStatus {
/// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus`
///
/// # Errors
///
/// Returns an `Error` corresponding to `EINVAL` for invalid status values.
///
/// # Examples
///
/// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`:
///
/// ```
/// use nix::sys::wait::WaitStatus;
/// use nix::sys::signal::Signal;
/// let pid = nix::unistd::Pid::from_raw(1);
/// let status = WaitStatus::from_raw(pid, 0x0002);
/// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
/// ```
pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
Ok(if exited(status) {
WaitStatus::Exited(pid, exit_status(status))
} else if signaled(status) {
WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
} else if stopped(status) {
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
let status_additional = stop_additional(status);
Ok(if syscall_stop(status) {
WaitStatus::PtraceSyscall(pid)
} else if status_additional == 0 {
WaitStatus::Stopped(pid, stop_signal(status)?)
} else {
WaitStatus::PtraceEvent(pid, stop_signal(status)?,
stop_additional(status))
})
}
} else {
fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
}
}
}
return decode_stopped(pid, status);
} else {
assert!(continued(status));
WaitStatus::Continued(pid)
})
}
}
/// Wait for a process to change status
///
/// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html)
pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
use self::WaitStatus::*;
let mut status: i32 = 0;
let option_bits = match options {
Some(bits) => bits.bits(),
None => 0,
};
let res = unsafe {
libc::waitpid(
pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
&mut status as *mut c_int,
option_bits,
)
};
match Errno::result(res)? {
0 => Ok(StillAlive),
res => WaitStatus::from_raw(Pid::from_raw(res), status),
}
}
/// Wait for any child process to change status or a signal is received.
///
/// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html)
pub fn wait() -> Result<WaitStatus> {
waitpid(None, None)
}

260
vendor/nix-v0.23.1-patched/src/time.rs vendored Normal file
View file

@ -0,0 +1,260 @@
use crate::sys::time::TimeSpec;
#[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "linux",
target_os = "android",
target_os = "emscripten",
))]
use crate::unistd::Pid;
use crate::{Errno, Result};
use libc::{self, clockid_t};
use std::mem::MaybeUninit;
/// Clock identifier
///
/// Newtype pattern around `clockid_t` (which is just alias). It pervents bugs caused by
/// accidentally passing wrong value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ClockId(clockid_t);
impl ClockId {
/// Creates `ClockId` from raw `clockid_t`
pub const fn from_raw(clk_id: clockid_t) -> Self {
ClockId(clk_id)
}
/// Returns `ClockId` of a `pid` CPU-time clock
#[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "linux",
target_os = "android",
target_os = "emscripten",
))]
pub fn pid_cpu_clock_id(pid: Pid) -> Result<Self> {
clock_getcpuclockid(pid)
}
/// Returns resolution of the clock id
#[cfg(not(target_os = "redox"))]
pub fn res(self) -> Result<TimeSpec> {
clock_getres(self)
}
/// Returns the current time on the clock id
pub fn now(self) -> Result<TimeSpec> {
clock_gettime(self)
}
/// Sets time to `timespec` on the clock id
#[cfg(not(any(
target_os = "macos",
target_os = "ios",
all(
not(any(target_env = "uclibc", target_env = "newlibc")),
any(target_os = "redox", target_os = "hermit",),
),
)))]
pub fn set_time(self, timespec: TimeSpec) -> Result<()> {
clock_settime(self, timespec)
}
/// Gets the raw `clockid_t` wrapped by `self`
pub const fn as_raw(self) -> clockid_t {
self.0
}
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(target_os = "linux", target_os = "android", target_os = "emscripten"),
)
))]
pub const CLOCK_BOOTTIME: ClockId = ClockId(libc::CLOCK_BOOTTIME);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(target_os = "linux", target_os = "android", target_os = "emscripten")
)
))]
pub const CLOCK_BOOTTIME_ALARM: ClockId = ClockId(libc::CLOCK_BOOTTIME_ALARM);
pub const CLOCK_MONOTONIC: ClockId = ClockId(libc::CLOCK_MONOTONIC);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(target_os = "linux", target_os = "android", target_os = "emscripten")
)
))]
pub const CLOCK_MONOTONIC_COARSE: ClockId = ClockId(libc::CLOCK_MONOTONIC_COARSE);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_MONOTONIC_FAST: ClockId = ClockId(libc::CLOCK_MONOTONIC_FAST);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_MONOTONIC_PRECISE: ClockId = ClockId(libc::CLOCK_MONOTONIC_PRECISE);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(target_os = "linux", target_os = "android", target_os = "emscripten")
)
))]
pub const CLOCK_MONOTONIC_RAW: ClockId = ClockId(libc::CLOCK_MONOTONIC_RAW);
#[cfg(any(
target_os = "fuchsia",
target_env = "uclibc",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
all(
not(target_env = "newlib"),
any(target_os = "linux", target_os = "android", target_os = "emscripten")
)
))]
pub const CLOCK_PROCESS_CPUTIME_ID: ClockId = ClockId(libc::CLOCK_PROCESS_CPUTIME_ID);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_PROF: ClockId = ClockId(libc::CLOCK_PROF);
pub const CLOCK_REALTIME: ClockId = ClockId(libc::CLOCK_REALTIME);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(target_os = "linux", target_os = "android", target_os = "emscripten")
)
))]
pub const CLOCK_REALTIME_ALARM: ClockId = ClockId(libc::CLOCK_REALTIME_ALARM);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(target_os = "linux", target_os = "android", target_os = "emscripten")
)
))]
pub const CLOCK_REALTIME_COARSE: ClockId = ClockId(libc::CLOCK_REALTIME_COARSE);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_REALTIME_FAST: ClockId = ClockId(libc::CLOCK_REALTIME_FAST);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_REALTIME_PRECISE: ClockId = ClockId(libc::CLOCK_REALTIME_PRECISE);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_SECOND: ClockId = ClockId(libc::CLOCK_SECOND);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(
target_os = "emscripten",
all(target_os = "linux", target_env = "musl")
)
)
))]
pub const CLOCK_SGI_CYCLE: ClockId = ClockId(libc::CLOCK_SGI_CYCLE);
#[cfg(any(
target_os = "fuchsia",
all(
not(any(target_env = "uclibc", target_env = "newlib")),
any(
target_os = "emscripten",
all(target_os = "linux", target_env = "musl")
)
)
))]
pub const CLOCK_TAI: ClockId = ClockId(libc::CLOCK_TAI);
#[cfg(any(
target_env = "uclibc",
target_os = "fuchsia",
target_os = "ios",
target_os = "macos",
target_os = "freebsd",
target_os = "dragonfly",
all(
not(target_env = "newlib"),
any(target_os = "linux", target_os = "android", target_os = "emscripten",),
),
))]
pub const CLOCK_THREAD_CPUTIME_ID: ClockId = ClockId(libc::CLOCK_THREAD_CPUTIME_ID);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_UPTIME: ClockId = ClockId(libc::CLOCK_UPTIME);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_UPTIME_FAST: ClockId = ClockId(libc::CLOCK_UPTIME_FAST);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_UPTIME_PRECISE: ClockId = ClockId(libc::CLOCK_UPTIME_PRECISE);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub const CLOCK_VIRTUAL: ClockId = ClockId(libc::CLOCK_VIRTUAL);
}
impl From<ClockId> for clockid_t {
fn from(clock_id: ClockId) -> Self {
clock_id.as_raw()
}
}
impl From<clockid_t> for ClockId {
fn from(clk_id: clockid_t) -> Self {
ClockId::from_raw(clk_id)
}
}
impl std::fmt::Display for ClockId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
/// Get the resolution of the specified clock, (see
/// [clock_getres(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_getres.html)).
#[cfg(not(target_os = "redox"))]
pub fn clock_getres(clock_id: ClockId) -> Result<TimeSpec> {
let mut c_time: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
let ret = unsafe { libc::clock_getres(clock_id.as_raw(), c_time.as_mut_ptr()) };
Errno::result(ret)?;
let res = unsafe { c_time.assume_init() };
Ok(TimeSpec::from(res))
}
/// Get the time of the specified clock, (see
/// [clock_gettime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_gettime.html)).
pub fn clock_gettime(clock_id: ClockId) -> Result<TimeSpec> {
let mut c_time: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
let ret = unsafe { libc::clock_gettime(clock_id.as_raw(), c_time.as_mut_ptr()) };
Errno::result(ret)?;
let res = unsafe { c_time.assume_init() };
Ok(TimeSpec::from(res))
}
/// Set the time of the specified clock, (see
/// [clock_settime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_settime.html)).
#[cfg(not(any(
target_os = "macos",
target_os = "ios",
all(
not(any(target_env = "uclibc", target_env = "newlibc")),
any(target_os = "redox", target_os = "hermit",),
),
)))]
pub fn clock_settime(clock_id: ClockId, timespec: TimeSpec) -> Result<()> {
let ret = unsafe { libc::clock_settime(clock_id.as_raw(), timespec.as_ref()) };
Errno::result(ret).map(drop)
}
/// Get the clock id of the specified process id, (see
/// [clock_getcpuclockid(3)](https://pubs.opengroup.org/onlinepubs/009695399/functions/clock_getcpuclockid.html)).
#[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "linux",
target_os = "android",
target_os = "emscripten",
))]
pub fn clock_getcpuclockid(pid: Pid) -> Result<ClockId> {
let mut clk_id: MaybeUninit<libc::clockid_t> = MaybeUninit::uninit();
let ret = unsafe { libc::clock_getcpuclockid(pid.into(), clk_id.as_mut_ptr()) };
if ret == 0 {
let res = unsafe { clk_id.assume_init() };
Ok(ClockId::from(res))
} else {
Err(Errno::from_i32(ret))
}
}

View file

@ -0,0 +1,43 @@
#[cfg(not(target_env = "musl"))]
use crate::Result;
#[cfg(not(target_env = "musl"))]
use crate::errno::Errno;
#[cfg(not(target_env = "musl"))]
use std::mem;
use crate::sys::signal::SigSet;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct UContext {
context: libc::ucontext_t,
}
impl UContext {
#[cfg(not(target_env = "musl"))]
pub fn get() -> Result<UContext> {
let mut context = mem::MaybeUninit::<libc::ucontext_t>::uninit();
let res = unsafe { libc::getcontext(context.as_mut_ptr()) };
Errno::result(res).map(|_| unsafe {
UContext { context: context.assume_init()}
})
}
#[cfg(not(target_env = "musl"))]
pub fn set(&self) -> Result<()> {
let res = unsafe {
libc::setcontext(&self.context as *const libc::ucontext_t)
};
Errno::result(res).map(drop)
}
pub fn sigmask_mut(&mut self) -> &mut SigSet {
unsafe {
&mut *(&mut self.context.uc_sigmask as *mut libc::sigset_t as *mut SigSet)
}
}
pub fn sigmask(&self) -> &SigSet {
unsafe {
&*(&self.context.uc_sigmask as *const libc::sigset_t as *const SigSet)
}
}
}

2994
vendor/nix-v0.23.1-patched/src/unistd.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,141 @@
use cfg_if::cfg_if;
#[macro_export] macro_rules! skip {
($($reason: expr),+) => {
use ::std::io::{self, Write};
let stderr = io::stderr();
let mut handle = stderr.lock();
writeln!(handle, $($reason),+).unwrap();
return;
}
}
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
#[macro_export] macro_rules! require_capability {
($name:expr, $capname:ident) => {
use ::caps::{Capability, CapSet, has_cap};
if !has_cap(None, CapSet::Effective, Capability::$capname)
.unwrap()
{
skip!("{} requires capability {}. Skipping test.", $name, Capability::$capname);
}
}
}
} else if #[cfg(not(target_os = "redox"))] {
#[macro_export] macro_rules! require_capability {
($name:expr, $capname:ident) => {}
}
}
}
/// Skip the test if we don't have the ability to mount file systems.
#[cfg(target_os = "freebsd")]
#[macro_export] macro_rules! require_mount {
($name:expr) => {
use ::sysctl::CtlValue;
use nix::unistd::Uid;
if !Uid::current().is_root() && CtlValue::Int(0) == ::sysctl::value("vfs.usermount").unwrap()
{
skip!("{} requires the ability to mount file systems. Skipping test.", $name);
}
}
}
#[cfg(any(target_os = "linux", target_os= "android"))]
#[macro_export] macro_rules! skip_if_cirrus {
($reason:expr) => {
if std::env::var_os("CIRRUS_CI").is_some() {
skip!("{}", $reason);
}
}
}
#[cfg(target_os = "freebsd")]
#[macro_export] macro_rules! skip_if_jailed {
($name:expr) => {
use ::sysctl::CtlValue;
if let CtlValue::Int(1) = ::sysctl::value("security.jail.jailed")
.unwrap()
{
skip!("{} cannot run in a jail. Skipping test.", $name);
}
}
}
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
#[macro_export] macro_rules! skip_if_not_root {
($name:expr) => {
use nix::unistd::Uid;
if !Uid::current().is_root() {
skip!("{} requires root privileges. Skipping test.", $name);
}
};
}
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
#[macro_export] macro_rules! skip_if_seccomp {
($name:expr) => {
if let Ok(s) = std::fs::read_to_string("/proc/self/status") {
for l in s.lines() {
let mut fields = l.split_whitespace();
if fields.next() == Some("Seccomp:") &&
fields.next() != Some("0")
{
skip!("{} cannot be run in Seccomp mode. Skipping test.",
stringify!($name));
}
}
}
}
}
} else if #[cfg(not(target_os = "redox"))] {
#[macro_export] macro_rules! skip_if_seccomp {
($name:expr) => {}
}
}
}
cfg_if! {
if #[cfg(target_os = "linux")] {
#[macro_export] macro_rules! require_kernel_version {
($name:expr, $version_requirement:expr) => {
use semver::{Version, VersionReq};
let version_requirement = VersionReq::parse($version_requirement)
.expect("Bad match_version provided");
let uname = nix::sys::utsname::uname();
println!("{}", uname.sysname());
println!("{}", uname.nodename());
println!("{}", uname.release());
println!("{}", uname.version());
println!("{}", uname.machine());
// Fix stuff that the semver parser can't handle
let fixed_release = &uname.release().to_string()
// Fedora 33 reports version as 4.18.el8_2.x86_64 or
// 5.18.200-fc33.x86_64. Remove the underscore.
.replace("_", "-")
// Cirrus-CI reports version as 4.19.112+ . Remove the +
.replace("+", "");
let mut version = Version::parse(fixed_release).unwrap();
//Keep only numeric parts
version.pre = semver::Prerelease::EMPTY;
version.build = semver::BuildMetadata::EMPTY;
if !version_requirement.matches(&version) {
skip!("Skip {} because kernel version `{}` doesn't match the requirement `{}`",
stringify!($name), version, version_requirement);
}
}
}
}
}

View file

@ -0,0 +1,47 @@
mod test_signal;
// NOTE: DragonFly lacks a kernel-level implementation of Posix AIO as of
// this writing. There is an user-level implementation, but whether aio
// works or not heavily depends on which pthread implementation is chosen
// by the user at link time. For this reason we do not want to run aio test
// cases on DragonFly.
#[cfg(any(target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd"))]
mod test_aio;
#[cfg(not(target_os = "redox"))]
mod test_mman;
#[cfg(target_os = "linux")]
mod test_signalfd;
#[cfg(not(target_os = "redox"))]
mod test_socket;
#[cfg(not(target_os = "redox"))]
mod test_sockopt;
#[cfg(not(target_os = "redox"))]
mod test_select;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod test_sysinfo;
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
mod test_termios;
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
mod test_ioctl;
mod test_wait;
mod test_uio;
#[cfg(target_os = "linux")]
mod test_epoll;
#[cfg(target_os = "linux")]
mod test_inotify;
mod test_pthread;
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
mod test_ptrace;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod test_timerfd;

View file

@ -0,0 +1,620 @@
use libc::{c_int, c_void};
use nix::Result;
use nix::errno::*;
use nix::sys::aio::*;
use nix::sys::signal::{SaFlags, SigAction, sigaction, SigevNotify, SigHandler, Signal, SigSet};
use nix::sys::time::{TimeSpec, TimeValLike};
use std::io::{Write, Read, Seek, SeekFrom};
use std::ops::Deref;
use std::os::unix::io::AsRawFd;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{thread, time};
use tempfile::tempfile;
// Helper that polls an AioCb for completion or error
fn poll_aio(aiocb: &mut Pin<Box<AioCb>>) -> Result<()> {
loop {
let err = aiocb.error();
if err != Err(Errno::EINPROGRESS) { return err; };
thread::sleep(time::Duration::from_millis(10));
}
}
// Helper that polls a component of an LioCb for completion or error
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
fn poll_lio(liocb: &mut LioCb, i: usize) -> Result<()> {
loop {
let err = liocb.error(i);
if err != Err(Errno::EINPROGRESS) { return err; };
thread::sleep(time::Duration::from_millis(10));
}
}
#[test]
fn test_accessors() {
let mut rbuf = vec![0; 4];
let aiocb = AioCb::from_mut_slice( 1001,
2, //offset
&mut rbuf,
42, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 99
},
LioOpcode::LIO_NOP);
assert_eq!(1001, aiocb.fd());
assert_eq!(Some(LioOpcode::LIO_NOP), aiocb.lio_opcode());
assert_eq!(4, aiocb.nbytes());
assert_eq!(2, aiocb.offset());
assert_eq!(42, aiocb.priority());
let sev = aiocb.sigevent().sigevent();
assert_eq!(Signal::SIGUSR2 as i32, sev.sigev_signo);
assert_eq!(99, sev.sigev_value.sival_ptr as i64);
}
// Tests AioCb.cancel. We aren't trying to test the OS's implementation, only
// our bindings. So it's sufficient to check that AioCb.cancel returned any
// AioCancelStat value.
#[test]
#[cfg_attr(target_env = "musl", ignore)]
fn test_cancel() {
let wbuf: &[u8] = b"CDEF";
let f = tempfile().unwrap();
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
0, //offset
wbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
let err = aiocb.error();
assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS));
let cancelstat = aiocb.cancel();
assert!(cancelstat.is_ok());
// Wait for aiocb to complete, but don't care whether it succeeded
let _ = poll_aio(&mut aiocb);
let _ = aiocb.aio_return();
}
// Tests using aio_cancel_all for all outstanding IOs.
#[test]
#[cfg_attr(target_env = "musl", ignore)]
fn test_aio_cancel_all() {
let wbuf: &[u8] = b"CDEF";
let f = tempfile().unwrap();
let mut aiocb = AioCb::from_slice(f.as_raw_fd(),
0, //offset
wbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
let err = aiocb.error();
assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS));
let cancelstat = aio_cancel_all(f.as_raw_fd());
assert!(cancelstat.is_ok());
// Wait for aiocb to complete, but don't care whether it succeeded
let _ = poll_aio(&mut aiocb);
let _ = aiocb.aio_return();
}
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_fsync() {
const INITIAL: &[u8] = b"abcdef123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiocb = AioCb::from_fd( f.as_raw_fd(),
0, //priority
SigevNotify::SigevNone);
let err = aiocb.fsync(AioFsyncMode::O_SYNC);
assert!(err.is_ok());
poll_aio(&mut aiocb).unwrap();
aiocb.aio_return().unwrap();
}
/// `AioCb::fsync` should not modify the `AioCb` object if `libc::aio_fsync` returns
/// an error
// Skip on Linux, because Linux's AIO implementation can't detect errors
// synchronously
#[test]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
fn test_fsync_error() {
use std::mem;
const INITIAL: &[u8] = b"abcdef123456";
// Create an invalid AioFsyncMode
let mode = unsafe { mem::transmute(666) };
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiocb = AioCb::from_fd( f.as_raw_fd(),
0, //priority
SigevNotify::SigevNone);
let err = aiocb.fsync(mode);
assert!(err.is_err());
}
#[test]
// On Cirrus on Linux, this test fails due to a glibc bug.
// https://github.com/nix-rust/nix/issues/1099
#[cfg_attr(target_os = "linux", ignore)]
// On Cirrus, aio_suspend is failing with EINVAL
// https://github.com/nix-rust/nix/issues/1361
#[cfg_attr(target_os = "macos", ignore)]
fn test_aio_suspend() {
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEFG";
let timeout = TimeSpec::seconds(10);
let mut rbuf = vec![0; 4];
let rlen = rbuf.len();
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut wcb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_WRITE);
let mut rcb = AioCb::from_mut_slice( f.as_raw_fd(),
8, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_READ);
wcb.write().unwrap();
rcb.read().unwrap();
loop {
{
let cbbuf = [wcb.as_ref(), rcb.as_ref()];
let r = aio_suspend(&cbbuf[..], Some(timeout));
match r {
Err(Errno::EINTR) => continue,
Err(e) => panic!("aio_suspend returned {:?}", e),
Ok(_) => ()
};
}
if rcb.error() != Err(Errno::EINPROGRESS) &&
wcb.error() != Err(Errno::EINPROGRESS) {
break
}
}
assert_eq!(wcb.aio_return().unwrap() as usize, WBUF.len());
assert_eq!(rcb.aio_return().unwrap() as usize, rlen);
}
// Test a simple aio operation with no completion notification. We must poll
// for completion
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_read() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
const EXPECT: &[u8] = b"cdef";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(),
2, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.read().unwrap();
let err = poll_aio(&mut aiocb);
assert_eq!(err, Ok(()));
assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len());
}
assert_eq!(EXPECT, rbuf.deref().deref());
}
/// `AioCb::read` should not modify the `AioCb` object if `libc::aio_read`
/// returns an error
// Skip on Linux, because Linux's AIO implementation can't detect errors
// synchronously
#[test]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
fn test_read_error() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(),
-1, //an invalid offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
assert!(aiocb.read().is_err());
}
// Tests from_mut_slice
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_read_into_mut_slice() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
const EXPECT: &[u8] = b"cdef";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let mut aiocb = AioCb::from_mut_slice( f.as_raw_fd(),
2, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.read().unwrap();
let err = poll_aio(&mut aiocb);
assert_eq!(err, Ok(()));
assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len());
}
assert_eq!(rbuf, EXPECT);
}
// Tests from_ptr
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_read_into_pointer() {
const INITIAL: &[u8] = b"abcdef123456";
let mut rbuf = vec![0; 4];
const EXPECT: &[u8] = b"cdef";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
// Safety: ok because rbuf lives until after poll_aio
let mut aiocb = unsafe {
AioCb::from_mut_ptr( f.as_raw_fd(),
2, //offset
rbuf.as_mut_ptr() as *mut c_void,
rbuf.len(),
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP)
};
aiocb.read().unwrap();
let err = poll_aio(&mut aiocb);
assert_eq!(err, Ok(()));
assert_eq!(aiocb.aio_return().unwrap() as usize, EXPECT.len());
}
assert_eq!(rbuf, EXPECT);
}
// Test reading into an immutable buffer. It should fail
// FIXME: This test fails to panic on Linux/musl
#[test]
#[should_panic(expected = "Can't read into an immutable buffer")]
#[cfg_attr(target_env = "musl", ignore)]
fn test_read_immutable_buffer() {
let rbuf: &[u8] = b"CDEF";
let f = tempfile().unwrap();
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.read().unwrap();
}
// Test a simple aio operation with no completion notification. We must poll
// for completion. Unlike test_aio_read, this test uses AioCb::from_slice
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_write() {
const INITIAL: &[u8] = b"abcdef123456";
let wbuf = "CDEF".to_string().into_bytes();
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
&wbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
let err = poll_aio(&mut aiocb);
assert_eq!(err, Ok(()));
assert_eq!(aiocb.aio_return().unwrap() as usize, wbuf.len());
f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
// Tests `AioCb::from_ptr`
#[test]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_write_from_pointer() {
const INITIAL: &[u8] = b"abcdef123456";
let wbuf = "CDEF".to_string().into_bytes();
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
// Safety: ok because aiocb outlives poll_aio
let mut aiocb = unsafe {
AioCb::from_ptr( f.as_raw_fd(),
2, //offset
wbuf.as_ptr() as *const c_void,
wbuf.len(),
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP)
};
aiocb.write().unwrap();
let err = poll_aio(&mut aiocb);
assert_eq!(err, Ok(()));
assert_eq!(aiocb.aio_return().unwrap() as usize, wbuf.len());
f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
/// `AioCb::write` should not modify the `AioCb` object if `libc::aio_write`
/// returns an error
// Skip on Linux, because Linux's AIO implementation can't detect errors
// synchronously
#[test]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
fn test_write_error() {
let wbuf = "CDEF".to_string().into_bytes();
let mut aiocb = AioCb::from_slice( 666, // An invalid file descriptor
0, //offset
&wbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
assert!(aiocb.write().is_err());
}
lazy_static! {
pub static ref SIGNALED: AtomicBool = AtomicBool::new(false);
}
extern fn sigfunc(_: c_int) {
SIGNALED.store(true, Ordering::Relaxed);
}
// Test an aio operation with completion delivered by a signal
// FIXME: This test is ignored on mips because of failures in qemu in CI
#[test]
#[cfg_attr(any(all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips", target_arch = "mips64"), ignore)]
fn test_write_sigev_signal() {
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let sa = SigAction::new(SigHandler::Handler(sigfunc),
SaFlags::SA_RESETHAND,
SigSet::empty());
SIGNALED.store(false, Ordering::Relaxed);
unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap();
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEF";
let mut rbuf = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevSignal {
signal: Signal::SIGUSR2,
si_value: 0 //TODO: validate in sigfunc
},
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
while !SIGNALED.load(Ordering::Relaxed) {
thread::sleep(time::Duration::from_millis(10));
}
assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len());
f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf, EXPECT);
}
// Test LioCb::listio with LIO_WAIT, so all AIO ops should be complete by the
// time listio returns.
#[test]
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_liocb_listio_wait() {
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEF";
let mut rbuf = vec![0; 4];
let rlen = rbuf.len();
let mut rbuf2 = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let mut liocb = LioCbBuilder::with_capacity(2)
.emplace_slice(
f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_WRITE
).emplace_mut_slice(
f.as_raw_fd(),
8, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_READ
).finish();
let err = liocb.listio(LioMode::LIO_WAIT, SigevNotify::SigevNone);
err.expect("lio_listio");
assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len());
assert_eq!(liocb.aio_return(1).unwrap() as usize, rlen);
}
assert_eq!(rbuf.deref().deref(), b"3456");
f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf2).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf2, EXPECT);
}
// Test LioCb::listio with LIO_NOWAIT and no SigEvent, so we must use some other
// mechanism to check for the individual AioCb's completion.
#[test]
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg_attr(all(target_env = "musl", target_arch = "x86_64"), ignore)]
fn test_liocb_listio_nowait() {
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEF";
let mut rbuf = vec![0; 4];
let rlen = rbuf.len();
let mut rbuf2 = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
f.write_all(INITIAL).unwrap();
{
let mut liocb = LioCbBuilder::with_capacity(2)
.emplace_slice(
f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_WRITE
).emplace_mut_slice(
f.as_raw_fd(),
8, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_READ
).finish();
let err = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone);
err.expect("lio_listio");
poll_lio(&mut liocb, 0).unwrap();
poll_lio(&mut liocb, 1).unwrap();
assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len());
assert_eq!(liocb.aio_return(1).unwrap() as usize, rlen);
}
assert_eq!(rbuf.deref().deref(), b"3456");
f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf2).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf2, EXPECT);
}
// Test LioCb::listio with LIO_NOWAIT and a SigEvent to indicate when all
// AioCb's are complete.
// FIXME: This test is ignored on mips/mips64 because of failures in qemu in CI.
#[test]
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg_attr(any(target_arch = "mips", target_arch = "mips64", target_env = "musl"), ignore)]
fn test_liocb_listio_signal() {
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEF";
let mut rbuf = vec![0; 4];
let rlen = rbuf.len();
let mut rbuf2 = Vec::new();
const EXPECT: &[u8] = b"abCDEF123456";
let mut f = tempfile().unwrap();
let sa = SigAction::new(SigHandler::Handler(sigfunc),
SaFlags::SA_RESETHAND,
SigSet::empty());
let sigev_notify = SigevNotify::SigevSignal { signal: Signal::SIGUSR2,
si_value: 0 };
f.write_all(INITIAL).unwrap();
{
let mut liocb = LioCbBuilder::with_capacity(2)
.emplace_slice(
f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_WRITE
).emplace_mut_slice(
f.as_raw_fd(),
8, //offset
&mut rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_READ
).finish();
SIGNALED.store(false, Ordering::Relaxed);
unsafe { sigaction(Signal::SIGUSR2, &sa) }.unwrap();
let err = liocb.listio(LioMode::LIO_NOWAIT, sigev_notify);
err.expect("lio_listio");
while !SIGNALED.load(Ordering::Relaxed) {
thread::sleep(time::Duration::from_millis(10));
}
assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len());
assert_eq!(liocb.aio_return(1).unwrap() as usize, rlen);
}
assert_eq!(rbuf.deref().deref(), b"3456");
f.seek(SeekFrom::Start(0)).unwrap();
let len = f.read_to_end(&mut rbuf2).unwrap();
assert_eq!(len, EXPECT.len());
assert_eq!(rbuf2, EXPECT);
}
// Try to use LioCb::listio to read into an immutable buffer. It should fail
// FIXME: This test fails to panic on Linux/musl
#[test]
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[should_panic(expected = "Can't read into an immutable buffer")]
#[cfg_attr(target_env = "musl", ignore)]
fn test_liocb_listio_read_immutable() {
let rbuf: &[u8] = b"abcd";
let f = tempfile().unwrap();
let mut liocb = LioCbBuilder::with_capacity(1)
.emplace_slice(
f.as_raw_fd(),
2, //offset
rbuf,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_READ
).finish();
let _ = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone);
}

View file

@ -0,0 +1,29 @@
// Test dropping an AioCb that hasn't yet finished.
// This must happen in its own process, because on OSX this test seems to hose
// the AIO subsystem and causes subsequent tests to fail
#[test]
#[should_panic(expected = "Dropped an in-progress AioCb")]
#[cfg(all(not(target_env = "musl"),
any(target_os = "linux",
target_os = "ios",
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd")))]
fn test_drop() {
use nix::sys::aio::*;
use nix::sys::signal::*;
use std::os::unix::io::AsRawFd;
use tempfile::tempfile;
const WBUF: &[u8] = b"CDEF";
let f = tempfile().unwrap();
f.set_len(6).unwrap();
let mut aiocb = AioCb::from_slice( f.as_raw_fd(),
2, //offset
WBUF,
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_NOP);
aiocb.write().unwrap();
}

View file

@ -0,0 +1,23 @@
use nix::sys::epoll::{EpollCreateFlags, EpollFlags, EpollOp, EpollEvent};
use nix::sys::epoll::{epoll_create1, epoll_ctl};
use nix::errno::Errno;
#[test]
pub fn test_epoll_errno() {
let efd = epoll_create1(EpollCreateFlags::empty()).unwrap();
let result = epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Errno::ENOENT);
let result = epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, None);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Errno::EINVAL);
}
#[test]
pub fn test_epoll_ctl() {
let efd = epoll_create1(EpollCreateFlags::empty()).unwrap();
let mut event = EpollEvent::new(EpollFlags::EPOLLIN | EpollFlags::EPOLLERR, 1);
epoll_ctl(efd, EpollOp::EpollCtlAdd, 1, &mut event).unwrap();
epoll_ctl(efd, EpollOp::EpollCtlDel, 1, None).unwrap();
}

View file

@ -0,0 +1,63 @@
use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify};
use nix::errno::Errno;
use std::ffi::OsString;
use std::fs::{rename, File};
#[test]
pub fn test_inotify() {
let instance = Inotify::init(InitFlags::IN_NONBLOCK)
.unwrap();
let tempdir = tempfile::tempdir().unwrap();
instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap();
let events = instance.read_events();
assert_eq!(events.unwrap_err(), Errno::EAGAIN);
File::create(tempdir.path().join("test")).unwrap();
let events = instance.read_events().unwrap();
assert_eq!(events[0].name, Some(OsString::from("test")));
}
#[test]
pub fn test_inotify_multi_events() {
let instance = Inotify::init(InitFlags::IN_NONBLOCK)
.unwrap();
let tempdir = tempfile::tempdir().unwrap();
instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap();
let events = instance.read_events();
assert_eq!(events.unwrap_err(), Errno::EAGAIN);
File::create(tempdir.path().join("test")).unwrap();
rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap();
// Now there should be 5 events in queue:
// - IN_CREATE on test
// - IN_OPEN on test
// - IN_CLOSE_WRITE on test
// - IN_MOVED_FROM on test with a cookie
// - IN_MOVED_TO on test2 with the same cookie
let events = instance.read_events().unwrap();
assert_eq!(events.len(), 5);
assert_eq!(events[0].mask, AddWatchFlags::IN_CREATE);
assert_eq!(events[0].name, Some(OsString::from("test")));
assert_eq!(events[1].mask, AddWatchFlags::IN_OPEN);
assert_eq!(events[1].name, Some(OsString::from("test")));
assert_eq!(events[2].mask, AddWatchFlags::IN_CLOSE_WRITE);
assert_eq!(events[2].name, Some(OsString::from("test")));
assert_eq!(events[3].mask, AddWatchFlags::IN_MOVED_FROM);
assert_eq!(events[3].name, Some(OsString::from("test")));
assert_eq!(events[4].mask, AddWatchFlags::IN_MOVED_TO);
assert_eq!(events[4].name, Some(OsString::from("test2")));
assert_eq!(events[3].cookie, events[4].cookie);
}

View file

@ -0,0 +1,337 @@
#![allow(dead_code)]
// Simple tests to ensure macro generated fns compile
ioctl_none_bad!(do_bad, 0x1234);
ioctl_read_bad!(do_bad_read, 0x1234, u16);
ioctl_write_int_bad!(do_bad_write_int, 0x1234);
ioctl_write_ptr_bad!(do_bad_write_ptr, 0x1234, u8);
ioctl_readwrite_bad!(do_bad_readwrite, 0x1234, u32);
ioctl_none!(do_none, 0, 0);
ioctl_read!(read_test, 0, 0, u32);
ioctl_write_int!(write_ptr_int, 0, 0);
ioctl_write_ptr!(write_ptr_u8, 0, 0, u8);
ioctl_write_ptr!(write_ptr_u32, 0, 0, u32);
ioctl_write_ptr!(write_ptr_u64, 0, 0, u64);
ioctl_readwrite!(readwrite_test, 0, 0, u64);
ioctl_read_buf!(readbuf_test, 0, 0, u32);
const SPI_IOC_MAGIC: u8 = b'k';
const SPI_IOC_MESSAGE: u8 = 0;
ioctl_write_buf!(writebuf_test_consts, SPI_IOC_MAGIC, SPI_IOC_MESSAGE, u8);
ioctl_write_buf!(writebuf_test_u8, 0, 0, u8);
ioctl_write_buf!(writebuf_test_u32, 0, 0, u32);
ioctl_write_buf!(writebuf_test_u64, 0, 0, u64);
ioctl_readwrite_buf!(readwritebuf_test, 0, 0, u32);
// See C code for source of values for op calculations (does NOT work for mips/powerpc):
// https://gist.github.com/posborne/83ea6880770a1aef332e
//
// TODO: Need a way to compute these constants at test time. Using precomputed
// values is fragile and needs to be maintained.
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux {
#[test]
fn test_op_none() {
if cfg!(any(target_arch = "mips", target_arch = "mips64", target_arch="powerpc", target_arch="powerpc64")){
assert_eq!(request_code_none!(b'q', 10) as u32, 0x2000_710A);
assert_eq!(request_code_none!(b'a', 255) as u32, 0x2000_61FF);
} else {
assert_eq!(request_code_none!(b'q', 10) as u32, 0x0000_710A);
assert_eq!(request_code_none!(b'a', 255) as u32, 0x0000_61FF);
}
}
#[test]
fn test_op_write() {
if cfg!(any(target_arch = "mips", target_arch = "mips64", target_arch="powerpc", target_arch="powerpc64")){
assert_eq!(request_code_write!(b'z', 10, 1) as u32, 0x8001_7A0A);
assert_eq!(request_code_write!(b'z', 10, 512) as u32, 0x8200_7A0A);
} else {
assert_eq!(request_code_write!(b'z', 10, 1) as u32, 0x4001_7A0A);
assert_eq!(request_code_write!(b'z', 10, 512) as u32, 0x4200_7A0A);
}
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_op_write_64() {
if cfg!(any(target_arch = "mips64", target_arch="powerpc64")){
assert_eq!(request_code_write!(b'z', 10, 1u64 << 32) as u32,
0x8000_7A0A);
} else {
assert_eq!(request_code_write!(b'z', 10, 1u64 << 32) as u32,
0x4000_7A0A);
}
}
#[test]
fn test_op_read() {
if cfg!(any(target_arch = "mips", target_arch = "mips64", target_arch="powerpc", target_arch="powerpc64")){
assert_eq!(request_code_read!(b'z', 10, 1) as u32, 0x4001_7A0A);
assert_eq!(request_code_read!(b'z', 10, 512) as u32, 0x4200_7A0A);
} else {
assert_eq!(request_code_read!(b'z', 10, 1) as u32, 0x8001_7A0A);
assert_eq!(request_code_read!(b'z', 10, 512) as u32, 0x8200_7A0A);
}
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_op_read_64() {
if cfg!(any(target_arch = "mips64", target_arch="powerpc64")){
assert_eq!(request_code_read!(b'z', 10, 1u64 << 32) as u32,
0x4000_7A0A);
} else {
assert_eq!(request_code_read!(b'z', 10, 1u64 << 32) as u32,
0x8000_7A0A);
}
}
#[test]
fn test_op_read_write() {
assert_eq!(request_code_readwrite!(b'z', 10, 1) as u32, 0xC001_7A0A);
assert_eq!(request_code_readwrite!(b'z', 10, 512) as u32, 0xC200_7A0A);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_op_read_write_64() {
assert_eq!(request_code_readwrite!(b'z', 10, 1u64 << 32) as u32,
0xC000_7A0A);
}
}
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
mod bsd {
#[test]
fn test_op_none() {
assert_eq!(request_code_none!(b'q', 10), 0x2000_710A);
assert_eq!(request_code_none!(b'a', 255), 0x2000_61FF);
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
#[test]
fn test_op_write_int() {
assert_eq!(request_code_write_int!(b'v', 4), 0x2004_7604);
assert_eq!(request_code_write_int!(b'p', 2), 0x2004_7002);
}
#[test]
fn test_op_write() {
assert_eq!(request_code_write!(b'z', 10, 1), 0x8001_7A0A);
assert_eq!(request_code_write!(b'z', 10, 512), 0x8200_7A0A);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_op_write_64() {
assert_eq!(request_code_write!(b'z', 10, 1u64 << 32), 0x8000_7A0A);
}
#[test]
fn test_op_read() {
assert_eq!(request_code_read!(b'z', 10, 1), 0x4001_7A0A);
assert_eq!(request_code_read!(b'z', 10, 512), 0x4200_7A0A);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_op_read_64() {
assert_eq!(request_code_read!(b'z', 10, 1u64 << 32), 0x4000_7A0A);
}
#[test]
fn test_op_read_write() {
assert_eq!(request_code_readwrite!(b'z', 10, 1), 0xC001_7A0A);
assert_eq!(request_code_readwrite!(b'z', 10, 512), 0xC200_7A0A);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_op_read_write_64() {
assert_eq!(request_code_readwrite!(b'z', 10, 1u64 << 32), 0xC000_7A0A);
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
mod linux_ioctls {
use std::mem;
use std::os::unix::io::AsRawFd;
use tempfile::tempfile;
use libc::{TCGETS, TCSBRK, TCSETS, TIOCNXCL, termios};
use nix::errno::Errno;
ioctl_none_bad!(tiocnxcl, TIOCNXCL);
#[test]
fn test_ioctl_none_bad() {
let file = tempfile().unwrap();
let res = unsafe { tiocnxcl(file.as_raw_fd()) };
assert_eq!(res, Err(Errno::ENOTTY));
}
ioctl_read_bad!(tcgets, TCGETS, termios);
#[test]
fn test_ioctl_read_bad() {
let file = tempfile().unwrap();
let mut termios = unsafe { mem::zeroed() };
let res = unsafe { tcgets(file.as_raw_fd(), &mut termios) };
assert_eq!(res, Err(Errno::ENOTTY));
}
ioctl_write_int_bad!(tcsbrk, TCSBRK);
#[test]
fn test_ioctl_write_int_bad() {
let file = tempfile().unwrap();
let res = unsafe { tcsbrk(file.as_raw_fd(), 0) };
assert_eq!(res, Err(Errno::ENOTTY));
}
ioctl_write_ptr_bad!(tcsets, TCSETS, termios);
#[test]
fn test_ioctl_write_ptr_bad() {
let file = tempfile().unwrap();
let termios: termios = unsafe { mem::zeroed() };
let res = unsafe { tcsets(file.as_raw_fd(), &termios) };
assert_eq!(res, Err(Errno::ENOTTY));
}
// FIXME: Find a suitable example for `ioctl_readwrite_bad`
// From linux/videodev2.h
ioctl_none!(log_status, b'V', 70);
#[test]
fn test_ioctl_none() {
let file = tempfile().unwrap();
let res = unsafe { log_status(file.as_raw_fd()) };
assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
}
#[repr(C)]
pub struct v4l2_audio {
index: u32,
name: [u8; 32],
capability: u32,
mode: u32,
reserved: [u32; 2],
}
// From linux/videodev2.h
ioctl_write_ptr!(s_audio, b'V', 34, v4l2_audio);
#[test]
fn test_ioctl_write_ptr() {
let file = tempfile().unwrap();
let data: v4l2_audio = unsafe { mem::zeroed() };
let res = unsafe { s_audio(file.as_raw_fd(), &data) };
assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
}
// From linux/net/bluetooth/hci_sock.h
const HCI_IOC_MAGIC: u8 = b'H';
const HCI_IOC_HCIDEVUP: u8 = 201;
ioctl_write_int!(hcidevup, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP);
#[test]
fn test_ioctl_write_int() {
let file = tempfile().unwrap();
let res = unsafe { hcidevup(file.as_raw_fd(), 0) };
assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
}
// From linux/videodev2.h
ioctl_read!(g_audio, b'V', 33, v4l2_audio);
#[test]
fn test_ioctl_read() {
let file = tempfile().unwrap();
let mut data: v4l2_audio = unsafe { mem::zeroed() };
let res = unsafe { g_audio(file.as_raw_fd(), &mut data) };
assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
}
// From linux/videodev2.h
ioctl_readwrite!(enum_audio, b'V', 65, v4l2_audio);
#[test]
fn test_ioctl_readwrite() {
let file = tempfile().unwrap();
let mut data: v4l2_audio = unsafe { mem::zeroed() };
let res = unsafe { enum_audio(file.as_raw_fd(), &mut data) };
assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
}
// FIXME: Find a suitable example for `ioctl_read_buf`.
#[repr(C)]
pub struct spi_ioc_transfer {
tx_buf: u64,
rx_buf: u64,
len: u32,
speed_hz: u32,
delay_usecs: u16,
bits_per_word: u8,
cs_change: u8,
tx_nbits: u8,
rx_nbits: u8,
pad: u16,
}
// From linux/spi/spidev.h
ioctl_write_buf!(spi_ioc_message, super::SPI_IOC_MAGIC, super::SPI_IOC_MESSAGE, spi_ioc_transfer);
#[test]
fn test_ioctl_write_buf() {
let file = tempfile().unwrap();
let data: [spi_ioc_transfer; 4] = unsafe { mem::zeroed() };
let res = unsafe { spi_ioc_message(file.as_raw_fd(), &data[..]) };
assert!(res == Err(Errno::ENOTTY) || res == Err(Errno::ENOSYS));
}
// FIXME: Find a suitable example for `ioctl_readwrite_buf`.
}
#[cfg(target_os = "freebsd")]
mod freebsd_ioctls {
use std::mem;
use std::os::unix::io::AsRawFd;
use tempfile::tempfile;
use libc::termios;
use nix::errno::Errno;
// From sys/sys/ttycom.h
const TTY_IOC_MAGIC: u8 = b't';
const TTY_IOC_TYPE_NXCL: u8 = 14;
const TTY_IOC_TYPE_GETA: u8 = 19;
const TTY_IOC_TYPE_SETA: u8 = 20;
ioctl_none!(tiocnxcl, TTY_IOC_MAGIC, TTY_IOC_TYPE_NXCL);
#[test]
fn test_ioctl_none() {
let file = tempfile().unwrap();
let res = unsafe { tiocnxcl(file.as_raw_fd()) };
assert_eq!(res, Err(Errno::ENOTTY));
}
ioctl_read!(tiocgeta, TTY_IOC_MAGIC, TTY_IOC_TYPE_GETA, termios);
#[test]
fn test_ioctl_read() {
let file = tempfile().unwrap();
let mut termios = unsafe { mem::zeroed() };
let res = unsafe { tiocgeta(file.as_raw_fd(), &mut termios) };
assert_eq!(res, Err(Errno::ENOTTY));
}
ioctl_write_ptr!(tiocseta, TTY_IOC_MAGIC, TTY_IOC_TYPE_SETA, termios);
#[test]
fn test_ioctl_write_ptr() {
let file = tempfile().unwrap();
let termios: termios = unsafe { mem::zeroed() };
let res = unsafe { tiocseta(file.as_raw_fd(), &termios) };
assert_eq!(res, Err(Errno::ENOTTY));
}
}

View file

@ -0,0 +1,106 @@
// vim: tw=80
// Annoyingly, Cargo is unable to conditionally build an entire test binary. So
// we must disable the test here rather than in Cargo.toml
#![cfg(target_os = "freebsd")]
use nix::errno::*;
use nix::libc::off_t;
use nix::sys::aio::*;
use nix::sys::signal::SigevNotify;
use nix::unistd::{SysconfVar, sysconf};
use std::os::unix::io::AsRawFd;
use std::{thread, time};
use sysctl::CtlValue;
use tempfile::tempfile;
const BYTES_PER_OP: usize = 512;
/// Attempt to collect final status for all of `liocb`'s operations, freeing
/// system resources
fn finish_liocb(liocb: &mut LioCb) {
for j in 0..liocb.len() {
loop {
let e = liocb.error(j);
match e {
Ok(()) => break,
Err(Errno::EINPROGRESS) =>
thread::sleep(time::Duration::from_millis(10)),
Err(x) => panic!("aio_error({:?})", x)
}
}
assert_eq!(liocb.aio_return(j).unwrap(), BYTES_PER_OP as isize);
}
}
// Deliberately exceed system resource limits, causing lio_listio to return EIO.
// This test must run in its own process since it deliberately uses all AIO
// resources. ATM it is only enabled on FreeBSD, because I don't know how to
// check system AIO limits on other operating systems.
#[test]
fn test_lio_listio_resubmit() {
let mut resubmit_count = 0;
// Lookup system resource limits
let alm = sysconf(SysconfVar::AIO_LISTIO_MAX)
.expect("sysconf").unwrap() as usize;
let maqpp = if let CtlValue::Int(x) = sysctl::value(
"vfs.aio.max_aio_queue_per_proc").unwrap(){
x as usize
} else {
panic!("unknown sysctl");
};
// Find lio_listio sizes that satisfy the AIO_LISTIO_MAX constraint and also
// result in a final lio_listio call that can only partially be queued
let target_ops = maqpp + alm / 2;
let num_listios = (target_ops + alm - 3) / (alm - 2);
let ops_per_listio = (target_ops + num_listios - 1) / num_listios;
assert!((num_listios - 1) * ops_per_listio < maqpp,
"the last lio_listio won't make any progress; fix the algorithm");
println!("Using {:?} LioCbs of {:?} operations apiece", num_listios,
ops_per_listio);
let f = tempfile().unwrap();
let buffer_set = (0..num_listios).map(|_| {
(0..ops_per_listio).map(|_| {
vec![0u8; BYTES_PER_OP]
}).collect::<Vec<_>>()
}).collect::<Vec<_>>();
let mut liocbs = (0..num_listios).map(|i| {
let mut builder = LioCbBuilder::with_capacity(ops_per_listio);
for j in 0..ops_per_listio {
let offset = (BYTES_PER_OP * (i * ops_per_listio + j)) as off_t;
builder = builder.emplace_slice(f.as_raw_fd(),
offset,
&buffer_set[i][j][..],
0, //priority
SigevNotify::SigevNone,
LioOpcode::LIO_WRITE);
}
let mut liocb = builder.finish();
let mut err = liocb.listio(LioMode::LIO_NOWAIT, SigevNotify::SigevNone);
while err == Err(Errno::EIO) ||
err == Err(Errno::EAGAIN) ||
err == Err(Errno::EINTR) {
//
thread::sleep(time::Duration::from_millis(10));
resubmit_count += 1;
err = liocb.listio_resubmit(LioMode::LIO_NOWAIT,
SigevNotify::SigevNone);
}
liocb
}).collect::<Vec<_>>();
// Ensure that every AioCb completed
for liocb in liocbs.iter_mut() {
finish_liocb(liocb);
}
if resubmit_count > 0 {
println!("Resubmitted {:?} times, test passed", resubmit_count);
} else {
println!("Never resubmitted. Test ambiguous");
}
}

View file

@ -0,0 +1,92 @@
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
#[test]
fn test_mmap_anonymous() {
unsafe {
let ptr = mmap(std::ptr::null_mut(), 1,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, -1, 0)
.unwrap() as *mut u8;
assert_eq !(*ptr, 0x00u8);
*ptr = 0xffu8;
assert_eq !(*ptr, 0xffu8);
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
fn test_mremap_grow() {
use nix::sys::mman::{mremap, MRemapFlags};
use nix::libc::{c_void, size_t};
const ONE_K : size_t = 1024;
let slice : &mut[u8] = unsafe {
let mem = mmap(std::ptr::null_mut(), ONE_K,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, -1, 0)
.unwrap();
std::slice::from_raw_parts_mut(mem as * mut u8, ONE_K)
};
assert_eq !(slice[ONE_K - 1], 0x00);
slice[ONE_K - 1] = 0xFF;
assert_eq !(slice[ONE_K - 1], 0xFF);
let slice : &mut[u8] = unsafe {
#[cfg(target_os = "linux")]
let mem = mremap(slice.as_mut_ptr() as * mut c_void, ONE_K, 10 * ONE_K,
MRemapFlags::MREMAP_MAYMOVE, None)
.unwrap();
#[cfg(target_os = "netbsd")]
let mem = mremap(slice.as_mut_ptr() as * mut c_void, ONE_K, 10 * ONE_K,
MRemapFlags::MAP_REMAPDUP, None)
.unwrap();
std::slice::from_raw_parts_mut(mem as * mut u8, 10 * ONE_K)
};
// The first KB should still have the old data in it.
assert_eq !(slice[ONE_K - 1], 0xFF);
// The additional range should be zero-init'd and accessible.
assert_eq !(slice[10 * ONE_K - 1], 0x00);
slice[10 * ONE_K - 1] = 0xFF;
assert_eq !(slice[10 * ONE_K - 1], 0xFF);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
// Segfaults for unknown reasons under QEMU for 32-bit targets
#[cfg_attr(all(target_pointer_width = "32", qemu), ignore)]
fn test_mremap_shrink() {
use nix::sys::mman::{mremap, MRemapFlags};
use nix::libc::{c_void, size_t};
const ONE_K : size_t = 1024;
let slice : &mut[u8] = unsafe {
let mem = mmap(std::ptr::null_mut(), 10 * ONE_K,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, -1, 0)
.unwrap();
std::slice::from_raw_parts_mut(mem as * mut u8, ONE_K)
};
assert_eq !(slice[ONE_K - 1], 0x00);
slice[ONE_K - 1] = 0xFF;
assert_eq !(slice[ONE_K - 1], 0xFF);
let slice : &mut[u8] = unsafe {
#[cfg(target_os = "linux")]
let mem = mremap(slice.as_mut_ptr() as * mut c_void, 10 * ONE_K, ONE_K,
MRemapFlags::empty(), None)
.unwrap();
// Since we didn't supply MREMAP_MAYMOVE, the address should be the
// same.
#[cfg(target_os = "netbsd")]
let mem = mremap(slice.as_mut_ptr() as * mut c_void, 10 * ONE_K, ONE_K,
MRemapFlags::MAP_FIXED, None)
.unwrap();
assert_eq !(mem, slice.as_mut_ptr() as * mut c_void);
std::slice::from_raw_parts_mut(mem as * mut u8, ONE_K)
};
// The first KB should still be accessible and have the old data in it.
assert_eq !(slice[ONE_K - 1], 0xFF);
}

View file

@ -0,0 +1,22 @@
use nix::sys::pthread::*;
#[cfg(any(target_env = "musl", target_os = "redox"))]
#[test]
fn test_pthread_self() {
let tid = pthread_self();
assert!(tid != ::std::ptr::null_mut());
}
#[cfg(not(any(target_env = "musl", target_os = "redox")))]
#[test]
fn test_pthread_self() {
let tid = pthread_self();
assert!(tid != 0);
}
#[test]
#[cfg(not(target_os = "redox"))]
fn test_pthread_kill_none() {
pthread_kill(pthread_self(), None)
.expect("Should be able to send signal to my thread.");
}

View file

@ -0,0 +1,219 @@
use nix::errno::Errno;
use nix::unistd::getpid;
use nix::sys::ptrace;
#[cfg(any(target_os = "android", target_os = "linux"))]
use nix::sys::ptrace::Options;
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::mem;
use crate::*;
#[test]
fn test_ptrace() {
// Just make sure ptrace can be called at all, for now.
// FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
require_capability!("test_ptrace", CAP_SYS_PTRACE);
let err = ptrace::attach(getpid()).unwrap_err();
assert!(err == Errno::EPERM || err == Errno::EINVAL ||
err == Errno::ENOSYS);
}
// Just make sure ptrace_setoptions can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_setoptions() {
require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE);
let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD).unwrap_err();
assert!(err != Errno::EOPNOTSUPP);
}
// Just make sure ptrace_getevent can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_getevent() {
require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE);
let err = ptrace::getevent(getpid()).unwrap_err();
assert!(err != Errno::EOPNOTSUPP);
}
// Just make sure ptrace_getsiginfo can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_getsiginfo() {
require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE);
if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) {
panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!");
}
}
// Just make sure ptrace_setsiginfo can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_setsiginfo() {
require_capability!("test_ptrace_setsiginfo", CAP_SYS_PTRACE);
let siginfo = unsafe { mem::zeroed() };
if let Err(Errno::EOPNOTSUPP) = ptrace::setsiginfo(getpid(), &siginfo) {
panic!("ptrace_setsiginfo returns Errno::EOPNOTSUPP!");
}
}
#[test]
fn test_ptrace_cont() {
use nix::sys::ptrace;
use nix::sys::signal::{raise, Signal};
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use nix::unistd::fork;
use nix::unistd::ForkResult::*;
require_capability!("test_ptrace_cont", CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
// FIXME: qemu-user doesn't implement ptrace on all architectures
// and retunrs ENOSYS in this case.
// We (ab)use this behavior to detect the affected platforms
// and skip the test then.
// On valid platforms the ptrace call should return Errno::EPERM, this
// is already tested by `test_ptrace`.
let err = ptrace::attach(getpid()).unwrap_err();
if err == Errno::ENOSYS {
return;
}
match unsafe{fork()}.expect("Error: Fork Failed") {
Child => {
ptrace::traceme().unwrap();
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
loop {
raise(Signal::SIGTRAP).unwrap();
}
},
Parent { child } => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
ptrace::cont(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
match waitpid(child, None) {
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
// FIXME It's been observed on some systems (apple) the
// tracee may not be killed but remain as a zombie process
// affecting other wait based tests. Add an extra kill just
// to make sure there are no zombies.
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
}
}
_ => panic!("The process should have been killed"),
}
},
}
}
#[cfg(target_os = "linux")]
#[test]
fn test_ptrace_interrupt() {
use nix::sys::ptrace;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use nix::unistd::fork;
use nix::unistd::ForkResult::*;
use std::thread::sleep;
use std::time::Duration;
require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
match unsafe{fork()}.expect("Error: Fork Failed") {
Child => {
loop {
sleep(Duration::from_millis(1000));
}
},
Parent { child } => {
ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
ptrace::interrupt(child).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128)));
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
ptrace::detach(child, Some(Signal::SIGKILL)).unwrap();
match waitpid(child, None) {
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
}
}
_ => panic!("The process should have been killed"),
}
},
}
}
// ptrace::{setoptions, getregs} are only available in these platforms
#[cfg(all(target_os = "linux",
any(target_arch = "x86_64",
target_arch = "x86"),
target_env = "gnu"))]
#[test]
fn test_ptrace_syscall() {
use nix::sys::signal::kill;
use nix::sys::ptrace;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::fork;
use nix::unistd::getpid;
use nix::unistd::ForkResult::*;
require_capability!("test_ptrace_syscall", CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
match unsafe{fork()}.expect("Error: Fork Failed") {
Child => {
ptrace::traceme().unwrap();
// first sigstop until parent is ready to continue
let pid = getpid();
kill(pid, Signal::SIGSTOP).unwrap();
kill(pid, Signal::SIGTERM).unwrap();
unsafe { ::libc::_exit(0); }
},
Parent { child } => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)));
// set this option to recognize syscall-stops
ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
#[cfg(target_arch = "x86_64")]
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_rax as libc::c_long;
#[cfg(target_arch = "x86")]
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
// kill entry
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
// kill exit
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
// receive signal
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTERM)));
// inject signal
ptrace::syscall(child, Signal::SIGTERM).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)));
},
}
}

View file

@ -0,0 +1,82 @@
use nix::sys::select::*;
use nix::unistd::{pipe, write};
use nix::sys::signal::SigSet;
use nix::sys::time::{TimeSpec, TimeValLike};
#[test]
pub fn test_pselect() {
let _mtx = crate::SIGNAL_MTX
.lock()
.expect("Mutex got poisoned by another test");
let (r1, w1) = pipe().unwrap();
write(w1, b"hi!").unwrap();
let (r2, _w2) = pipe().unwrap();
let mut fd_set = FdSet::new();
fd_set.insert(r1);
fd_set.insert(r2);
let timeout = TimeSpec::seconds(10);
let sigmask = SigSet::empty();
assert_eq!(
1,
pselect(None, &mut fd_set, None, None, &timeout, &sigmask).unwrap()
);
assert!(fd_set.contains(r1));
assert!(!fd_set.contains(r2));
}
#[test]
pub fn test_pselect_nfds2() {
let (r1, w1) = pipe().unwrap();
write(w1, b"hi!").unwrap();
let (r2, _w2) = pipe().unwrap();
let mut fd_set = FdSet::new();
fd_set.insert(r1);
fd_set.insert(r2);
let timeout = TimeSpec::seconds(10);
assert_eq!(
1,
pselect(
::std::cmp::max(r1, r2) + 1,
&mut fd_set,
None,
None,
&timeout,
None
).unwrap()
);
assert!(fd_set.contains(r1));
assert!(!fd_set.contains(r2));
}
macro_rules! generate_fdset_bad_fd_tests {
($fd:expr, $($method:ident),* $(,)?) => {
$(
#[test]
#[should_panic]
fn $method() {
FdSet::new().$method($fd);
}
)*
}
}
mod test_fdset_negative_fd {
use super::*;
generate_fdset_bad_fd_tests!(-1, insert, remove, contains);
}
mod test_fdset_too_large_fd {
use super::*;
use std::convert::TryInto;
generate_fdset_bad_fd_tests!(
FD_SETSIZE.try_into().unwrap(),
insert,
remove,
contains,
);
}

View file

@ -0,0 +1,121 @@
#[cfg(not(target_os = "redox"))]
use nix::errno::Errno;
use nix::sys::signal::*;
use nix::unistd::*;
use std::convert::TryFrom;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_kill_none() {
kill(getpid(), None).expect("Should be able to send signal to myself.");
}
#[test]
#[cfg(not(target_os = "fuchsia"))]
fn test_killpg_none() {
killpg(getpgrp(), None)
.expect("Should be able to send signal to my process group.");
}
#[test]
fn test_old_sigaction_flags() {
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
extern "C" fn handler(_: ::libc::c_int) {}
let act = SigAction::new(
SigHandler::Handler(handler),
SaFlags::empty(),
SigSet::empty(),
);
let oact = unsafe { sigaction(SIGINT, &act) }.unwrap();
let _flags = oact.flags();
let oact = unsafe { sigaction(SIGINT, &act) }.unwrap();
let _flags = oact.flags();
}
#[test]
fn test_sigprocmask_noop() {
sigprocmask(SigmaskHow::SIG_BLOCK, None, None)
.expect("this should be an effective noop");
}
#[test]
fn test_sigprocmask() {
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
// This needs to be a signal that rust doesn't use in the test harness.
const SIGNAL: Signal = Signal::SIGCHLD;
let mut old_signal_set = SigSet::empty();
sigprocmask(SigmaskHow::SIG_BLOCK, None, Some(&mut old_signal_set))
.expect("expect to be able to retrieve old signals");
// Make sure the old set doesn't contain the signal, otherwise the following
// test don't make sense.
assert!(!old_signal_set.contains(SIGNAL),
"the {:?} signal is already blocked, please change to a \
different one", SIGNAL);
// Now block the signal.
let mut signal_set = SigSet::empty();
signal_set.add(SIGNAL);
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&signal_set), None)
.expect("expect to be able to block signals");
// And test it again, to make sure the change was effective.
old_signal_set.clear();
sigprocmask(SigmaskHow::SIG_BLOCK, None, Some(&mut old_signal_set))
.expect("expect to be able to retrieve old signals");
assert!(old_signal_set.contains(SIGNAL),
"expected the {:?} to be blocked", SIGNAL);
// Reset the signal.
sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&signal_set), None)
.expect("expect to be able to block signals");
}
lazy_static! {
static ref SIGNALED: AtomicBool = AtomicBool::new(false);
}
extern fn test_sigaction_handler(signal: libc::c_int) {
let signal = Signal::try_from(signal).unwrap();
SIGNALED.store(signal == Signal::SIGINT, Ordering::Relaxed);
}
#[cfg(not(target_os = "redox"))]
extern fn test_sigaction_action(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) {}
#[test]
#[cfg(not(target_os = "redox"))]
fn test_signal_sigaction() {
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let action_handler = SigHandler::SigAction(test_sigaction_action);
assert_eq!(unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(), Errno::ENOTSUP);
}
#[test]
fn test_signal() {
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
unsafe { signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap();
raise(Signal::SIGINT).unwrap();
assert_eq!(unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), SigHandler::SigIgn);
let handler = SigHandler::Handler(test_sigaction_handler);
assert_eq!(unsafe { signal(Signal::SIGINT, handler) }.unwrap(), SigHandler::SigDfl);
raise(Signal::SIGINT).unwrap();
assert!(SIGNALED.load(Ordering::Relaxed));
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
assert_eq!(unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), handler);
// System V based OSes (e.g. illumos and Solaris) always resets the
// disposition to SIG_DFL prior to calling the signal handler
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
assert_eq!(unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap(), SigHandler::SigDfl);
// Restore default signal handler
unsafe { signal(Signal::SIGINT, SigHandler::SigDfl) }.unwrap();
}

View file

@ -0,0 +1,27 @@
use std::convert::TryFrom;
#[test]
fn test_signalfd() {
use nix::sys::signalfd::SignalFd;
use nix::sys::signal::{self, raise, Signal, SigSet};
// Grab the mutex for altering signals so we don't interfere with other tests.
let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
// Block the SIGUSR1 signal from automatic processing for this thread
let mut mask = SigSet::empty();
mask.add(signal::SIGUSR1);
mask.thread_block().unwrap();
let mut fd = SignalFd::new(&mask).unwrap();
// Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill`
// because `kill` with `getpid` isn't correct during multi-threaded execution like during a
// cargo test session. Instead use `raise` which does the correct thing by default.
raise(signal::SIGUSR1).expect("Error: raise(SIGUSR1) failed");
// And now catch that same signal.
let res = fd.read_signal().unwrap().unwrap();
let signo = Signal::try_from(res.ssi_signo as i32).unwrap();
assert_eq!(signo, signal::SIGUSR1);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,199 @@
use rand::{thread_rng, Rng};
use nix::sys::socket::{socket, sockopt, getsockopt, setsockopt, AddressFamily, SockType, SockFlag, SockProtocol};
#[cfg(any(target_os = "android", target_os = "linux"))]
use crate::*;
// NB: FreeBSD supports LOCAL_PEERCRED for SOCK_SEQPACKET, but OSX does not.
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
))]
#[test]
pub fn test_local_peercred_seqpacket() {
use nix::{
unistd::{Gid, Uid},
sys::socket::socketpair
};
let (fd1, _fd2) = socketpair(AddressFamily::Unix, SockType::SeqPacket, None,
SockFlag::empty()).unwrap();
let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap();
assert_eq!(xucred.version(), 0);
assert_eq!(Uid::from_raw(xucred.uid()), Uid::current());
assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current());
}
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "macos",
target_os = "ios"
))]
#[test]
pub fn test_local_peercred_stream() {
use nix::{
unistd::{Gid, Uid},
sys::socket::socketpair
};
let (fd1, _fd2) = socketpair(AddressFamily::Unix, SockType::Stream, None,
SockFlag::empty()).unwrap();
let xucred = getsockopt(fd1, sockopt::LocalPeerCred).unwrap();
assert_eq!(xucred.version(), 0);
assert_eq!(Uid::from_raw(xucred.uid()), Uid::current());
assert_eq!(Gid::from_raw(xucred.groups()[0]), Gid::current());
}
#[cfg(target_os = "linux")]
#[test]
fn is_so_mark_functional() {
use nix::sys::socket::sockopt;
require_capability!("is_so_mark_functional", CAP_NET_ADMIN);
let s = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), None).unwrap();
setsockopt(s, sockopt::Mark, &1337).unwrap();
let mark = getsockopt(s, sockopt::Mark).unwrap();
assert_eq!(mark, 1337);
}
#[test]
fn test_so_buf() {
let fd = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), SockProtocol::Udp)
.unwrap();
let bufsize: usize = thread_rng().gen_range(4096..131_072);
setsockopt(fd, sockopt::SndBuf, &bufsize).unwrap();
let actual = getsockopt(fd, sockopt::SndBuf).unwrap();
assert!(actual >= bufsize);
setsockopt(fd, sockopt::RcvBuf, &bufsize).unwrap();
let actual = getsockopt(fd, sockopt::RcvBuf).unwrap();
assert!(actual >= bufsize);
}
#[test]
fn test_so_tcp_maxseg() {
use std::net::SocketAddr;
use std::str::FromStr;
use nix::sys::socket::{accept, bind, connect, listen, InetAddr, SockAddr};
use nix::unistd::{close, write};
let std_sa = SocketAddr::from_str("127.0.0.1:4001").unwrap();
let inet_addr = InetAddr::from_std(&std_sa);
let sock_addr = SockAddr::new_inet(inet_addr);
let rsock = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), SockProtocol::Tcp)
.unwrap();
bind(rsock, &sock_addr).unwrap();
listen(rsock, 10).unwrap();
let initial = getsockopt(rsock, sockopt::TcpMaxSeg).unwrap();
// Initial MSS is expected to be 536 (https://tools.ietf.org/html/rfc879#section-1) but some
// platforms keep it even lower. This might fail if you've tuned your initial MSS to be larger
// than 700
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
let segsize: u32 = 873;
assert!(initial < segsize);
setsockopt(rsock, sockopt::TcpMaxSeg, &segsize).unwrap();
} else {
assert!(initial < 700);
}
}
// Connect and check the MSS that was advertised
let ssock = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), SockProtocol::Tcp)
.unwrap();
connect(ssock, &sock_addr).unwrap();
let rsess = accept(rsock).unwrap();
write(rsess, b"hello").unwrap();
let actual = getsockopt(ssock, sockopt::TcpMaxSeg).unwrap();
// Actual max segment size takes header lengths into account, max IPv4 options (60 bytes) + max
// TCP options (40 bytes) are subtracted from the requested maximum as a lower boundary.
cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
assert!((segsize - 100) <= actual);
assert!(actual <= segsize);
} else {
assert!(initial < actual);
assert!(536 < actual);
}
}
close(rsock).unwrap();
close(ssock).unwrap();
}
// The CI doesn't supported getsockopt and setsockopt on emulated processors.
// It's beleived that a QEMU issue, the tests run ok on a fully emulated system.
// Current CI just run the binary with QEMU but the Kernel remains the same as the host.
// So the syscall doesn't work properly unless the kernel is also emulated.
#[test]
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
any(target_os = "freebsd", target_os = "linux")
))]
fn test_tcp_congestion() {
use std::ffi::OsString;
let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), None).unwrap();
let val = getsockopt(fd, sockopt::TcpCongestion).unwrap();
setsockopt(fd, sockopt::TcpCongestion, &val).unwrap();
setsockopt(fd, sockopt::TcpCongestion, &OsString::from("tcp_congestion_does_not_exist")).unwrap_err();
assert_eq!(
getsockopt(fd, sockopt::TcpCongestion).unwrap(),
val
);
}
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_bindtodevice() {
skip_if_not_root!("test_bindtodevice");
let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), None).unwrap();
let val = getsockopt(fd, sockopt::BindToDevice).unwrap();
setsockopt(fd, sockopt::BindToDevice, &val).unwrap();
assert_eq!(
getsockopt(fd, sockopt::BindToDevice).unwrap(),
val
);
}
#[test]
fn test_so_tcp_keepalive() {
let fd = socket(AddressFamily::Inet, SockType::Stream, SockFlag::empty(), SockProtocol::Tcp).unwrap();
setsockopt(fd, sockopt::KeepAlive, &true).unwrap();
assert!(getsockopt(fd, sockopt::KeepAlive).unwrap());
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "nacl"))] {
let x = getsockopt(fd, sockopt::TcpKeepIdle).unwrap();
setsockopt(fd, sockopt::TcpKeepIdle, &(x + 1)).unwrap();
assert_eq!(getsockopt(fd, sockopt::TcpKeepIdle).unwrap(), x + 1);
let x = getsockopt(fd, sockopt::TcpKeepCount).unwrap();
setsockopt(fd, sockopt::TcpKeepCount, &(x + 1)).unwrap();
assert_eq!(getsockopt(fd, sockopt::TcpKeepCount).unwrap(), x + 1);
let x = getsockopt(fd, sockopt::TcpKeepInterval).unwrap();
setsockopt(fd, sockopt::TcpKeepInterval, &(x + 1)).unwrap();
assert_eq!(getsockopt(fd, sockopt::TcpKeepInterval).unwrap(), x + 1);
}
}
#[test]
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
fn test_ttl_opts() {
let fd4 = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None).unwrap();
setsockopt(fd4, sockopt::Ipv4Ttl, &1)
.expect("setting ipv4ttl on an inet socket should succeed");
let fd6 = socket(AddressFamily::Inet6, SockType::Datagram, SockFlag::empty(), None).unwrap();
setsockopt(fd6, sockopt::Ipv6Ttl, &1)
.expect("setting ipv6ttl on an inet6 socket should succeed");
}

View file

@ -0,0 +1,18 @@
use nix::sys::sysinfo::*;
#[test]
fn sysinfo_works() {
let info = sysinfo().unwrap();
let (l1, l5, l15) = info.load_average();
assert!(l1 >= 0.0);
assert!(l5 >= 0.0);
assert!(l15 >= 0.0);
info.uptime(); // just test Duration construction
assert!(info.swap_free() <= info.swap_total(),
"more swap available than installed (free: {}, total: {})",
info.swap_free(),
info.swap_total());
}

View file

@ -0,0 +1,130 @@
use std::os::unix::prelude::*;
use tempfile::tempfile;
use nix::fcntl;
use nix::errno::Errno;
use nix::pty::openpty;
use nix::sys::termios::{self, LocalFlags, OutputFlags, tcgetattr};
use nix::unistd::{read, write, close};
/// Helper function analogous to `std::io::Write::write_all`, but for `RawFd`s
fn write_all(f: RawFd, buf: &[u8]) {
let mut len = 0;
while len < buf.len() {
len += write(f, &buf[len..]).unwrap();
}
}
// Test tcgetattr on a terminal
#[test]
fn test_tcgetattr_pty() {
// openpty uses ptname(3) internally
let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
let pty = openpty(None, None).expect("openpty failed");
assert!(termios::tcgetattr(pty.slave).is_ok());
close(pty.master).expect("closing the master failed");
close(pty.slave).expect("closing the slave failed");
}
// Test tcgetattr on something that isn't a terminal
#[test]
fn test_tcgetattr_enotty() {
let file = tempfile().unwrap();
assert_eq!(termios::tcgetattr(file.as_raw_fd()).err(),
Some(Errno::ENOTTY));
}
// Test tcgetattr on an invalid file descriptor
#[test]
fn test_tcgetattr_ebadf() {
assert_eq!(termios::tcgetattr(-1).err(),
Some(Errno::EBADF));
}
// Test modifying output flags
#[test]
fn test_output_flags() {
// openpty uses ptname(3) internally
let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
// Open one pty to get attributes for the second one
let mut termios = {
let pty = openpty(None, None).expect("openpty failed");
assert!(pty.master > 0);
assert!(pty.slave > 0);
let termios = tcgetattr(pty.slave).expect("tcgetattr failed");
close(pty.master).unwrap();
close(pty.slave).unwrap();
termios
};
// Make sure postprocessing '\r' isn't specified by default or this test is useless.
assert!(!termios.output_flags.contains(OutputFlags::OPOST | OutputFlags::OCRNL));
// Specify that '\r' characters should be transformed to '\n'
// OPOST is specified to enable post-processing
termios.output_flags.insert(OutputFlags::OPOST | OutputFlags::OCRNL);
// Open a pty
let pty = openpty(None, &termios).unwrap();
assert!(pty.master > 0);
assert!(pty.slave > 0);
// Write into the master
let string = "foofoofoo\r";
write_all(pty.master, string.as_bytes());
// Read from the slave verifying that the output has been properly transformed
let mut buf = [0u8; 10];
crate::read_exact(pty.slave, &mut buf);
let transformed_string = "foofoofoo\n";
close(pty.master).unwrap();
close(pty.slave).unwrap();
assert_eq!(&buf, transformed_string.as_bytes());
}
// Test modifying local flags
#[test]
fn test_local_flags() {
// openpty uses ptname(3) internally
let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
// Open one pty to get attributes for the second one
let mut termios = {
let pty = openpty(None, None).unwrap();
assert!(pty.master > 0);
assert!(pty.slave > 0);
let termios = tcgetattr(pty.slave).unwrap();
close(pty.master).unwrap();
close(pty.slave).unwrap();
termios
};
// Make sure echo is specified by default or this test is useless.
assert!(termios.local_flags.contains(LocalFlags::ECHO));
// Disable local echo
termios.local_flags.remove(LocalFlags::ECHO);
// Open a new pty with our modified termios settings
let pty = openpty(None, &termios).unwrap();
assert!(pty.master > 0);
assert!(pty.slave > 0);
// Set the master is in nonblocking mode or reading will never return.
let flags = fcntl::fcntl(pty.master, fcntl::F_GETFL).unwrap();
let new_flags = fcntl::OFlag::from_bits_truncate(flags) | fcntl::OFlag::O_NONBLOCK;
fcntl::fcntl(pty.master, fcntl::F_SETFL(new_flags)).unwrap();
// Write into the master
let string = "foofoofoo\r";
write_all(pty.master, string.as_bytes());
// Try to read from the master, which should not have anything as echoing was disabled.
let mut buf = [0u8; 10];
let read = read(pty.master, &mut buf).unwrap_err();
close(pty.master).unwrap();
close(pty.slave).unwrap();
assert_eq!(read, Errno::EAGAIN);
}

View file

@ -0,0 +1,61 @@
use nix::sys::time::{TimeSpec, TimeValLike};
use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags};
use std::time::Instant;
#[test]
pub fn test_timerfd_oneshot() {
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
let before = Instant::now();
timer
.set(
Expiration::OneShot(TimeSpec::seconds(1)),
TimerSetTimeFlags::empty(),
)
.unwrap();
timer.wait().unwrap();
let millis = before.elapsed().as_millis();
assert!(millis > 900);
}
#[test]
pub fn test_timerfd_interval() {
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
let before = Instant::now();
timer
.set(
Expiration::IntervalDelayed(TimeSpec::seconds(1), TimeSpec::seconds(2)),
TimerSetTimeFlags::empty(),
)
.unwrap();
timer.wait().unwrap();
let start_delay = before.elapsed().as_millis();
assert!(start_delay > 900);
timer.wait().unwrap();
let interval_delay = before.elapsed().as_millis();
assert!(interval_delay > 2900);
}
#[test]
pub fn test_timerfd_unset() {
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty()).unwrap();
timer
.set(
Expiration::OneShot(TimeSpec::seconds(1)),
TimerSetTimeFlags::empty(),
)
.unwrap();
timer.unset().unwrap();
assert!(timer.get().unwrap() == None);
}

View file

@ -0,0 +1,255 @@
use nix::sys::uio::*;
use nix::unistd::*;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use std::{cmp, iter};
use std::fs::{OpenOptions};
use std::os::unix::io::AsRawFd;
#[cfg(not(target_os = "redox"))]
use tempfile::tempfile;
use tempfile::tempdir;
#[test]
fn test_writev() {
let mut to_write = Vec::with_capacity(16 * 128);
for _ in 0..16 {
let s: String = thread_rng()
.sample_iter(&Alphanumeric)
.map(char::from)
.take(128)
.collect();
let b = s.as_bytes();
to_write.extend(b.iter().cloned());
}
// Allocate and fill iovecs
let mut iovecs = Vec::new();
let mut consumed = 0;
while consumed < to_write.len() {
let left = to_write.len() - consumed;
let slice_len = if left <= 64 { left } else { thread_rng().gen_range(64..cmp::min(256, left)) };
let b = &to_write[consumed..consumed+slice_len];
iovecs.push(IoVec::from_slice(b));
consumed += slice_len;
}
let pipe_res = pipe();
assert!(pipe_res.is_ok());
let (reader, writer) = pipe_res.ok().unwrap();
// FileDesc will close its filedesc (reader).
let mut read_buf: Vec<u8> = iter::repeat(0u8).take(128 * 16).collect();
// Blocking io, should write all data.
let write_res = writev(writer, &iovecs);
// Successful write
assert!(write_res.is_ok());
let written = write_res.ok().unwrap();
// Check whether we written all data
assert_eq!(to_write.len(), written);
let read_res = read(reader, &mut read_buf[..]);
// Successful read
assert!(read_res.is_ok());
let read = read_res.ok().unwrap() as usize;
// Check we have read as much as we written
assert_eq!(read, written);
// Check equality of written and read data
assert_eq!(&to_write, &read_buf);
let close_res = close(writer);
assert!(close_res.is_ok());
let close_res = close(reader);
assert!(close_res.is_ok());
}
#[test]
#[cfg(not(target_os = "redox"))]
fn test_readv() {
let s:String = thread_rng()
.sample_iter(&Alphanumeric)
.map(char::from)
.take(128)
.collect();
let to_write = s.as_bytes().to_vec();
let mut storage = Vec::new();
let mut allocated = 0;
while allocated < to_write.len() {
let left = to_write.len() - allocated;
let vec_len = if left <= 64 { left } else { thread_rng().gen_range(64..cmp::min(256, left)) };
let v: Vec<u8> = iter::repeat(0u8).take(vec_len).collect();
storage.push(v);
allocated += vec_len;
}
let mut iovecs = Vec::with_capacity(storage.len());
for v in &mut storage {
iovecs.push(IoVec::from_mut_slice(&mut v[..]));
}
let pipe_res = pipe();
assert!(pipe_res.is_ok());
let (reader, writer) = pipe_res.ok().unwrap();
// Blocking io, should write all data.
let write_res = write(writer, &to_write);
// Successful write
assert!(write_res.is_ok());
let read_res = readv(reader, &mut iovecs[..]);
assert!(read_res.is_ok());
let read = read_res.ok().unwrap();
// Check whether we've read all data
assert_eq!(to_write.len(), read);
// Cccumulate data from iovecs
let mut read_buf = Vec::with_capacity(to_write.len());
for iovec in &iovecs {
read_buf.extend(iovec.as_slice().iter().cloned());
}
// Check whether iovecs contain all written data
assert_eq!(read_buf.len(), to_write.len());
// Check equality of written and read data
assert_eq!(&read_buf, &to_write);
let close_res = close(reader);
assert!(close_res.is_ok());
let close_res = close(writer);
assert!(close_res.is_ok());
}
#[test]
#[cfg(not(target_os = "redox"))]
fn test_pwrite() {
use std::io::Read;
let mut file = tempfile().unwrap();
let buf = [1u8;8];
assert_eq!(Ok(8), pwrite(file.as_raw_fd(), &buf, 8));
let mut file_content = Vec::new();
file.read_to_end(&mut file_content).unwrap();
let mut expected = vec![0u8;8];
expected.extend(vec![1;8]);
assert_eq!(file_content, expected);
}
#[test]
fn test_pread() {
use std::io::Write;
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("pread_test_file");
let mut file = OpenOptions::new().write(true).read(true).create(true)
.truncate(true).open(path).unwrap();
let file_content: Vec<u8> = (0..64).collect();
file.write_all(&file_content).unwrap();
let mut buf = [0u8;16];
assert_eq!(Ok(16), pread(file.as_raw_fd(), &mut buf, 16));
let expected: Vec<_> = (16..32).collect();
assert_eq!(&buf[..], &expected[..]);
}
#[test]
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
fn test_pwritev() {
use std::io::Read;
let to_write: Vec<u8> = (0..128).collect();
let expected: Vec<u8> = [vec![0;100], to_write.clone()].concat();
let iovecs = [
IoVec::from_slice(&to_write[0..17]),
IoVec::from_slice(&to_write[17..64]),
IoVec::from_slice(&to_write[64..128]),
];
let tempdir = tempdir().unwrap();
// pwritev them into a temporary file
let path = tempdir.path().join("pwritev_test_file");
let mut file = OpenOptions::new().write(true).read(true).create(true)
.truncate(true).open(path).unwrap();
let written = pwritev(file.as_raw_fd(), &iovecs, 100).ok().unwrap();
assert_eq!(written, to_write.len());
// Read the data back and make sure it matches
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
assert_eq!(contents, expected);
}
#[test]
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
fn test_preadv() {
use std::io::Write;
let to_write: Vec<u8> = (0..200).collect();
let expected: Vec<u8> = (100..200).collect();
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("preadv_test_file");
let mut file = OpenOptions::new().read(true).write(true).create(true)
.truncate(true).open(path).unwrap();
file.write_all(&to_write).unwrap();
let mut buffers: Vec<Vec<u8>> = vec![
vec![0; 24],
vec![0; 1],
vec![0; 75],
];
{
// Borrow the buffers into IoVecs and preadv into them
let iovecs: Vec<_> = buffers.iter_mut().map(
|buf| IoVec::from_mut_slice(&mut buf[..])).collect();
assert_eq!(Ok(100), preadv(file.as_raw_fd(), &iovecs, 100));
}
let all = buffers.concat();
assert_eq!(all, expected);
}
#[test]
#[cfg(target_os = "linux")]
// qemu-user doesn't implement process_vm_readv/writev on most arches
#[cfg_attr(qemu, ignore)]
fn test_process_vm_readv() {
use nix::unistd::ForkResult::*;
use nix::sys::signal::*;
use nix::sys::wait::*;
use crate::*;
require_capability!("test_process_vm_readv", CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
// Pre-allocate memory in the child, since allocation isn't safe
// post-fork (~= async-signal-safe)
let mut vector = vec![1u8, 2, 3, 4, 5];
let (r, w) = pipe().unwrap();
match unsafe{fork()}.expect("Error: Fork Failed") {
Parent { child } => {
close(w).unwrap();
// wait for child
read(r, &mut [0u8]).unwrap();
close(r).unwrap();
let ptr = vector.as_ptr() as usize;
let remote_iov = RemoteIoVec { base: ptr, len: 5 };
let mut buf = vec![0u8; 5];
let ret = process_vm_readv(child,
&[IoVec::from_mut_slice(&mut buf)],
&[remote_iov]);
kill(child, SIGTERM).unwrap();
waitpid(child, None).unwrap();
assert_eq!(Ok(5), ret);
assert_eq!(20u8, buf.iter().sum());
},
Child => {
let _ = close(r);
for i in &mut vector {
*i += 1;
}
let _ = write(w, b"\0");
let _ = close(w);
loop { let _ = pause(); }
},
}
}

View file

@ -0,0 +1,107 @@
use nix::errno::Errno;
use nix::unistd::*;
use nix::unistd::ForkResult::*;
use nix::sys::signal::*;
use nix::sys::wait::*;
use libc::_exit;
#[test]
#[cfg(not(target_os = "redox"))]
fn test_wait_signal() {
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
match unsafe{fork()}.expect("Error: Fork Failed") {
Child => {
pause();
unsafe { _exit(123) }
},
Parent { child } => {
kill(child, Some(SIGKILL)).expect("Error: Kill Failed");
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, SIGKILL, false)));
},
}
}
#[test]
fn test_wait_exit() {
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
// Safe: Child only calls `_exit`, which is async-signal-safe.
match unsafe{fork()}.expect("Error: Fork Failed") {
Child => unsafe { _exit(12); },
Parent { child } => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 12)));
},
}
}
#[test]
fn test_waitstatus_from_raw() {
let pid = Pid::from_raw(1);
assert_eq!(WaitStatus::from_raw(pid, 0x0002), Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false)));
assert_eq!(WaitStatus::from_raw(pid, 0x0200), Ok(WaitStatus::Exited(pid, 2)));
assert_eq!(WaitStatus::from_raw(pid, 0x7f7f), Err(Errno::EINVAL));
}
#[test]
fn test_waitstatus_pid() {
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
match unsafe{fork()}.unwrap() {
Child => unsafe { _exit(0) },
Parent { child } => {
let status = waitpid(child, None).unwrap();
assert_eq!(status.pid(), Some(child));
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
// FIXME: qemu-user doesn't implement ptrace on most arches
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod ptrace {
use nix::sys::ptrace::{self, Options, Event};
use nix::sys::signal::*;
use nix::sys::wait::*;
use nix::unistd::*;
use nix::unistd::ForkResult::*;
use libc::_exit;
use crate::*;
fn ptrace_child() -> ! {
ptrace::traceme().unwrap();
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
raise(SIGTRAP).unwrap();
unsafe { _exit(0) }
}
fn ptrace_parent(child: Pid) {
// Wait for the raised SIGTRAP
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
// We want to test a syscall stop and a PTRACE_EVENT stop
assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok());
// First, stop on the next system call, which will be exit()
assert!(ptrace::syscall(child, None).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
// Then get the ptrace event for the process exiting
assert!(ptrace::cont(child, None).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, Event::PTRACE_EVENT_EXIT as i32)));
// Finally get the normal wait() result, now that the process has exited
assert!(ptrace::cont(child, None).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
}
#[test]
fn test_wait_ptrace() {
require_capability!("test_wait_ptrace", CAP_SYS_PTRACE);
let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test");
match unsafe{fork()}.expect("Error: Fork Failed") {
Child => ptrace_child(),
Parent { child } => ptrace_parent(child),
}
}
}

103
vendor/nix-v0.23.1-patched/test/test.rs vendored Normal file
View file

@ -0,0 +1,103 @@
#[macro_use]
extern crate cfg_if;
#[cfg_attr(not(target_os = "redox"), macro_use)]
extern crate nix;
#[macro_use]
extern crate lazy_static;
mod common;
mod sys;
#[cfg(not(target_os = "redox"))]
mod test_dir;
mod test_fcntl;
#[cfg(any(target_os = "android",
target_os = "linux"))]
mod test_kmod;
#[cfg(target_os = "freebsd")]
mod test_nmount;
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "fushsia",
target_os = "linux",
target_os = "netbsd"))]
mod test_mq;
#[cfg(not(target_os = "redox"))]
mod test_net;
mod test_nix_path;
mod test_resource;
mod test_poll;
#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
mod test_pty;
#[cfg(any(target_os = "android",
target_os = "linux"))]
mod test_sched;
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos"))]
mod test_sendfile;
mod test_stat;
mod test_time;
mod test_unistd;
use std::os::unix::io::RawFd;
use std::path::PathBuf;
use std::sync::{Mutex, RwLock, RwLockWriteGuard};
use nix::unistd::{chdir, getcwd, read};
/// Helper function analogous to `std::io::Read::read_exact`, but for `RawFD`s
fn read_exact(f: RawFd, buf: &mut [u8]) {
let mut len = 0;
while len < buf.len() {
// get_mut would be better than split_at_mut, but it requires nightly
let (_, remaining) = buf.split_at_mut(len);
len += read(f, remaining).unwrap();
}
}
lazy_static! {
/// Any test that changes the process's current working directory must grab
/// the RwLock exclusively. Any process that cares about the current
/// working directory must grab it shared.
pub static ref CWD_LOCK: RwLock<()> = RwLock::new(());
/// Any test that creates child processes must grab this mutex, regardless
/// of what it does with those children.
pub static ref FORK_MTX: Mutex<()> = Mutex::new(());
/// Any test that changes the process's supplementary groups must grab this
/// mutex
pub static ref GROUPS_MTX: Mutex<()> = Mutex::new(());
/// Any tests that loads or unloads kernel modules must grab this mutex
pub static ref KMOD_MTX: Mutex<()> = Mutex::new(());
/// Any test that calls ptsname(3) must grab this mutex.
pub static ref PTSNAME_MTX: Mutex<()> = Mutex::new(());
/// Any test that alters signal handling must grab this mutex.
pub static ref SIGNAL_MTX: Mutex<()> = Mutex::new(());
}
/// RAII object that restores a test's original directory on drop
struct DirRestore<'a> {
d: PathBuf,
_g: RwLockWriteGuard<'a, ()>
}
impl<'a> DirRestore<'a> {
fn new() -> Self {
let guard = crate::CWD_LOCK.write()
.expect("Lock got poisoned by another test");
DirRestore{
_g: guard,
d: getcwd().unwrap(),
}
}
}
impl<'a> Drop for DirRestore<'a> {
fn drop(&mut self) {
let r = chdir(&self.d);
if std::thread::panicking() {
r.unwrap();
}
}
}

View file

@ -0,0 +1,9 @@
use std::env;
#[test]
fn clearenv() {
env::set_var("FOO", "BAR");
unsafe { nix::env::clearenv() }.unwrap();
assert_eq!(env::var("FOO").unwrap_err(), env::VarError::NotPresent);
assert_eq!(env::vars().count(), 0);
}

View file

@ -0,0 +1,56 @@
use nix::dir::{Dir, Type};
use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
use std::fs::File;
use tempfile::tempdir;
#[cfg(test)]
fn flags() -> OFlag {
#[cfg(target_os = "illumos")]
let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC;
#[cfg(not(target_os = "illumos"))]
let f = OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_DIRECTORY;
f
}
#[test]
#[allow(clippy::unnecessary_sort_by)] // False positive
fn read() {
let tmp = tempdir().unwrap();
File::create(&tmp.path().join("foo")).unwrap();
::std::os::unix::fs::symlink("foo", tmp.path().join("bar")).unwrap();
let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap();
let mut entries: Vec<_> = dir.iter().map(|e| e.unwrap()).collect();
entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
let entry_names: Vec<_> = entries
.iter()
.map(|e| e.file_name().to_str().unwrap().to_owned())
.collect();
assert_eq!(&entry_names[..], &[".", "..", "bar", "foo"]);
// Check file types. The system is allowed to return DT_UNKNOWN (aka None here) but if it does
// return a type, ensure it's correct.
assert!(&[Some(Type::Directory), None].contains(&entries[0].file_type())); // .: dir
assert!(&[Some(Type::Directory), None].contains(&entries[1].file_type())); // ..: dir
assert!(&[Some(Type::Symlink), None].contains(&entries[2].file_type())); // bar: symlink
assert!(&[Some(Type::File), None].contains(&entries[3].file_type())); // foo: regular file
}
#[test]
fn rewind() {
let tmp = tempdir().unwrap();
let mut dir = Dir::open(tmp.path(), flags(), Mode::empty()).unwrap();
let entries1: Vec<_> = dir.iter().map(|e| e.unwrap().file_name().to_owned()).collect();
let entries2: Vec<_> = dir.iter().map(|e| e.unwrap().file_name().to_owned()).collect();
let entries3: Vec<_> = dir.into_iter().map(|e| e.unwrap().file_name().to_owned()).collect();
assert_eq!(entries1, entries2);
assert_eq!(entries2, entries3);
}
#[test]
fn ebadf() {
assert_eq!(Dir::from_fd(-1).unwrap_err(), nix::Error::EBADF);
}

View file

@ -0,0 +1,540 @@
#[cfg(not(target_os = "redox"))]
use nix::errno::*;
#[cfg(not(target_os = "redox"))]
use nix::fcntl::{open, OFlag, readlink};
#[cfg(not(target_os = "redox"))]
use nix::fcntl::{openat, readlinkat, renameat};
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
use nix::fcntl::{RenameFlags, renameat2};
#[cfg(not(target_os = "redox"))]
use nix::sys::stat::Mode;
#[cfg(not(target_os = "redox"))]
use nix::unistd::{close, read};
#[cfg(not(target_os = "redox"))]
use tempfile::{self, NamedTempFile};
#[cfg(not(target_os = "redox"))]
use std::fs::File;
#[cfg(not(target_os = "redox"))]
use std::io::prelude::*;
#[cfg(not(target_os = "redox"))]
use std::os::unix::fs;
#[test]
#[cfg(not(target_os = "redox"))]
fn test_openat() {
const CONTENTS: &[u8] = b"abcd";
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(CONTENTS).unwrap();
let dirfd = open(tmp.path().parent().unwrap(),
OFlag::empty(),
Mode::empty()).unwrap();
let fd = openat(dirfd,
tmp.path().file_name().unwrap(),
OFlag::O_RDONLY,
Mode::empty()).unwrap();
let mut buf = [0u8; 1024];
assert_eq!(4, read(fd, &mut buf).unwrap());
assert_eq!(CONTENTS, &buf[0..4]);
close(fd).unwrap();
close(dirfd).unwrap();
}
#[test]
#[cfg(not(target_os = "redox"))]
fn test_renameat() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
File::create(&old_path).unwrap();
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap();
assert_eq!(renameat(Some(old_dirfd), "old", Some(new_dirfd), "new").unwrap_err(),
Errno::ENOENT);
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").exists());
}
#[test]
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
fn test_renameat2_behaves_like_renameat_with_no_flags() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
File::create(&old_path).unwrap();
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::empty(),
)
.unwrap();
assert_eq!(
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::empty()
)
.unwrap_err(),
Errno::ENOENT
);
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").exists());
}
#[test]
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
fn test_renameat2_exchange() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
{
let mut old_f = File::create(&old_path).unwrap();
old_f.write_all(b"old").unwrap();
}
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let new_path = new_dir.path().join("new");
{
let mut new_f = File::create(&new_path).unwrap();
new_f.write_all(b"new").unwrap();
}
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::RENAME_EXCHANGE,
)
.unwrap();
let mut buf = String::new();
let mut new_f = File::open(&new_path).unwrap();
new_f.read_to_string(&mut buf).unwrap();
assert_eq!(buf, "old");
buf = "".to_string();
let mut old_f = File::open(&old_path).unwrap();
old_f.read_to_string(&mut buf).unwrap();
assert_eq!(buf, "new");
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
}
#[test]
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",
target_arch = "s390x"
)
))]
fn test_renameat2_noreplace() {
let old_dir = tempfile::tempdir().unwrap();
let old_dirfd = open(old_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let old_path = old_dir.path().join("old");
File::create(&old_path).unwrap();
let new_dir = tempfile::tempdir().unwrap();
let new_dirfd = open(new_dir.path(), OFlag::empty(), Mode::empty()).unwrap();
let new_path = new_dir.path().join("new");
File::create(&new_path).unwrap();
assert_eq!(
renameat2(
Some(old_dirfd),
"old",
Some(new_dirfd),
"new",
RenameFlags::RENAME_NOREPLACE
)
.unwrap_err(),
Errno::EEXIST
);
close(old_dirfd).unwrap();
close(new_dirfd).unwrap();
assert!(new_dir.path().join("new").exists());
assert!(old_dir.path().join("old").exists());
}
#[test]
#[cfg(not(target_os = "redox"))]
fn test_readlink() {
let tempdir = tempfile::tempdir().unwrap();
let src = tempdir.path().join("a");
let dst = tempdir.path().join("b");
println!("a: {:?}, b: {:?}", &src, &dst);
fs::symlink(&src.as_path(), &dst.as_path()).unwrap();
let dirfd = open(tempdir.path(),
OFlag::empty(),
Mode::empty()).unwrap();
let expected_dir = src.to_str().unwrap();
assert_eq!(readlink(&dst).unwrap().to_str().unwrap(), expected_dir);
assert_eq!(readlinkat(dirfd, "b").unwrap().to_str().unwrap(), expected_dir);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux_android {
use std::io::prelude::*;
use std::io::SeekFrom;
use std::os::unix::prelude::*;
use libc::loff_t;
use nix::fcntl::*;
use nix::sys::uio::IoVec;
use nix::unistd::{close, pipe, read, write};
use tempfile::tempfile;
#[cfg(any(target_os = "linux"))]
use tempfile::NamedTempFile;
use crate::*;
/// This test creates a temporary file containing the contents
/// 'foobarbaz' and uses the `copy_file_range` call to transfer
/// 3 bytes at offset 3 (`bar`) to another empty file at offset 0. The
/// resulting file is read and should contain the contents `bar`.
/// The from_offset should be updated by the call to reflect
/// the 3 bytes read (6).
#[test]
// QEMU does not support copy_file_range. Skip under qemu
#[cfg_attr(qemu, ignore)]
fn test_copy_file_range() {
const CONTENTS: &[u8] = b"foobarbaz";
let mut tmp1 = tempfile().unwrap();
let mut tmp2 = tempfile().unwrap();
tmp1.write_all(CONTENTS).unwrap();
tmp1.flush().unwrap();
let mut from_offset: i64 = 3;
copy_file_range(
tmp1.as_raw_fd(),
Some(&mut from_offset),
tmp2.as_raw_fd(),
None,
3,
)
.unwrap();
let mut res: String = String::new();
tmp2.seek(SeekFrom::Start(0)).unwrap();
tmp2.read_to_string(&mut res).unwrap();
assert_eq!(res, String::from("bar"));
assert_eq!(from_offset, 6);
}
#[test]
fn test_splice() {
const CONTENTS: &[u8] = b"abcdef123456";
let mut tmp = tempfile().unwrap();
tmp.write_all(CONTENTS).unwrap();
let (rd, wr) = pipe().unwrap();
let mut offset: loff_t = 5;
let res = splice(tmp.as_raw_fd(), Some(&mut offset),
wr, None, 2, SpliceFFlags::empty()).unwrap();
assert_eq!(2, res);
let mut buf = [0u8; 1024];
assert_eq!(2, read(rd, &mut buf).unwrap());
assert_eq!(b"f1", &buf[0..2]);
assert_eq!(7, offset);
close(rd).unwrap();
close(wr).unwrap();
}
#[test]
fn test_tee() {
let (rd1, wr1) = pipe().unwrap();
let (rd2, wr2) = pipe().unwrap();
write(wr1, b"abc").unwrap();
let res = tee(rd1, wr2, 2, SpliceFFlags::empty()).unwrap();
assert_eq!(2, res);
let mut buf = [0u8; 1024];
// Check the tee'd bytes are at rd2.
assert_eq!(2, read(rd2, &mut buf).unwrap());
assert_eq!(b"ab", &buf[0..2]);
// Check all the bytes are still at rd1.
assert_eq!(3, read(rd1, &mut buf).unwrap());
assert_eq!(b"abc", &buf[0..3]);
close(rd1).unwrap();
close(wr1).unwrap();
close(rd2).unwrap();
close(wr2).unwrap();
}
#[test]
fn test_vmsplice() {
let (rd, wr) = pipe().unwrap();
let buf1 = b"abcdef";
let buf2 = b"defghi";
let iovecs = vec![
IoVec::from_slice(&buf1[0..3]),
IoVec::from_slice(&buf2[0..3])
];
let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap();
assert_eq!(6, res);
// Check the bytes can be read at rd.
let mut buf = [0u8; 32];
assert_eq!(6, read(rd, &mut buf).unwrap());
assert_eq!(b"abcdef", &buf[0..6]);
close(rd).unwrap();
close(wr).unwrap();
}
#[cfg(any(target_os = "linux"))]
#[test]
fn test_fallocate() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
fallocate(fd, FallocateFlags::empty(), 0, 100).unwrap();
// Check if we read exactly 100 bytes
let mut buf = [0u8; 200];
assert_eq!(100, read(fd, &mut buf).unwrap());
}
// The tests below are disabled for the listed targets
// due to OFD locks not being available in the kernel/libc
// versions used in the CI environment, probably because
// they run under QEMU.
#[test]
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
fn test_ofd_write_lock() {
use nix::sys::stat::fstat;
use std::mem;
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap();
if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
// OverlayFS is a union file system. It returns one inode value in
// stat(2), but a different one shows up in /proc/locks. So we must
// skip the test.
skip!("/proc/locks does not work on overlayfs");
}
let inode = fstat(fd).expect("fstat failed").st_ino as usize;
let mut flock: libc::flock = unsafe {
mem::zeroed() // required for Linux/mips
};
flock.l_type = libc::F_WRLCK as libc::c_short;
flock.l_whence = libc::SEEK_SET as libc::c_short;
flock.l_start = 0;
flock.l_len = 0;
flock.l_pid = 0;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write lock failed");
assert_eq!(
Some(("OFDLCK".to_string(), "WRITE".to_string())),
lock_info(inode)
);
flock.l_type = libc::F_UNLCK as libc::c_short;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("write unlock failed");
assert_eq!(None, lock_info(inode));
}
#[test]
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
fn test_ofd_read_lock() {
use nix::sys::stat::fstat;
use std::mem;
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let statfs = nix::sys::statfs::fstatfs(&tmp).unwrap();
if statfs.filesystem_type() == nix::sys::statfs::OVERLAYFS_SUPER_MAGIC {
// OverlayFS is a union file system. It returns one inode value in
// stat(2), but a different one shows up in /proc/locks. So we must
// skip the test.
skip!("/proc/locks does not work on overlayfs");
}
let inode = fstat(fd).expect("fstat failed").st_ino as usize;
let mut flock: libc::flock = unsafe {
mem::zeroed() // required for Linux/mips
};
flock.l_type = libc::F_RDLCK as libc::c_short;
flock.l_whence = libc::SEEK_SET as libc::c_short;
flock.l_start = 0;
flock.l_len = 0;
flock.l_pid = 0;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read lock failed");
assert_eq!(
Some(("OFDLCK".to_string(), "READ".to_string())),
lock_info(inode)
);
flock.l_type = libc::F_UNLCK as libc::c_short;
fcntl(fd, FcntlArg::F_OFD_SETLKW(&flock)).expect("read unlock failed");
assert_eq!(None, lock_info(inode));
}
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
fn lock_info(inode: usize) -> Option<(String, String)> {
use std::{
fs::File,
io::BufReader
};
let file = File::open("/proc/locks").expect("open /proc/locks failed");
let buf = BufReader::new(file);
for line in buf.lines() {
let line = line.unwrap();
let parts: Vec<_> = line.split_whitespace().collect();
let lock_type = parts[1];
let lock_access = parts[3];
let ino_parts: Vec<_> = parts[5].split(':').collect();
let ino: usize = ino_parts[2].parse().unwrap();
if ino == inode {
return Some((lock_type.to_string(), lock_access.to_string()));
}
}
None
}
}
#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_os = "freebsd"))]
mod test_posix_fadvise {
use tempfile::NamedTempFile;
use std::os::unix::io::{RawFd, AsRawFd};
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
#[test]
fn test_success() {
let tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let res = posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED);
assert!(res.is_ok());
}
#[test]
fn test_errno() {
let (rd, _wr) = pipe().unwrap();
let res = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED);
assert_eq!(res, Err(Errno::ESPIPE));
}
}
#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_os = "freebsd"))]
mod test_posix_fallocate {
use tempfile::NamedTempFile;
use std::{io::Read, os::unix::io::{RawFd, AsRawFd}};
use nix::errno::Errno;
use nix::fcntl::*;
use nix::unistd::pipe;
#[test]
fn success() {
const LEN: usize = 100;
let mut tmp = NamedTempFile::new().unwrap();
let fd = tmp.as_raw_fd();
let res = posix_fallocate(fd, 0, LEN as libc::off_t);
match res {
Ok(_) => {
let mut data = [1u8; LEN];
assert_eq!(tmp.read(&mut data).expect("read failure"), LEN);
assert_eq!(&data[..], &[0u8; LEN][..]);
}
Err(Errno::EINVAL) => {
// POSIX requires posix_fallocate to return EINVAL both for
// invalid arguments (i.e. len < 0) and if the operation is not
// supported by the file system.
// There's no way to tell for sure whether the file system
// supports posix_fallocate, so we must pass the test if it
// returns EINVAL.
}
_ => res.unwrap(),
}
}
#[test]
fn errno() {
let (rd, _wr) = pipe().unwrap();
let err = posix_fallocate(rd as RawFd, 0, 100).unwrap_err();
match err {
Errno::EINVAL | Errno::ENODEV | Errno::ESPIPE | Errno::EBADF => (),
errno =>
panic!(
"unexpected errno {}",
errno,
),
}
}
}

View file

@ -0,0 +1,7 @@
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

View file

@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: GPL-2.0+ or MIT
*/
#include <linux/module.h>
#include <linux/kernel.h>
static int number= 1;
static char *who = "World";
module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "Just some number");
module_param(who, charp, 0000);
MODULE_PARM_DESC(who, "Whot to greet");
int init_module(void)
{
printk(KERN_INFO "Hello %s (%d)!\n", who, number);
return 0;
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodbye %s (%d)!\n", who, number);
}
MODULE_LICENSE("Dual MIT/GPL");

View file

@ -0,0 +1,166 @@
use std::fs::copy;
use std::path::PathBuf;
use std::process::Command;
use tempfile::{tempdir, TempDir};
use crate::*;
fn compile_kernel_module() -> (PathBuf, String, TempDir) {
let _m = crate::FORK_MTX
.lock()
.expect("Mutex got poisoned by another test");
let tmp_dir = tempdir().expect("unable to create temporary build directory");
copy(
"test/test_kmod/hello_mod/hello.c",
&tmp_dir.path().join("hello.c"),
).expect("unable to copy hello.c to temporary build directory");
copy(
"test/test_kmod/hello_mod/Makefile",
&tmp_dir.path().join("Makefile"),
).expect("unable to copy Makefile to temporary build directory");
let status = Command::new("make")
.current_dir(tmp_dir.path())
.status()
.expect("failed to run make");
assert!(status.success());
// Return the relative path of the build kernel module
(tmp_dir.path().join("hello.ko"), "hello".to_owned(), tmp_dir)
}
use nix::errno::Errno;
use nix::kmod::{delete_module, DeleteModuleFlags};
use nix::kmod::{finit_module, init_module, ModuleInitFlags};
use std::ffi::CString;
use std::fs::File;
use std::io::Read;
#[test]
fn test_finit_and_delete_module() {
require_capability!("test_finit_and_delete_module", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
let f = File::open(kmod_path).expect("unable to open kernel module");
finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty())
.expect("unable to load kernel module");
delete_module(
&CString::new(kmod_name).unwrap(),
DeleteModuleFlags::empty(),
).expect("unable to unload kernel module");
}
#[test]
fn test_finit_and_delete_module_with_params() {
require_capability!("test_finit_and_delete_module_with_params", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
let f = File::open(kmod_path).expect("unable to open kernel module");
finit_module(
&f,
&CString::new("who=Rust number=2018").unwrap(),
ModuleInitFlags::empty(),
).expect("unable to load kernel module");
delete_module(
&CString::new(kmod_name).unwrap(),
DeleteModuleFlags::empty(),
).expect("unable to unload kernel module");
}
#[test]
fn test_init_and_delete_module() {
require_capability!("test_init_and_delete_module", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
let mut f = File::open(kmod_path).expect("unable to open kernel module");
let mut contents: Vec<u8> = Vec::new();
f.read_to_end(&mut contents)
.expect("unable to read kernel module content to buffer");
init_module(&contents, &CString::new("").unwrap()).expect("unable to load kernel module");
delete_module(
&CString::new(kmod_name).unwrap(),
DeleteModuleFlags::empty(),
).expect("unable to unload kernel module");
}
#[test]
fn test_init_and_delete_module_with_params() {
require_capability!("test_init_and_delete_module_with_params", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
let mut f = File::open(kmod_path).expect("unable to open kernel module");
let mut contents: Vec<u8> = Vec::new();
f.read_to_end(&mut contents)
.expect("unable to read kernel module content to buffer");
init_module(&contents, &CString::new("who=Nix number=2015").unwrap())
.expect("unable to load kernel module");
delete_module(
&CString::new(kmod_name).unwrap(),
DeleteModuleFlags::empty(),
).expect("unable to unload kernel module");
}
#[test]
fn test_finit_module_invalid() {
require_capability!("test_finit_module_invalid", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let kmod_path = "/dev/zero";
let f = File::open(kmod_path).expect("unable to open kernel module");
let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty());
assert_eq!(result.unwrap_err(), Errno::EINVAL);
}
#[test]
fn test_finit_module_twice_and_delete_module() {
require_capability!("test_finit_module_twice_and_delete_module", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
let f = File::open(kmod_path).expect("unable to open kernel module");
finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty())
.expect("unable to load kernel module");
let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty());
assert_eq!(result.unwrap_err(), Errno::EEXIST);
delete_module(
&CString::new(kmod_name).unwrap(),
DeleteModuleFlags::empty(),
).expect("unable to unload kernel module");
}
#[test]
fn test_delete_module_not_loaded() {
require_capability!("test_delete_module_not_loaded", CAP_SYS_MODULE);
let _m0 = crate::KMOD_MTX.lock().expect("Mutex got poisoned by another test");
let _m1 = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test");
let result = delete_module(&CString::new("hello").unwrap(), DeleteModuleFlags::empty());
assert_eq!(result.unwrap_err(), Errno::ENOENT);
}

View file

@ -0,0 +1,236 @@
mod common;
// Impelmentation note: to allow unprivileged users to run it, this test makes
// use of user and mount namespaces. On systems that allow unprivileged user
// namespaces (Linux >= 3.8 compiled with CONFIG_USER_NS), the test should run
// without root.
#[cfg(target_os = "linux")]
mod test_mount {
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::fs::PermissionsExt;
use std::process::{self, Command};
use libc::{EACCES, EROFS};
use nix::errno::Errno;
use nix::mount::{mount, umount, MsFlags};
use nix::sched::{unshare, CloneFlags};
use nix::sys::stat::{self, Mode};
use nix::unistd::getuid;
static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
exit 23";
const EXPECTED_STATUS: i32 = 23;
const NONE: Option<&'static [u8]> = None;
#[allow(clippy::bind_instead_of_map)] // False positive
pub fn test_mount_tmpfs_without_flags_allows_rwx() {
let tempdir = tempfile::tempdir().unwrap();
mount(NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::empty(),
NONE)
.unwrap_or_else(|e| panic!("mount failed: {}", e));
let test_path = tempdir.path().join("test");
// Verify write.
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(&test_path)
.or_else(|e|
if Errno::from_i32(e.raw_os_error().unwrap()) == Errno::EOVERFLOW {
// Skip tests on certain Linux kernels which have a bug
// regarding tmpfs in namespaces.
// Ubuntu 14.04 and 16.04 are known to be affected; 16.10 is
// not. There is no legitimate reason for open(2) to return
// EOVERFLOW here.
// https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
let stderr = io::stderr();
let mut handle = stderr.lock();
writeln!(handle, "Buggy Linux kernel detected. Skipping test.")
.unwrap();
process::exit(0);
} else {
panic!("open failed: {}", e);
}
)
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {}", e));
// Verify read.
let mut buf = Vec::new();
File::open(&test_path)
.and_then(|mut f| f.read_to_end(&mut buf))
.unwrap_or_else(|e| panic!("read failed: {}", e));
assert_eq!(buf, SCRIPT_CONTENTS);
// Verify execute.
assert_eq!(EXPECTED_STATUS,
Command::new(&test_path)
.status()
.unwrap_or_else(|e| panic!("exec failed: {}", e))
.code()
.unwrap_or_else(|| panic!("child killed by signal")));
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
}
pub fn test_mount_rdonly_disallows_write() {
let tempdir = tempfile::tempdir().unwrap();
mount(NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::MS_RDONLY,
NONE)
.unwrap_or_else(|e| panic!("mount failed: {}", e));
// EROFS: Read-only file system
assert_eq!(EROFS as i32,
File::create(tempdir.path().join("test")).unwrap_err().raw_os_error().unwrap());
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
}
pub fn test_mount_noexec_disallows_exec() {
let tempdir = tempfile::tempdir().unwrap();
mount(NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::MS_NOEXEC,
NONE)
.unwrap_or_else(|e| panic!("mount failed: {}", e));
let test_path = tempdir.path().join("test");
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(&test_path)
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {}", e));
// Verify that we cannot execute despite a+x permissions being set.
let mode = stat::Mode::from_bits_truncate(fs::metadata(&test_path)
.map(|md| md.permissions().mode())
.unwrap_or_else(|e| {
panic!("metadata failed: {}", e)
}));
assert!(mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
"{:?} did not have execute permissions",
&test_path);
// EACCES: Permission denied
assert_eq!(EACCES as i32,
Command::new(&test_path).status().unwrap_err().raw_os_error().unwrap());
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
}
pub fn test_mount_bind() {
let tempdir = tempfile::tempdir().unwrap();
let file_name = "test";
{
let mount_point = tempfile::tempdir().unwrap();
mount(Some(tempdir.path()),
mount_point.path(),
NONE,
MsFlags::MS_BIND,
NONE)
.unwrap_or_else(|e| panic!("mount failed: {}", e));
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(mount_point.path().join(file_name))
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {}", e));
umount(mount_point.path()).unwrap_or_else(|e| panic!("umount failed: {}", e));
}
// Verify the file written in the mount shows up in source directory, even
// after unmounting.
let mut buf = Vec::new();
File::open(tempdir.path().join(file_name))
.and_then(|mut f| f.read_to_end(&mut buf))
.unwrap_or_else(|e| panic!("read failed: {}", e));
assert_eq!(buf, SCRIPT_CONTENTS);
}
pub fn setup_namespaces() {
// Hold on to the uid in the parent namespace.
let uid = getuid();
unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER).unwrap_or_else(|e| {
let stderr = io::stderr();
let mut handle = stderr.lock();
writeln!(handle,
"unshare failed: {}. Are unprivileged user namespaces available?",
e).unwrap();
writeln!(handle, "mount is not being tested").unwrap();
// Exit with success because not all systems support unprivileged user namespaces, and
// that's not what we're testing for.
process::exit(0);
});
// Map user as uid 1000.
fs::OpenOptions::new()
.write(true)
.open("/proc/self/uid_map")
.and_then(|mut f| f.write(format!("1000 {} 1\n", uid).as_bytes()))
.unwrap_or_else(|e| panic!("could not write uid map: {}", e));
}
}
// Test runner
/// Mimic normal test output (hackishly).
#[cfg(target_os = "linux")]
macro_rules! run_tests {
( $($test_fn:ident),* ) => {{
println!();
$(
print!("test test_mount::{} ... ", stringify!($test_fn));
$test_fn();
println!("ok");
)*
println!();
}}
}
#[cfg(target_os = "linux")]
fn main() {
use test_mount::{setup_namespaces, test_mount_tmpfs_without_flags_allows_rwx,
test_mount_rdonly_disallows_write, test_mount_noexec_disallows_exec,
test_mount_bind};
skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351");
setup_namespaces();
run_tests!(test_mount_tmpfs_without_flags_allows_rwx,
test_mount_rdonly_disallows_write,
test_mount_noexec_disallows_exec,
test_mount_bind);
}
#[cfg(not(target_os = "linux"))]
fn main() {}

Some files were not shown because too many files have changed in this diff Show more