diff options
author | David LeGare <legare@google.com> | 2022-03-16 20:38:46 +0000 |
---|---|---|
committer | David LeGare <legare@google.com> | 2022-03-16 20:38:46 +0000 |
commit | d1a942bdc440f245e373c350a2bc7b2a88099600 (patch) | |
tree | fb2abcd53c7bec252defd92f2b61bb3ddff8949d | |
parent | 5a6b9aece57c94d34ef08f8a543142ef9a0dbbac (diff) | |
download | nix-d1a942bdc440f245e373c350a2bc7b2a88099600.tar.gz |
Update nix to 0.23.1
Test: atest
Change-Id: I72c986bfa9c7439aa0225ce31f029b542b4c0ba7
87 files changed, 2791 insertions, 1132 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index fb7ddb9..90eb8fa 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,6 @@ { "git": { - "sha1": "b29a85165b0bf494974642cc6ba58a924f143948" - } -} + "sha1": "afba7c5a33dd4fd62b047e64089487cf822ccec2" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 3d432e0..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -/CHANGELOG.md merge=union @@ -26,7 +26,7 @@ rust_library { host_supported: true, crate_name: "nix", cargo_env_compat: true, - cargo_pkg_version: "0.22.1", + cargo_pkg_version: "0.23.1", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5367d7a..77d5b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,132 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## [0.22.1] - 13 August 2021 +## [0.23.1] - 2021-12-16 + ### Added ### Changed + +- Relaxed the bitflags requirement from 1.3.1 to 1.1. This partially reverts + #1492. From now on, the MSRV is not guaranteed to work with all versions of + all dependencies, just with some version of all dependencies. + (#[1607](https://github.com/nix-rust/nix/pull/1607)) + ### Fixed -- Locked bitflags to < 1.3.0 to fix the build with rust < 1.46.0. +- Fixed soundness issues in `FdSet::insert`, `FdSet::remove`, and + `FdSet::contains` involving file descriptors outside of the range + `0..FD_SETSIZE`. + (#[1575](https://github.com/nix-rust/nix/pull/1575)) + +### Removed + +## [0.23.0] - 2021-09-28 +### Added + +- Added the `LocalPeerCred` sockopt. + (#[1482](https://github.com/nix-rust/nix/pull/1482)) +- Added `TimeSpec::from_duration` and `TimeSpec::from_timespec` + (#[1465](https://github.com/nix-rust/nix/pull/1465)) +- Added `IPV6_V6ONLY` sockopt. + (#[1470](https://github.com/nix-rust/nix/pull/1470)) +- Added `impl From<User> for libc::passwd` trait implementation to convert a `User` + into a `libc::passwd`. Consumes the `User` struct to give ownership over + the member pointers. + (#[1471](https://github.com/nix-rust/nix/pull/1471)) +- Added `pthread_kill`. + (#[1472](https://github.com/nix-rust/nix/pull/1472)) +- Added `mknodat`. + (#[1473](https://github.com/nix-rust/nix/pull/1473)) +- Added `setrlimit` and `getrlimit`. + (#[1302](https://github.com/nix-rust/nix/pull/1302)) +- Added `ptrace::interrupt` method for platforms that support `PTRACE_INTERRUPT` + (#[1422](https://github.com/nix-rust/nix/pull/1422)) +- Added `IP6T_SO_ORIGINAL_DST` sockopt. + (#[1490](https://github.com/nix-rust/nix/pull/1490)) +- Added the `PTRACE_EVENT_STOP` variant to the `sys::ptrace::Event` enum + (#[1335](https://github.com/nix-rust/nix/pull/1335)) +- Exposed `SockAddr::from_raw_sockaddr` + (#[1447](https://github.com/nix-rust/nix/pull/1447)) +- Added `TcpRepair` + (#[1503](https://github.com/nix-rust/nix/pull/1503)) +- Enabled `pwritev` and `preadv` for more operating systems. + (#[1511](https://github.com/nix-rust/nix/pull/1511)) +- Added support for `TCP_MAXSEG` TCP Maximum Segment Size socket options + (#[1292](https://github.com/nix-rust/nix/pull/1292)) +- Added `Ipv4RecvErr` and `Ipv6RecvErr` sockopts and associated control messages. + (#[1514](https://github.com/nix-rust/nix/pull/1514)) +- Added `AsRawFd` implementation on `PollFd`. + (#[1516](https://github.com/nix-rust/nix/pull/1516)) +- Added `Ipv4Ttl` and `Ipv6Ttl` sockopts. + (#[1515](https://github.com/nix-rust/nix/pull/1515)) +- Added `MAP_EXCL`, `MAP_ALIGNED_SUPER`, and `MAP_CONCEAL` mmap flags, and + exposed `MAP_ANONYMOUS` for all operating systems. + (#[1522](https://github.com/nix-rust/nix/pull/1522)) + (#[1525](https://github.com/nix-rust/nix/pull/1525)) + (#[1531](https://github.com/nix-rust/nix/pull/1531)) + (#[1534](https://github.com/nix-rust/nix/pull/1534)) +- Added read/write accessors for 'events' on `PollFd`. + (#[1517](https://github.com/nix-rust/nix/pull/1517)) + +### Changed + +- `FdSet::{contains, highest, fds}` no longer require a mutable reference. + (#[1464](https://github.com/nix-rust/nix/pull/1464)) +- `User::gecos` and corresponding `libc::passwd::pw_gecos` are supported on + 64-bit Android, change conditional compilation to include the field in + 64-bit Android builds + (#[1471](https://github.com/nix-rust/nix/pull/1471)) +- `eventfd`s are supported on Android, change conditional compilation to + include `sys::eventfd::eventfd` and `sys::eventfd::EfdFlags`for Android + builds. + (#[1481](https://github.com/nix-rust/nix/pull/1481)) +- Most enums that come from C, for example `Errno`, are now marked as + `#[non_exhaustive]`. + (#[1474](https://github.com/nix-rust/nix/pull/1474)) +- Many more functions, mostly contructors, are now `const`. + (#[1476](https://github.com/nix-rust/nix/pull/1476)) + (#[1492](https://github.com/nix-rust/nix/pull/1492)) +- `sys::event::KEvent::filter` now returns a `Result` instead of being + infalliable. The only cases where it will now return an error are cases + where it previously would've had undefined behavior. + (#[1484](https://github.com/nix-rust/nix/pull/1484)) +- Minimum supported Rust version is now 1.46.0. + ([#1492](https://github.com/nix-rust/nix/pull/1492)) +- Rework `UnixAddr` to encapsulate internals better in order to fix soundness + issues. No longer allows creating a `UnixAddr` from a raw `sockaddr_un`. + ([#1496](https://github.com/nix-rust/nix/pull/1496)) +- Raised bitflags to 1.3.0 and the MSRV to 1.46.0. + ([#1492](https://github.com/nix-rust/nix/pull/1492)) + +### Fixed + +- `posix_fadvise` now returns errors in the conventional way, rather than as a + non-zero value in `Ok()`. + (#[1538](https://github.com/nix-rust/nix/pull/1538)) +- Added more errno definitions for better backwards compatibility with + Nix 0.21.0. + (#[1467](https://github.com/nix-rust/nix/pull/1467)) +- Fixed potential undefined behavior in `Signal::try_from` on some platforms. + (#[1484](https://github.com/nix-rust/nix/pull/1484)) +- Fixed buffer overflow in `unistd::getgrouplist`. + (#[1545](https://github.com/nix-rust/nix/pull/1545)) + ### Removed - Removed a couple of termios constants on redox that were never actually supported. (#[1483](https://github.com/nix-rust/nix/pull/1483)) +- Removed `nix::sys::signal::NSIG`. It was of dubious utility, and not correct + for all platforms. + (#[1484](https://github.com/nix-rust/nix/pull/1484)) +- Removed support for 32-bit Apple targets, since they've been dropped by both + Rustc and Xcode. + (#[1492](https://github.com/nix-rust/nix/pull/1492)) +- Deprecated `SockAddr/InetAddr::to_str` in favor of `ToString::to_string` + (#[1495](https://github.com/nix-rust/nix/pull/1495)) +- Removed `SigevNotify` on OpenBSD and Redox. + (#[1511](https://github.com/nix-rust/nix/pull/1511)) ## [0.22.0] - 9 July 2021 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 55990c4..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,114 +0,0 @@ -# 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 diff --git a/CONVENTIONS.md b/CONVENTIONS.md deleted file mode 100644 index 2461085..0000000 --- a/CONVENTIONS.md +++ /dev/null @@ -1,86 +0,0 @@ -# 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 @@ -11,10 +11,11 @@ [package] edition = "2018" +rust-version = "1.46" name = "nix" -version = "0.22.1" +version = "0.23.1" authors = ["The nix-rust Project Developers"] -exclude = ["/.gitignore", "/.cirrus.yml", "/ci/*", "/Cross.toml", "/RELEASE_PROCEDURE.md", "/bors.toml"] +include = ["src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] description = "Rust friendly bindings to *nix APIs" categories = ["os::unix-apis"] license = "MIT" @@ -47,13 +48,13 @@ harness = false name = "test-ptymaster-drop" path = "test/test_ptymaster_drop.rs" [dependencies.bitflags] -version = ">= 1.1.0, < 1.3.0" +version = "1.1" [dependencies.cfg-if] version = "1.0" [dependencies.libc] -version = "0.2.99" +version = "0.2.102" features = ["extra_traits"] [dev-dependencies.assert-impl] version = "0.1" @@ -61,6 +62,9 @@ version = "0.1" [dev-dependencies.lazy_static] version = "1.2" +[dev-dependencies.parking_lot] +version = "0.11.2" + [dev-dependencies.rand] version = "0.8" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 7a527d3..d2ca8ee 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -2,19 +2,13 @@ name = "nix" description = "Rust friendly bindings to *nix APIs" edition = "2018" -version = "0.22.1" +version = "0.23.1" +rust-version = "1.46" authors = ["The nix-rust Project Developers"] repository = "https://github.com/nix-rust/nix" license = "MIT" categories = ["os::unix-apis"] -exclude = [ - "/.gitignore", - "/.cirrus.yml", - "/ci/*", - "/Cross.toml", - "/RELEASE_PROCEDURE.md", - "/bors.toml" -] +include = ["src/**/*", "test/**/*", "LICENSE", "README.md", "CHANGELOG.md"] [package.metadata.docs.rs] targets = [ @@ -32,8 +26,8 @@ targets = [ ] [dependencies] -libc = { version = "0.2.99", features = [ "extra_traits" ] } -bitflags = ">= 1.1.0, < 1.3.0" +libc = { version = "0.2.102", features = [ "extra_traits" ] } +bitflags = "1.1" cfg-if = "1.0" [target.'cfg(not(target_os = "redox"))'.dependencies] @@ -45,6 +39,7 @@ cc = "1" [dev-dependencies] assert-impl = "0.1" lazy_static = "1.2" +parking_lot = "0.11.2" rand = "0.8" tempfile = "3.2.0" semver = "1.0.0" @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/nix/nix-0.22.1.crate" + value: "https://static.crates.io/crates/nix/nix-0.23.1.crate" } - version: "0.22.1" + version: "0.23.1" license_type: NOTICE last_upgrade_date { - year: 2021 - month: 9 - day: 22 + year: 2022 + month: 3 + day: 16 } } @@ -1,7 +1,7 @@ # 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://meritbadge.herokuapp.com/nix)](https://crates.io/crates/nix) +[![crates.io](https://img.shields.io/crates/v/nix.svg)](https://crates.io/crates/nix) [Documentation (Releases)](https://docs.rs/nix/) @@ -68,33 +68,26 @@ Tier 2: * aarch64-linux-android * arm-linux-androideabi * arm-unknown-linux-musleabi - * armv7-apple-ios * armv7-linux-androideabi - * armv7s-apple-ios - * i386-apple-ios - * i686-apple-darwin * 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-redox + * x86_64-unknown-dragonfly * x86_64-unknown-linux-gnux32 + * x86_64-unknown-openbsd + * x86_64-unknown-redox -## Usage - -`nix` requires Rust 1.41.0 or newer. - -To use `nix`, add this to your `Cargo.toml`: +## Minimum Supported Rust Version (MSRV) -```toml -[dependencies] -nix = "0.22.1" -``` +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 diff --git a/patches/0001-Allow-android-compiled-binaries-to-use-timerfd.patch b/patches/0001-Allow-android-compiled-binaries-to-use-timerfd.patch deleted file mode 100644 index e164a3c..0000000 --- a/patches/0001-Allow-android-compiled-binaries-to-use-timerfd.patch +++ /dev/null @@ -1,26 +0,0 @@ -From e09a4f4e8661e8036819deb97dc1d9fcf2a49d2b Mon Sep 17 00:00:00 2001 -From: Zach Johnson <zachoverflow@google.com> -Date: Tue, 17 Nov 2020 11:05:57 -0800 -Subject: [PATCH] Allow android compiled binaries to use timerfd - -Bug: 171749953 -Test: compile -Change-Id: Iaf16615cf834df02537e3b569ab08dd98a497a70 ---- - src/sys/mod.rs | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/sys/mod.rs b/src/sys/mod.rs -index bf7f541..b8b9e6f 100644 ---- a/src/sys/mod.rs -+++ b/src/sys/mod.rs -@@ -103,5 +103,5 @@ pub mod wait; - #[cfg(any(target_os = "android", target_os = "linux"))] - pub mod inotify; - --#[cfg(target_os = "linux")] -+#[cfg(any(target_os = "android", target_os = "linux"))] - pub mod timerfd; --- -2.29.2.299.gdc1121823c-goog - @@ -84,7 +84,7 @@ impl AsRawFd for Dir { 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(Error::from(Errno::EBADF)) { + if !std::thread::panicking() && e == Err(Errno::EBADF) { panic!("Closing an invalid file descriptor!"); }; } @@ -211,6 +211,7 @@ impl Entry { 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) } @@ -1,3 +1,4 @@ +//! Environment variables use cfg_if::cfg_if; use std::fmt; @@ -38,7 +39,6 @@ impl std::error::Error for ClearEnvError {} /// 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", @@ -47,13 +47,13 @@ pub unsafe fn clearenv() -> std::result::Result<(), ClearEnvError> { target_os = "linux", target_os = "android", target_os = "emscripten"))] { - ret = libc::clearenv(); + let ret = libc::clearenv(); } else { use std::env; for (name, _) in env::vars_os() { env::remove_var(name); } - ret = 0; + let ret = 0; } } diff --git a/src/errno.rs b/src/errno.rs index 00e2014..3da246e 100644 --- a/src/errno.rs +++ b/src/errno.rs @@ -63,7 +63,7 @@ impl Errno { since = "0.22.0", note = "It's a no-op now; just delete it." )] - pub fn as_errno(self) -> Option<Self> { + pub const fn as_errno(self) -> Option<Self> { Some(self) } @@ -72,8 +72,9 @@ impl Errno { since = "0.22.0", note = "It's a no-op now; just delete it." )] + #[allow(clippy::wrong_self_convention)] // False positive pub fn from_errno(errno: Errno) -> Error { - Error::from(errno) + errno } /// Create a new invalid argument error (`EINVAL`) @@ -81,7 +82,7 @@ impl Errno { since = "0.22.0", note = "Use Errno::EINVAL instead" )] - pub fn invalid_argument() -> Error { + pub const fn invalid_argument() -> Error { Errno::EINVAL } @@ -93,7 +94,7 @@ impl Errno { desc(self) } - pub fn from_i32(err: i32) -> Errno { + pub const fn from_i32(err: i32) -> Errno { from_i32(err) } @@ -103,6 +104,7 @@ impl Errno { /// Returns `Ok(value)` if it does not contain the sentinel value. This /// should not be used when `-1` is not the errno sentinel value. + #[inline] pub fn result<S: ErrnoSentinel + PartialEq<S>>(value: S) -> Result<S> { if value == S::sentinel() { Err(Self::last()) @@ -122,7 +124,7 @@ impl Errno { )] #[allow(non_snake_case)] #[inline] - pub fn Sys(errno: Errno) -> Error { + pub const fn Sys(errno: Errno) -> Error { errno } } @@ -768,6 +770,7 @@ fn desc(errno: Errno) -> &'static str { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -905,13 +908,29 @@ mod consts { EHWPOISON = libc::EHWPOISON, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EDEADLOCK instead" + )] + pub const EDEADLOCK: Errno = Errno::EDEADLK; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ENOTSUP instead" + )] + pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; + impl Errno { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; pub const EDEADLOCK: Errno = Errno::EDEADLK; pub const ENOTSUP: Errno = Errno::EOPNOTSUPP; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -1057,6 +1076,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -1167,13 +1187,29 @@ mod consts { EQFULL = libc::EQFULL, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ELAST instead" + )] + pub const ELAST: Errno = Errno::EQFULL; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EDEADLOCK instead" + )] + pub const EDEADLOCK: Errno = Errno::EDEADLK; + impl Errno { pub const ELAST: Errno = Errno::EQFULL; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; pub const EDEADLOCK: Errno = Errno::EDEADLK; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -1292,6 +1328,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -1392,6 +1429,27 @@ mod consts { EOWNERDEAD = libc::EOWNERDEAD, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ELAST instead" + )] + pub const ELAST: Errno = Errno::EOWNERDEAD; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EDEADLOCK instead" + )] + pub const EDEADLOCK: Errno = Errno::EDEADLK; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EOPNOTSUPP instead" + )] + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + impl Errno { pub const ELAST: Errno = Errno::EOWNERDEAD; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; @@ -1399,7 +1457,7 @@ mod consts { pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -1509,6 +1567,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -1607,6 +1666,27 @@ mod consts { EASYNC = libc::EASYNC, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ELAST instead" + )] + pub const ELAST: Errno = Errno::EASYNC; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EDEADLOCK instead" + )] + pub const EDEADLOCK: Errno = Errno::EDEADLK; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EOPNOTSUPP instead" + )] + pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; + impl Errno { pub const ELAST: Errno = Errno::EASYNC; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; @@ -1614,7 +1694,7 @@ mod consts { pub const EOPNOTSUPP: Errno = Errno::ENOTSUP; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -1722,6 +1802,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -1821,12 +1902,23 @@ mod consts { EPROTO = libc::EPROTO, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ELAST instead" + )] + pub const ELAST: Errno = Errno::ENOTSUP; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + impl Errno { pub const ELAST: Errno = Errno::ENOTSUP; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -1934,6 +2026,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -2034,12 +2127,23 @@ mod consts { EPROTO = libc::EPROTO, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ELAST instead" + )] + pub const ELAST: Errno = Errno::ENOTSUP; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + impl Errno { pub const ELAST: Errno = Errno::ENOTSUP; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -2148,6 +2252,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -2237,11 +2342,17 @@ mod consts { EPROTO = libc::EPROTO, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + impl Errno { pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { @@ -2339,6 +2450,7 @@ mod consts { mod consts { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(i32)] + #[non_exhaustive] pub enum Errno { UnknownErrno = 0, EPERM = libc::EPERM, @@ -2464,12 +2576,23 @@ mod consts { ESTALE = libc::ESTALE, } + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::ELAST instead" + )] + pub const ELAST: Errno = Errno::ELAST; + #[deprecated( + since = "0.22.1", + note = "use nix::errno::Errno::EWOULDBLOCK instead" + )] + pub const EWOULDBLOCK: Errno = Errno::EAGAIN; + impl Errno { pub const ELAST: Errno = Errno::ESTALE; pub const EWOULDBLOCK: Errno = Errno::EAGAIN; } - pub fn from_i32(e: i32) -> Errno { + pub const fn from_i32(e: i32) -> Errno { use self::Errno::*; match e { diff --git a/src/fcntl.rs b/src/fcntl.rs index f8f1372..dd8e59a 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -325,7 +325,7 @@ fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result Some(next_size) => try_size = next_size, // It's absurd that this would happen, but handle it sanely // anyway. - None => break Err(super::Error::from(Errno::ENAMETOOLONG)), + None => break Err(Errno::ENAMETOOLONG), } } } @@ -374,6 +374,7 @@ libc_bitflags!( #[cfg(not(target_os = "redox"))] #[derive(Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] pub enum FcntlArg<'a> { F_DUPFD(RawFd), F_DUPFD_CLOEXEC(RawFd), @@ -405,6 +406,7 @@ pub enum FcntlArg<'a> { #[cfg(target_os = "redox")] #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +#[non_exhaustive] pub enum FcntlArg { F_DUPFD(RawFd), F_DUPFD_CLOEXEC(RawFd), @@ -454,6 +456,7 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> { } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] pub enum FlockArg { LockShared, LockExclusive, @@ -643,12 +646,12 @@ pub fn fallocate( ))] mod posix_fadvise { use crate::errno::Errno; - use libc; use std::os::unix::io::RawFd; use crate::Result; libc_enum! { #[repr(i32)] + #[non_exhaustive] pub enum PosixFadviseAdvice { POSIX_FADV_NORMAL, POSIX_FADV_SEQUENTIAL, @@ -664,9 +667,14 @@ mod posix_fadvise { offset: libc::off_t, len: libc::off_t, advice: PosixFadviseAdvice, - ) -> Result<libc::c_int> { + ) -> Result<()> { let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) }; - Errno::result(res) + + if res == 0 { + Ok(()) + } else { + Err(Errno::from_i32(res)) + } } } @@ -683,6 +691,6 @@ pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Resu match Errno::result(res) { Err(err) => Err(err), Ok(0) => Ok(()), - Ok(errno) => Err(crate::Error::from(Errno::from_i32(errno))), + Ok(errno) => Err(Errno::from_i32(errno)), } } diff --git a/src/features.rs b/src/features.rs index bcda45d..ed80fd7 100644 --- a/src/features.rs +++ b/src/features.rs @@ -94,22 +94,28 @@ mod os { } } -#[cfg(any(target_os = "illumos"))] +#[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 fn socket_atomic_cloexec() -> bool { + pub const fn socket_atomic_cloexec() -> bool { true } } -#[cfg(any(target_os = "macos", target_os = "freebsd", - target_os = "dragonfly", target_os = "ios", - target_os = "openbsd", target_os = "netbsd", - target_os = "redox", target_os = "fuchsia", +#[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 fn socket_atomic_cloexec() -> bool { + pub const fn socket_atomic_cloexec() -> bool { false } } diff --git a/src/kmod.rs b/src/kmod.rs index 89e577a..c42068c 100644 --- a/src/kmod.rs +++ b/src/kmod.rs @@ -2,7 +2,6 @@ //! //! For more details see -use libc; use std::ffi::CStr; use std::os::unix::io::AsRawFd; @@ -5,15 +5,13 @@ #![crate_name = "nix"] #![cfg(unix)] #![allow(non_camel_case_types)] -// latest bitflags triggers a rustc bug with cross-crate macro expansions causing dead_code -// warnings even though the macro expands into something with allow(dead_code) -#![allow(dead_code)] #![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; @@ -23,13 +21,14 @@ pub use libc; // Public crates #[cfg(not(target_os = "redox"))] +#[allow(missing_docs)] pub mod dir; pub mod env; +#[allow(missing_docs)] pub mod errno; -#[deny(missing_docs)] pub mod features; +#[allow(missing_docs)] pub mod fcntl; -#[deny(missing_docs)] #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", @@ -42,6 +41,7 @@ pub mod fcntl; 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", @@ -52,23 +52,24 @@ pub mod mount; target_os = "fushsia", target_os = "linux", target_os = "netbsd"))] +#[allow(missing_docs)] pub mod mqueue; -#[deny(missing_docs)] #[cfg(not(target_os = "redox"))] pub mod net; -#[deny(missing_docs)] pub mod poll; -#[deny(missing_docs)] #[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; /* @@ -101,11 +102,17 @@ pub type Result<T> = result::Result<T, Errno>; /// 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; } @@ -153,7 +160,7 @@ impl NixPath for CStr { where F: FnOnce(&CStr) -> T { // Equivalence with the [u8] impl. if self.len() >= PATH_MAX as usize { - return Err(Error::from(Errno::ENAMETOOLONG)) + return Err(Errno::ENAMETOOLONG) } Ok(f(self)) @@ -174,11 +181,11 @@ impl NixPath for [u8] { let mut buf = [0u8; PATH_MAX as usize]; if self.len() >= PATH_MAX as usize { - return Err(Error::from(Errno::ENAMETOOLONG)) + return Err(Errno::ENAMETOOLONG) } match self.iter().position(|b| *b == 0) { - Some(_) => Err(Error::from(Errno::EINVAL)), + Some(_) => Err(Errno::EINVAL), None => { unsafe { // TODO: Replace with bytes::copy_memory. rust-lang/rust#24028 diff --git a/src/macros.rs b/src/macros.rs index 7d6ac8d..3ccbfdd 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -83,9 +83,9 @@ macro_rules! libc_bitflags { macro_rules! libc_enum { // Exit rule. (@make_enum + name: $BitFlags:ident, { $v:vis - name: $BitFlags:ident, attrs: [$($attrs:tt)*], entries: [$($entries:tt)*], } @@ -97,38 +97,98 @@ macro_rules! libc_enum { } }; + // 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 - name: $BitFlags:ident, attrs: $attrs:tt, + from_type: $repr:path, }, - $entries:tt; + $entries:tt, + $try_froms:tt; ) => { libc_enum! { @make_enum + name: $BitFlags, { $v - name: $BitFlags, attrs: $attrs, entries: $entries, + from_type: $repr, + try_froms: $try_froms } } }; // Munch an attr. (@accumulate_entries + name: $BitFlags:ident, $prefix:tt, - [$($entries:tt)*]; + [$($entries:tt)*], + [$($try_froms:tt)*]; #[$attr:meta] $($tail:tt)* ) => { libc_enum! { @accumulate_entries + name: $BitFlags, $prefix, [ $($entries)* #[$attr] + ], + [ + $($try_froms)* + #[$attr] ]; $($tail)* } @@ -136,32 +196,47 @@ macro_rules! libc_enum { // Munch last ident if not followed by a comma. (@accumulate_entries + name: $BitFlags:ident, $prefix:tt, - [$($entries: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)*]; - $entry:ident, $($tail: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)* } @@ -169,16 +244,24 @@ macro_rules! libc_enum { // Munch an ident and cast it to the given type; covers terminating comma. (@accumulate_entries + name: $BitFlags:ident, $prefix:tt, - [$($entries:tt)*]; - $entry:ident as $ty:ty, $($tail: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)* } @@ -193,11 +276,34 @@ macro_rules! libc_enum { ) => { 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 - name: $BitFlags, attrs: [$(#[$attr])*], + from_type: $repr, }, + [], []; $($vals)* } diff --git a/src/mount/bsd.rs b/src/mount/bsd.rs index 0144908..627bfa5 100644 --- a/src/mount/bsd.rs +++ b/src/mount/bsd.rs @@ -111,7 +111,7 @@ impl NmountError { } /// Returns the inner [`Error`] - pub fn error(&self) -> Error { + pub const fn error(&self) -> Error { self.errno } @@ -347,6 +347,7 @@ impl<'a> Nmount<'a> { self } + /// Create a new `Nmount` struct with no options pub fn new() -> Self { Self::default() } @@ -382,7 +383,7 @@ impl<'a> Nmount<'a> { Some(CStr::from_bytes_with_nul(sl).unwrap()) } }; - Err(NmountError::new(error.into(), errmsg)) + Err(NmountError::new(error, errmsg)) } } } @@ -396,7 +397,7 @@ impl<'a> Drop for Nmount<'a> { // Free the owned string. Safe because we recorded ownership, // and Nmount does not implement Clone. unsafe { - CString::from_raw(iov.0.iov_base as *mut c_char); + drop(CString::from_raw(iov.0.iov_base as *mut c_char)); } } } diff --git a/src/mount/linux.rs b/src/mount/linux.rs index edb8afb..4cb2fa5 100644 --- a/src/mount/linux.rs +++ b/src/mount/linux.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use libc::{self, c_ulong, c_int}; use crate::{Result, NixPath}; use crate::errno::Errno; diff --git a/src/mount/mod.rs b/src/mount/mod.rs index 8538bf3..14bf2a9 100644 --- a/src/mount/mod.rs +++ b/src/mount/mod.rs @@ -1,3 +1,4 @@ +//! Mount file systems #[cfg(any(target_os = "android", target_os = "linux"))] mod linux; diff --git a/src/mqueue.rs b/src/mqueue.rs index 3e49480..34fd802 100644 --- a/src/mqueue.rs +++ b/src/mqueue.rs @@ -59,7 +59,7 @@ impl MqAttr { } } - pub fn flags(&self) -> mq_attr_member_t { + pub const fn flags(&self) -> mq_attr_member_t { self.mq_attr.mq_flags } } @@ -155,6 +155,7 @@ pub fn mq_setattr(mqd: mqd_t, newattr: &MqAttr) -> Result<MqAttr> { /// 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()), diff --git a/src/poll.rs b/src/poll.rs index 0c3f208..8556c1b 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -3,7 +3,7 @@ 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::RawFd; +use std::os::unix::io::{AsRawFd, RawFd}; use crate::Result; use crate::errno::Errno; @@ -25,7 +25,7 @@ pub struct PollFd { impl PollFd { /// Creates a new `PollFd` specifying the events of interest /// for a given file descriptor. - pub fn new(fd: RawFd, events: PollFlags) -> PollFd { + pub const fn new(fd: RawFd, events: PollFlags) -> PollFd { PollFd { pollfd: libc::pollfd { fd, @@ -35,10 +35,27 @@ impl PollFd { } } - /// Returns the events that occured in the last call to `poll` or `ppoll`. + /// 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! { @@ -10,7 +10,7 @@ use std::os::unix::prelude::*; use crate::sys::termios::Termios; use crate::unistd::{self, ForkResult, Pid}; -use crate::{Result, Error, fcntl}; +use crate::{Result, fcntl}; use crate::errno::Errno; /// Representation of a master/slave pty pair @@ -99,7 +99,7 @@ impl io::Write for PtyMaster { #[inline] pub fn grantpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 { - return Err(Error::from(Errno::last())); + return Err(Errno::last()); } Ok(()) @@ -145,7 +145,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> { }; if fd < 0 { - return Err(Error::from(Errno::last())); + return Err(Errno::last()); } Ok(PtyMaster(fd)) @@ -171,7 +171,7 @@ pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> { pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> { let name_ptr = libc::ptsname(fd.as_raw_fd()); if name_ptr.is_null() { - return Err(Error::from(Errno::last())); + return Err(Errno::last()); } let name = CStr::from_ptr(name_ptr); @@ -195,7 +195,7 @@ pub fn ptsname_r(fd: &PtyMaster) -> Result<String> { let cname = unsafe { let cap = name_buf.capacity(); if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 { - return Err(Error::last()); + return Err(crate::Error::last()); } CStr::from_ptr(name_buf.as_ptr()) }; @@ -213,7 +213,7 @@ pub fn ptsname_r(fd: &PtyMaster) -> Result<String> { #[inline] pub fn unlockpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { - return Err(Error::from(Errno::last())); + return Err(Errno::last()); } Ok(()) diff --git a/src/sched.rs b/src/sched.rs index bf51bc1..c2dd7b8 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,3 +1,7 @@ +//! 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"))] @@ -11,38 +15,75 @@ mod sched_linux_like { use std::option::Option; use std::os::unix::io::RawFd; use crate::unistd::Pid; - use crate::{Error, Result}; + 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; - CLONE_SETTLS; - CLONE_PARENT_SETTID; - CLONE_CHILD_CLEARTID; + // 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; - CLONE_CHILD_SETTID; + // 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. @@ -68,7 +109,7 @@ mod sched_linux_like { /// `field` is the CPU id to test pub fn is_set(&self, field: usize) -> Result<bool> { if field >= CpuSet::count() { - Err(Error::from(Errno::EINVAL)) + Err(Errno::EINVAL) } else { Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) }) } @@ -78,7 +119,7 @@ mod sched_linux_like { /// `field` is the CPU id to add pub fn set(&mut self, field: usize) -> Result<()> { if field >= CpuSet::count() { - Err(Error::from(Errno::EINVAL)) + Err(Errno::EINVAL) } else { unsafe { libc::CPU_SET(field, &mut self.cpu_set); } Ok(()) @@ -89,7 +130,7 @@ mod sched_linux_like { /// `field` is the CPU id to remove pub fn unset(&mut self, field: usize) -> Result<()> { if field >= CpuSet::count() { - Err(Error::from(Errno::EINVAL)) + Err(Errno::EINVAL) } else { unsafe { libc::CPU_CLR(field, &mut self.cpu_set);} Ok(()) @@ -97,7 +138,7 @@ mod sched_linux_like { } /// Return the maximum number of CPU in CpuSet - pub fn count() -> usize { + pub const fn count() -> usize { 8 * mem::size_of::<libc::cpu_set_t>() } } @@ -212,12 +253,18 @@ mod sched_linux_like { 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()) }; diff --git a/src/sys/aio.rs b/src/sys/aio.rs index b63affb..e64a2a8 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -21,7 +21,7 @@ //! [`aio_cancel_all`](fn.aio_cancel_all.html), though the operating system may //! not support this for all filesystems and devices. -use crate::{Error, Result}; +use crate::Result; use crate::errno::Errno; use std::os::unix::io::RawFd; use libc::{c_void, off_t, size_t}; @@ -39,6 +39,7 @@ libc_enum! { /// Mode for `AioCb::fsync`. Controls whether only data or both data and /// metadata are synced. #[repr(i32)] + #[non_exhaustive] pub enum AioFsyncMode { /// do it like `fsync` O_SYNC, @@ -57,9 +58,13 @@ libc_enum! { /// given `aiocb` should be used for a read operation, a write operation, or /// ignored. Has no effect for any other aio functions. #[repr(i32)] + #[non_exhaustive] pub enum LioOpcode { + /// No operation LIO_NOP, + /// Write data as if by a call to [`AioCb::write`] LIO_WRITE, + /// Write data as if by a call to [`AioCb::read`] LIO_READ, } } @@ -143,15 +148,13 @@ impl<'a> AioCb<'a> { /// # use std::{thread, time}; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; - /// # fn main() { /// let f = tempfile().unwrap(); /// let mut aiocb = AioCb::from_fd( f.as_raw_fd(), 0, SigevNone); /// aiocb.fsync(AioFsyncMode::O_SYNC).expect("aio_fsync failed early"); - /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); /// } /// aiocb.aio_return().expect("aio_fsync failed late"); - /// # } /// ``` pub fn from_fd(fd: RawFd, prio: libc::c_int, sigev_notify: SigevNotify) -> Pin<Box<AioCb<'a>>> { @@ -224,7 +227,6 @@ impl<'a> AioCb<'a> { /// # use std::io::Write; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; - /// # fn main() { /// const INITIAL: &[u8] = b"abcdef123456"; /// const LEN: usize = 4; /// let mut rbuf = vec![0; LEN]; @@ -238,13 +240,12 @@ impl<'a> AioCb<'a> { /// SigevNotify::SigevNone, /// LioOpcode::LIO_NOP); /// aiocb.read().unwrap(); - /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); /// } /// assert_eq!(aiocb.aio_return().unwrap() as usize, LEN); /// } /// assert_eq!(rbuf, b"cdef"); - /// # } /// ``` pub fn from_mut_slice(fd: RawFd, offs: off_t, buf: &'a mut [u8], prio: libc::c_int, sigev_notify: SigevNotify, @@ -399,7 +400,6 @@ impl<'a> AioCb<'a> { /// # use std::{thread, time}; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; - /// # fn main() { /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), @@ -409,11 +409,10 @@ impl<'a> AioCb<'a> { /// SigevNotify::SigevNone, /// LioOpcode::LIO_NOP); /// aiocb.write().unwrap(); - /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); /// } /// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); - /// # } /// ``` // Note: another solution to the problem of writing const buffers would be // to genericize AioCb for both &mut [u8] and &[u8] buffers. AioCb::read @@ -475,7 +474,6 @@ impl<'a> AioCb<'a> { /// # use std::io::Write; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; - /// # fn main() { /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), @@ -487,13 +485,12 @@ impl<'a> AioCb<'a> { /// aiocb.write().unwrap(); /// let cs = aiocb.cancel().unwrap(); /// if cs == AioCancelStat::AioNotCanceled { - /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); /// } /// } /// // Must call `aio_return`, but ignore the result /// let _ = aiocb.aio_return(); - /// # } /// ``` /// /// # References @@ -508,7 +505,7 @@ impl<'a> AioCb<'a> { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), - -1 => Err(Error::from(Errno::last())), + -1 => Err(Errno::last()), _ => panic!("unknown aio_cancel return value") } } @@ -519,8 +516,8 @@ impl<'a> AioCb<'a> { }; match r { 0 => Ok(()), - num if num > 0 => Err(Error::from(Errno::from_i32(num))), - -1 => Err(Error::from(Errno::last())), + num if num > 0 => Err(Errno::from_i32(num)), + -1 => Err(Errno::last()), num => panic!("unknown aio_error return value {:?}", num) } } @@ -543,7 +540,6 @@ impl<'a> AioCb<'a> { /// # use std::{thread, time}; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; - /// # fn main() { /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), @@ -553,11 +549,10 @@ impl<'a> AioCb<'a> { /// SigevNotify::SigevNone, /// LioOpcode::LIO_NOP); /// aiocb.write().unwrap(); - /// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { + /// while (aiocb.error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); /// } /// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); - /// # } /// ``` /// /// # References @@ -706,7 +701,6 @@ impl<'a> AioCb<'a> { /// # use std::io::Write; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; -/// # fn main() { /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), @@ -718,13 +712,12 @@ impl<'a> AioCb<'a> { /// aiocb.write().unwrap(); /// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); /// if cs == AioCancelStat::AioNotCanceled { -/// while (aiocb.error() == Err(Error::from(Errno::EINPROGRESS))) { +/// while (aiocb.error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); /// } /// } /// // Must call `aio_return`, but ignore the result /// let _ = aiocb.aio_return(); -/// # } /// ``` /// /// # References @@ -735,7 +728,7 @@ pub fn aio_cancel_all(fd: RawFd) -> Result<AioCancelStat> { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), - -1 => Err(Error::from(Errno::last())), + -1 => Err(Errno::last()), _ => panic!("unknown aio_cancel return value") } } @@ -754,7 +747,6 @@ pub fn aio_cancel_all(fd: RawFd) -> Result<AioCancelStat> { /// # use nix::sys::signal::SigevNotify; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; -/// # fn main() { /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiocb = AioCb::from_slice( f.as_raw_fd(), @@ -766,7 +758,6 @@ pub fn aio_cancel_all(fd: RawFd) -> Result<AioCancelStat> { /// aiocb.write().unwrap(); /// aio_suspend(&[aiocb.as_ref()], None).expect("aio_suspend failed"); /// assert_eq!(aiocb.aio_return().unwrap() as usize, WBUF.len()); -/// # } /// ``` /// # References /// @@ -838,6 +829,7 @@ unsafe impl<'a> Sync for LioCb<'a> {} #[cfg(not(any(target_os = "ios", target_os = "macos")))] impl<'a> LioCb<'a> { + /// Are no [`AioCb`]s contained? pub fn is_empty(&self) -> bool { self.aiocbs.is_empty() } @@ -870,7 +862,6 @@ impl<'a> LioCb<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::os::unix::io::AsRawFd; /// # use tempfile::tempfile; - /// # fn main() { /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut liocb = LioCbBuilder::with_capacity(1) @@ -885,7 +876,6 @@ impl<'a> LioCb<'a> { /// liocb.listio(LioMode::LIO_WAIT, /// SigevNotify::SigevNone).unwrap(); /// assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - /// # } /// ``` /// /// # References @@ -930,7 +920,6 @@ impl<'a> LioCb<'a> { /// # use std::os::unix::io::AsRawFd; /// # use std::{thread, time}; /// # use tempfile::tempfile; - /// # fn main() { /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut liocb = LioCbBuilder::with_capacity(1) @@ -943,13 +932,12 @@ impl<'a> LioCb<'a> { /// LioOpcode::LIO_WRITE /// ).finish(); /// let mut err = liocb.listio(LioMode::LIO_WAIT, SigevNotify::SigevNone); - /// while err == Err(Error::from(Errno::EIO)) || - /// err == Err(Error::from(Errno::EAGAIN)) { + /// while err == Err(Errno::EIO) || + /// err == Err(Errno::EAGAIN) { /// thread::sleep(time::Duration::from_millis(10)); /// err = liocb.listio_resubmit(LioMode::LIO_WAIT, SigevNotify::SigevNone); /// } /// assert_eq!(liocb.aio_return(0).unwrap() as usize, WBUF.len()); - /// # } /// ``` /// /// # References diff --git a/src/sys/epoll.rs b/src/sys/epoll.rs index b73af13..6bc2a25 100644 --- a/src/sys/epoll.rs +++ b/src/sys/epoll.rs @@ -1,4 +1,4 @@ -use crate::{Error, Result}; +use crate::Result; use crate::errno::Errno; use libc::{self, c_int}; use std::os::unix::io::RawFd; @@ -29,6 +29,7 @@ libc_bitflags!( #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(i32)] +#[non_exhaustive] pub enum EpollOp { EpollCtlAdd = libc::EPOLL_CTL_ADD, EpollCtlDel = libc::EPOLL_CTL_DEL, @@ -85,7 +86,7 @@ pub fn epoll_ctl<'a, T>(epfd: RawFd, op: EpollOp, fd: RawFd, event: T) -> Result { let mut event: Option<&mut EpollEvent> = event.into(); if event.is_none() && op != EpollOp::EpollCtlDel { - Err(Error::from(Errno::EINVAL)) + Err(Errno::EINVAL) } else { let res = unsafe { if let Some(ref mut event) = event { diff --git a/src/sys/event.rs b/src/sys/event.rs index 8050af3..c648f5e 100644 --- a/src/sys/event.rs +++ b/src/sys/event.rs @@ -6,9 +6,9 @@ use crate::{Errno, Result}; 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; -use std::mem; // Redefine kevent in terms of programmer-friendly enums and bitfields. #[repr(C)] @@ -36,6 +36,7 @@ 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 @@ -75,6 +76,7 @@ libc_enum! { EVFILT_VNODE, EVFILT_WRITE, } + impl TryFrom<type_of_event_filter> } #[cfg(any(target_os = "dragonfly", target_os = "freebsd", @@ -232,8 +234,8 @@ impl KEvent { self.kevent.ident } - pub fn filter(&self) -> EventFilter { - unsafe { mem::transmute(self.kevent.filter as type_of_event_filter) } + pub fn filter(&self) -> Result<EventFilter> { + self.kevent.filter.try_into() } pub fn flags(&self) -> EventFlag { @@ -312,6 +314,8 @@ pub fn ev_set(ev: &mut KEvent, #[test] fn test_struct_kevent() { + use std::mem; + let udata : intptr_t = 12345; let actual = KEvent::new(0xdead_beef, @@ -321,10 +325,24 @@ fn test_struct_kevent() { 0x1337, udata); assert_eq!(0xdead_beef, actual.ident()); - assert_eq!(libc::EVFILT_READ, actual.filter() as type_of_event_filter); + 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()); +} diff --git a/src/sys/eventfd.rs b/src/sys/eventfd.rs index baaaa89..c54f952 100644 --- a/src/sys/eventfd.rs +++ b/src/sys/eventfd.rs @@ -1,4 +1,3 @@ -use libc; use std::os::unix::io::RawFd; use crate::Result; use crate::errno::Errno; diff --git a/src/sys/memfd.rs b/src/sys/memfd.rs index 51b7e6b..642676b 100644 --- a/src/sys/memfd.rs +++ b/src/sys/memfd.rs @@ -1,16 +1,43 @@ -use libc; +//! Interfaces for managing memory-backed files. + use std::os::unix::io::RawFd; use crate::Result; use crate::errno::Errno; use std::ffi::CStr; libc_bitflags!( + /// Options that change the behavior of [`memfd_create`]. pub struct MemFdCreateFlag: libc::c_uint { + /// Set the close-on-exec ([`FD_CLOEXEC`]) flag on the new file descriptor. + /// + /// By default, the new file descriptor is set to remain open across an [`execve`] + /// (the `FD_CLOEXEC` flag is initially disabled). This flag can be used to change + /// this default. The file offset is set to the beginning of the file (see [`lseek`]). + /// + /// See also the description of the `O_CLOEXEC` flag in [`open(2)`]. + /// + /// [`execve`]: crate::unistd::execve + /// [`lseek`]: crate::unistd::lseek + /// [`FD_CLOEXEC`]: crate::fcntl::FdFlag::FD_CLOEXEC + /// [`open(2)`]: https://man7.org/linux/man-pages/man2/open.2.html MFD_CLOEXEC; + /// Allow sealing operations on this file. + /// + /// See also the file sealing notes given in [`memfd_create(2)`]. + /// + /// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html MFD_ALLOW_SEALING; } ); +/// Creates an anonymous file that lives in memory, and return a file-descriptor to it. +/// +/// The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on. +/// However, unlike a regular file, it lives in RAM and has a volatile backing storage. +/// +/// For more information, see [`memfd_create(2)`]. +/// +/// [`memfd_create(2)`]: https://man7.org/linux/man-pages/man2/memfd_create.2.html pub fn memfd_create(name: &CStr, flags: MemFdCreateFlag) -> Result<RawFd> { let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags.bits()) diff --git a/src/sys/mman.rs b/src/sys/mman.rs index 58edf08..0ef1ca8 100644 --- a/src/sys/mman.rs +++ b/src/sys/mman.rs @@ -1,4 +1,6 @@ -use crate::{Error, Result}; +//! Memory management declarations. + +use crate::Result; #[cfg(not(target_os = "android"))] use crate::NixPath; use crate::errno::Errno; @@ -30,7 +32,7 @@ libc_bitflags!{ } libc_bitflags!{ - /// Additional parameters for `mmap()`. + /// Additional parameters for [`mmap`]. pub struct MapFlags: c_int { /// Compatibility flag. Ignored. MAP_FILE; @@ -40,10 +42,13 @@ libc_bitflags!{ 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. - #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] MAP_ANONYMOUS; /// Put the mapping into the first 2GB of the process address space. #[cfg(any(all(any(target_os = "android", target_os = "linux"), @@ -65,8 +70,8 @@ libc_bitflags!{ MAP_LOCKED; /// Do not reserve swap space for this mapping. /// - /// This was removed in FreeBSD 11. - #[cfg(not(target_os = "freebsd"))] + /// 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"))] @@ -122,39 +127,55 @@ libc_bitflags!{ MAP_NOSYNC; /// Rename private pages to a file. /// - /// This was removed in FreeBSD 11. - #[cfg(any(target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))] + /// 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"))] + #[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 = "freebsd")] + MAP_ALIGNED_SUPER; + /// Pages will be discarded in the core dumps. + #[cfg(target_os = "openbsd")] + MAP_CONCEAL; } } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "netbsd"))] libc_bitflags!{ - /// Options for `mremap()`. + /// 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). + /// Used by [`madvise`]. #[repr(i32)] + #[non_exhaustive] pub enum MmapAdvise { /// No further special treatment. This is the default. MADV_NORMAL, @@ -191,7 +212,8 @@ libc_enum!{ all(target_os = "linux", any( target_arch = "aarch64", target_arch = "arm", - target_arch = "ppc", + target_arch = "powerpc", + target_arch = "powerpc64", target_arch = "s390x", target_arch = "x86", target_arch = "x86_64", @@ -244,7 +266,7 @@ libc_enum!{ } libc_bitflags!{ - /// Configuration flags for `msync`. + /// Configuration flags for [`msync`]. pub struct MsFlags: c_int { /// Schedule an update but return immediately. MS_ASYNC; @@ -262,7 +284,7 @@ libc_bitflags!{ } libc_bitflags!{ - /// Flags for `mlockall`. + /// Flags for [`mlockall`]. pub struct MlockAllFlags: c_int { /// Lock pages that are currently mapped into the address space of the process. MCL_CURRENT; @@ -278,7 +300,9 @@ libc_bitflags!{ /// /// # Safety /// -/// `addr` must meet all the requirements described in the `mlock(2)` man page. +/// `addr` must meet all the requirements described in the [`mlock(2)`] man page. +/// +/// [`mlock(2)`]: https://man7.org/linux/man-pages/man2/mlock.2.html pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> { Errno::result(libc::mlock(addr, length)).map(drop) } @@ -288,25 +312,28 @@ pub unsafe fn mlock(addr: *const c_void, length: size_t) -> Result<()> { /// /// # Safety /// -/// `addr` must meet all the requirements described in the `munlock(2)` man +/// `addr` must meet all the requirements described in the [`munlock(2)`] man /// page. +/// +/// [`munlock(2)`]: https://man7.org/linux/man-pages/man2/munlock.2.html pub unsafe fn munlock(addr: *const c_void, length: size_t) -> Result<()> { Errno::result(libc::munlock(addr, length)).map(drop) } /// Locks all memory pages mapped into this process' address space. /// -/// Locked pages never move to the swap area. -/// -/// # Safety +/// Locked pages never move to the swap area. For more information, see [`mlockall(2)`]. /// -/// `addr` must meet all the requirements described in the `mlockall(2)` man -/// page. +/// [`mlockall(2)`]: https://man7.org/linux/man-pages/man2/mlockall.2.html pub fn mlockall(flags: MlockAllFlags) -> Result<()> { unsafe { Errno::result(libc::mlockall(flags.bits())) }.map(drop) } /// Unlocks all memory pages mapped into this process' address space. +/// +/// For more information, see [`munlockall(2)`]. +/// +/// [`munlockall(2)`]: https://man7.org/linux/man-pages/man2/munlockall.2.html pub fn munlockall() -> Result<()> { unsafe { Errno::result(libc::munlockall()) }.map(drop) } @@ -315,12 +342,14 @@ pub fn munlockall() -> Result<()> { /// /// # Safety /// -/// See the `mmap(2)` man page for detailed requirements. +/// See the [`mmap(2)`] man page for detailed requirements. +/// +/// [`mmap(2)`]: https://man7.org/linux/man-pages/man2/mmap.2.html pub unsafe fn mmap(addr: *mut c_void, length: size_t, prot: ProtFlags, flags: MapFlags, fd: RawFd, offset: off_t) -> Result<*mut c_void> { let ret = libc::mmap(addr, length, prot.bits(), flags.bits(), fd, offset); if ret == libc::MAP_FAILED { - Err(Error::from(Errno::last())) + Err(Errno::last()) } else { Ok(ret) } @@ -333,7 +362,7 @@ pub unsafe fn mmap(addr: *mut c_void, length: size_t, prot: ProtFlags, flags: Ma /// /// See the `mremap(2)` [man page](https://man7.org/linux/man-pages/man2/mremap.2.html) for /// detailed requirements. -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "netbsd"))] pub unsafe fn mremap( addr: *mut c_void, old_size: size_t, @@ -341,10 +370,19 @@ pub unsafe fn mremap( 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(Error::from(Errno::last())) + Err(Errno::last()) } else { Ok(ret) } @@ -354,8 +392,10 @@ pub unsafe fn mremap( /// /// # Safety /// -/// `addr` must meet all the requirements described in the `munmap(2)` man +/// `addr` must meet all the requirements described in the [`munmap(2)`] man /// page. +/// +/// [`munmap(2)`]: https://man7.org/linux/man-pages/man2/munmap.2.html pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> { Errno::result(libc::munmap(addr, len)).map(drop) } @@ -364,8 +404,10 @@ pub unsafe fn munmap(addr: *mut c_void, len: size_t) -> Result<()> { /// /// # Safety /// -/// See the `madvise(2)` man page. Take special care when using -/// `MmapAdvise::MADV_FREE`. +/// See the [`madvise(2)`] man page. Take special care when using +/// [`MmapAdvise::MADV_FREE`]. +/// +/// [`madvise(2)`]: https://man7.org/linux/man-pages/man2/madvise.2.html pub unsafe fn madvise(addr: *mut c_void, length: size_t, advise: MmapAdvise) -> Result<()> { Errno::result(libc::madvise(addr, length, advise as i32)).map(drop) } @@ -403,12 +445,19 @@ pub unsafe fn mprotect(addr: *mut c_void, length: size_t, prot: ProtFlags) -> Re /// /// # Safety /// -/// `addr` must meet all the requirements described in the `msync(2)` man +/// `addr` must meet all the requirements described in the [`msync(2)`] man /// page. +/// +/// [`msync(2)`]: https://man7.org/linux/man-pages/man2/msync.2.html pub unsafe fn msync(addr: *mut c_void, length: size_t, flags: MsFlags) -> Result<()> { Errno::result(libc::msync(addr, length, flags.bits())).map(drop) } +/// Creates and opens a new, or opens an existing, POSIX shared memory object. +/// +/// For more information, see [`shm_open(3)`]. +/// +/// [`shm_open(3)`]: https://man7.org/linux/man-pages/man3/shm_open.3.html #[cfg(not(target_os = "android"))] pub fn shm_open<P: ?Sized + NixPath>(name: &P, flag: OFlag, mode: Mode) -> Result<RawFd> { let ret = name.with_nix_path(|cstr| { @@ -425,6 +474,11 @@ pub fn shm_open<P: ?Sized + NixPath>(name: &P, flag: OFlag, mode: Mode) -> Resul Errno::result(ret) } +/// Performs the converse of [`shm_open`], removing an object previously created. +/// +/// For more information, see [`shm_unlink(3)`]. +/// +/// [`shm_unlink(3)`]: https://man7.org/linux/man-pages/man3/shm_unlink.3.html #[cfg(not(target_os = "android"))] pub fn shm_unlink<P: ?Sized + NixPath>(name: &P) -> Result<()> { let ret = name.with_nix_path(|cstr| { diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 51f0eed..156b0d9 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -1,3 +1,4 @@ +//! Mostly platform-specific functionality #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios", @@ -7,6 +8,7 @@ pub mod aio; #[cfg(any(target_os = "android", target_os = "linux"))] +#[allow(missing_docs)] pub mod epoll; #[cfg(any(target_os = "dragonfly", @@ -15,9 +17,11 @@ pub mod epoll; target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] +#[allow(missing_docs)] pub mod event; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "android", target_os = "linux"))] +#[allow(missing_docs)] pub mod eventfd; #[cfg(any(target_os = "android", @@ -37,9 +41,11 @@ pub mod ioctl; 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; @@ -51,14 +57,19 @@ pub mod pthread; 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; @@ -72,11 +83,14 @@ 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", @@ -92,10 +106,13 @@ 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; @@ -105,7 +122,9 @@ 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; diff --git a/src/sys/personality.rs b/src/sys/personality.rs index 6548b65..b15956c 100644 --- a/src/sys/personality.rs +++ b/src/sys/personality.rs @@ -39,7 +39,7 @@ pub fn get() -> Result<Persona> { libc::personality(0xFFFFFFFF) }; - Errno::result(res).map(|r| Persona::from_bits_truncate(r)) + Errno::result(res).map(Persona::from_bits_truncate) } /// Set the current process personality. @@ -66,5 +66,5 @@ pub fn set(persona: Persona) -> Result<Persona> { libc::personality(persona.bits() as c_ulong) }; - Errno::result(res).map(|r| Persona::from_bits_truncate(r)) + Errno::result(res).map(Persona::from_bits_truncate) } diff --git a/src/sys/pthread.rs b/src/sys/pthread.rs index f730408..d42e45d 100644 --- a/src/sys/pthread.rs +++ b/src/sys/pthread.rs @@ -1,5 +1,14 @@ +//! 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 @@ -11,3 +20,19 @@ pub type Pthread = pthread_t; 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) +} diff --git a/src/sys/ptrace/bsd.rs b/src/sys/ptrace/bsd.rs index 141dfbc..a62881e 100644 --- a/src/sys/ptrace/bsd.rs +++ b/src/sys/ptrace/bsd.rs @@ -24,6 +24,7 @@ cfg_if! { libc_enum! { #[repr(i32)] /// Ptrace Request enum defining the action to be taken. + #[non_exhaustive] pub enum Request { PT_TRACE_ME, PT_READ_I, diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index 4ac4393..3723679 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -33,6 +33,7 @@ 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, @@ -97,11 +98,9 @@ libc_enum!{ #[cfg(all(target_os = "linux", not(any(target_arch = "mips", target_arch = "mips64"))))] PTRACE_SETREGSET, - #[cfg(all(target_os = "linux", not(any(target_arch = "mips", - target_arch = "mips64"))))] + #[cfg(target_os = "linux")] PTRACE_SEIZE, - #[cfg(all(target_os = "linux", not(any(target_arch = "mips", - target_arch = "mips64"))))] + #[cfg(target_os = "linux")] PTRACE_INTERRUPT, #[cfg(all(target_os = "linux", not(any(target_arch = "mips", target_arch = "mips64"))))] @@ -123,6 +122,7 @@ libc_enum!{ /// 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, @@ -137,9 +137,11 @@ libc_enum!{ /// 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. + /// Stop triggered by a seccomp rule on a tracee. PTRACE_EVENT_SECCOMP, - // PTRACE_EVENT_STOP not provided by libc because it's defined in glibc 2.26 + /// Stop triggered by the `INTERRUPT` syscall, or a group stop, + /// or when a new child is attached. + PTRACE_EVENT_STOP, } } @@ -337,7 +339,7 @@ pub fn attach(pid: Pid) -> Result<()> { /// 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(all(target_os = "linux", not(any(target_arch = "mips", target_arch = "mips64"))))] +#[cfg(target_os = "linux")] pub fn seize(pid: Pid, options: Options) -> Result<()> { unsafe { ptrace_other( @@ -382,6 +384,16 @@ pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { } } +/// 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);` @@ -391,7 +403,7 @@ pub fn kill(pid: Pid) -> Result<()> { } } -/// Move the stopped tracee process forward by a single step as with +/// 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 @@ -401,18 +413,17 @@ pub fn kill(pid: Pid) -> Result<()> { /// ```rust /// use nix::sys::ptrace::step; /// use nix::unistd::Pid; -/// use nix::sys::signal::Signal; +/// use nix::sys::signal::Signal; /// use nix::sys::wait::*; -/// fn main() { -/// // 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); -/// } -/// _ => {}, +/// +/// // 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<()> { diff --git a/src/sys/quota.rs b/src/sys/quota.rs index 1933013..6e34e38 100644 --- a/src/sys/quota.rs +++ b/src/sys/quota.rs @@ -42,6 +42,7 @@ libc_enum!{ libc_enum!{ /// The scope of the quota. #[repr(i32)] + #[non_exhaustive] pub enum QuotaType { /// Specify a user quota USRQUOTA, @@ -53,6 +54,7 @@ libc_enum!{ libc_enum!{ /// The type of quota format to use. #[repr(i32)] + #[non_exhaustive] pub enum QuotaFmt { /// Use the original quota format. QFMT_VFS_OLD, diff --git a/src/sys/reboot.rs b/src/sys/reboot.rs index 5b37682..46ab68b 100644 --- a/src/sys/reboot.rs +++ b/src/sys/reboot.rs @@ -1,8 +1,7 @@ //! Reboot/shutdown or enable/disable Ctrl-Alt-Delete. -use crate::{Error, Result}; +use crate::Result; use crate::errno::Errno; -use libc; use std::convert::Infallible; use std::mem::drop; @@ -12,6 +11,7 @@ libc_enum! { /// 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, @@ -26,7 +26,7 @@ pub fn reboot(how: RebootMode) -> Result<Infallible> { unsafe { libc::reboot(how as libc::c_int) }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } /// Enable or disable the reboot keystroke (Ctrl-Alt-Delete). diff --git a/src/sys/resource.rs b/src/sys/resource.rs new file mode 100644 index 0000000..f3bfb67 --- /dev/null +++ b/src/sys/resource.rs @@ -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) +} diff --git a/src/sys/select.rs b/src/sys/select.rs index 5eb6423..4d7576a 100644 --- a/src/sys/select.rs +++ b/src/sys/select.rs @@ -1,3 +1,5 @@ +//! Portably monitor a group of file descriptors for readiness. +use std::convert::TryFrom; use std::iter::FusedIterator; use std::mem; use std::ops::Range; @@ -11,11 +13,20 @@ 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 { @@ -24,18 +35,25 @@ impl FdSet { } } + /// 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) }; } - pub fn contains(&mut self, fd: RawFd) -> bool { - unsafe { libc::FD_ISSET(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) }; } @@ -57,7 +75,7 @@ impl FdSet { /// ``` /// /// [`select`]: fn.select.html - pub fn highest(&mut self) -> Option<RawFd> { + pub fn highest(&self) -> Option<RawFd> { self.fds(None).next_back() } @@ -79,7 +97,7 @@ impl FdSet { /// assert_eq!(fds, vec![4, 9]); /// ``` #[inline] - pub fn fds(&mut self, highest: Option<RawFd>) -> Fds { + pub fn fds(&self, highest: Option<RawFd>) -> Fds { Fds { set: self, range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE), @@ -96,7 +114,7 @@ impl Default for FdSet { /// Iterator over `FdSet`. #[derive(Debug)] pub struct Fds<'a> { - set: &'a mut FdSet, + set: &'a FdSet, range: Range<usize>, } @@ -104,7 +122,7 @@ impl<'a> Iterator for Fds<'a> { type Item = RawFd; fn next(&mut self) -> Option<RawFd> { - while let Some(i) = self.range.next() { + for i in &mut self.range { if self.set.contains(i as RawFd) { return Some(i as RawFd); } diff --git a/src/sys/sendfile.rs b/src/sys/sendfile.rs index a12c041..7a210c6 100644 --- a/src/sys/sendfile.rs +++ b/src/sys/sendfile.rs @@ -1,3 +1,5 @@ +//! Send data from a file to a socket, bypassing userland. + use cfg_if::cfg_if; use std::os::unix::io::RawFd; use std::ptr; diff --git a/src/sys/signal.rs b/src/sys/signal.rs index 273b352..61bdc74 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -1,12 +1,11 @@ // Portions of this file are Copyright 2014 The Rust Project Developers. // See https://www.rust-lang.org/policies/licenses. -///! Operating system signals. +//! Operating system signals. use crate::{Error, Result}; use crate::errno::Errno; use crate::unistd::Pid; -use std::convert::TryFrom; use std::mem; use std::fmt; use std::str::FromStr; @@ -18,58 +17,94 @@ use std::ptr; pub use self::sigevent::*; libc_enum!{ + /// Types of operating system signals // Currently there is only one definition of c_int in libc, as well as only one // type for signal constants. // We would prefer to use the libc::c_int alias in the repr attribute. Unfortunately // this is not (yet) possible. #[repr(i32)] + #[non_exhaustive] pub enum Signal { + /// Hangup SIGHUP, + /// Interrupt SIGINT, + /// Quit SIGQUIT, + /// Illegal instruction (not reset when caught) SIGILL, + /// Trace trap (not reset when caught) SIGTRAP, + /// Abort SIGABRT, + /// Bus error SIGBUS, + /// Floating point exception SIGFPE, + /// Kill (cannot be caught or ignored) SIGKILL, + /// User defined signal 1 SIGUSR1, + /// Segmentation violation SIGSEGV, + /// User defined signal 2 SIGUSR2, + /// Write on a pipe with no one to read it SIGPIPE, + /// Alarm clock SIGALRM, + /// Software termination signal from kill SIGTERM, + /// Stack fault (obsolete) #[cfg(all(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux"), not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))))] SIGSTKFLT, + /// To parent on child stop or exit SIGCHLD, + /// Continue a stopped process SIGCONT, + /// Sendable stop signal not from tty SIGSTOP, + /// Stop signal from tty SIGTSTP, + /// To readers pgrp upon background tty read SIGTTIN, + /// Like TTIN if (tp->t_local<OSTOP) SIGTTOU, + /// Urgent condition on IO channel SIGURG, + /// Exceeded CPU time limit SIGXCPU, + /// Exceeded file size limit SIGXFSZ, + /// Virtual time alarm SIGVTALRM, + /// Profiling time alarm SIGPROF, + /// Window size changes SIGWINCH, + /// Input/output possible signal SIGIO, #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux"))] + /// Power failure imminent. SIGPWR, + /// Bad system call SIGSYS, #[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux", target_os = "redox")))] + /// Emulator trap SIGEMT, #[cfg(not(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux", target_os = "redox")))] + /// Information request SIGINFO, } + impl TryFrom<i32> } impl FromStr for Signal { @@ -121,7 +156,7 @@ impl FromStr for Signal { target_os = "fuchsia", target_os = "linux", target_os = "redox")))] "SIGINFO" => Signal::SIGINFO, - _ => return Err(Error::from(Errno::EINVAL)), + _ => return Err(Errno::EINVAL), }) } } @@ -132,7 +167,7 @@ impl Signal { /// This function is equivalent to `<Signal as AsRef<str>>::as_ref()`, /// with difference that returned string is `'static` /// and not bound to `self`'s lifetime. - pub fn as_str(self) -> &'static str { + pub const fn as_str(self) -> &'static str { match self { Signal::SIGHUP => "SIGHUP", Signal::SIGINT => "SIGINT", @@ -334,9 +369,8 @@ const SIGNALS: [Signal; 31] = [ SIGEMT, SIGINFO]; -pub const NSIG: libc::c_int = 32; - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +/// Iterate through all signals defined by this operating system pub struct SignalIterator { next: usize, } @@ -356,25 +390,17 @@ impl Iterator for SignalIterator { } impl Signal { - pub fn iterator() -> SignalIterator { + /// Iterate through all signals defined by this OS + pub const fn iterator() -> SignalIterator { SignalIterator{next: 0} } } -impl TryFrom<libc::c_int> for Signal { - type Error = Error; - - fn try_from(signum: libc::c_int) -> Result<Signal> { - if 0 < signum && signum < NSIG { - Ok(unsafe { mem::transmute(signum) }) - } else { - Err(Error::from(Errno::EINVAL)) - } - } -} - +/// Alias for [`SIGABRT`] pub const SIGIOT : Signal = SIGABRT; +/// Alias for [`SIGIO`] pub const SIGPOLL : Signal = SIGIO; +/// Alias for [`SIGSYS`] pub const SIGUNUSED : Signal = SIGSYS; #[cfg(not(target_os = "redox"))] @@ -383,26 +409,48 @@ type SaFlags_t = libc::c_int; type SaFlags_t = libc::c_ulong; libc_bitflags!{ + /// Controls the behavior of a [`SigAction`] pub struct SaFlags: SaFlags_t { + /// When catching a [`Signal::SIGCHLD`] signal, the signal will be + /// generated only when a child process exits, not when a child process + /// stops. SA_NOCLDSTOP; + /// When catching a [`Signal::SIGCHLD`] signal, the system will not + /// create zombie processes when children of the calling process exit. SA_NOCLDWAIT; + /// Further occurrences of the delivered signal are not masked during + /// the execution of the handler. SA_NODEFER; + /// The system will deliver the signal to the process on a signal stack, + /// specified by each thread with sigaltstack(2). SA_ONSTACK; + /// The handler is reset back to the default at the moment the signal is + /// delivered. SA_RESETHAND; + /// Requests that certain system calls restart if interrupted by this + /// signal. See the man page for complete details. SA_RESTART; + /// This flag is controlled internally by Nix. SA_SIGINFO; } } libc_enum! { + /// Specifies how certain functions should manipulate a signal mask #[repr(i32)] + #[non_exhaustive] pub enum SigmaskHow { + /// The new mask is the union of the current mask and the specified set. SIG_BLOCK, + /// The new mask is the intersection of the current mask and the + /// complement of the specified set. SIG_UNBLOCK, + /// The current mask is replaced by the specified set. SIG_SETMASK, } } +/// Specifies a set of [`Signal`]s that may be blocked, waited for, etc. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct SigSet { sigset: libc::sigset_t @@ -410,6 +458,7 @@ pub struct SigSet { impl SigSet { + /// Initialize to include all signals. pub fn all() -> SigSet { let mut sigset = mem::MaybeUninit::uninit(); let _ = unsafe { libc::sigfillset(sigset.as_mut_ptr()) }; @@ -417,6 +466,7 @@ impl SigSet { unsafe{ SigSet { sigset: sigset.assume_init() } } } + /// Initialize to include nothing. pub fn empty() -> SigSet { let mut sigset = mem::MaybeUninit::uninit(); let _ = unsafe { libc::sigemptyset(sigset.as_mut_ptr()) }; @@ -424,18 +474,22 @@ impl SigSet { unsafe{ SigSet { sigset: sigset.assume_init() } } } + /// Add the specified signal to the set. pub fn add(&mut self, signal: Signal) { unsafe { libc::sigaddset(&mut self.sigset as *mut libc::sigset_t, signal as libc::c_int) }; } + /// Remove all signals from this set. pub fn clear(&mut self) { unsafe { libc::sigemptyset(&mut self.sigset as *mut libc::sigset_t) }; } + /// Remove the specified signal from this set. pub fn remove(&mut self, signal: Signal) { unsafe { libc::sigdelset(&mut self.sigset as *mut libc::sigset_t, signal as libc::c_int) }; } + /// Return whether this set includes the specified signal. pub fn contains(&self, signal: Signal) -> bool { let res = unsafe { libc::sigismember(&self.sigset as *const libc::sigset_t, signal as libc::c_int) }; @@ -446,6 +500,8 @@ impl SigSet { } } + /// Merge all of `other`'s signals into this set. + // TODO: use libc::sigorset on supported operating systems. pub fn extend(&mut self, other: &SigSet) { for signal in Signal::iterator() { if other.contains(signal) { @@ -487,6 +543,8 @@ impl SigSet { /// signal mask becomes pending, and returns the accepted signal. #[cfg(not(target_os = "redox"))] // RedoxFS does not yet support sigwait pub fn wait(&self) -> Result<Signal> { + use std::convert::TryFrom; + let mut signum = mem::MaybeUninit::uninit(); let res = unsafe { libc::sigwait(&self.sigset as *const libc::sigset_t, signum.as_mut_ptr()) }; @@ -582,9 +640,31 @@ impl SigAction { match self.sigaction.sa_sigaction { libc::SIG_DFL => SigHandler::SigDfl, libc::SIG_IGN => SigHandler::SigIgn, - f if self.flags().contains(SaFlags::SA_SIGINFO) => - SigHandler::SigAction( unsafe { mem::transmute(f) } ), - f => SigHandler::Handler( unsafe { mem::transmute(f) } ), + p if self.flags().contains(SaFlags::SA_SIGINFO) => + SigHandler::SigAction( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(_, _, _)) + } + as extern fn(_, _, _)), + p => SigHandler::Handler( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(libc::c_int)) + } + as extern fn(libc::c_int)), } } @@ -594,7 +674,18 @@ impl SigAction { match self.sigaction.sa_handler { libc::SIG_DFL => SigHandler::SigDfl, libc::SIG_IGN => SigHandler::SigIgn, - f => SigHandler::Handler( unsafe { mem::transmute(f) } ), + p => SigHandler::Handler( + // Safe for one of two reasons: + // * The SigHandler was created by SigHandler::new, in which + // case the pointer is correct, or + // * The SigHandler was created by signal or sigaction, which + // are unsafe functions, so the caller should've somehow + // ensured that it is correctly initialized. + unsafe{ + *(&p as *const usize + as *const extern fn(libc::c_int)) + } + as extern fn(libc::c_int)), } } } @@ -606,9 +697,16 @@ impl SigAction { /// /// # Safety /// -/// Signal handlers may be called at any point during execution, which limits what is safe to do in -/// the body of the signal-catching function. Be certain to only make syscalls that are explicitly -/// marked safe for signal handlers and only share global data using atomics. +/// * Signal handlers may be called at any point during execution, which limits +/// what is safe to do in the body of the signal-catching function. Be certain +/// to only make syscalls that are explicitly marked safe for signal handlers +/// and only share global data using atomics. +/// +/// * There is also no guarantee that the old signal handler was installed +/// correctly. If it was installed by this crate, it will be. But if it was +/// installed by, for example, C code, then there is no guarantee its function +/// pointer is valid. In that case, this function effectively dereferences a +/// raw pointer of unknown provenance. pub unsafe fn sigaction(signal: Signal, sigaction: &SigAction) -> Result<SigAction> { let mut oldact = mem::MaybeUninit::<libc::sigaction>::uninit(); @@ -681,13 +779,16 @@ pub unsafe fn signal(signal: Signal, handler: SigHandler) -> Result<SigHandler> SigHandler::SigIgn => libc::signal(signal, libc::SIG_IGN), SigHandler::Handler(handler) => libc::signal(signal, handler as libc::sighandler_t), #[cfg(not(target_os = "redox"))] - SigHandler::SigAction(_) => return Err(Error::from(Errno::ENOTSUP)), + SigHandler::SigAction(_) => return Err(Errno::ENOTSUP), }; Errno::result(res).map(|oldhandler| { match oldhandler { libc::SIG_DFL => SigHandler::SigDfl, libc::SIG_IGN => SigHandler::SigIgn, - f => SigHandler::Handler(mem::transmute(f)), + p => SigHandler::Handler( + *(&p as *const usize + as *const extern fn(libc::c_int)) + as extern fn(libc::c_int)), } }) } @@ -754,6 +855,24 @@ pub fn sigprocmask(how: SigmaskHow, set: Option<&SigSet>, oldset: Option<&mut Si Errno::result(res).map(drop) } +/// Send a signal to a process +/// +/// # Arguments +/// +/// * `pid` - Specifies which processes should receive the signal. +/// - If positive, specifies an individual process +/// - If zero, the signal will be sent to all processes whose group +/// ID is equal to the process group ID of the sender. This is a +/// variant of [`killpg`]. +/// - If `-1` and the process has super-user privileges, the signal +/// is sent to all processes exclusing system processes. +/// - If less than `-1`, the signal is sent to all processes whose +/// process group ID is equal to the absolute value of `pid`. +/// * `signal` - Signal to send. If `None`, error checking is performed +/// but no signal is actually sent. +/// +/// See Also +/// [`kill(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html) pub fn kill<T: Into<Option<Signal>>>(pid: Pid, signal: T) -> Result<()> { let res = unsafe { libc::kill(pid.into(), match signal.into() { @@ -764,12 +883,16 @@ pub fn kill<T: Into<Option<Signal>>>(pid: Pid, signal: T) -> Result<()> { Errno::result(res).map(drop) } -/// Send a signal to a process group [(see -/// killpg(3))](https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html). +/// Send a signal to a process group /// -/// If `pgrp` less then or equal 1, the behavior is platform-specific. -/// If `signal` is `None`, `killpg` will only preform error checking and won't -/// send any signal. +/// # Arguments +/// +/// * `pgrp` - Process group to signal. If less then or equal 1, the behavior +/// is platform-specific. +/// * `signal` - Signal to send. If `None`, `killpg` will only preform error +/// checking and won't send any signal. +/// +/// See Also [killpg(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html). #[cfg(not(target_os = "fuchsia"))] pub fn killpg<T: Into<Option<Signal>>>(pgrp: Pid, signal: T) -> Result<()> { let res = unsafe { libc::killpg(pgrp.into(), @@ -781,6 +904,9 @@ pub fn killpg<T: Into<Option<Signal>>>(pgrp: Pid, signal: T) -> Result<()> { Errno::result(res).map(drop) } +/// Send a signal to the current thread +/// +/// See Also [raise(3)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html) pub fn raise(signal: Signal) -> Result<()> { let res = unsafe { libc::raise(signal as libc::c_int) }; @@ -788,36 +914,51 @@ pub fn raise(signal: Signal) -> Result<()> { } +/// Identifies a thread for [`SigevNotify::SigevThreadId`] #[cfg(target_os = "freebsd")] pub type type_of_thread_id = libc::lwpid_t; +/// Identifies a thread for [`SigevNotify::SigevThreadId`] #[cfg(target_os = "linux")] pub type type_of_thread_id = libc::pid_t; -/// Used to request asynchronous notification of certain events, for example, -/// with POSIX AIO, POSIX message queues, and POSIX timers. +/// Specifies the notification method used by a [`SigEvent`] // sigval is actually a union of a int and a void*. But it's never really used // as a pointer, because neither libc nor the kernel ever dereference it. nix // therefore presents it as an intptr_t, which is how kevent uses it. +#[cfg(not(any(target_os = "openbsd", target_os = "redox")))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SigevNotify { /// No notification will be delivered SigevNone, - /// The signal given by `signal` will be delivered to the process. The - /// value in `si_value` will be present in the `si_value` field of the - /// `siginfo_t` structure of the queued signal. - SigevSignal { signal: Signal, si_value: libc::intptr_t }, + /// Notify by delivering a signal to the process. + SigevSignal { + /// Signal to deliver + signal: Signal, + /// Will be present in the `si_value` field of the [`libc::siginfo_t`] + /// structure of the queued signal. + si_value: libc::intptr_t + }, // Note: SIGEV_THREAD is not implemented because libc::sigevent does not // expose a way to set the union members needed by SIGEV_THREAD. - /// A new `kevent` is posted to the kqueue `kq`. The `kevent`'s `udata` - /// field will contain the value in `udata`. + /// Notify by delivering an event to a kqueue. #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] - SigevKevent { kq: RawFd, udata: libc::intptr_t }, - /// The signal `signal` is queued to the thread whose LWP ID is given in - /// `thread_id`. The value stored in `si_value` will be present in the - /// `si_value` of the `siginfo_t` structure of the queued signal. + SigevKevent { + /// File descriptor of the kqueue to notify. + kq: RawFd, + /// Will be contained in the kevent's `udata` field. + udata: libc::intptr_t + }, + /// Notify by delivering a signal to a thread. #[cfg(any(target_os = "freebsd", target_os = "linux"))] - SigevThreadId { signal: Signal, thread_id: type_of_thread_id, - si_value: libc::intptr_t }, + SigevThreadId { + /// Signal to send + signal: Signal, + /// LWP ID of the thread to notify + thread_id: type_of_thread_id, + /// Will be present in the `si_value` field of the [`libc::siginfo_t`] + /// structure of the queued signal. + si_value: libc::intptr_t + }, } #[cfg(not(any(target_os = "openbsd", target_os = "redox")))] @@ -899,6 +1040,7 @@ mod sigevent { fn set_tid(_sev: &mut libc::sigevent, _sigev_notify: &SigevNotify) { } + /// Return a copy of the inner structure pub fn sigevent(&self) -> libc::sigevent { self.sigevent } @@ -949,7 +1091,7 @@ mod tests { #[test] fn test_from_str_invalid_value() { - let errval = Err(Error::from(Errno::EINVAL)); + let errval = Err(Errno::EINVAL); assert_eq!("NOSIGNAL".parse::<Signal>(), errval); assert_eq!("kill".parse::<Signal>(), errval); assert_eq!("9".parse::<Signal>(), errval); diff --git a/src/sys/signalfd.rs b/src/sys/signalfd.rs index 49811a1..bc4a452 100644 --- a/src/sys/signalfd.rs +++ b/src/sys/signalfd.rs @@ -15,9 +15,8 @@ //! //! Please note that signal discarding is not specific to `signalfd`, but also happens with regular //! signal handlers. -use libc; use crate::unistd; -use crate::{Error, Result}; +use crate::Result; use crate::errno::Errno; pub use crate::sys::signal::{self, SigSet}; pub use libc::signalfd_siginfo as siginfo; @@ -34,7 +33,8 @@ libc_bitflags!{ } pub const SIGNALFD_NEW: RawFd = -1; -pub const SIGNALFD_SIGINFO_SIZE: usize = 128; +#[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. /// @@ -98,15 +98,14 @@ impl SignalFd { } pub fn read_signal(&mut self) -> Result<Option<siginfo>> { - let mut buffer = mem::MaybeUninit::<[u8; SIGNALFD_SIGINFO_SIZE]>::uninit(); + 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, - SIGNALFD_SIGINFO_SIZE as libc::size_t) + libc::read(self.0, buffer.as_mut_ptr() as *mut libc::c_void, size) }).map(|r| r as usize); match res { - Ok(SIGNALFD_SIGINFO_SIZE) => Ok(Some(unsafe { mem::transmute(buffer.assume_init()) })), + 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) @@ -117,7 +116,7 @@ impl SignalFd { impl Drop for SignalFd { fn drop(&mut self) { let e = unistd::close(self.0); - if !std::thread::panicking() && e == Err(Error::from(Errno::EBADF)) { + if !std::thread::panicking() && e == Err(Errno::EBADF) { panic!("Closing an invalid file descriptor!"); }; } @@ -144,14 +143,6 @@ impl Iterator for SignalFd { #[cfg(test)] mod tests { use super::*; - use std::mem; - use libc; - - - #[test] - fn check_siginfo_size() { - assert_eq!(mem::size_of::<libc::signalfd_siginfo>(), SIGNALFD_SIGINFO_SIZE); - } #[test] fn create_signalfd() { diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs index d486056..b119642 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -1,5 +1,5 @@ use super::sa_family_t; -use crate::{Error, Result, NixPath}; +use crate::{Result, NixPath}; use crate::errno::Errno; use memoffset::offset_of; use std::{fmt, mem, net, ptr, slice}; @@ -32,6 +32,7 @@ pub use self::vsock::VsockAddr; /// These constants specify the protocol family to be used /// in [`socket`](fn.socket.html) and [`socketpair`](fn.socketpair.html) #[repr(i32)] +#[non_exhaustive] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum AddressFamily { /// Local communication (see [`unix(7)`](https://man7.org/linux/man-pages/man7/unix.7.html)) @@ -236,7 +237,7 @@ impl AddressFamily { /// /// Currently only supports these address families: Unix, Inet (v4 & v6), Netlink, Link/Packet /// and System. Returns None for unsupported or unknown address families. - pub fn from_i32(family: i32) -> Option<AddressFamily> { + pub const fn from_i32(family: i32) -> Option<AddressFamily> { match family { libc::AF_UNIX => Some(AddressFamily::Unix), libc::AF_INET => Some(AddressFamily::Inet), @@ -269,6 +270,7 @@ pub enum InetAddr { } impl InetAddr { + #[allow(clippy::needless_update)] // It isn't needless on all OSes pub fn from_std(std: &net::SocketAddr) -> InetAddr { match *std { net::SocketAddr::V4(ref addr) => { @@ -292,6 +294,7 @@ impl InetAddr { } } + #[allow(clippy::needless_update)] // It isn't needless on all OSes pub fn new(ip: IpAddr, port: u16) -> InetAddr { match ip { IpAddr::V4(ref ip) => { @@ -313,7 +316,7 @@ impl InetAddr { } } /// Gets the IP address associated with this socket address. - pub fn ip(&self) -> IpAddr { + pub const fn ip(&self) -> IpAddr { match *self { InetAddr::V4(ref sa) => IpAddr::V4(Ipv4Addr(sa.sin_addr)), InetAddr::V6(ref sa) => IpAddr::V6(Ipv6Addr(sa.sin6_addr)), @@ -321,7 +324,7 @@ impl InetAddr { } /// Gets the port number associated with this socket address - pub fn port(&self) -> u16 { + pub const fn port(&self) -> u16 { match *self { InetAddr::V6(ref sa) => u16::from_be(sa.sin6_port), InetAddr::V4(ref sa) => u16::from_be(sa.sin_port), @@ -343,6 +346,7 @@ impl InetAddr { } } + #[deprecated(since = "0.23.0", note = "use .to_string() instead")] pub fn to_str(&self) -> String { format!("{}", self) } @@ -372,7 +376,7 @@ impl IpAddr { /// Create a new IpAddr that contains an IPv4 address. /// /// The result will represent the IP address a.b.c.d - pub fn new_v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr { + pub const fn new_v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr { IpAddr::V4(Ipv4Addr::new(a, b, c, d)) } @@ -381,7 +385,7 @@ impl IpAddr { /// The result will represent the IP address a:b:c:d:e:f #[allow(clippy::many_single_char_names)] #[allow(clippy::too_many_arguments)] - pub fn new_v6(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> IpAddr { + pub const fn new_v6(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> IpAddr { IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h)) } @@ -392,7 +396,7 @@ impl IpAddr { } } - pub fn to_std(&self) -> net::IpAddr { + pub const fn to_std(&self) -> net::IpAddr { match *self { IpAddr::V4(ref ip) => net::IpAddr::V4(ip.to_std()), IpAddr::V6(ref ip) => net::IpAddr::V6(ip.to_std()), @@ -420,11 +424,11 @@ pub struct Ipv4Addr(pub libc::in_addr); impl Ipv4Addr { #[allow(clippy::identity_op)] // More readable this way - pub fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { - let ip = ((u32::from(a) << 24) | - (u32::from(b) << 16) | - (u32::from(c) << 8) | - (u32::from(d) << 0)).to_be(); + pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr { + let ip = (((a as u32) << 24) | + ((b as u32) << 16) | + ((c as u32) << 8) | + ((d as u32) << 0)).to_be(); Ipv4Addr(libc::in_addr { s_addr: ip }) } @@ -436,16 +440,16 @@ impl Ipv4Addr { Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) } - pub fn any() -> Ipv4Addr { + pub const fn any() -> Ipv4Addr { Ipv4Addr(libc::in_addr { s_addr: libc::INADDR_ANY }) } - pub fn octets(self) -> [u8; 4] { + pub const fn octets(self) -> [u8; 4] { let bits = u32::from_be(self.0.s_addr); [(bits >> 24) as u8, (bits >> 16) as u8, (bits >> 8) as u8, bits as u8] } - pub fn to_std(self) -> net::Ipv4Addr { + pub const fn to_std(self) -> net::Ipv4Addr { let bits = self.octets(); net::Ipv4Addr::new(bits[0], bits[1], bits[2], bits[3]) } @@ -486,7 +490,7 @@ macro_rules! to_u16_array { impl Ipv6Addr { #[allow(clippy::many_single_char_names)] #[allow(clippy::too_many_arguments)] - pub fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { + pub const fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr { Ipv6Addr(libc::in6_addr{s6_addr: to_u8_array!(a,b,c,d,e,f,g,h)}) } @@ -496,11 +500,11 @@ impl Ipv6Addr { } /// Return the eight 16-bit segments that make up this address - pub fn segments(&self) -> [u16; 8] { + pub const fn segments(&self) -> [u16; 8] { to_u16_array!(self, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) } - pub fn to_std(&self) -> net::Ipv6Addr { + pub const fn to_std(&self) -> net::Ipv6Addr { let s = self.segments(); net::Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]) } @@ -513,15 +517,42 @@ impl fmt::Display for Ipv6Addr { } /// A wrapper around `sockaddr_un`. -/// -/// This also tracks the length of `sun_path` address (excluding -/// a terminating null), because it may not be null-terminated. For example, -/// unconnected and Linux abstract sockets are never null-terminated, and POSIX -/// does not require that `sun_len` include the terminating null even for normal -/// sockets. Note that the actual sockaddr length is greater by -/// `offset_of!(libc::sockaddr_un, sun_path)` #[derive(Clone, Copy, Debug)] -pub struct UnixAddr(pub libc::sockaddr_un, pub usize); +pub struct UnixAddr { + // INVARIANT: sun & path_len are valid as defined by docs for from_raw_parts + sun: libc::sockaddr_un, + path_len: usize, +} + +// linux man page unix(7) says there are 3 kinds of unix socket: +// pathname: addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1 +// unnamed: addrlen = sizeof(sa_family_t) +// abstract: addren > sizeof(sa_family_t), name = sun_path[..(addrlen - sizeof(sa_family_t))] +// +// what we call path_len = addrlen - offsetof(struct sockaddr_un, sun_path) +#[derive(PartialEq, Eq, Hash)] +enum UnixAddrKind<'a> { + Pathname(&'a Path), + Unnamed, + #[cfg(any(target_os = "android", target_os = "linux"))] + Abstract(&'a [u8]), +} +impl<'a> UnixAddrKind<'a> { + /// Safety: sun & path_len must be valid + unsafe fn get(sun: &'a libc::sockaddr_un, path_len: usize) -> Self { + if path_len == 0 { + return Self::Unnamed; + } + #[cfg(any(target_os = "android", target_os = "linux"))] + if sun.sun_path[0] == 0 { + let name = + slice::from_raw_parts(sun.sun_path.as_ptr().add(1) as *const u8, path_len - 1); + return Self::Abstract(name); + } + let pathname = slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, path_len - 1); + Self::Pathname(Path::new(OsStr::from_bytes(pathname))) + } +} impl UnixAddr { /// Create a new sockaddr_un representing a filesystem path. @@ -535,15 +566,15 @@ impl UnixAddr { let bytes = cstr.to_bytes(); - if bytes.len() > ret.sun_path.len() { - return Err(Error::from(Errno::ENAMETOOLONG)); + if bytes.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); } ptr::copy_nonoverlapping(bytes.as_ptr(), ret.sun_path.as_mut_ptr() as *mut u8, bytes.len()); - Ok(UnixAddr(ret, bytes.len())) + Ok(UnixAddr::from_raw_parts(ret, bytes.len() + 1)) } })? } @@ -562,8 +593,8 @@ impl UnixAddr { .. mem::zeroed() }; - if path.len() + 1 > ret.sun_path.len() { - return Err(Error::from(Errno::ENAMETOOLONG)); + if path.len() >= ret.sun_path.len() { + return Err(Errno::ENAMETOOLONG); } // Abstract addresses are represented by sun_path[0] == @@ -572,28 +603,39 @@ impl UnixAddr { ret.sun_path.as_mut_ptr().offset(1) as *mut u8, path.len()); - Ok(UnixAddr(ret, path.len() + 1)) + Ok(UnixAddr::from_raw_parts(ret, path.len() + 1)) + } + } + + /// Create a UnixAddr from a raw `sockaddr_un` struct and a size. `path_len` is the "addrlen" + /// of this address, but minus `offsetof(struct sockaddr_un, sun_path)`. Basically the length + /// of the data in `sun_path`. + /// + /// # Safety + /// This pair of sockaddr_un & path_len must be a valid unix addr, which means: + /// - path_len <= sockaddr_un.sun_path.len() + /// - if this is a unix addr with a pathname, sun.sun_path is a nul-terminated fs path and + /// sun.sun_path[path_len - 1] == 0 || sun.sun_path[path_len] == 0 + pub(crate) unsafe fn from_raw_parts(sun: libc::sockaddr_un, mut path_len: usize) -> UnixAddr { + if let UnixAddrKind::Pathname(_) = UnixAddrKind::get(&sun, path_len) { + if sun.sun_path[path_len - 1] != 0 { + assert_eq!(sun.sun_path[path_len], 0); + path_len += 1 + } } + UnixAddr { sun, path_len } } - fn sun_path(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.0.sun_path.as_ptr() as *const u8, self.1) } + fn kind(&self) -> UnixAddrKind<'_> { + // SAFETY: our sockaddr is always valid because of the invariant on the struct + unsafe { UnixAddrKind::get(&self.sun, self.path_len) } } /// If this address represents a filesystem path, return that path. pub fn path(&self) -> Option<&Path> { - if self.1 == 0 || self.0.sun_path[0] == 0 { - // unnamed or abstract - None - } else { - let p = self.sun_path(); - // POSIX only requires that `sun_len` be at least long enough to - // contain the pathname, and it need not be null-terminated. So we - // need to create a string that is the shorter of the - // null-terminated length or the full length. - let ptr = &self.0.sun_path as *const libc::c_char; - let reallen = unsafe { libc::strnlen(ptr, p.len()) }; - Some(Path::new(<OsStr as OsStrExt>::from_bytes(&p[..reallen]))) + match self.kind() { + UnixAddrKind::Pathname(path) => Some(path), + _ => None, } } @@ -603,31 +645,55 @@ impl UnixAddr { /// leading null byte. `None` is returned for unnamed or path-backed sockets. #[cfg(any(target_os = "android", target_os = "linux"))] pub fn as_abstract(&self) -> Option<&[u8]> { - if self.1 >= 1 && self.0.sun_path[0] == 0 { - Some(&self.sun_path()[1..]) - } else { - // unnamed or filesystem path - None + match self.kind() { + UnixAddrKind::Abstract(name) => Some(name), + _ => None, } } + + /// Returns the addrlen of this socket - `offsetof(struct sockaddr_un, sun_path)` + #[inline] + pub fn path_len(&self) -> usize { + self.path_len + } + /// Returns a pointer to the raw `sockaddr_un` struct + #[inline] + pub fn as_ptr(&self) -> *const libc::sockaddr_un { + &self.sun + } + /// Returns a mutable pointer to the raw `sockaddr_un` struct + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut libc::sockaddr_un { + &mut self.sun + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +fn fmt_abstract(abs: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + use fmt::Write; + f.write_str("@\"")?; + for &b in abs { + use fmt::Display; + char::from(b).escape_default().fmt(f)?; + } + f.write_char('"')?; + Ok(()) } impl fmt::Display for UnixAddr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.1 == 0 { - f.write_str("<unbound UNIX socket>") - } else if let Some(path) = self.path() { - path.display().fmt(f) - } else { - let display = String::from_utf8_lossy(&self.sun_path()[1..]); - write!(f, "@{}", display) + match self.kind() { + UnixAddrKind::Pathname(path) => path.display().fmt(f), + UnixAddrKind::Unnamed => f.pad("<unbound UNIX socket>"), + #[cfg(any(target_os = "android", target_os = "linux"))] + UnixAddrKind::Abstract(name) => fmt_abstract(name, f), } } } impl PartialEq for UnixAddr { fn eq(&self, other: &UnixAddr) -> bool { - self.sun_path() == other.sun_path() + self.kind() == other.kind() } } @@ -635,12 +701,13 @@ impl Eq for UnixAddr {} impl Hash for UnixAddr { fn hash<H: Hasher>(&self, s: &mut H) { - ( self.0.sun_family, self.sun_path() ).hash(s) + self.kind().hash(s) } } /// Represents a socket address #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] pub enum SockAddr { Inet(InetAddr), Unix(UnixAddr), @@ -686,7 +753,7 @@ impl SockAddr { #[cfg(any(target_os = "ios", target_os = "macos"))] pub fn new_sys_control(sockfd: RawFd, name: &str, unit: u32) -> Result<SockAddr> { - SysControlAddr::from_name(sockfd, name, unit).map(|a| SockAddr::SysControl(a)) + SysControlAddr::from_name(sockfd, name, unit).map(SockAddr::SysControl) } #[cfg(any(target_os = "android", target_os = "linux"))] @@ -720,6 +787,7 @@ impl SockAddr { } } + #[deprecated(since = "0.23.0", note = "use .to_string() instead")] pub fn to_str(&self) -> String { format!("{}", self) } @@ -801,12 +869,12 @@ impl SockAddr { }, mem::size_of_val(addr) as libc::socklen_t ), - SockAddr::Unix(UnixAddr(ref addr, len)) => ( + SockAddr::Unix(UnixAddr { ref sun, path_len }) => ( // This cast is always allowed in C unsafe { - &*(addr as *const libc::sockaddr_un as *const libc::sockaddr) + &*(sun as *const libc::sockaddr_un as *const libc::sockaddr) }, - (len + offset_of!(libc::sockaddr_un, sun_path)) as libc::socklen_t + (path_len + offset_of!(libc::sockaddr_un, sun_path)) as libc::socklen_t ), #[cfg(any(target_os = "android", target_os = "linux"))] SockAddr::Netlink(NetlinkAddr(ref sa)) => ( @@ -913,11 +981,11 @@ pub mod netlink { NetlinkAddr(addr) } - pub fn pid(&self) -> u32 { + pub const fn pid(&self) -> u32 { self.0.nl_pid } - pub fn groups(&self) -> u32 { + pub const fn groups(&self) -> u32 { self.0.nl_groups } } @@ -998,7 +1066,7 @@ pub mod sys_control { use libc::{self, c_uchar}; use std::{fmt, mem}; use std::os::unix::io::RawFd; - use crate::{Errno, Error, Result}; + use crate::{Errno, Result}; // FIXME: Move type into `libc` #[repr(C)] @@ -1009,7 +1077,7 @@ pub mod sys_control { pub ctl_name: [c_uchar; MAX_KCTL_NAME], } - const CTL_IOC_MAGIC: u8 = 'N' as u8; + const CTL_IOC_MAGIC: u8 = b'N'; const CTL_IOC_INFO: u8 = 3; const MAX_KCTL_NAME: usize = 96; @@ -1020,7 +1088,7 @@ pub mod sys_control { pub struct SysControlAddr(pub libc::sockaddr_ctl); impl SysControlAddr { - pub fn new(id: u32, unit: u32) -> SysControlAddr { + pub const fn new(id: u32, unit: u32) -> SysControlAddr { let addr = libc::sockaddr_ctl { sc_len: mem::size_of::<libc::sockaddr_ctl>() as c_uchar, sc_family: AddressFamily::System as c_uchar, @@ -1035,7 +1103,7 @@ pub mod sys_control { pub fn from_name(sockfd: RawFd, name: &str, unit: u32) -> Result<SysControlAddr> { if name.len() > MAX_KCTL_NAME { - return Err(Error::from(Errno::ENAMETOOLONG)); + return Err(Errno::ENAMETOOLONG); } let mut ctl_name = [0; MAX_KCTL_NAME]; @@ -1047,11 +1115,11 @@ pub mod sys_control { Ok(SysControlAddr::new(info.ctl_id, unit)) } - pub fn id(&self) -> u32 { + pub const fn id(&self) -> u32 { self.0.sc_id } - pub fn unit(&self) -> u32 { + pub const fn unit(&self) -> u32 { self.0.sc_unit } } @@ -1372,11 +1440,8 @@ mod tests { let name = String::from("nix\0abstract\0test"); let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap(); - let sun_path1 = addr.sun_path(); - let sun_path2 = [0u8, 110, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101, 115, 116]; - assert_eq!(sun_path1.len(), sun_path2.len()); - for i in 0..sun_path1.len() { - assert_eq!(sun_path1[i], sun_path2[i]); - } + let sun_path1 = unsafe { &(*addr.as_ptr()).sun_path[..addr.path_len()] }; + let sun_path2 = [0, 110, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101, 115, 116]; + assert_eq!(sun_path1, sun_path2); } } diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index da5573c..97eea3d 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -2,7 +2,7 @@ //! //! [Further reading](https://man7.org/linux/man-pages/man7/socket.7.html) use cfg_if::cfg_if; -use crate::{Error, Result, errno::Errno}; +use crate::{Result, errno::Errno}; use libc::{self, c_void, c_int, iovec, socklen_t, size_t, CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA, CMSG_LEN}; use memoffset::offset_of; @@ -14,6 +14,7 @@ use crate::sys::time::TimeVal; use crate::sys::uio::IoVec; mod addr; +#[deny(missing_docs)] pub mod sockopt; /* @@ -70,6 +71,7 @@ pub use libc::{c_uint, CMSG_SPACE}; /// when creating a socket with [`socket()`](fn.socket.html) #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(i32)] +#[non_exhaustive] pub enum SockType { /// Provides sequenced, reliable, two-way, connection- /// based byte streams. An out-of-band data transmission @@ -94,6 +96,7 @@ pub enum SockType { /// to specify the protocol to use. #[repr(i32)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] pub enum SockProtocol { /// TCP protocol ([ip(7)](https://man7.org/linux/man-pages/man7/ip.7.html)) Tcp = libc::IPPROTO_TCP, @@ -308,9 +311,9 @@ cfg_if! { } } - impl Into<libc::ucred> for UnixCredentials { - fn into(self) -> libc::ucred { - self.0 + impl From<UnixCredentials> for libc::ucred { + fn from(uc: UnixCredentials) -> Self { + uc.0 } } } else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] { @@ -356,6 +359,38 @@ cfg_if! { } } +cfg_if!{ + if #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios" + ))] { + /// Return type of [`LocalPeerCred`](crate::sys::socket::sockopt::LocalPeerCred) + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct XuCred(libc::xucred); + + impl XuCred { + /// Structure layout version + pub fn version(&self) -> u32 { + self.0.cr_version + } + + /// Effective user ID + pub fn uid(&self) -> libc::uid_t { + self.0.cr_uid + } + + /// Returns a list of group identifiers (the first one being the + /// effective GID) + pub fn groups(&self) -> &[libc::gid_t] { + &self.0.cr_groups + } + } + } +} + /// Request for multicast socket operations /// /// This is a wrapper type around `ip_mreq`. @@ -384,7 +419,7 @@ pub struct Ipv6MembershipRequest(libc::ipv6_mreq); impl Ipv6MembershipRequest { /// Instantiate a new `Ipv6MembershipRequest` - pub fn new(group: Ipv6Addr) -> Self { + pub const fn new(group: Ipv6Addr) -> Self { Ipv6MembershipRequest(libc::ipv6_mreq { ipv6mr_multiaddr: group.0, ipv6mr_interface: 0, @@ -490,16 +525,14 @@ impl<'a> Iterator for CmsgIterator<'a> { // // See https://github.com/nix-rust/nix/issues/999 #[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] pub enum ControlMessageOwned { - /// Received version of - /// [`ControlMessage::ScmRights`][#enum.ControlMessage.html#variant.ScmRights] + /// Received version of [`ControlMessage::ScmRights`] ScmRights(Vec<RawFd>), - /// Received version of - /// [`ControlMessage::ScmCredentials`][#enum.ControlMessage.html#variant.ScmCredentials] + /// Received version of [`ControlMessage::ScmCredentials`] #[cfg(any(target_os = "android", target_os = "linux"))] ScmCredentials(UnixCredentials), - /// Received version of - /// [`ControlMessage::ScmCreds`][#enum.ControlMessage.html#variant.ScmCreds] + /// Received version of [`ControlMessage::ScmCreds`] #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] ScmCreds(UnixCredentials), /// A message of type `SCM_TIMESTAMP`, containing the time the @@ -621,6 +654,13 @@ pub enum ControlMessageOwned { #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] RxqOvfl(u32), + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + Ipv4RecvErr(libc::sock_extended_err, Option<sockaddr_in>), + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + Ipv6RecvErr(libc::sock_extended_err, Option<sockaddr_in6>), + /// Catch-all variant for unimplemented cmsg types. #[doc(hidden)] Unknown(UnknownCmsg), @@ -724,6 +764,16 @@ impl ControlMessageOwned { let drop_counter = ptr::read_unaligned(p as *const u32); ControlMessageOwned::RxqOvfl(drop_counter) }, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::IPPROTO_IP, libc::IP_RECVERR) => { + let (err, addr) = Self::recv_err_helper::<sockaddr_in>(p, len); + ControlMessageOwned::Ipv4RecvErr(err, addr) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => { + let (err, addr) = Self::recv_err_helper::<sockaddr_in6>(p, len); + ControlMessageOwned::Ipv6RecvErr(err, addr) + }, (_, _) => { let sl = slice::from_raw_parts(p, len); let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl)); @@ -731,6 +781,24 @@ impl ControlMessageOwned { } } } + + #[cfg(any(target_os = "android", target_os = "linux"))] + unsafe fn recv_err_helper<T>(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option<T>) { + let ee = p as *const libc::sock_extended_err; + let err = ptr::read_unaligned(ee); + + // For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len] + // CMSG_DATA buffer. For local errors, there is no address included in the control + // message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to + // validate that the address object is in-bounds before we attempt to copy it. + let addrp = libc::SO_EE_OFFENDER(ee) as *const T; + + if addrp.offset(1) as usize - (p as usize) > len { + (err, None) + } else { + (err, Some(ptr::read_unaligned(addrp))) + } + } } /// A type-safe zero-copy wrapper around a single control message, as used wih @@ -739,6 +807,7 @@ impl ControlMessageOwned { /// /// [Further reading](https://man7.org/linux/man-pages/man3/cmsg.3.html) #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] pub enum ControlMessage<'a> { /// A message of type `SCM_RIGHTS`, containing an array of file /// descriptors passed between processes. @@ -772,7 +841,7 @@ pub enum ControlMessage<'a> { /// /// Credentials are always overwritten by the kernel, so this variant does have /// any data, unlike the receive-side - /// [`ControlMessageOwned::ScmCreds`][#enum.ControlMessageOwned.html#variant.ScmCreds]. + /// [`ControlMessageOwned::ScmCreds`]. /// /// For further information, please refer to the /// [`unix(4)`](https://www.freebsd.org/cgi/man.cgi?query=unix) man page. @@ -1231,6 +1300,7 @@ pub struct RecvMmsgData<'a, I> target_os = "freebsd", target_os = "netbsd", ))] +#[allow(clippy::needless_collect)] // Complicated false positive pub fn recvmmsg<'a, I>( fd: RawFd, data: impl std::iter::IntoIterator<Item=&'a mut RecvMmsgData<'a, I>, @@ -1646,21 +1716,19 @@ pub fn send(fd: RawFd, buf: &[u8], flags: MsgFlags) -> Result<usize> { * */ -/// Represents a socket option that can be accessed or set. Used as an argument -/// to `getsockopt` +/// Represents a socket option that can be retrieved. pub trait GetSockOpt : Copy { type Val; - #[doc(hidden)] + /// Look up the value of this socket option on the given socket. fn get(&self, fd: RawFd) -> Result<Self::Val>; } -/// Represents a socket option that can be accessed or set. Used as an argument -/// to `setsockopt` +/// Represents a socket option that can be set. pub trait SetSockOpt : Clone { type Val; - #[doc(hidden)] + /// Set the value of this socket option on the given socket. fn set(&self, fd: RawFd, val: &Self::Val) -> Result<()>; } @@ -1744,21 +1812,21 @@ pub fn sockaddr_storage_to_addr( addr: &sockaddr_storage, len: usize) -> Result<SockAddr> { - assert!(len <= mem::size_of::<sockaddr_un>()); + assert!(len <= mem::size_of::<sockaddr_storage>()); if len < mem::size_of_val(&addr.ss_family) { - return Err(Error::from(Errno::ENOTCONN)); + return Err(Errno::ENOTCONN); } match c_int::from(addr.ss_family) { libc::AF_INET => { - assert_eq!(len as usize, mem::size_of::<sockaddr_in>()); + assert!(len as usize >= mem::size_of::<sockaddr_in>()); let sin = unsafe { *(addr as *const sockaddr_storage as *const sockaddr_in) }; Ok(SockAddr::Inet(InetAddr::V4(sin))) } libc::AF_INET6 => { - assert_eq!(len as usize, mem::size_of::<sockaddr_in6>()); + assert!(len as usize >= mem::size_of::<sockaddr_in6>()); let sin6 = unsafe { *(addr as *const _ as *const sockaddr_in6) }; @@ -1766,18 +1834,18 @@ pub fn sockaddr_storage_to_addr( } libc::AF_UNIX => { let pathlen = len - offset_of!(sockaddr_un, sun_path); - let sun = unsafe { - *(addr as *const _ as *const sockaddr_un) - }; - Ok(SockAddr::Unix(UnixAddr(sun, pathlen))) + unsafe { + let sun = *(addr as *const _ as *const sockaddr_un); + Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, pathlen))) + } } #[cfg(any(target_os = "android", target_os = "linux"))] libc::AF_PACKET => { use libc::sockaddr_ll; + // Don't assert anything about the size. // Apparently the Linux kernel can return smaller sizes when // the value in the last element of sockaddr_ll (`sll_addr`) is // smaller than the declared size of that field - assert!(len as usize <= mem::size_of::<sockaddr_ll>()); let sll = unsafe { *(addr as *const _ as *const sockaddr_ll) }; diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index e2f2caf..fcb4be8 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -1,3 +1,4 @@ +//! Socket options as used by `setsockopt` and `getsockopt`. use cfg_if::cfg_if; use super::{GetSockOpt, SetSockOpt}; use crate::Result; @@ -30,7 +31,7 @@ const TCP_CA_NAME_MAX: usize = 16; /// # Arguments /// /// * `$name:ident`: name of the type you want to implement `SetSockOpt` for. -/// * `$level:path` : socket layer, or a `protocol level`: could be *raw sockets* +/// * `$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. @@ -41,7 +42,7 @@ const TCP_CA_NAME_MAX: usize = 16; /// * 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:path, $flag:path, $ty:ty, $setter:ty) => { + ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => { impl SetSockOpt for $name { type Val = $ty; @@ -82,7 +83,7 @@ macro_rules! setsockopt_impl { /// * 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:path, $flag:path, $ty:ty, $getter:ty) => { + ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => { impl GetSockOpt for $name { type Val = $ty; @@ -117,7 +118,7 @@ macro_rules! getsockopt_impl { /// * `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:path` : socket layer, or a `protocol level`: could be *raw sockets* +/// * `$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. @@ -128,73 +129,99 @@ macro_rules! getsockopt_impl { /// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`. /// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`. macro_rules! sockopt_impl { - (GetOnly, $name:ident, $level:path, $flag:path, bool) => { - sockopt_impl!(GetOnly, $name, $level, $flag, bool, GetBool); + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, bool, GetBool); }; - (GetOnly, $name:ident, $level:path, $flag:path, u8) => { - sockopt_impl!(GetOnly, $name, $level, $flag, u8, GetU8); + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, GetU8); }; - (GetOnly, $name:ident, $level:path, $flag:path, usize) => { - sockopt_impl!(GetOnly, $name, $level, $flag, usize, GetUsize); + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, usize, GetUsize); }; - (SetOnly, $name:ident, $level:path, $flag:path, bool) => { - sockopt_impl!(SetOnly, $name, $level, $flag, bool, SetBool); + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, bool, SetBool); }; - (SetOnly, $name:ident, $level:path, $flag:path, u8) => { - sockopt_impl!(SetOnly, $name, $level, $flag, u8, SetU8); + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, SetU8); }; - (SetOnly, $name:ident, $level:path, $flag:path, usize) => { - sockopt_impl!(SetOnly, $name, $level, $flag, usize, SetUsize); + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, usize, SetUsize); }; - (Both, $name:ident, $level:path, $flag:path, bool) => { - sockopt_impl!(Both, $name, $level, $flag, bool, GetBool, SetBool); + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, bool, GetBool, SetBool); }; - (Both, $name:ident, $level:path, $flag:path, u8) => { - sockopt_impl!(Both, $name, $level, $flag, u8, GetU8, SetU8); + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, u8, GetU8, SetU8); }; - (Both, $name:ident, $level:path, $flag:path, usize) => { - sockopt_impl!(Both, $name, $level, $flag, usize, GetUsize, SetUsize); + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, usize, GetUsize, SetUsize); }; - (Both, $name:ident, $level:path, $flag:path, OsString<$array:ty>) => { - sockopt_impl!(Both, $name, $level, $flag, OsString, GetOsString<$array>, SetOsString); + ($(#[$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 */ - (GetOnly, $name:ident, $level:path, $flag:path, $ty:ty) => { - sockopt_impl!(GetOnly, $name, $level, $flag, $ty, GetStruct<$ty>); + ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) => + { + sockopt_impl!($(#[$attr])* + $name, GetOnly, $level, $flag, $ty, GetStruct<$ty>); }; - (GetOnly, $name:ident, $level:path, $flag:path, $ty:ty, $getter: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); }; - (SetOnly, $name:ident, $level:path, $flag:path, $ty:ty) => { - sockopt_impl!(SetOnly, $name, $level, $flag, $ty, SetStruct<$ty>); + ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) => + { + sockopt_impl!($(#[$attr])* + $name, SetOnly, $level, $flag, $ty, SetStruct<$ty>); }; - (SetOnly, $name:ident, $level:path, $flag:path, $ty:ty, $setter: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); }; - (Both, $name:ident, $level:path, $flag:path, $ty:ty, $getter:ty, $setter:ty) => { + ($(#[$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; @@ -202,8 +229,10 @@ macro_rules! sockopt_impl { getsockopt_impl!($name, $level, $flag, $ty, $getter); }; - (Both, $name:ident, $level:path, $flag:path, $ty:ty) => { - sockopt_impl!(Both, $name, $level, $flag, $ty, GetStruct<$ty>, SetStruct<$ty>); + ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => { + sockopt_impl!($(#[$attr])* + $name, Both, $level, $flag, $ty, GetStruct<$ty>, + SetStruct<$ty>); }; } @@ -213,17 +242,45 @@ macro_rules! sockopt_impl { * */ -sockopt_impl!(Both, ReuseAddr, libc::SOL_SOCKET, libc::SO_REUSEADDR, bool); +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!(Both, ReusePort, libc::SOL_SOCKET, libc::SO_REUSEPORT, bool); -sockopt_impl!(Both, TcpNoDelay, libc::IPPROTO_TCP, libc::TCP_NODELAY, bool); -sockopt_impl!(Both, Linger, libc::SOL_SOCKET, libc::SO_LINGER, libc::linger); -sockopt_impl!(SetOnly, IpAddMembership, libc::IPPROTO_IP, libc::IP_ADD_MEMBERSHIP, super::IpMembershipRequest); -sockopt_impl!(SetOnly, IpDropMembership, libc::IPPROTO_IP, libc::IP_DROP_MEMBERSHIP, super::IpMembershipRequest); +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!(SetOnly, Ipv6AddMembership, libc::IPPROTO_IPV6, libc::IPV6_ADD_MEMBERSHIP, super::Ipv6MembershipRequest); - sockopt_impl!(SetOnly, Ipv6DropMembership, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, super::Ipv6MembershipRequest); + 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", @@ -232,64 +289,185 @@ cfg_if! { target_os = "netbsd", target_os = "openbsd", target_os = "solaris"))] { - sockopt_impl!(SetOnly, Ipv6AddMembership, libc::IPPROTO_IPV6, libc::IPV6_JOIN_GROUP, super::Ipv6MembershipRequest); - sockopt_impl!(SetOnly, Ipv6DropMembership, libc::IPPROTO_IPV6, libc::IPV6_LEAVE_GROUP, super::Ipv6MembershipRequest); + 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!(Both, IpMulticastTtl, libc::IPPROTO_IP, libc::IP_MULTICAST_TTL, u8); -sockopt_impl!(Both, IpMulticastLoop, libc::IPPROTO_IP, libc::IP_MULTICAST_LOOP, bool); +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!(Both, IpFreebind, libc::IPPROTO_IP, libc::IP_FREEBIND, bool); -sockopt_impl!(Both, ReceiveTimeout, libc::SOL_SOCKET, libc::SO_RCVTIMEO, TimeVal); -sockopt_impl!(Both, SendTimeout, libc::SOL_SOCKET, libc::SO_SNDTIMEO, TimeVal); -sockopt_impl!(Both, Broadcast, libc::SOL_SOCKET, libc::SO_BROADCAST, bool); -sockopt_impl!(Both, OobInline, libc::SOL_SOCKET, libc::SO_OOBINLINE, bool); -sockopt_impl!(GetOnly, SocketError, libc::SOL_SOCKET, libc::SO_ERROR, i32); -sockopt_impl!(Both, KeepAlive, libc::SOL_SOCKET, libc::SO_KEEPALIVE, bool); +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!(GetOnly, PeerCredentials, libc::SOL_SOCKET, libc::SO_PEERCRED, super::UnixCredentials); +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!(Both, TcpKeepAlive, libc::IPPROTO_TCP, libc::TCP_KEEPALIVE, u32); +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!(Both, TcpKeepIdle, libc::IPPROTO_TCP, libc::TCP_KEEPIDLE, u32); +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!(Both, TcpKeepCount, libc::IPPROTO_TCP, libc::TCP_KEEPCNT, u32); +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!(Both, TcpKeepInterval, libc::IPPROTO_TCP, libc::TCP_KEEPINTVL, u32); +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!(Both, TcpUserTimeout, libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT, u32); -sockopt_impl!(Both, RcvBuf, libc::SOL_SOCKET, libc::SO_RCVBUF, usize); -sockopt_impl!(Both, SndBuf, libc::SOL_SOCKET, libc::SO_SNDBUF, usize); +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!(SetOnly, RcvBufForce, libc::SOL_SOCKET, libc::SO_RCVBUFFORCE, usize); +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!(SetOnly, SndBufForce, libc::SOL_SOCKET, libc::SO_SNDBUFFORCE, usize); -sockopt_impl!(GetOnly, SockType, libc::SOL_SOCKET, libc::SO_TYPE, super::SockType); -sockopt_impl!(GetOnly, AcceptConn, libc::SOL_SOCKET, libc::SO_ACCEPTCONN, bool); +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!(Both, BindToDevice, libc::SOL_SOCKET, libc::SO_BINDTODEVICE, OsString<[u8; libc::IFNAMSIZ]>); +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!(GetOnly, OriginalDst, libc::SOL_IP, libc::SO_ORIGINAL_DST, libc::sockaddr_in); -sockopt_impl!(Both, ReceiveTimestamp, libc::SOL_SOCKET, libc::SO_TIMESTAMP, bool); +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!(Both, ReceiveTimestampns, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, bool); +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!(Both, IpTransparent, libc::SOL_IP, libc::IP_TRANSPARENT, bool); +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!(Both, BindAny, libc::SOL_SOCKET, libc::SO_BINDANY, bool); +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!(Both, BindAny, libc::IPPROTO_IP, libc::IP_BINDANY, bool); +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!(Both, Mark, libc::SOL_SOCKET, libc::SO_MARK, u32); +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!(Both, PassCred, libc::SOL_SOCKET, libc::SO_PASSCRED, bool); +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!(Both, TcpCongestion, libc::IPPROTO_TCP, libc::TCP_CONGESTION, OsString<[u8; TCP_CA_NAME_MAX]>); +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", @@ -297,7 +475,10 @@ sockopt_impl!(Both, TcpCongestion, libc::IPPROTO_TCP, libc::TCP_CONGESTION, OsSt target_os = "macos", target_os = "netbsd", ))] -sockopt_impl!(Both, Ipv4PacketInfo, libc::IPPROTO_IP, libc::IP_PKTINFO, bool); +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", @@ -307,7 +488,10 @@ sockopt_impl!(Both, Ipv4PacketInfo, libc::IPPROTO_IP, libc::IP_PKTINFO, bool); target_os = "netbsd", target_os = "openbsd", ))] -sockopt_impl!(Both, Ipv6RecvPacketInfo, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, bool); +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", @@ -315,7 +499,10 @@ sockopt_impl!(Both, Ipv6RecvPacketInfo, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTIN target_os = "netbsd", target_os = "openbsd", ))] -sockopt_impl!(Both, Ipv4RecvIf, libc::IPPROTO_IP, libc::IP_RECVIF, bool); +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", @@ -323,14 +510,49 @@ sockopt_impl!(Both, Ipv4RecvIf, libc::IPPROTO_IP, libc::IP_RECVIF, bool); target_os = "netbsd", target_os = "openbsd", ))] -sockopt_impl!(Both, Ipv4RecvDstAddr, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, bool); +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!(Both, UdpGsoSegment, libc::SOL_UDP, libc::UDP_SEGMENT, libc::c_int); +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!(Both, UdpGroSegment, libc::IPPROTO_UDP, libc::UDP_GRO, bool); +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!(Both, RxqOvfl, libc::SOL_SOCKET, libc::SO_RXQ_OVFL, libc::c_int); - +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; @@ -353,6 +575,8 @@ impl SetSockOpt for AlgSetAeadAuthSize { } } +#[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>); @@ -387,9 +611,9 @@ impl<T> SetSockOpt for AlgSetKey<T> where T: AsRef<[u8]> + Clone { */ /// Helper trait that describes what is expected from a `GetSockOpt` getter. -unsafe trait Get<T> { +trait Get<T> { /// Returns an uninitialized value. - unsafe fn uninit() -> Self; + 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; @@ -401,7 +625,7 @@ unsafe trait Get<T> { } /// Helper trait that describes what is expected from a `SetSockOpt` setter. -unsafe trait Set<'a, T> { +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 @@ -418,8 +642,8 @@ struct GetStruct<T> { val: MaybeUninit<T>, } -unsafe impl<T> Get<T> for GetStruct<T> { - unsafe fn uninit() -> Self { +impl<T> Get<T> for GetStruct<T> { + fn uninit() -> Self { GetStruct { len: mem::size_of::<T>() as socklen_t, val: MaybeUninit::uninit(), @@ -445,7 +669,7 @@ struct SetStruct<'a, T: 'static> { ptr: &'a T, } -unsafe impl<'a, T> Set<'a, T> for SetStruct<'a, T> { +impl<'a, T> Set<'a, T> for SetStruct<'a, T> { fn new(ptr: &'a T) -> SetStruct<'a, T> { SetStruct { ptr } } @@ -465,8 +689,8 @@ struct GetBool { val: MaybeUninit<c_int>, } -unsafe impl Get<bool> for GetBool { - unsafe fn uninit() -> Self { +impl Get<bool> for GetBool { + fn uninit() -> Self { GetBool { len: mem::size_of::<c_int>() as socklen_t, val: MaybeUninit::uninit(), @@ -492,7 +716,7 @@ struct SetBool { val: c_int, } -unsafe impl<'a> Set<'a, bool> for SetBool { +impl<'a> Set<'a, bool> for SetBool { fn new(val: &'a bool) -> SetBool { SetBool { val: if *val { 1 } else { 0 } } } @@ -512,8 +736,8 @@ struct GetU8 { val: MaybeUninit<u8>, } -unsafe impl Get<u8> for GetU8 { - unsafe fn uninit() -> Self { +impl Get<u8> for GetU8 { + fn uninit() -> Self { GetU8 { len: mem::size_of::<u8>() as socklen_t, val: MaybeUninit::uninit(), @@ -539,7 +763,7 @@ struct SetU8 { val: u8, } -unsafe impl<'a> Set<'a, u8> for SetU8 { +impl<'a> Set<'a, u8> for SetU8 { fn new(val: &'a u8) -> SetU8 { SetU8 { val: *val as u8 } } @@ -559,8 +783,8 @@ struct GetUsize { val: MaybeUninit<c_int>, } -unsafe impl Get<usize> for GetUsize { - unsafe fn uninit() -> Self { +impl Get<usize> for GetUsize { + fn uninit() -> Self { GetUsize { len: mem::size_of::<c_int>() as socklen_t, val: MaybeUninit::uninit(), @@ -586,7 +810,7 @@ struct SetUsize { val: c_int, } -unsafe impl<'a> Set<'a, usize> for SetUsize { +impl<'a> Set<'a, usize> for SetUsize { fn new(val: &'a usize) -> SetUsize { SetUsize { val: *val as c_int } } @@ -606,8 +830,8 @@ struct GetOsString<T: AsMut<[u8]>> { val: MaybeUninit<T>, } -unsafe impl<T: AsMut<[u8]>> Get<OsString> for GetOsString<T> { - unsafe fn uninit() -> Self { +impl<T: AsMut<[u8]>> Get<OsString> for GetOsString<T> { + fn uninit() -> Self { GetOsString { len: mem::size_of::<T>() as socklen_t, val: MaybeUninit::uninit(), @@ -634,7 +858,7 @@ struct SetOsString<'a> { val: &'a OsStr, } -unsafe impl<'a> Set<'a, OsString> for SetOsString<'a> { +impl<'a> Set<'a, OsString> for SetOsString<'a> { fn new(val: &'a OsString) -> SetOsString { SetOsString { val: val.as_os_str() } } diff --git a/src/sys/stat.rs b/src/sys/stat.rs index 15451e7..c8f1041 100644 --- a/src/sys/stat.rs +++ b/src/sys/stat.rs @@ -9,6 +9,7 @@ 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; @@ -22,6 +23,7 @@ libc_bitflags!( ); libc_bitflags! { + /// "File mode / permissions" flags. pub struct Mode: mode_t { S_IRWXU; S_IRUSR; @@ -41,30 +43,45 @@ libc_bitflags! { } } +/// 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) - } + 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 fn major(dev: dev_t) -> u64 { +pub const fn major(dev: dev_t) -> u64 { ((dev >> 32) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff) } #[cfg(target_os = "linux")] -pub fn minor(dev: dev_t) -> u64 { +pub const fn minor(dev: dev_t) -> u64 { ((dev >> 12) & 0xffff_ff00) | ((dev ) & 0x0000_00ff) } #[cfg(target_os = "linux")] -pub fn makedev(major: u64, minor: u64) -> dev_t { +pub const fn makedev(major: u64, minor: u64) -> dev_t { ((major & 0xffff_f000) << 32) | ((major & 0x0000_0fff) << 8) | ((minor & 0xffff_ff00) << 12) | @@ -239,6 +256,7 @@ pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> { } /// Flags for `utimensat` function. +// TODO: replace with fcntl::AtFlags #[derive(Clone, Copy, Debug)] pub enum UtimensatFlags { FollowSymlink, diff --git a/src/sys/statfs.rs b/src/sys/statfs.rs index 27b7259..829be57 100644 --- a/src/sys/statfs.rs +++ b/src/sys/statfs.rs @@ -1,3 +1,6 @@ +//! 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; @@ -6,11 +9,14 @@ 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); @@ -26,6 +32,7 @@ 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", @@ -36,63 +43,94 @@ type fs_type_t = libc::__fsword_t; #[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); @@ -451,6 +489,14 @@ impl Debug for Statfs { } } +/// 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(); @@ -459,6 +505,14 @@ pub fn statfs<P: ?Sized + NixPath>(path: &P) -> Result<Statfs> { } } +/// 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(); diff --git a/src/sys/statvfs.rs b/src/sys/statvfs.rs index 508fa8d..15e7a7d 100644 --- a/src/sys/statvfs.rs +++ b/src/sys/statvfs.rs @@ -150,7 +150,7 @@ mod test { #[test] fn statvfs_call() { - statvfs("/".as_bytes()).unwrap(); + statvfs(&b"/"[..]).unwrap(); } #[test] diff --git a/src/sys/termios.rs b/src/sys/termios.rs index 36a3601..01d4608 100644 --- a/src/sys/termios.rs +++ b/src/sys/termios.rs @@ -152,11 +152,11 @@ //! # } //! ``` use cfg_if::cfg_if; -use crate::{Error, Result}; +use crate::Result; use crate::errno::Errno; use libc::{self, c_int, tcflag_t}; use std::cell::{Ref, RefCell}; -use std::convert::{From, TryFrom}; +use std::convert::From; use std::mem; use std::os::unix::io::RawFd; @@ -256,6 +256,7 @@ libc_enum!{ /// B0 is special and will disable the port. #[cfg_attr(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64"), repr(u64))] #[cfg_attr(not(all(any(target_os = "ios", target_os = "macos"), target_pointer_width = "64")), repr(u32))] + #[non_exhaustive] pub enum BaudRate { B0, B50, @@ -339,119 +340,7 @@ libc_enum!{ #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] B4000000, } -} - -impl TryFrom<libc::speed_t> for BaudRate { - type Error = Error; - - fn try_from(s: libc::speed_t) -> Result<BaudRate> { - use libc::{B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, - B9600, B19200, B38400, B57600, B115200, B230400}; - #[cfg(any(target_os = "android", target_os = "linux"))] - use libc::{B500000, B576000, B1000000, B1152000, B1500000, B2000000}; - #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] - use libc::{B2500000, B3000000, B3500000, B4000000}; - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - use libc::{B7200, B14400, B28800, B76800}; - #[cfg(any(target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd"))] - use libc::{B460800, B921600}; - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - use libc::{B153600, B307200, B460800, B921600}; - - match s { - B0 => Ok(BaudRate::B0), - B50 => Ok(BaudRate::B50), - B75 => Ok(BaudRate::B75), - B110 => Ok(BaudRate::B110), - B134 => Ok(BaudRate::B134), - B150 => Ok(BaudRate::B150), - B200 => Ok(BaudRate::B200), - B300 => Ok(BaudRate::B300), - B600 => Ok(BaudRate::B600), - B1200 => Ok(BaudRate::B1200), - B1800 => Ok(BaudRate::B1800), - B2400 => Ok(BaudRate::B2400), - B4800 => Ok(BaudRate::B4800), - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - B7200 => Ok(BaudRate::B7200), - B9600 => Ok(BaudRate::B9600), - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - B14400 => Ok(BaudRate::B14400), - B19200 => Ok(BaudRate::B19200), - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - B28800 => Ok(BaudRate::B28800), - B38400 => Ok(BaudRate::B38400), - B57600 => Ok(BaudRate::B57600), - #[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd"))] - B76800 => Ok(BaudRate::B76800), - B115200 => Ok(BaudRate::B115200), - #[cfg(any(target_os = "illumos", - target_os = "solaris"))] - B153600 => Ok(BaudRate::B153600), - B230400 => Ok(BaudRate::B230400), - #[cfg(any(target_os = "illumos", - target_os = "solaris"))] - B307200 => Ok(BaudRate::B307200), - #[cfg(any(target_os = "android", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "solaris"))] - B460800 => Ok(BaudRate::B460800), - #[cfg(any(target_os = "android", target_os = "linux"))] - B500000 => Ok(BaudRate::B500000), - #[cfg(any(target_os = "android", target_os = "linux"))] - B576000 => Ok(BaudRate::B576000), - #[cfg(any(target_os = "android", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "solaris"))] - B921600 => Ok(BaudRate::B921600), - #[cfg(any(target_os = "android", target_os = "linux"))] - B1000000 => Ok(BaudRate::B1000000), - #[cfg(any(target_os = "android", target_os = "linux"))] - B1152000 => Ok(BaudRate::B1152000), - #[cfg(any(target_os = "android", target_os = "linux"))] - B1500000 => Ok(BaudRate::B1500000), - #[cfg(any(target_os = "android", target_os = "linux"))] - B2000000 => Ok(BaudRate::B2000000), - #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] - B2500000 => Ok(BaudRate::B2500000), - #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] - B3000000 => Ok(BaudRate::B3000000), - #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] - B3500000 => Ok(BaudRate::B3500000), - #[cfg(any(target_os = "android", all(target_os = "linux", not(target_arch = "sparc64"))))] - B4000000 => Ok(BaudRate::B4000000), - _ => Err(Error::from(Errno::EINVAL)) - } - } + impl TryFrom<libc::speed_t> } #[cfg(any(target_os = "freebsd", @@ -472,6 +361,7 @@ libc_enum! { /// /// Used as an argument to `tcsetattr()` #[repr(i32)] + #[non_exhaustive] pub enum SetArg { /// The change will occur immediately TCSANOW, @@ -487,6 +377,7 @@ libc_enum! { /// /// Used as an argument to `tcflush()`. #[repr(i32)] + #[non_exhaustive] pub enum FlushArg { /// Flush data that was received but not read TCIFLUSH, @@ -502,6 +393,7 @@ libc_enum! { /// /// Used as an argument to `tcflow()`. #[repr(i32)] + #[non_exhaustive] pub enum FlowArg { /// Suspend transmission TCOOFF, @@ -518,6 +410,7 @@ libc_enum! { libc_enum! { /// Indices into the `termios.c_cc` array for special characters. #[repr(usize)] + #[non_exhaustive] pub enum SpecialCharacterIndices { VDISCARD, #[cfg(any(target_os = "dragonfly", @@ -1113,6 +1006,7 @@ pub fn tcgetsid(fd: RawFd) -> Result<Pid> { #[cfg(test)] mod test { use super::*; + use std::convert::TryFrom; #[test] fn try_from() { diff --git a/src/sys/time.rs b/src/sys/time.rs index 7546d1b..ac42471 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -77,11 +77,7 @@ impl From<timespec> for TimeSpec { impl From<Duration> for TimeSpec { fn from(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 - }) + Self::from_duration(duration) } } @@ -191,13 +187,25 @@ impl TimeSpec { } #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 - pub fn tv_sec(&self) -> time_t { + pub const fn tv_sec(&self) -> time_t { self.0.tv_sec } - pub fn tv_nsec(&self) -> timespec_tv_nsec_t { + 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 { @@ -396,11 +404,11 @@ impl TimeVal { } #[cfg_attr(target_env = "musl", allow(deprecated))] // https://github.com/rust-lang/libc/issues/1848 - pub fn tv_sec(&self) -> time_t { + pub const fn tv_sec(&self) -> time_t { self.0.tv_sec } - pub fn tv_usec(&self) -> suseconds_t { + pub const fn tv_usec(&self) -> suseconds_t { self.0.tv_usec } } diff --git a/src/sys/timerfd.rs b/src/sys/timerfd.rs index 44915be..705a3c4 100644 --- a/src/sys/timerfd.rs +++ b/src/sys/timerfd.rs @@ -58,6 +58,7 @@ 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, @@ -87,7 +88,7 @@ bitflags! { struct TimerSpec(libc::itimerspec); impl TimerSpec { - pub fn none() -> Self { + pub const fn none() -> Self { Self(libc::itimerspec { it_interval: libc::timespec { tv_sec: 0, @@ -256,14 +257,9 @@ impl TimerFd { /// /// Note: If the alarm is unset, then you will wait forever. pub fn wait(&self) -> Result<()> { - loop { - if let Err(e) = read(self.fd, &mut [0u8; 8]) { - match e { - Errno::EINTR => continue, - _ => return Err(e), - } - } else { - break; + while let Err(e) = read(self.fd, &mut [0u8; 8]) { + if e != Errno::EINTR { + return Err(e) } } diff --git a/src/sys/uio.rs b/src/sys/uio.rs index 48a0efd..3abcde2 100644 --- a/src/sys/uio.rs +++ b/src/sys/uio.rs @@ -1,5 +1,4 @@ -// Silence invalid warnings due to rust-lang/rust#16719 -#![allow(improper_ctypes)] +//! Vectored I/O use crate::Result; use crate::errno::Errno; @@ -7,12 +6,18 @@ 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) }; @@ -25,11 +30,7 @@ pub fn readv(fd: RawFd, iov: &mut [IoVec<&mut [u8]>]) -> Result<usize> { /// or an error occurs. The file offset is not changed. /// /// See also: [`writev`](fn.writev.html) and [`pwrite`](fn.pwrite.html) -#[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd"))] +#[cfg(not(target_os = "redox"))] pub fn pwritev(fd: RawFd, iov: &[IoVec<&[u8]>], offset: off_t) -> Result<usize> { let res = unsafe { @@ -46,11 +47,7 @@ pub fn pwritev(fd: RawFd, iov: &[IoVec<&[u8]>], /// changed. /// /// See also: [`readv`](fn.readv.html) and [`pread`](fn.pread.html) -#[cfg(any(target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd"))] +#[cfg(not(target_os = "redox"))] pub fn preadv(fd: RawFd, iov: &[IoVec<&mut [u8]>], offset: off_t) -> Result<usize> { let res = unsafe { @@ -60,6 +57,10 @@ pub fn preadv(fd: RawFd, iov: &[IoVec<&mut [u8]>], 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, @@ -69,6 +70,10 @@ pub fn pwrite(fd: RawFd, buf: &[u8], offset: off_t) -> Result<usize> { 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, @@ -166,11 +171,17 @@ pub fn process_vm_readv( Errno::result(res).map(|r| r as usize) } +/// A vector of buffers. +/// +/// Vectored I/O methods like [`writev`] and [`readv`] use this structure for +/// both reading and writing. Each `IoVec` specifies the base address and +/// length of an area in memory. #[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct IoVec<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; @@ -192,6 +203,7 @@ impl<'a> IoVec<&'a [u8]> { }, 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, @@ -201,6 +213,7 @@ impl<'a> IoVec<&'a [u8]> { } 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, diff --git a/src/sys/utsname.rs b/src/sys/utsname.rs index bf1a814..98edee0 100644 --- a/src/sys/utsname.rs +++ b/src/sys/utsname.rs @@ -1,34 +1,42 @@ +//! 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(); diff --git a/src/sys/wait.rs b/src/sys/wait.rs index 6c5c0f0..ee49e37 100644 --- a/src/sys/wait.rs +++ b/src/sys/wait.rs @@ -1,3 +1,4 @@ +//! Wait for a process to change status use crate::errno::Errno; use crate::sys::signal::Signal; use crate::unistd::Pid; @@ -7,9 +8,17 @@ 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", @@ -19,7 +28,11 @@ libc_bitflags!( 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", @@ -45,6 +58,7 @@ libc_bitflags!( /// 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; } @@ -213,6 +227,9 @@ impl WaitStatus { } } +/// 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::*; @@ -237,6 +254,9 @@ pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Re } } +/// 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) } diff --git a/src/time.rs b/src/time.rs index 45dd26e..6275b59 100644 --- a/src/time.rs +++ b/src/time.rs @@ -6,7 +6,7 @@ use crate::sys::time::TimeSpec; target_os = "android", target_os = "emscripten", ))] -use crate::{unistd::Pid, Error}; +use crate::unistd::Pid; use crate::{Errno, Result}; use libc::{self, clockid_t}; use std::mem::MaybeUninit; @@ -20,7 +20,7 @@ pub struct ClockId(clockid_t); impl ClockId { /// Creates `ClockId` from raw `clockid_t` - pub fn from_raw(clk_id: clockid_t) -> Self { + pub const fn from_raw(clk_id: clockid_t) -> Self { ClockId(clk_id) } @@ -61,7 +61,7 @@ impl ClockId { } /// Gets the raw `clockid_t` wrapped by `self` - pub fn as_raw(self) -> clockid_t { + pub const fn as_raw(self) -> clockid_t { self.0 } @@ -255,6 +255,6 @@ pub fn clock_getcpuclockid(pid: Pid) -> Result<ClockId> { let res = unsafe { clk_id.assume_init() }; Ok(ClockId::from(res)) } else { - Err(Error::from(Errno::from_i32(ret))) + Err(Errno::from_i32(ret)) } } diff --git a/src/ucontext.rs b/src/ucontext.rs index a5b8cc7..f2338bd 100644 --- a/src/ucontext.rs +++ b/src/ucontext.rs @@ -1,4 +1,3 @@ -use libc; #[cfg(not(target_env = "musl"))] use crate::Result; #[cfg(not(target_env = "musl"))] diff --git a/src/unistd.rs b/src/unistd.rs index de3b049..2c89d77 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -180,10 +180,7 @@ impl ForkResult { /// Return `true` if this is the child process of the `fork()` #[inline] pub fn is_child(self) -> bool { - match self { - ForkResult::Child => true, - _ => false - } + matches!(self, ForkResult::Child) } /// Returns `true` if this is the parent process of the `fork()` @@ -200,14 +197,19 @@ impl ForkResult { /// be created that are identical with the exception of their pid and the /// return value of this function. As an example: /// -/// ```no_run -/// use nix::unistd::{fork, ForkResult}; +/// ``` +/// use nix::{sys::wait::waitpid,unistd::{fork, ForkResult, write}}; /// /// match unsafe{fork()} { /// Ok(ForkResult::Parent { child, .. }) => { /// println!("Continuing execution in parent process, new child has pid: {}", child); +/// waitpid(child, None).unwrap(); +/// } +/// Ok(ForkResult::Child) => { +/// // Unsafe to use `println!` (or `unwrap`) here. See Safety. +/// write(libc::STDOUT_FILENO, "I'm a new child process\n".as_bytes()).ok(); +/// unsafe { libc::_exit(0) }; /// } -/// Ok(ForkResult::Child) => println!("I'm a new child process"), /// Err(_) => println!("Fork failed"), /// } /// ``` @@ -393,7 +395,7 @@ pub fn dup3(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result<RawFd> { #[inline] fn dup3_polyfill(oldfd: RawFd, newfd: RawFd, flags: OFlag) -> Result<RawFd> { if oldfd == newfd { - return Err(Error::from(Errno::EINVAL)); + return Err(Errno::EINVAL); } let fd = dup2(oldfd, newfd)?; @@ -567,7 +569,7 @@ fn reserve_double_buffer_size<T>(buf: &mut Vec<T>, limit: usize) -> Result<()> { use std::cmp::min; if buf.capacity() >= limit { - return Err(Error::from(Errno::ERANGE)) + return Err(Errno::ERANGE) } let capacity = min(buf.capacity() * 2, limit); @@ -610,9 +612,9 @@ pub fn getcwd() -> Result<PathBuf> { let error = Errno::last(); // ERANGE means buffer was too small to store directory name if error != Errno::ERANGE { - return Err(Error::from(error)); + return Err(error); } - } + } // Trigger the internal buffer resizing logic. reserve_double_buffer_size(&mut buf, PATH_MAX as usize)?; @@ -734,7 +736,7 @@ pub fn execv<S: AsRef<CStr>>(path: &CStr, argv: &[S]) -> Result<Infallible> { libc::execv(path.as_ptr(), args_p.as_ptr()) }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } @@ -759,7 +761,7 @@ pub fn execve<SA: AsRef<CStr>, SE: AsRef<CStr>>(path: &CStr, args: &[SA], env: & libc::execve(path.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } /// Replace the current process image with a new one and replicate shell `PATH` @@ -779,7 +781,7 @@ pub fn execvp<S: AsRef<CStr>>(filename: &CStr, args: &[S]) -> Result<Infallible> libc::execvp(filename.as_ptr(), args_p.as_ptr()) }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } /// Replace the current process image with a new one and replicate shell `PATH` @@ -800,7 +802,7 @@ pub fn execvpe<SA: AsRef<CStr>, SE: AsRef<CStr>>(filename: &CStr, args: &[SA], e libc::execvpe(filename.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } /// Replace the current process image with a new one (see @@ -828,7 +830,7 @@ pub fn fexecve<SA: AsRef<CStr> ,SE: AsRef<CStr>>(fd: RawFd, args: &[SA], env: &[ libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } /// Execute program relative to a directory file descriptor (see @@ -853,7 +855,7 @@ pub fn execveat<SA: AsRef<CStr>,SE: AsRef<CStr>>(dirfd: RawFd, pathname: &CStr, args_p.as_ptr(), env_p.as_ptr(), flags); }; - Err(Error::from(Errno::last())) + Err(Errno::last()) } /// Daemonize this process by detaching from the controlling terminal (see @@ -1073,10 +1075,10 @@ pub fn pipe() -> std::result::Result<(RawFd, RawFd), Error> { /// The following flags are supported, and will be set atomically as the pipe is /// created: /// -/// `O_CLOEXEC`: Set the close-on-exec flag for the new file descriptors. -#[cfg_attr(target_os = "linux", doc = "`O_DIRECT`: Create a pipe that performs I/O in \"packet\" mode. ")] -#[cfg_attr(target_os = "netbsd", doc = "`O_NOSIGPIPE`: Return `EPIPE` instead of raising `SIGPIPE`. ")] -/// `O_NONBLOCK`: Set the non-blocking flag for the ends of the pipe. +/// - `O_CLOEXEC`: Set the close-on-exec flag for the new file descriptors. +#[cfg_attr(target_os = "linux", doc = "- `O_DIRECT`: Create a pipe that performs I/O in \"packet\" mode.")] +#[cfg_attr(target_os = "netbsd", doc = "- `O_NOSIGPIPE`: Return `EPIPE` instead of raising `SIGPIPE`.")] +/// - `O_NONBLOCK`: Set the non-blocking flag for the ends of the pipe. /// /// See also [pipe(2)](https://man7.org/linux/man-pages/man2/pipe.2.html) #[cfg(any(target_os = "android", @@ -1133,9 +1135,9 @@ pub fn isatty(fd: RawFd) -> Result<bool> { } else { match Errno::last() { Errno::ENOTTY => Ok(false), - err => Err(Error::from(err)), + err => Err(err), } - } + } } } @@ -1415,6 +1417,14 @@ pub fn getgroups() -> Result<Vec<Gid>> { // Next, get the number of groups so we can size our Vec let ngroups = unsafe { libc::getgroups(0, ptr::null_mut()) }; + // If there are no supplementary groups, return early. + // This prevents a potential buffer over-read if the number of groups + // increases from zero before the next call. It would return the total + // number of groups beyond the capacity of the buffer. + if ngroups == 0 { + return Ok(Vec::new()); + } + // Now actually get the groups. We try multiple times in case the number of // groups has changed since the first call to getgroups() and the buffer is // now too small. @@ -1436,7 +1446,7 @@ pub fn getgroups() -> Result<Vec<Gid>> { // EINVAL indicates that the buffer size was too // small, resize it up to ngroups_max as limit. reserve_double_buffer_size(&mut groups, ngroups_max) - .or(Err(Error::from(Errno::EINVAL)))?; + .or(Err(Errno::EINVAL))?; }, Err(e) => return Err(e) } @@ -1530,8 +1540,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { Ok(None) | Err(_) => <c_int>::max_value(), }; use std::cmp::min; - let mut ngroups = min(ngroups_max, 8); - let mut groups = Vec::<Gid>::with_capacity(ngroups as usize); + let mut groups = Vec::<Gid>::with_capacity(min(ngroups_max, 8) as usize); cfg_if! { if #[cfg(any(target_os = "ios", target_os = "macos"))] { type getgrouplist_group_t = c_int; @@ -1541,6 +1550,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { } let gid: gid_t = group.into(); loop { + let mut ngroups = groups.capacity() as i32; let ret = unsafe { libc::getgrouplist(user.as_ptr(), gid as getgrouplist_group_t, @@ -1558,7 +1568,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { // groups as possible, but Linux manpages do not mention this // behavior. reserve_double_buffer_size(&mut groups, ngroups_max as usize) - .map_err(|_| Error::from(Errno::EINVAL))?; + .map_err(|_| Errno::EINVAL)?; } } } @@ -1801,6 +1811,7 @@ pub fn mkstemp<P: ?Sized + NixPath>(template: &P) -> Result<(RawFd, PathBuf)> { /// - [unistd.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html) #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(i32)] +#[non_exhaustive] pub enum PathconfVar { #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux", target_os = "netbsd", target_os = "openbsd", target_os = "redox"))] @@ -1916,7 +1927,7 @@ pub fn fpathconf(fd: RawFd, var: PathconfVar) -> Result<Option<c_long>> { if errno::errno() == 0 { Ok(None) } else { - Err(Error::from(Errno::last())) + Err(Errno::last()) } } else { Ok(Some(raw)) @@ -1955,7 +1966,7 @@ pub fn pathconf<P: ?Sized + NixPath>(path: &P, var: PathconfVar) -> Result<Optio if errno::errno() == 0 { Ok(None) } else { - Err(Error::from(Errno::last())) + Err(Errno::last()) } } else { Ok(Some(raw)) @@ -1980,6 +1991,7 @@ pub fn pathconf<P: ?Sized + NixPath>(path: &P, var: PathconfVar) -> Result<Optio /// - [limits.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(i32)] +#[non_exhaustive] pub enum SysconfVar { /// Maximum number of I/O operations in a single list I/O call supported by /// the implementation. @@ -2454,7 +2466,7 @@ pub fn sysconf(var: SysconfVar) -> Result<Option<c_long>> { if errno::errno() == 0 { Ok(None) } else { - Err(Error::from(Errno::last())) + Err(Errno::last()) } } else { Ok(Some(raw)) @@ -2624,7 +2636,7 @@ pub struct User { /// Group ID pub gid: Gid, /// User information - #[cfg(not(target_os = "android"))] + #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] pub gecos: CString, /// Home directory pub dir: PathBuf, @@ -2660,7 +2672,7 @@ impl From<&libc::passwd> for User { User { name: CStr::from_ptr((*pw).pw_name).to_string_lossy().into_owned(), passwd: CString::new(CStr::from_ptr((*pw).pw_passwd).to_bytes()).unwrap(), - #[cfg(not(target_os = "android"))] + #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] gecos: CString::new(CStr::from_ptr((*pw).pw_gecos).to_bytes()).unwrap(), dir: PathBuf::from(OsStr::from_bytes(CStr::from_ptr((*pw).pw_dir).to_bytes())), shell: PathBuf::from(OsStr::from_bytes(CStr::from_ptr((*pw).pw_shell).to_bytes())), @@ -2690,6 +2702,58 @@ impl From<&libc::passwd> for User { } #[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd +impl From<User> for libc::passwd { + fn from(u: User) -> Self { + let name = match CString::new(u.name) { + Ok(n) => n.into_raw(), + Err(_) => CString::new("").unwrap().into_raw(), + }; + let dir = match u.dir.into_os_string().into_string() { + Ok(s) => CString::new(s.as_str()).unwrap().into_raw(), + Err(_) => CString::new("").unwrap().into_raw(), + }; + let shell = match u.shell.into_os_string().into_string() { + Ok(s) => CString::new(s.as_str()).unwrap().into_raw(), + Err(_) => CString::new("").unwrap().into_raw(), + }; + Self { + pw_name: name, + pw_passwd: u.passwd.into_raw(), + #[cfg(not(all(target_os = "android", target_pointer_width = "32")))] + pw_gecos: u.gecos.into_raw(), + pw_dir: dir, + pw_shell: shell, + pw_uid: u.uid.0, + pw_gid: u.gid.0, + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + pw_class: u.class.into_raw(), + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + pw_change: u.change, + #[cfg(not(any(target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "linux", + target_os = "solaris")))] + pw_expire: u.expire, + #[cfg(target_os = "illumos")] + pw_age: CString::new("").unwrap().into_raw(), + #[cfg(target_os = "illumos")] + pw_comment: CString::new("").unwrap().into_raw(), + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + pw_fields: 0, + } + } +} + +#[cfg(not(target_os = "redox"))] // RedoxFS does not support passwd impl User { fn from_anything<F>(f: F) -> Result<Option<Self>> where @@ -2721,7 +2785,7 @@ impl User { // Trigger the internal buffer resizing logic. reserve_double_buffer_size(&mut cbuf, buflimit)?; } else { - return Err(Error::from(Errno::last())); + return Err(Errno::last()); } } } @@ -2842,7 +2906,7 @@ impl Group { // Trigger the internal buffer resizing logic. reserve_double_buffer_size(&mut cbuf, buflimit)?; } else { - return Err(Error::from(Errno::last())); + return Err(Errno::last()); } } } @@ -2901,7 +2965,7 @@ pub fn ttyname(fd: RawFd) -> Result<PathBuf> { let ret = unsafe { libc::ttyname_r(fd, c_buf, buf.len()) }; if ret != 0 { - return Err(Error::from(Errno::from_i32(ret))); + return Err(Errno::from_i32(ret)); } let nul = buf.iter().position(|c| *c == b'\0').unwrap(); diff --git a/test/common/mod.rs b/test/common/mod.rs index cdc3258..84a0b4f 100644 --- a/test/common/mod.rs +++ b/test/common/mod.rs @@ -14,19 +14,19 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(any(target_os = "android", target_os = "linux"))] { #[macro_export] macro_rules! require_capability { - ($capname:ident) => { + ($name:expr, $capname:ident) => { use ::caps::{Capability, CapSet, has_cap}; if !has_cap(None, CapSet::Effective, Capability::$capname) .unwrap() { - skip!("Insufficient capabilities. Skipping test."); + skip!("{} requires capability {}. Skipping test.", $name, Capability::$capname); } } } } else if #[cfg(not(target_os = "redox"))] { #[macro_export] macro_rules! require_capability { - ($capname:ident) => {} + ($name:expr, $capname:ident) => {} } } } diff --git a/test/sys/mod.rs b/test/sys/mod.rs index 4f5316f..e73d9b1 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -11,6 +11,8 @@ mod test_signal; 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"))] @@ -41,5 +43,5 @@ mod test_pthread; target_os = "netbsd", target_os = "openbsd"))] mod test_ptrace; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "android", target_os = "linux"))] mod test_timerfd; diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index 3208410..80cd053 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -1,5 +1,5 @@ use libc::{c_int, c_void}; -use nix::{Error, Result}; +use nix::Result; use nix::errno::*; use nix::sys::aio::*; use nix::sys::signal::{SaFlags, SigAction, sigaction, SigevNotify, SigHandler, Signal, SigSet}; @@ -16,7 +16,7 @@ use tempfile::tempfile; fn poll_aio(aiocb: &mut Pin<Box<AioCb>>) -> Result<()> { loop { let err = aiocb.error(); - if err != Err(Error::from(Errno::EINPROGRESS)) { return err; }; + if err != Err(Errno::EINPROGRESS) { return err; }; thread::sleep(time::Duration::from_millis(10)); } } @@ -26,7 +26,7 @@ fn poll_aio(aiocb: &mut Pin<Box<AioCb>>) -> Result<()> { fn poll_lio(liocb: &mut LioCb, i: usize) -> Result<()> { loop { let err = liocb.error(i); - if err != Err(Error::from(Errno::EINPROGRESS)) { return err; }; + if err != Err(Errno::EINPROGRESS) { return err; }; thread::sleep(time::Duration::from_millis(10)); } } @@ -70,7 +70,7 @@ fn test_cancel() { LioOpcode::LIO_NOP); aiocb.write().unwrap(); let err = aiocb.error(); - assert!(err == Ok(()) || err == Err(Error::from(Errno::EINPROGRESS))); + assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); let cancelstat = aiocb.cancel(); assert!(cancelstat.is_ok()); @@ -95,7 +95,7 @@ fn test_aio_cancel_all() { LioOpcode::LIO_NOP); aiocb.write().unwrap(); let err = aiocb.error(); - assert!(err == Ok(()) || err == Err(Error::from(Errno::EINPROGRESS))); + assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); let cancelstat = aio_cancel_all(f.as_raw_fd()); assert!(cancelstat.is_ok()); @@ -182,8 +182,8 @@ fn test_aio_suspend() { Ok(_) => () }; } - if rcb.error() != Err(Error::from(Errno::EINPROGRESS)) && - wcb.error() != Err(Error::from(Errno::EINPROGRESS)) { + if rcb.error() != Err(Errno::EINPROGRESS) && + wcb.error() != Err(Errno::EINPROGRESS) { break } } @@ -406,7 +406,7 @@ extern fn sigfunc(_: c_int) { #[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 _m = crate::SIGNAL_MTX.lock(); let sa = SigAction::new(SigHandler::Handler(sigfunc), SaFlags::SA_RESETHAND, SigSet::empty()); @@ -544,7 +544,7 @@ fn test_liocb_listio_nowait() { #[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"); + let _m = crate::SIGNAL_MTX.lock(); const INITIAL: &[u8] = b"abcdef123456"; const WBUF: &[u8] = b"CDEF"; let mut rbuf = vec![0; 4]; diff --git a/test/sys/test_epoll.rs b/test/sys/test_epoll.rs index 57bc484..8d44cd0 100644 --- a/test/sys/test_epoll.rs +++ b/test/sys/test_epoll.rs @@ -1,6 +1,5 @@ use nix::sys::epoll::{EpollCreateFlags, EpollFlags, EpollOp, EpollEvent}; use nix::sys::epoll::{epoll_create1, epoll_ctl}; -use nix::Error; use nix::errno::Errno; #[test] @@ -8,11 +7,11 @@ 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(), Error::from(Errno::ENOENT)); + 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(), Error::from(Errno::EINVAL)); + assert_eq!(result.unwrap_err(), Errno::EINVAL); } #[test] diff --git a/test/sys/test_inotify.rs b/test/sys/test_inotify.rs index 121b726..137816a 100644 --- a/test/sys/test_inotify.rs +++ b/test/sys/test_inotify.rs @@ -1,7 +1,5 @@ use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify}; -use nix::Error; use nix::errno::Errno; -use tempfile; use std::ffi::OsString; use std::fs::{rename, File}; @@ -14,7 +12,7 @@ pub fn test_inotify() { instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); let events = instance.read_events(); - assert_eq!(events.unwrap_err(), Error::from(Errno::EAGAIN)); + assert_eq!(events.unwrap_err(), Errno::EAGAIN); File::create(tempdir.path().join("test")).unwrap(); @@ -31,7 +29,7 @@ pub fn test_inotify_multi_events() { instance.add_watch(tempdir.path(), AddWatchFlags::IN_ALL_EVENTS).unwrap(); let events = instance.read_events(); - assert_eq!(events.unwrap_err(), Error::from(Errno::EAGAIN)); + assert_eq!(events.unwrap_err(), Errno::EAGAIN); File::create(tempdir.path().join("test")).unwrap(); rename(tempdir.path().join("test"), tempdir.path().join("test2")).unwrap(); diff --git a/test/sys/test_mman.rs b/test/sys/test_mman.rs index 152fff6..a7ceedc 100644 --- a/test/sys/test_mman.rs +++ b/test/sys/test_mman.rs @@ -1,27 +1,24 @@ -use nix::Error; -use nix::libc::{c_void, size_t}; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; -#[cfg(target_os = "linux")] -use nix::sys::mman::{mremap, MRemapFlags}; - #[test] fn test_mmap_anonymous() { - let ref mut byte = unsafe { + unsafe { let ptr = mmap(std::ptr::null_mut(), 1, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, -1, 0) - .unwrap(); - *(ptr as * mut u8) - }; - assert_eq !(*byte, 0x00u8); - *byte = 0xffu8; - assert_eq !(*byte, 0xffu8); + .unwrap() as *mut u8; + assert_eq !(*ptr, 0x00u8); + *ptr = 0xffu8; + assert_eq !(*ptr, 0xffu8); + } } #[test] -#[cfg(target_os = "linux")] +#[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, @@ -35,9 +32,14 @@ fn test_mremap_grow() { 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) }; @@ -51,8 +53,13 @@ fn test_mremap_grow() { } #[test] -#[cfg(target_os = "linux")] +#[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, @@ -66,11 +73,16 @@ fn test_mremap_shrink() { 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) }; diff --git a/test/sys/test_pthread.rs b/test/sys/test_pthread.rs index 1fc3dd9..fa9b510 100644 --- a/test/sys/test_pthread.rs +++ b/test/sys/test_pthread.rs @@ -13,3 +13,10 @@ 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."); +} diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 985945d..83fff9a 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -13,7 +13,7 @@ use crate::*; 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!(CAP_SYS_PTRACE); + require_capability!("test_ptrace", CAP_SYS_PTRACE); let err = ptrace::attach(getpid()).unwrap_err(); assert!(err == Errno::EPERM || err == Errno::EINVAL || err == Errno::ENOSYS); @@ -23,7 +23,7 @@ fn test_ptrace() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptrace_setoptions() { - require_capability!(CAP_SYS_PTRACE); + require_capability!("test_ptrace_setoptions", CAP_SYS_PTRACE); let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD).unwrap_err(); assert!(err != Errno::EOPNOTSUPP); } @@ -32,7 +32,7 @@ fn test_ptrace_setoptions() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptrace_getevent() { - require_capability!(CAP_SYS_PTRACE); + require_capability!("test_ptrace_getevent", CAP_SYS_PTRACE); let err = ptrace::getevent(getpid()).unwrap_err(); assert!(err != Errno::EOPNOTSUPP); } @@ -41,7 +41,7 @@ fn test_ptrace_getevent() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptrace_getsiginfo() { - require_capability!(CAP_SYS_PTRACE); + require_capability!("test_ptrace_getsiginfo", CAP_SYS_PTRACE); if let Err(Errno::EOPNOTSUPP) = ptrace::getsiginfo(getpid()) { panic!("ptrace_getsiginfo returns Errno::EOPNOTSUPP!"); } @@ -51,7 +51,7 @@ fn test_ptrace_getsiginfo() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptrace_setsiginfo() { - require_capability!(CAP_SYS_PTRACE); + 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!"); @@ -67,9 +67,9 @@ fn test_ptrace_cont() { use nix::unistd::fork; use nix::unistd::ForkResult::*; - require_capability!(CAP_SYS_PTRACE); + require_capability!("test_ptrace_cont", CAP_SYS_PTRACE); - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); // FIXME: qemu-user doesn't implement ptrace on all architectures // and retunrs ENOSYS in this case. @@ -114,6 +114,48 @@ fn test_ptrace_cont() { } } +#[cfg(target_os = "linux")] +#[test] +fn test_ptrace_interrupt() { + use nix::sys::ptrace; + use nix::sys::signal::Signal; + use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + use std::thread::sleep; + use std::time::Duration; + + require_capability!("test_ptrace_interrupt", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + match unsafe{fork()}.expect("Error: Fork Failed") { + Child => { + loop { + sleep(Duration::from_millis(1000)); + } + + }, + Parent { child } => { + ptrace::seize(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap(); + ptrace::interrupt(child).unwrap(); + assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, Signal::SIGTRAP, 128))); + ptrace::syscall(child, None).unwrap(); + assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child))); + ptrace::detach(child, Some(Signal::SIGKILL)).unwrap(); + match waitpid(child, None) { + Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => { + let _ = waitpid(child, Some(WaitPidFlag::WNOHANG)); + while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() { + let _ = waitpid(child, Some(WaitPidFlag::WNOHANG)); + } + } + _ => panic!("The process should have been killed"), + } + }, + } +} + // ptrace::{setoptions, getregs} are only available in these platforms #[cfg(all(target_os = "linux", any(target_arch = "x86_64", @@ -129,9 +171,9 @@ fn test_ptrace_syscall() { use nix::unistd::getpid; use nix::unistd::ForkResult::*; - require_capability!(CAP_SYS_PTRACE); + require_capability!("test_ptrace_syscall", CAP_SYS_PTRACE); - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); match unsafe{fork()}.expect("Error: Fork Failed") { Child => { diff --git a/test/sys/test_select.rs b/test/sys/test_select.rs index 3795108..2f7396b 100644 --- a/test/sys/test_select.rs +++ b/test/sys/test_select.rs @@ -5,9 +5,7 @@ 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 _mtx = crate::SIGNAL_MTX.lock(); let (r1, w1) = pipe().unwrap(); write(w1, b"hi!").unwrap(); @@ -52,3 +50,31 @@ pub fn test_pselect_nfds2() { assert!(fd_set.contains(r1)); assert!(!fd_set.contains(r2)); } + +macro_rules! generate_fdset_bad_fd_tests { + ($fd:expr, $($method:ident),* $(,)?) => { + $( + #[test] + #[should_panic] + fn $method() { + FdSet::new().$method($fd); + } + )* + } +} + +mod test_fdset_negative_fd { + use super::*; + generate_fdset_bad_fd_tests!(-1, insert, remove, contains); +} + +mod test_fdset_too_large_fd { + use super::*; + use std::convert::TryInto; + generate_fdset_bad_fd_tests!( + FD_SETSIZE.try_into().unwrap(), + insert, + remove, + contains, + ); +} diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs index 1b89af5..fdd2568 100644 --- a/test/sys/test_signal.rs +++ b/test/sys/test_signal.rs @@ -19,7 +19,7 @@ fn test_killpg_none() { #[test] fn test_old_sigaction_flags() { - let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::SIGNAL_MTX.lock(); extern "C" fn handler(_: ::libc::c_int) {} let act = SigAction::new( @@ -41,7 +41,7 @@ fn test_sigprocmask_noop() { #[test] fn test_sigprocmask() { - let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::SIGNAL_MTX.lock(); // This needs to be a signal that rust doesn't use in the test harness. const SIGNAL: Signal = Signal::SIGCHLD; @@ -89,7 +89,7 @@ extern fn test_sigaction_action(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut #[test] #[cfg(not(target_os = "redox"))] fn test_signal_sigaction() { - let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::SIGNAL_MTX.lock(); let action_handler = SigHandler::SigAction(test_sigaction_action); assert_eq!(unsafe { signal(Signal::SIGINT, action_handler) }.unwrap_err(), Errno::ENOTSUP); @@ -97,7 +97,7 @@ fn test_signal_sigaction() { #[test] fn test_signal() { - let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::SIGNAL_MTX.lock(); unsafe { signal(Signal::SIGINT, SigHandler::SigIgn) }.unwrap(); raise(Signal::SIGINT).unwrap(); diff --git a/test/sys/test_signalfd.rs b/test/sys/test_signalfd.rs index af04c22..b6f748b 100644 --- a/test/sys/test_signalfd.rs +++ b/test/sys/test_signalfd.rs @@ -6,7 +6,7 @@ fn test_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"); + let _m = crate::SIGNAL_MTX.lock(); // Block the SIGUSR1 signal from automatic processing for this thread let mut mask = SigSet::empty(); diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 5471afe..0f6fac6 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -1,12 +1,13 @@ -use nix::sys::socket::{AddressFamily, InetAddr, UnixAddr, getsockname}; +use nix::sys::socket::{AddressFamily, InetAddr, SockAddr, UnixAddr, getsockname, sockaddr, sockaddr_in6, sockaddr_storage_to_addr}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use std::mem::{self, MaybeUninit}; use std::net::{self, Ipv6Addr, SocketAddr, SocketAddrV6}; use std::os::unix::io::RawFd; use std::path::Path; use std::slice; use std::str::FromStr; -use libc::c_char; +use libc::{c_char, sockaddr_storage}; #[cfg(any(target_os = "linux", target_os= "android"))] use crate::*; @@ -27,13 +28,36 @@ pub fn test_inetv4_addr_to_sock_addr() { _ => panic!("nope"), } - assert_eq!(addr.to_str(), "127.0.0.1:3000"); + assert_eq!(addr.to_string(), "127.0.0.1:3000"); let inet = addr.to_std(); assert_eq!(actual, inet); } #[test] +pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() { + let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap(); + let addr = InetAddr::from_std(&actual); + let sockaddr = SockAddr::new_inet(addr); + + let (storage, ffi_size) = { + let mut storage = MaybeUninit::<sockaddr_storage>::zeroed(); + let storage_ptr = storage.as_mut_ptr().cast::<sockaddr>(); + let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); + assert_eq!(mem::size_of::<sockaddr>(), ffi_size as usize); + unsafe { + storage_ptr.copy_from_nonoverlapping(ffi_ptr as *const sockaddr, 1); + (storage.assume_init(), ffi_size) + } + }; + + let from_storage = sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); + assert_eq!(from_storage, sockaddr); + let from_storage = sockaddr_storage_to_addr(&storage, mem::size_of::<sockaddr_storage>()).unwrap(); + assert_eq!(from_storage, sockaddr); +} + +#[test] pub fn test_inetv6_addr_to_sock_addr() { let port: u16 = 3000; let flowinfo: u32 = 1; @@ -54,6 +78,33 @@ pub fn test_inetv6_addr_to_sock_addr() { assert_eq!(actual, addr.to_std()); } +#[test] +pub fn test_inetv6_addr_roundtrip_sockaddr_storage_to_addr() { + let port: u16 = 3000; + let flowinfo: u32 = 1; + let scope_id: u32 = 2; + let ip: Ipv6Addr = "fe80::1".parse().unwrap(); + + let actual = SocketAddr::V6(SocketAddrV6::new(ip, port, flowinfo, scope_id)); + let addr = InetAddr::from_std(&actual); + let sockaddr = SockAddr::new_inet(addr); + + let (storage, ffi_size) = { + let mut storage = MaybeUninit::<sockaddr_storage>::zeroed(); + let storage_ptr = storage.as_mut_ptr().cast::<sockaddr_in6>(); + let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); + assert_eq!(mem::size_of::<sockaddr_in6>(), ffi_size as usize); + unsafe { + storage_ptr.copy_from_nonoverlapping((ffi_ptr as *const sockaddr).cast::<sockaddr_in6>(), 1); + (storage.assume_init(), ffi_size) + } + }; + + let from_storage = sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); + assert_eq!(from_storage, sockaddr); + let from_storage = sockaddr_storage_to_addr(&storage, mem::size_of::<sockaddr_storage>()).unwrap(); + assert_eq!(from_storage, sockaddr); +} #[test] pub fn test_path_to_sock_addr() { @@ -62,9 +113,9 @@ pub fn test_path_to_sock_addr() { let addr = UnixAddr::new(actual).unwrap(); let expect: &[c_char] = unsafe { - slice::from_raw_parts(path.as_bytes().as_ptr() as *const c_char, path.len()) + slice::from_raw_parts(path.as_ptr() as *const c_char, path.len()) }; - assert_eq!(&addr.0.sun_path[..8], expect); + assert_eq!(unsafe { &(*addr.as_ptr()).sun_path[..8] }, expect); assert_eq!(addr.path(), Some(actual)); } @@ -82,7 +133,7 @@ pub fn test_addr_equality_path() { let addr1 = UnixAddr::new(actual).unwrap(); let mut addr2 = addr1; - addr2.0.sun_path[10] = 127; + unsafe { (*addr2.as_mut_ptr()).sun_path[10] = 127 }; assert_eq!(addr1, addr2); assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); @@ -101,12 +152,12 @@ pub fn test_abstract_sun_path_too_long() { pub fn test_addr_equality_abstract() { let name = String::from("nix\0abstract\0test"); let addr1 = UnixAddr::new_abstract(name.as_bytes()).unwrap(); - let mut addr2 = addr1.clone(); + let mut addr2 = addr1; assert_eq!(addr1, addr2); assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); - addr2.0.sun_path[17] = 127; + unsafe { (*addr2.as_mut_ptr()).sun_path[17] = 127 }; assert_ne!(addr1, addr2); assert_ne!(calculate_hash(&addr1), calculate_hash(&addr2)); } @@ -129,7 +180,7 @@ pub fn test_abstract_uds_addr() { assert_eq!(addr.path(), None); // Internally, name is null-prefixed (abstract namespace) - assert_eq!(addr.0.sun_path[0], 0); + assert_eq!(unsafe { (*addr.as_ptr()).sun_path[0] }, 0); } #[test] @@ -143,8 +194,7 @@ pub fn test_getsockname() { .expect("socket failed"); let sockaddr = SockAddr::new_unix(&sockname).unwrap(); bind(sock, &sockaddr).expect("bind failed"); - assert_eq!(sockaddr.to_str(), - getsockname(sock).expect("getsockname failed").to_str()); + assert_eq!(sockaddr, getsockname(sock).expect("getsockname failed")); } #[test] @@ -237,9 +287,9 @@ mod recvfrom { use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment}; #[test] - // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU - // support is suspected. - #[cfg_attr(not(any(target_arch = "x86_64", target_arch="i686")), ignore)] + // Disable the test under emulation because it fails in Cirrus-CI. Lack + // of QEMU support is suspected. + #[cfg_attr(qemu, ignore)] pub fn gso() { require_kernel_version!(udp_offload::gso, ">= 4.18"); @@ -291,9 +341,9 @@ mod recvfrom { } #[test] - // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU - // support is suspected. - #[cfg_attr(not(any(target_arch = "x86_64", target_arch="i686")), ignore)] + // Disable the test on emulated platforms because it fails in Cirrus-CI. + // Lack of QEMU support is suspected. + #[cfg_attr(qemu, ignore)] pub fn gro() { require_kernel_version!(udp_offload::gro, ">= 5.3"); @@ -317,7 +367,6 @@ mod recvfrom { target_os = "freebsd", target_os = "netbsd", ))] - #[allow(clippy::vec_init_then_push)] #[test] pub fn udp_sendmmsg() { use nix::sys::uio::IoVec; @@ -344,14 +393,14 @@ mod recvfrom { let from = sendrecv(rsock, ssock, move |s, m, flags| { let iov = [IoVec::from_slice(m)]; - let mut msgs = Vec::new(); - msgs.push( + let mut msgs = vec![ SendMmsgData { iov: &iov, cmsgs: &[], addr: Some(sock_addr), _lt: Default::default(), - }); + } + ]; let batch_size = 15; @@ -532,7 +581,7 @@ pub fn test_recvmsg_ebadf() { // Disable the test on emulated platforms due to a bug in QEMU versions < // 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch="i686")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] pub fn test_scm_rights() { use nix::sys::uio::IoVec; @@ -586,11 +635,10 @@ pub fn test_scm_rights() { } // Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "i686")), ignore)] #[cfg(any(target_os = "linux", target_os= "android"))] +#[cfg_attr(qemu, ignore)] #[test] pub fn test_af_alg_cipher() { - use libc; use nix::sys::uio::IoVec; use nix::unistd::read; use nix::sys::socket::{socket, sendmsg, bind, accept, setsockopt, @@ -654,12 +702,14 @@ pub fn test_af_alg_cipher() { assert_eq!(decrypted, payload); } -// Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "i686")), ignore)] +// Disable the test on emulated platforms due to not enabled support of AF_ALG +// in QEMU from rust cross #[cfg(any(target_os = "linux", target_os= "android"))] +#[cfg_attr(qemu, ignore)] #[test] pub fn test_af_alg_aead() { use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT}; + use nix::fcntl::{fcntl, FcntlArg, OFlag}; use nix::sys::uio::IoVec; use nix::unistd::{read, close}; use nix::sys::socket::{socket, sendmsg, bind, accept, setsockopt, @@ -738,6 +788,11 @@ pub fn test_af_alg_aead() { // allocate buffer for decrypted data let mut decrypted = vec![0u8; payload_len + (assoc_size as usize) + auth_size]; + // Starting with kernel 4.9, the interface changed slightly such that the + // authentication tag memory is only needed in the output buffer for encryption + // and in the input buffer for decryption. + // Do not block on read, as we may have fewer bytes than buffer size + fcntl(session_socket,FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("fcntl non_blocking"); let num_bytes = read(session_socket, &mut decrypted).expect("read decrypt"); assert!(num_bytes >= payload_len + (assoc_size as usize)); @@ -780,7 +835,7 @@ pub fn test_sendmsg_ipv4packetinfo() { if let InetAddr::V4(sin) = inet_addr { cfg_if! { if #[cfg(target_os = "netbsd")] { - drop(sin); + let _dontcare = sin; let pi = libc::in_pktinfo { ipi_ifindex: 0, /* Unspecified interface */ ipi_addr: libc::in_addr { s_addr: 0 }, @@ -859,7 +914,7 @@ pub fn test_sendmsg_ipv6packetinfo() { /// Tests that passing multiple fds using a single `ControlMessage` works. // Disable the test on emulated platforms due to a bug in QEMU versions < // 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch="i686")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] fn test_scm_rights_single_cmsg_multiple_fds() { use std::os::unix::net::UnixDatagram; @@ -868,7 +923,6 @@ fn test_scm_rights_single_cmsg_multiple_fds() { use nix::sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, sendmsg, recvmsg}; use nix::sys::uio::IoVec; - use libc; let (send, receive) = UnixDatagram::pair().unwrap(); let thread = thread::spawn(move || { @@ -1006,13 +1060,11 @@ fn test_scm_credentials() { /// Ensure that we can send `SCM_CREDENTIALS` and `SCM_RIGHTS` with a single /// `sendmsg` call. #[cfg(any(target_os = "android", target_os = "linux"))] -// qemu's handling of multiple cmsgs is bugged, ignore tests on non-x86 +// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation // see https://bugs.launchpad.net/qemu/+bug/1781280 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "x86")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] fn test_scm_credentials_and_rights() { - use libc; - let space = cmsg_space!(libc::ucred, RawFd); test_impl_scm_credentials_and_rights(space); } @@ -1020,9 +1072,9 @@ fn test_scm_credentials_and_rights() { /// Ensure that passing a an oversized control message buffer to recvmsg /// still works. #[cfg(any(target_os = "android", target_os = "linux"))] -// qemu's handling of multiple cmsgs is bugged, ignore tests on non-x86 +// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation // see https://bugs.launchpad.net/qemu/+bug/1781280 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "x86")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] fn test_too_large_cmsgspace() { let space = vec![0u8; 1024]; @@ -1169,7 +1221,6 @@ fn loopback_address(family: AddressFamily) -> Option<nix::ifaddrs::InterfaceAddr use std::io; use std::io::Write; use nix::ifaddrs::getifaddrs; - use nix::sys::socket::SockAddr; use nix::net::if_::*; let addrs = match getifaddrs() { @@ -1212,14 +1263,16 @@ fn loopback_address(family: AddressFamily) -> Option<nix::ifaddrs::InterfaceAddr target_os = "netbsd", ))] // qemu doesn't seem to be emulating this correctly in these architectures -#[cfg_attr(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64", +#[cfg_attr(all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) ), ignore)] #[test] pub fn test_recv_ipv4pktinfo() { - use libc; use nix::sys::socket::sockopt::Ipv4PacketInfo; use nix::sys::socket::{bind, SockFlag, SockType}; use nix::sys::socket::{getsockname, setsockopt, socket}; @@ -1272,18 +1325,15 @@ pub fn test_recv_ipv4pktinfo() { ); let mut cmsgs = msg.cmsgs(); - match cmsgs.next() { - Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) => { - let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); - assert_eq!( - pktinfo.ipi_ifindex as libc::c_uint, - i, - "unexpected ifindex (expected {}, got {})", - i, - pktinfo.ipi_ifindex - ); - } - _ => (), + if let Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) = cmsgs.next() { + let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); + assert_eq!( + pktinfo.ipi_ifindex as libc::c_uint, + i, + "unexpected ifindex (expected {}, got {})", + i, + pktinfo.ipi_ifindex + ); } assert!(cmsgs.next().is_none(), "unexpected additional control msg"); assert_eq!(msg.bytes, 8); @@ -1302,14 +1352,16 @@ pub fn test_recv_ipv4pktinfo() { target_os = "openbsd", ))] // qemu doesn't seem to be emulating this correctly in these architectures -#[cfg_attr(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64", +#[cfg_attr(all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) ), ignore)] #[test] pub fn test_recvif() { - use libc; use nix::net::if_::*; use nix::sys::socket::sockopt::{Ipv4RecvIf, Ipv4RecvDstAddr}; use nix::sys::socket::{bind, SockFlag, SockType}; @@ -1413,14 +1465,16 @@ pub fn test_recvif() { target_os = "openbsd", ))] // qemu doesn't seem to be emulating this correctly in these architectures -#[cfg_attr(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64", +#[cfg_attr(all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) ), ignore)] #[test] pub fn test_recv_ipv6pktinfo() { - use libc; use nix::net::if_::*; use nix::sys::socket::sockopt::Ipv6RecvPacketInfo; use nix::sys::socket::{bind, SockFlag, SockType}; @@ -1494,9 +1548,9 @@ pub fn test_recv_ipv6pktinfo() { } #[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(graviton, ignore = "Not supported by the CI environment")] #[test] pub fn test_vsock() { - use libc; use nix::errno::Errno; use nix::sys::socket::{AddressFamily, socket, bind, connect, listen, SockAddr, SockType, SockFlag}; @@ -1538,9 +1592,9 @@ pub fn test_vsock() { thr.join().unwrap(); } -// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU -// support is suspected. -#[cfg_attr(not(any(target_arch = "x86_64")), ignore)] +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] #[cfg(all(target_os = "linux"))] #[test] fn test_recvmsg_timestampns() { @@ -1589,9 +1643,9 @@ fn test_recvmsg_timestampns() { nix::unistd::close(in_socket).unwrap(); } -// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU -// support is suspected. -#[cfg_attr(not(any(target_arch = "x86_64")), ignore)] +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] #[cfg(all(target_os = "linux"))] #[test] fn test_recvmmsg_timestampns() { @@ -1646,9 +1700,9 @@ fn test_recvmmsg_timestampns() { nix::unistd::close(in_socket).unwrap(); } -// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack of QEMU -// support is suspected. -#[cfg_attr(not(any(target_arch = "x86_64")), ignore)] +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] #[test] fn test_recvmsg_rxq_ovfl() { @@ -1728,3 +1782,160 @@ fn test_recvmsg_rxq_ovfl() { nix::unistd::close(in_socket).unwrap(); nix::unistd::close(out_socket).unwrap(); } + +#[cfg(any( + target_os = "linux", + target_os = "android", +))] +mod linux_errqueue { + use nix::sys::socket::*; + use super::{FromStr, SocketAddr}; + + // Send a UDP datagram to a bogus destination address and observe an ICMP error (v4). + // + // Disable the test on QEMU because QEMU emulation of IP_RECVERR is broken (as documented on PR + // #1514). + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recverr_v4() { + #[repr(u8)] + enum IcmpTypes { + DestUnreach = 3, // ICMP_DEST_UNREACH + } + #[repr(u8)] + enum IcmpUnreachCodes { + PortUnreach = 3, // ICMP_PORT_UNREACH + } + + test_recverr_impl::<sockaddr_in, _, _>( + "127.0.0.1:6800", + AddressFamily::Inet, + sockopt::Ipv4RecvErr, + libc::SO_EE_ORIGIN_ICMP, + IcmpTypes::DestUnreach as u8, + IcmpUnreachCodes::PortUnreach as u8, + // Closure handles protocol-specific testing and returns generic sock_extended_err for + // protocol-independent test impl. + |cmsg| { + if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) = cmsg { + if let Some(origin) = err_addr { + // Validate that our network error originated from 127.0.0.1:0. + assert_eq!(origin.sin_family, AddressFamily::Inet as _); + assert_eq!(Ipv4Addr(origin.sin_addr), Ipv4Addr::new(127, 0, 0, 1)); + assert_eq!(origin.sin_port, 0); + } else { + panic!("Expected some error origin"); + } + *ext_err + } else { + panic!("Unexpected control message {:?}", cmsg); + } + }, + ) + } + + // Essentially the same test as v4. + // + // Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on + // PR #1514). + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recverr_v6() { + #[repr(u8)] + enum IcmpV6Types { + DestUnreach = 1, // ICMPV6_DEST_UNREACH + } + #[repr(u8)] + enum IcmpV6UnreachCodes { + PortUnreach = 4, // ICMPV6_PORT_UNREACH + } + + test_recverr_impl::<sockaddr_in6, _, _>( + "[::1]:6801", + AddressFamily::Inet6, + sockopt::Ipv6RecvErr, + libc::SO_EE_ORIGIN_ICMP6, + IcmpV6Types::DestUnreach as u8, + IcmpV6UnreachCodes::PortUnreach as u8, + // Closure handles protocol-specific testing and returns generic sock_extended_err for + // protocol-independent test impl. + |cmsg| { + if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) = cmsg { + if let Some(origin) = err_addr { + // Validate that our network error originated from localhost:0. + assert_eq!(origin.sin6_family, AddressFamily::Inet6 as _); + assert_eq!( + Ipv6Addr(origin.sin6_addr), + Ipv6Addr::from_std(&"::1".parse().unwrap()), + ); + assert_eq!(origin.sin6_port, 0); + } else { + panic!("Expected some error origin"); + } + *ext_err + } else { + panic!("Unexpected control message {:?}", cmsg); + } + }, + ) + } + + fn test_recverr_impl<SA, OPT, TESTF>(sa: &str, + af: AddressFamily, + opt: OPT, + ee_origin: u8, + ee_type: u8, + ee_code: u8, + testf: TESTF) + where + OPT: SetSockOpt<Val = bool>, + TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err, + { + use nix::errno::Errno; + use nix::sys::uio::IoVec; + + const MESSAGE_CONTENTS: &str = "ABCDEF"; + + let sock_addr = { + let std_sa = SocketAddr::from_str(sa).unwrap(); + let inet_addr = InetAddr::from_std(&std_sa); + SockAddr::new_inet(inet_addr) + }; + let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None).unwrap(); + setsockopt(sock, opt, &true).unwrap(); + if let Err(e) = sendto(sock, MESSAGE_CONTENTS.as_bytes(), &sock_addr, MsgFlags::empty()) { + assert_eq!(e, Errno::EADDRNOTAVAIL); + println!("{:?} not available, skipping test.", af); + return; + } + + let mut buf = [0u8; 8]; + let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut cspace = cmsg_space!(libc::sock_extended_err, SA); + + let msg = recvmsg(sock, &iovec, Some(&mut cspace), MsgFlags::MSG_ERRQUEUE).unwrap(); + // The sent message / destination associated with the error is returned: + assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len()); + assert_eq!(&buf[..msg.bytes], MESSAGE_CONTENTS.as_bytes()); + // recvmsg(2): "The original destination address of the datagram that caused the error is + // supplied via msg_name;" however, this is not literally true. E.g., an earlier version + // of this test used 0.0.0.0 (::0) as the destination address, which was mutated into + // 127.0.0.1 (::1). + assert_eq!(msg.address, Some(sock_addr)); + + // Check for expected control message. + let ext_err = match msg.cmsgs().next() { + Some(cmsg) => testf(&cmsg), + None => panic!("No control message"), + }; + + assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32); + assert_eq!(ext_err.ee_origin, ee_origin); + // ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6) + // header. + assert_eq!(ext_err.ee_type, ee_type); + assert_eq!(ext_err.ee_code, ee_code); + // ip(7): ee_info contains the discovered MTU for EMSGSIZE errors. + assert_eq!(ext_err.ee_info, 0); + } +} diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index e0ed0f7..01920fd 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -3,12 +3,53 @@ use nix::sys::socket::{socket, sockopt, getsockopt, setsockopt, AddressFamily, S #[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!(CAP_NET_ADMIN); + 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(); @@ -29,6 +70,57 @@ fn test_so_buf() { 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. @@ -94,3 +186,14 @@ fn test_so_tcp_keepalive() { 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"); +} diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs index 63d6a51..4a86154 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -19,7 +19,7 @@ fn write_all(f: RawFd, buf: &[u8]) { #[test] fn test_tcgetattr_pty() { // openpty uses ptname(3) internally - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); let pty = openpty(None, None).expect("openpty failed"); assert!(termios::tcgetattr(pty.slave).is_ok()); @@ -46,7 +46,7 @@ fn test_tcgetattr_ebadf() { #[test] fn test_output_flags() { // openpty uses ptname(3) internally - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open one pty to get attributes for the second one let mut termios = { @@ -88,7 +88,7 @@ fn test_output_flags() { #[test] fn test_local_flags() { // openpty uses ptname(3) internally - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open one pty to get attributes for the second one let mut termios = { diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs index 9dd4f01..c63b581 100644 --- a/test/sys/test_uio.rs +++ b/test/sys/test_uio.rs @@ -141,7 +141,7 @@ fn test_pread() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(not(target_os = "redox"))] fn test_pwritev() { use std::io::Read; @@ -171,7 +171,7 @@ fn test_pwritev() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(not(target_os = "redox"))] fn test_preadv() { use std::io::Write; @@ -205,16 +205,16 @@ fn test_preadv() { #[test] #[cfg(target_os = "linux")] -// FIXME: qemu-user doesn't implement process_vm_readv/writev on most arches -#[cfg_attr(not(any(target_arch = "x86", target_arch = "x86_64")), ignore)] +// 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!(CAP_SYS_PTRACE); - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + require_capability!("test_process_vm_readv", CAP_SYS_PTRACE); + let _m = crate::FORK_MTX.lock(); // Pre-allocate memory in the child, since allocation isn't safe // post-fork (~= async-signal-safe) diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index 2d26fb8..afe4f42 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -8,7 +8,7 @@ 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"); + let _m = crate::FORK_MTX.lock(); // Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe. match unsafe{fork()}.expect("Error: Fork Failed") { @@ -25,7 +25,7 @@ fn test_wait_signal() { #[test] fn test_wait_exit() { - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); // Safe: Child only calls `_exit`, which is async-signal-safe. match unsafe{fork()}.expect("Error: Fork Failed") { @@ -46,7 +46,7 @@ fn test_waitstatus_from_raw() { #[test] fn test_waitstatus_pid() { - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); match unsafe{fork()}.unwrap() { Child => unsafe { _exit(0) }, @@ -96,8 +96,8 @@ mod ptrace { #[test] fn test_wait_ptrace() { - require_capability!(CAP_SYS_PTRACE); - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + require_capability!("test_wait_ptrace", CAP_SYS_PTRACE); + let _m = crate::FORK_MTX.lock(); match unsafe{fork()}.expect("Error: Fork Failed") { Child => ptrace_child(), diff --git a/test/test.rs b/test/test.rs index 94f8e22..aade937 100644 --- a/test/test.rs +++ b/test/test.rs @@ -24,6 +24,7 @@ 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; @@ -42,7 +43,7 @@ mod test_unistd; use std::os::unix::io::RawFd; use std::path::PathBuf; -use std::sync::{Mutex, RwLock, RwLockWriteGuard}; +use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use nix::unistd::{chdir, getcwd, read}; @@ -83,8 +84,7 @@ struct DirRestore<'a> { impl<'a> DirRestore<'a> { fn new() -> Self { - let guard = crate::CWD_LOCK.write() - .expect("Lock got poisoned by another test"); + let guard = crate::CWD_LOCK.write(); DirRestore{ _g: guard, d: getcwd().unwrap(), diff --git a/test/test_dir.rs b/test/test_dir.rs index 0dc7308..2940b6e 100644 --- a/test/test_dir.rs +++ b/test/test_dir.rs @@ -17,6 +17,7 @@ fn flags() -> OFlag { } #[test] +#[allow(clippy::unnecessary_sort_by)] // False positive fn read() { let tmp = tempdir().unwrap(); File::create(&tmp.path().join("foo")).unwrap(); diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs index ae6756e..db2acfb 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -28,8 +28,6 @@ use std::io::prelude::*; #[cfg(not(target_os = "redox"))] use std::os::unix::fs; -use crate::*; - #[test] #[cfg(not(target_os = "redox"))] fn test_openat() { @@ -129,14 +127,14 @@ fn test_renameat2_exchange() { let old_path = old_dir.path().join("old"); { let mut old_f = File::create(&old_path).unwrap(); - old_f.write(b"old").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(b"new").unwrap(); + new_f.write_all(b"new").unwrap(); } renameat2( Some(old_dirfd), @@ -238,14 +236,8 @@ mod linux_android { /// 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 platforms that use QEMU in CI - #[cfg_attr(all(target_os = "linux", any( - target_arch = "aarch64", - target_arch = "arm", - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64" - )), ignore)] + // QEMU does not support copy_file_range. Skip under qemu + #[cfg_attr(qemu, ignore)] fn test_copy_file_range() { const CONTENTS: &[u8] = b"foobarbaz"; @@ -327,9 +319,10 @@ mod linux_android { let buf1 = b"abcdef"; let buf2 = b"defghi"; - let mut iovecs = Vec::with_capacity(2); - iovecs.push(IoVec::from_slice(&buf1[0..3])); - iovecs.push(IoVec::from_slice(&buf2[0..3])); + let iovecs = vec![ + IoVec::from_slice(&buf1[0..3]), + IoVec::from_slice(&buf2[0..3]) + ]; let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap(); @@ -480,17 +473,16 @@ mod test_posix_fadvise { 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).unwrap(); + let res = posix_fadvise(fd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED); - assert_eq!(res, 0); + assert!(res.is_ok()); } #[test] fn test_errno() { let (rd, _wr) = pipe().unwrap(); - let errno = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED) - .unwrap(); - assert_eq!(errno, Errno::ESPIPE as i32); + let res = posix_fadvise(rd as RawFd, 0, 100, PosixFadviseAdvice::POSIX_FADV_WILLNEED); + assert_eq!(res, Err(Errno::ESPIPE)); } } diff --git a/test/test_kmod/mod.rs b/test/test_kmod/mod.rs index 7626330..8eef538 100644 --- a/test/test_kmod/mod.rs +++ b/test/test_kmod/mod.rs @@ -5,9 +5,7 @@ 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 _m = crate::FORK_MTX.lock(); let tmp_dir = tempdir().expect("unable to create temporary build directory"); @@ -34,16 +32,15 @@ fn compile_kernel_module() -> (PathBuf, String, TempDir) { use nix::errno::Errno; use nix::kmod::{delete_module, DeleteModuleFlags}; use nix::kmod::{finit_module, init_module, ModuleInitFlags}; -use nix::Error; use std::ffi::CString; use std::fs::File; use std::io::Read; #[test] fn test_finit_and_delete_module() { - require_capability!(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"); + require_capability!("test_finit_and_delete_module", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); @@ -58,10 +55,10 @@ fn test_finit_and_delete_module() { } #[test] -fn test_finit_and_delete_modul_with_params() { - require_capability!(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"); +fn test_finit_and_delete_module_with_params() { + require_capability!("test_finit_and_delete_module_with_params", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); @@ -80,9 +77,9 @@ fn test_finit_and_delete_modul_with_params() { #[test] fn test_init_and_delete_module() { - require_capability!(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"); + require_capability!("test_init_and_delete_module", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); @@ -90,7 +87,7 @@ fn test_init_and_delete_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(&mut contents, &CString::new("").unwrap()).expect("unable to load kernel module"); + init_module(&contents, &CString::new("").unwrap()).expect("unable to load kernel module"); delete_module( &CString::new(kmod_name).unwrap(), @@ -100,9 +97,9 @@ fn test_init_and_delete_module() { #[test] fn test_init_and_delete_module_with_params() { - require_capability!(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"); + require_capability!("test_init_and_delete_module_with_params", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); @@ -110,7 +107,7 @@ fn test_init_and_delete_module_with_params() { let mut contents: Vec<u8> = Vec::new(); f.read_to_end(&mut contents) .expect("unable to read kernel module content to buffer"); - init_module(&mut contents, &CString::new("who=Nix number=2015").unwrap()) + init_module(&contents, &CString::new("who=Nix number=2015").unwrap()) .expect("unable to load kernel module"); delete_module( @@ -121,23 +118,23 @@ fn test_init_and_delete_module_with_params() { #[test] fn test_finit_module_invalid() { - require_capability!(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"); + require_capability!("test_finit_module_invalid", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let kmod_path = "/dev/zero"; let f = File::open(kmod_path).expect("unable to open kernel module"); let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); - assert_eq!(result.unwrap_err(), Error::from(Errno::EINVAL)); + assert_eq!(result.unwrap_err(), Errno::EINVAL); } #[test] fn test_finit_module_twice_and_delete_module() { - require_capability!(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"); + require_capability!("test_finit_module_twice_and_delete_module", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); @@ -147,7 +144,7 @@ fn test_finit_module_twice_and_delete_module() { let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); - assert_eq!(result.unwrap_err(), Error::from(Errno::EEXIST)); + assert_eq!(result.unwrap_err(), Errno::EEXIST); delete_module( &CString::new(kmod_name).unwrap(), @@ -157,11 +154,11 @@ fn test_finit_module_twice_and_delete_module() { #[test] fn test_delete_module_not_loaded() { - require_capability!(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"); + require_capability!("test_delete_module_not_loaded", CAP_SYS_MODULE); + let _m0 = crate::KMOD_MTX.lock(); + let _m1 = crate::CWD_LOCK.read(); let result = delete_module(&CString::new("hello").unwrap(), DeleteModuleFlags::empty()); - assert_eq!(result.unwrap_err(), Error::from(Errno::ENOENT)); + assert_eq!(result.unwrap_err(), Errno::ENOENT); } diff --git a/test/test_mount.rs b/test/test_mount.rs index c1b6c8a..44287f9 100644 --- a/test/test_mount.rs +++ b/test/test_mount.rs @@ -21,14 +21,13 @@ mod test_mount { use nix::sys::stat::{self, Mode}; use nix::unistd::getuid; - use tempfile; - - static SCRIPT_CONTENTS: &'static [u8] = b"#!/bin/sh + 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(); diff --git a/test/test_mq.rs b/test/test_mq.rs index d082692..430df5d 100644 --- a/test/test_mq.rs +++ b/test/test_mq.rs @@ -60,7 +60,11 @@ fn test_mq_getattr() { // FIXME: Fix failures for mips in QEMU #[test] #[cfg(not(any(target_os = "netbsd")))] -#[cfg_attr(any(target_arch = "mips", target_arch = "mips64"), ignore)] +#[cfg_attr(all( + qemu, + any(target_arch = "mips", target_arch = "mips64") + ), ignore +)] fn test_mq_setattr() { use nix::mqueue::{mq_getattr, mq_setattr}; const MSG_SIZE: mq_attr_member_t = 32; @@ -97,7 +101,11 @@ fn test_mq_setattr() { // FIXME: Fix failures for mips in QEMU #[test] #[cfg(not(any(target_os = "netbsd")))] -#[cfg_attr(any(target_arch = "mips", target_arch = "mips64"), ignore)] +#[cfg_attr(all( + qemu, + any(target_arch = "mips", target_arch = "mips64") + ), ignore +)] fn test_mq_set_nonblocking() { use nix::mqueue::{mq_getattr, mq_set_nonblock, mq_remove_nonblock}; const MSG_SIZE: mq_attr_member_t = 32; diff --git a/test/test_poll.rs b/test/test_poll.rs index 0395512..e4b369f 100644 --- a/test/test_poll.rs +++ b/test/test_poll.rs @@ -64,3 +64,19 @@ fn test_ppoll() { assert_eq!(nfds, 1); assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); } + +#[test] +fn test_pollfd_fd() { + use std::os::unix::io::AsRawFd; + + let pfd = PollFd::new(0x1234, PollFlags::empty()); + assert_eq!(pfd.as_raw_fd(), 0x1234); +} + +#[test] +fn test_pollfd_events() { + let mut pfd = PollFd::new(-1, PollFlags::POLLIN); + assert_eq!(pfd.events(), PollFlags::POLLIN); + pfd.set_events(PollFlags::POLLOUT); + assert_eq!(pfd.events(), PollFlags::POLLOUT); +} diff --git a/test/test_pty.rs b/test/test_pty.rs index 52b6334..71932f2 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -29,7 +29,7 @@ fn test_explicit_close() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_equivalence() { - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); @@ -46,7 +46,7 @@ fn test_ptsname_equivalence() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_copy() { - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); @@ -80,7 +80,7 @@ fn test_ptsname_r_copy() { #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_ptsname_unique() { - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master1_fd = posix_openpt(OFlag::O_RDWR).unwrap(); @@ -98,7 +98,7 @@ fn test_ptsname_unique() { /// Common setup for testing PTTY pairs fn open_ptty_pair() -> (PtyMaster, File) { - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open a new PTTY master let master = posix_openpt(OFlag::O_RDWR).expect("posix_openpt failed"); @@ -114,6 +114,8 @@ fn open_ptty_pair() -> (PtyMaster, File) { let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty()).unwrap(); #[cfg(target_os = "illumos")] + // TODO: rewrite using ioctl! + #[allow(clippy::comparison_chain)] { use libc::{ioctl, I_FIND, I_PUSH}; @@ -185,7 +187,7 @@ fn test_write_ptty_pair() { #[test] fn test_openpty() { // openpty uses ptname(3) internally - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); let pty = openpty(None, None).unwrap(); assert!(pty.master > 0); @@ -220,7 +222,7 @@ fn test_openpty() { #[test] fn test_openpty_with_termios() { // openpty uses ptname(3) internally - let _m = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::PTSNAME_MTX.lock(); // Open one pty to get attributes for the second one let mut termios = { @@ -271,9 +273,9 @@ fn test_forkpty() { use nix::sys::signal::*; use nix::sys::wait::wait; // forkpty calls openpty which uses ptname(3) internally. - let _m0 = crate::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + let _m0 = crate::PTSNAME_MTX.lock(); // forkpty spawns a child process - let _m1 = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m1 = crate::FORK_MTX.lock(); let string = "naninani\n"; let echoed_string = "naninani\r\n"; diff --git a/test/test_resource.rs b/test/test_resource.rs new file mode 100644 index 0000000..5969750 --- /dev/null +++ b/test/test_resource.rs @@ -0,0 +1,23 @@ +#[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "illumos")))] +use nix::sys::resource::{getrlimit, setrlimit, Resource}; + +/// Tests the RLIMIT_NOFILE functionality of getrlimit(), where the resource RLIMIT_NOFILE refers +/// to the maximum file descriptor number that can be opened by the process (aka the maximum number +/// of file descriptors that the process can open, since Linux 4.5). +/// +/// We first fetch the existing file descriptor maximum values using getrlimit(), then edit the +/// soft limit to make sure it has a new and distinct value to the hard limit. We then setrlimit() +/// to put the new soft limit in effect, and then getrlimit() once more to ensure the limits have +/// been updated. +#[test] +#[cfg(not(any(target_os = "redox", target_os = "fuchsia", target_os = "illumos")))] +pub fn test_resource_limits_nofile() { + let (soft_limit, hard_limit) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); + + let soft_limit = Some(soft_limit.map_or(1024, |v| v - 1)); + assert_ne!(soft_limit, hard_limit); + setrlimit(Resource::RLIMIT_NOFILE, soft_limit, hard_limit).unwrap(); + + let (new_soft_limit, _) = getrlimit(Resource::RLIMIT_NOFILE).unwrap(); + assert_eq!(new_soft_limit, soft_limit); +} diff --git a/test/test_stat.rs b/test/test_stat.rs index 424371f..33cf748 100644 --- a/test/test_stat.rs +++ b/test/test_stat.rs @@ -11,7 +11,6 @@ use std::path::Path; #[cfg(not(any(target_os = "netbsd", target_os = "redox")))] use libc::{S_IFMT, S_IFLNK}; -#[cfg(not(target_os = "redox"))] use libc::mode_t; #[cfg(not(target_os = "redox"))] @@ -60,6 +59,7 @@ fn assert_stat_results(stat_result: Result<FileStat>) { #[cfg(not(any(target_os = "netbsd", target_os = "redox")))] // (Android's st_blocks is ulonglong which is always non-negative.) #[cfg_attr(target_os = "android", allow(unused_comparisons))] +#[allow(clippy::absurd_extreme_comparisons)] // Not absurd on all OSes fn assert_lstat_results(stat_result: Result<FileStat>) { let stats = stat_result.expect("stat call failed"); assert!(stats.st_dev > 0); // must be positive integer, exact number machine dependent @@ -306,3 +306,53 @@ fn test_mkdirat_fail() { let result = mkdirat(dirfd, filename, Mode::S_IRWXU).unwrap_err(); assert_eq!(result, Errno::ENOTDIR); } + +#[test] +#[cfg(not(any(target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "redox")))] +fn test_mknod() { + use stat::{lstat, mknod, SFlag}; + + let file_name = "test_file"; + let tempdir = tempfile::tempdir().unwrap(); + let target = tempdir.path().join(file_name); + mknod(&target, SFlag::S_IFREG, Mode::S_IRWXU, 0).unwrap(); + let mode = lstat(&target).unwrap().st_mode as mode_t; + assert!(mode & libc::S_IFREG == libc::S_IFREG); + assert!(mode & libc::S_IRWXU == libc::S_IRWXU); +} + +#[test] +#[cfg(not(any(target_os = "freebsd", + target_os = "illumos", + target_os = "ios", + target_os = "macos", + target_os = "redox")))] +fn test_mknodat() { + use fcntl::{AtFlags, OFlag}; + use nix::dir::Dir; + use stat::{fstatat, mknodat, SFlag}; + + let file_name = "test_file"; + let tempdir = tempfile::tempdir().unwrap(); + let target_dir = Dir::open(tempdir.path(), OFlag::O_DIRECTORY, Mode::S_IRWXU).unwrap(); + mknodat( + target_dir.as_raw_fd(), + file_name, + SFlag::S_IFREG, + Mode::S_IRWXU, + 0, + ) + .unwrap(); + let mode = fstatat( + target_dir.as_raw_fd(), + file_name, + AtFlags::AT_SYMLINK_NOFOLLOW, + ) + .unwrap() + .st_mode as mode_t; + assert!(mode & libc::S_IFREG == libc::S_IFREG); + assert!(mode & libc::S_IRWXU == libc::S_IRWXU); +} diff --git a/test/test_time.rs b/test/test_time.rs index c321352..dc307e5 100644 --- a/test/test_time.rs +++ b/test/test_time.rs @@ -6,11 +6,12 @@ target_os = "emscripten", ))] use nix::time::clock_getcpuclockid; -use nix::time::{clock_getres, clock_gettime, ClockId}; +use nix::time::{clock_gettime, ClockId}; +#[cfg(not(target_os = "redox"))] #[test] pub fn test_clock_getres() { - assert!(clock_getres(ClockId::CLOCK_REALTIME).is_ok()); + assert!(nix::time::clock_getres(ClockId::CLOCK_REALTIME).is_ok()); } #[test] @@ -31,6 +32,7 @@ pub fn test_clock_getcpuclockid() { assert!(clock_gettime(clock_id).is_ok()); } +#[cfg(not(target_os = "redox"))] #[test] pub fn test_clock_id_res() { assert!(ClockId::CLOCK_REALTIME.res().is_ok()); diff --git a/test/test_unistd.rs b/test/test_unistd.rs index b95f154..61062ad 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -10,7 +10,7 @@ use nix::sys::stat::{self, Mode, SFlag}; #[cfg(not(any(target_os = "redox", target_os = "fuchsia")))] use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname}; use nix::errno::Errno; -use std::{env, iter}; +use std::env; #[cfg(not(any(target_os = "fuchsia", target_os = "redox")))] use std::ffi::CString; #[cfg(not(target_os = "redox"))] @@ -28,7 +28,7 @@ use crate::*; #[test] #[cfg(not(any(target_os = "netbsd")))] fn test_fork_and_waitpid() { - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); // Safe: Child only calls `_exit`, which is signal-safe match unsafe{fork()}.expect("Error: Fork Failed") { @@ -56,7 +56,7 @@ fn test_fork_and_waitpid() { #[test] fn test_wait() { // Grab FORK_MTX so wait doesn't reap a different test's child process - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); // Safe: Child only calls `_exit`, which is signal-safe match unsafe{fork()}.expect("Error: Fork Failed") { @@ -116,7 +116,7 @@ fn test_mkfifo_directory() { target_os = "macos", target_os = "ios", target_os = "android", target_os = "redox")))] fn test_mkfifoat_none() { - let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); let mkfifoat_fifo = tempdir.path().join("mkfifoat_fifo"); @@ -151,10 +151,10 @@ fn test_mkfifoat() { target_os = "macos", target_os = "ios", target_os = "android", target_os = "redox")))] fn test_mkfifoat_directory_none() { - let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); + let _m = crate::CWD_LOCK.read(); // mkfifoat should fail if a directory is given - assert!(!mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_ok()); + assert!(mkfifoat(None, &env::temp_dir(), Mode::S_IRUSR).is_err()); } #[test] @@ -168,7 +168,7 @@ fn test_mkfifoat_directory() { let mkfifoat_dir = "mkfifoat_dir"; stat::mkdirat(dirfd, mkfifoat_dir, Mode::S_IRUSR).unwrap(); - assert!(!mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_ok()); + assert!(mkfifoat(Some(dirfd), mkfifoat_dir, Mode::S_IRUSR).is_err()); } #[test] @@ -206,7 +206,7 @@ fn test_setgroups() { // Skip this test when not run as root as `setgroups()` requires root. skip_if_not_root!("test_setgroups"); - let _m = crate::GROUPS_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::GROUPS_MTX.lock(); // Save the existing groups let old_groups = getgroups().unwrap(); @@ -234,7 +234,7 @@ fn test_initgroups() { // require root. skip_if_not_root!("test_initgroups"); - let _m = crate::GROUPS_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::GROUPS_MTX.lock(); // Save the existing groups let old_groups = getgroups().unwrap(); @@ -304,7 +304,7 @@ macro_rules! execve_test_factory( skip_if_seccomp!($test_name); } - let m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let m = crate::FORK_MTX.lock(); // The `exec`d process will write to `writer`, and we'll read that // data from `reader`. let (reader, writer) = pipe().unwrap(); @@ -443,7 +443,7 @@ fn test_getcwd() { // 4096 on linux, 1024 on macos) let mut inner_tmp_dir = tmpdir_path; for _ in 0..5 { - let newdir = iter::repeat("a").take(100).collect::<String>(); + let newdir = "a".repeat(100); inner_tmp_dir.push(newdir); assert!(mkdir(inner_tmp_dir.as_path(), Mode::S_IRWXU).is_ok()); } @@ -549,7 +549,7 @@ cfg_if!{ if #[cfg(any(target_os = "android", target_os = "linux"))] { macro_rules! require_acct{ () => { - require_capability!(CAP_SYS_PACCT); + require_capability!("test_acct", CAP_SYS_PACCT); } } } else if #[cfg(target_os = "freebsd")] { @@ -575,7 +575,7 @@ fn test_acct() { use std::process::Command; use std::{thread, time}; - let _m = crate::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::FORK_MTX.lock(); require_acct!(); let file = NamedTempFile::new().unwrap(); @@ -735,7 +735,7 @@ fn test_alarm() { }; // Maybe other tests that fork interfere with this one? - let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::SIGNAL_MTX.lock(); let handler = SigHandler::Handler(alarm_signal_handler); let signal_action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty()); @@ -773,7 +773,7 @@ fn test_alarm() { #[test] #[cfg(not(target_os = "redox"))] fn test_canceling_alarm() { - let _m = crate::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test"); + let _m = crate::SIGNAL_MTX.lock(); assert_eq!(alarm::cancel(), None); @@ -784,7 +784,7 @@ fn test_canceling_alarm() { #[test] #[cfg(not(target_os = "redox"))] fn test_symlinkat() { - let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); @@ -883,7 +883,7 @@ fn test_linkat_newdirfd_none() { #[test] #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] fn test_linkat_no_follow_symlink() { - let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); let oldfilename = "foo.txt"; @@ -920,7 +920,7 @@ fn test_linkat_no_follow_symlink() { #[test] #[cfg(not(target_os = "redox"))] fn test_linkat_follow_symlink() { - let _m = crate::CWD_LOCK.read().expect("Mutex got poisoned by another test"); + let _m = crate::CWD_LOCK.read(); let tempdir = tempdir().unwrap(); let oldfilename = "foo.txt"; @@ -1025,13 +1025,22 @@ fn test_access_file_exists() { assert!(access(&path, AccessFlags::R_OK | AccessFlags::W_OK).is_ok()); } +#[cfg(not(target_os = "redox"))] +#[test] +fn test_user_into_passwd() { + // get the UID of the "nobody" user + let nobody = User::from_name("nobody").unwrap().unwrap(); + let pwd: libc::passwd = nobody.into(); + let _: User = (&pwd).into(); +} + /// Tests setting the filesystem UID with `setfsuid`. #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_setfsuid() { use std::os::unix::fs::PermissionsExt; use std::{fs, io, thread}; - require_capability!(CAP_SETUID); + require_capability!("test_setfsuid", CAP_SETUID); // get the UID of the "nobody" user let nobody = User::from_name("nobody").unwrap().unwrap(); @@ -1042,7 +1051,7 @@ fn test_setfsuid() { dbg!(&temp_path); let temp_path_2 = (&temp_path).to_path_buf(); let mut permissions = fs::metadata(&temp_path).unwrap().permissions(); - permissions.set_mode(640); + permissions.set_mode(0o640); // spawn a new thread where to test setfsuid thread::spawn(move || { |