diff --git a/.github/actions/check_new_changelog/action.yml b/.github/actions/check_new_changelog/action.yml new file mode 100644 index 0000000000..c366654aae --- /dev/null +++ b/.github/actions/check_new_changelog/action.yml @@ -0,0 +1,46 @@ +name: 'Check new CHANGELOG' +description: 'Check new CHANGELOG kind and PR number' + +runs: + using: "composite" + steps: + - name: Get newly added CHANGELOGs + id: new-changelogs + uses: tj-actions/changed-files@v44 + with: + # Only checek the files under the `changelog` directory + files: changelog/** + + - name: Check them + shell: bash + if: steps.new-changelogs.outputs.added_files_count != 0 + env: + NEW_CHANGELOGS: ${{ steps.new-changelogs.outputs.added_files }} + PR_NUMBER: ${{ github.event.number }} + run: | + # `cl` will be something like "changelog/1.added.md" + for cl in ${NEW_CHANGELOGS}; do + # Trim the directory name + prefix="changelog/"; trimmed_cl=${cl/#$prefix}; cl="${trimmed_cl}"; + + # parse it + IFS='.' read id kind file_extension <<< "${cl}" + + # Check the kind field + if [ "$kind" != "added" ] && [ "$kind" != "changed" ] && [ "$kind" != "fixed" ] && [ "$kind" != "removed" ]; then + echo "Invalid CHANGELOG kind [${kind}] from [${cl}], available options are [added, changed, fixed, removed]"; + exit 1; + fi + + # Check the file extension + if [ "$file_extension" != "md" ]; then + echo "Invalid file extension [${file_extension}] from [${cl}], it should be [md]"; + exit 1; + fi + + # Check PR number + if [ "$id" != "$PR_NUMBER" ]; then + echo "Mismatched PR number [${id}] from [${cl}], it should be ${PR_NUMBER}"; + exit 1; + fi + done diff --git a/.github/workflows/check_new_changelog.yml b/.github/workflows/check_new_changelog.yml new file mode 100644 index 0000000000..b6ef26ab1c --- /dev/null +++ b/.github/workflows/check_new_changelog.yml @@ -0,0 +1,18 @@ +name: Check new CHANGELOGs + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + check_new_changelog: + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: check new CHANGELOG + uses: ./.github/actions/check_new_changelog diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a3a02c707..ed51548a7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ env: MSRV: 1.69.0 jobs: - macos: runs-on: macos-13 env: @@ -42,7 +41,7 @@ jobs: TARGET: '${{ env.TARGET }}' - name: before_cache_script - run: rm -rf $CARGO_HOME/registry/index + run: sudo rm -rf $CARGO_HOME/registry/index macos-aarch64: runs-on: macos-14 @@ -69,7 +68,7 @@ jobs: TARGET: "${{ env.TARGET }}" - name: before_cache_script - run: rm -rf $CARGO_HOME/registry/index + run: sudo rm -rf $CARGO_HOME/registry/index # Use cross for QEMU-based testing # cross needs to execute Docker, GitHub Action already has it installed @@ -163,7 +162,7 @@ jobs: TARGET: '${{ matrix.TARGET }}' - name: before_cache_script - run: rm -rf $CARGO_HOME/registry/index + run: sudo rm -rf $CARGO_HOME/registry/index; rust_stable: runs-on: ubuntu-20.04 @@ -189,7 +188,7 @@ jobs: TARGET: '${{ env.TARGET }}' - name: before_cache_script - run: rm -rf $CARGO_HOME/registry/index + run: sudo rm -rf $CARGO_HOME/registry/index diff --git a/CHANGELOG.md b/CHANGELOG.md index 37e4ab2d4c..b673cac562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,98 @@ This project adheres to [Semantic Versioning](https://semver.org/). # Change Log +## [0.29.0] - 2024-05-24 + + +### Added + +- Add `getregset()/setregset()` for Linux/glibc/x86/x86_64/aarch64/riscv64 and + `getregs()/setregs()` for Linux/glibc/aarch64/riscv64 + ([#2044](https://github.com/nix-rust/nix/pull/2044)) +- Add socket option Ipv6Ttl for apple targets. + ([#2287](https://github.com/nix-rust/nix/pull/2287)) +- Add socket option UtunIfname. + ([#2325](https://github.com/nix-rust/nix/pull/2325)) +- make SigAction repr(transparent) & can be converted to the libc raw type + ([#2326](https://github.com/nix-rust/nix/pull/2326)) +- Add `From` trait implementation for conversions between `sockaddr_in` and + `SockaddrIn`, `sockaddr_in6` and `SockaddrIn6` + ([#2328](https://github.com/nix-rust/nix/pull/2328)) +- Add socket option ReusePortLb for FreeBSD. + ([#2332](https://github.com/nix-rust/nix/pull/2332)) +- Added support for openat2 on linux. + ([#2339](https://github.com/nix-rust/nix/pull/2339)) +- Add if_indextoname function. + ([#2340](https://github.com/nix-rust/nix/pull/2340)) +- Add `mount` and `unmount` API for apple targets. + ([#2347](https://github.com/nix-rust/nix/pull/2347)) +- Added `_PC_MIN_HOLE_SIZE` for `pathconf` and `fpathconf`. + ([#2349](https://github.com/nix-rust/nix/pull/2349)) +- Added `impl AsFd for pty::PtyMaster` + ([#2355](https://github.com/nix-rust/nix/pull/2355)) +- Add `open` flag `O_SEARCH` to AIX, Empscripten, FreeBSD, Fuchsia, solarish, + WASI ([#2374](https://github.com/nix-rust/nix/pull/2374)) +- Add prctl function `prctl_set_vma_anon_name` for Linux/Android. + ([#2378](https://github.com/nix-rust/nix/pull/2378)) +- Add `sync(2)` for `apple_targets/solarish/haiku/aix/hurd`, `syncfs(2)` for + `hurd` and `fdatasync(2)` for `aix/hurd` + ([#2379](https://github.com/nix-rust/nix/pull/2379)) +- Add fdatasync support for Apple targets. + ([#2380](https://github.com/nix-rust/nix/pull/2380)) +- Add `fcntl::OFlag::O_PATH` for FreeBSD and Fuchsia + ([#2382](https://github.com/nix-rust/nix/pull/2382)) +- Added `PathconfVar::MIN_HOLE_SIZE` for apple_targets. + ([#2388](https://github.com/nix-rust/nix/pull/2388)) +- Add `open` flag `O_SEARCH` to apple_targets + ([#2391](https://github.com/nix-rust/nix/pull/2391)) +- `O_DSYNC` may now be used with `aio_fsync` and `fcntl` on FreeBSD. + ([#2404](https://github.com/nix-rust/nix/pull/2404)) +- Added `Flock::relock` for upgrading and downgrading locks. + ([#2407](https://github.com/nix-rust/nix/pull/2407)) + +### Changed + +- Change the `ForkptyResult` type to the following repr so that the + uninitialized + `master` field won't be accessed in the child process: + + ```rs + pub enum ForkptyResult { + Parent { + child: Pid, + master: OwnedFd, + }, + Child, + } + ``` ([#2315](https://github.com/nix-rust/nix/pull/2315)) +- Updated `cfg_aliases` dependency from version 0.1 to 0.2 + ([#2322](https://github.com/nix-rust/nix/pull/2322)) +- Change the signature of `ptrace::write` and `ptrace::write_user` to make them + safe ([#2324](https://github.com/nix-rust/nix/pull/2324)) +- Allow use of `SignalFd` through shared reference + + Like with many other file descriptors, concurrent use of signalfds is safe. + Changing the signal mask of and reading signals from a signalfd can now be + done + with the `SignalFd` API even if other references to it exist. + ([#2367](https://github.com/nix-rust/nix/pull/2367)) +- Changed tee, splice and vmsplice RawFd arguments to AsFd. + ([#2387](https://github.com/nix-rust/nix/pull/2387)) +- Added I/O safety to the sys/aio module. Most functions that previously + accepted a `AsRawFd` argument now accept an `AsFd` instead. + ([#2401](https://github.com/nix-rust/nix/pull/2401)) +- `RecvMsg::cmsgs()` now returns a `Result`, and checks that cmsgs were not + truncated. ([#2413](https://github.com/nix-rust/nix/pull/2413)) + +### Fixed + +- No longer panics when the `fanotify` queue overflows. + ([#2399](https://github.com/nix-rust/nix/pull/2399)) +- Fixed ControlMessageOwned::UdpGroSegments wrapped type from u16 to i32 to + reflect the used kernel's one. + ([#2406](https://github.com/nix-rust/nix/pull/2406)) + + ## [0.28.0] - 2024-02-24 diff --git a/CONVENTIONS.md b/CONVENTIONS.md index d71a3d17dd..036762e4ee 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -47,9 +47,13 @@ When creating newtypes, we use Rust's `CamelCase` type naming convention. ## cfg gates When creating operating-system-specific functionality, we gate it by -`#[cfg(target_os = ...)]`. If more than one operating system is affected, we +`#[cfg(target_os = ...)]`. If **MORE THAN ONE operating system** is affected, we prefer to use the cfg aliases defined in build.rs, like `#[cfg(bsd)]`. +Please **DO NOT** use cfg aliases for **ONLY ONE** system as [they are bad][mismatched_target_os]. + +[mismatched_target_os]: https://rust-lang.github.io/rust-clippy/master/index.html#/mismatched_target_os + ## Bitflags Many C functions have flags parameters that are combined from constants using @@ -70,9 +74,9 @@ libc_bitflags!{ PROT_READ; PROT_WRITE; PROT_EXEC; - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(linux_android)] PROT_GROWSDOWN; - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(linux_android)] PROT_GROWSUP; } } @@ -131,3 +135,20 @@ If you want to add a test for a feature that is in `xxx.rs`, then the test shoul be put in the corresponding `test_xxx.rs` file unless you cannot do this, e.g., the test involves private stuff and thus cannot be added outside of Nix, then it is allowed to leave the test in `xxx.rs`. + +## Syscall/libc function error handling + +Most syscall and libc functions return an [`ErrnoSentinel`][trait] value on error, +we has a nice utility function [`Errno::result()`][util] to convert it to the +Rusty `Result` type, e.g., here is how `dup(2)` uses it: + +```rs +pub fn dup(oldfd: RawFd) -> Result { + let res = unsafe { libc::dup(oldfd) }; + + Errno::result(res) +} +``` + +[trait]: https://docs.rs/nix/latest/nix/errno/trait.ErrnoSentinel.html +[util]: https://docs.rs/nix/latest/nix/errno/enum.Errno.html#method.result diff --git a/Cargo.toml b/Cargo.toml index d8176a7500..edf9973a13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "nix" description = "Rust friendly bindings to *nix APIs" edition = "2021" -version = "0.28.0" +version = "0.29.0" rust-version = "1.69" authors = ["The nix-rust Project Developers"] repository = "https://github.com/nix-rust/nix" @@ -28,7 +28,7 @@ targets = [ ] [dependencies] -libc = { version = "0.2.153", features = ["extra_traits"] } +libc = { version = "0.2.155", features = ["extra_traits"] } bitflags = "2.3.1" cfg-if = "1.0" pin-utils = { version = "0.1.0", optional = true } @@ -85,7 +85,7 @@ caps = "0.5.3" sysctl = "0.4" [build-dependencies] -cfg_aliases = "0.1.1" +cfg_aliases = "0.2" [[test]] name = "test" diff --git a/RELEASE_PROCEDURE.md b/RELEASE_PROCEDURE.md index b7d1fa3972..3f2041cec7 100644 --- a/RELEASE_PROCEDURE.md +++ b/RELEASE_PROCEDURE.md @@ -11,6 +11,9 @@ major bump. The release is prepared as follows: +> NOTE: the following procedure should be done directly against the master +> branch of the repo. + - Ask for a new libc version if, necessary. It usually is. Then update the dependency in `Cargo.toml` to rely on a release from crates.io. @@ -23,7 +26,12 @@ The release is prepared as follows: - Update the version number in `Cargo.toml` - Generate `CHANGELOG.md` for this release by `towncrier build --version= --yes` + +- Ensure you have a crates.io token + 1. With the `publich-update` scope + 2. Can be used for crate `nix` + 3. It is set via `cargo login` + - Confirm that everything's ready for a release by running `cargo release ` - Create the release with `cargo release -x ` -- Push the created tag to GitHub. diff --git a/build.rs b/build.rs index 4535af1f04..226a32cccb 100644 --- a/build.rs +++ b/build.rs @@ -14,12 +14,27 @@ fn main() { solaris: { target_os = "solaris" }, watchos: { target_os = "watchos" }, tvos: { target_os = "tvos" }, + visionos: { target_os = "visionos" }, - apple_targets: { any(ios, macos, watchos, tvos) }, + + // cfg aliases we would like to use + apple_targets: { any(ios, macos, watchos, tvos, visionos) }, bsd: { any(freebsd, dragonfly, netbsd, openbsd, apple_targets) }, + bsd_without_apple: { any(freebsd, dragonfly, netbsd, openbsd) }, linux_android: { any(android, linux) }, freebsdlike: { any(dragonfly, freebsd) }, netbsdlike: { any(netbsd, openbsd) }, solarish: { any(illumos, solaris) }, } + + // Below are Nix's custom cfg values that we need to let the compiler know + println!("cargo:rustc-check-cfg=cfg(apple_targets)"); + println!("cargo:rustc-check-cfg=cfg(bsd)"); + println!("cargo:rustc-check-cfg=cfg(bsd_without_apple)"); + println!("cargo:rustc-check-cfg=cfg(linux_android)"); + println!("cargo:rustc-check-cfg=cfg(freebsdlike)"); + println!("cargo:rustc-check-cfg=cfg(netbsdlike)"); + println!("cargo:rustc-check-cfg=cfg(solarish)"); + println!("cargo:rustc-check-cfg=cfg(fbsd14)"); + println!("cargo:rustc-check-cfg=cfg(qemu)"); } diff --git a/src/dir.rs b/src/dir.rs index ab70f064cc..20c5593702 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -59,7 +59,7 @@ impl Dir { Dir::from_fd(fd.into_raw_fd()) } - /// Converts from a file descriptor, closing it on success or failure. + /// Converts from a file descriptor, closing it on failure. #[doc(alias("fdopendir"))] pub fn from_fd(fd: RawFd) -> Result { let d = ptr::NonNull::new(unsafe { libc::fdopendir(fd) }).ok_or_else( diff --git a/src/fcntl.rs b/src/fcntl.rs index ccefe955de..cf87926c7b 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -114,7 +114,7 @@ libc_bitflags!( /// If the specified path isn't a directory, fail. O_DIRECTORY; /// Implicitly follow each `write()` with an `fdatasync()`. - #[cfg(any(linux_android, apple_targets, netbsdlike))] + #[cfg(any(linux_android, apple_targets, target_os = "freebsd", netbsdlike))] O_DSYNC; /// Error out if a file was not created. O_EXCL; @@ -151,7 +151,7 @@ libc_bitflags!( /// Obtain a file descriptor for low-level access. /// /// The file itself is not opened and other file operations will fail. - #[cfg(any(linux_android, target_os = "redox"))] + #[cfg(any(linux_android, target_os = "redox", target_os = "freebsd", target_os = "fuchsia"))] O_PATH; /// Only allow reading. /// @@ -164,8 +164,18 @@ libc_bitflags!( /// Similar to `O_DSYNC` but applies to `read`s instead. #[cfg(any(target_os = "linux", netbsdlike))] O_RSYNC; - /// Skip search permission checks. - #[cfg(target_os = "netbsd")] + /// Open directory for search only. Skip search permission checks on + /// later `openat()` calls using the obtained file descriptor. + #[cfg(any( + apple_targets, + solarish, + target_os = "netbsd", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "emscripten", + target_os = "aix", + target_os = "wasi" + ))] O_SEARCH; /// Open with a shared file lock. #[cfg(any(bsd, target_os = "redox"))] @@ -242,6 +252,119 @@ pub fn openat( Errno::result(fd) } +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + libc_bitflags! { + /// Path resolution flags. + /// + /// See [path resolution(7)](https://man7.org/linux/man-pages/man7/path_resolution.7.html) + /// for details of the resolution process. + pub struct ResolveFlag: libc::c_ulonglong { + /// Do not permit the path resolution to succeed if any component of + /// the resolution is not a descendant of the directory indicated by + /// dirfd. This causes absolute symbolic links (and absolute values of + /// pathname) to be rejected. + RESOLVE_BENEATH; + + /// Treat the directory referred to by dirfd as the root directory + /// while resolving pathname. + RESOLVE_IN_ROOT; + + /// Disallow all magic-link resolution during path resolution. Magic + /// links are symbolic link-like objects that are most notably found + /// in proc(5); examples include `/proc/[pid]/exe` and `/proc/[pid]/fd/*`. + /// + /// See symlink(7) for more details. + RESOLVE_NO_MAGICLINKS; + + /// Disallow resolution of symbolic links during path resolution. This + /// option implies RESOLVE_NO_MAGICLINKS. + RESOLVE_NO_SYMLINKS; + + /// Disallow traversal of mount points during path resolution (including + /// all bind mounts). + RESOLVE_NO_XDEV; + } + } + + /// Specifies how [openat2] should open a pathname. + /// + /// See + #[repr(transparent)] + #[derive(Clone, Copy, Debug)] + pub struct OpenHow(libc::open_how); + + impl OpenHow { + /// Create a new zero-filled `open_how`. + pub fn new() -> Self { + // safety: according to the man page, open_how MUST be zero-initialized + // on init so that unknown fields are also zeroed. + Self(unsafe { + std::mem::MaybeUninit::zeroed().assume_init() + }) + } + + /// Set the open flags used to open a file, completely overwriting any + /// existing flags. + pub fn flags(mut self, flags: OFlag) -> Self { + let flags = flags.bits() as libc::c_ulonglong; + self.0.flags = flags; + self + } + + /// Set the file mode new files will be created with, overwriting any + /// existing flags. + pub fn mode(mut self, mode: Mode) -> Self { + let mode = mode.bits() as libc::c_ulonglong; + self.0.mode = mode; + self + } + + /// Set resolve flags, completely overwriting any existing flags. + /// + /// See [ResolveFlag] for more detail. + pub fn resolve(mut self, resolve: ResolveFlag) -> Self { + let resolve = resolve.bits(); + self.0.resolve = resolve; + self + } + } + + // safety: default isn't derivable because libc::open_how must be zeroed + impl Default for OpenHow { + fn default() -> Self { + Self::new() + } + } + + /// Open or create a file for reading, writing or executing. + /// + /// `openat2` is an extension of the [`openat`] function that allows the caller + /// to control how path resolution happens. + /// + /// # See also + /// + /// [openat2](https://man7.org/linux/man-pages/man2/openat2.2.html) + pub fn openat2( + dirfd: RawFd, + path: &P, + mut how: OpenHow, + ) -> Result { + let fd = path.with_nix_path(|cstr| unsafe { + libc::syscall( + libc::SYS_openat2, + dirfd, + cstr.as_ptr(), + &mut how as *mut OpenHow, + std::mem::size_of::(), + ) + })?; + + Errno::result(fd as RawFd) + } + } +} + /// Change the name of a file. /// /// The `renameat` function is equivalent to `rename` except in the case where either `old_path` @@ -832,6 +955,30 @@ impl Flock { std::mem::forget(self); Ok(inner) } + + /// Relock the file. This can upgrade or downgrade the lock type. + /// + /// # Example + /// ``` + /// # use std::fs::File; + /// # use nix::fcntl::{Flock, FlockArg}; + /// # use tempfile::tempfile; + /// let f: std::fs::File = tempfile().unwrap(); + /// let locked_file = Flock::lock(f, FlockArg::LockExclusive).unwrap(); + /// // Do stuff, then downgrade the lock + /// locked_file.relock(FlockArg::LockShared).unwrap(); + /// ``` + pub fn relock(&self, arg: FlockArg) -> Result<()> { + let flags = match arg { + FlockArg::LockShared => libc::LOCK_SH, + FlockArg::LockExclusive => libc::LOCK_EX, + FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB, + FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB, + #[allow(deprecated)] + FlockArg::Unlock | FlockArg::UnlockNonblock => return Err(Errno::EINVAL), + }; + Errno::result(unsafe { libc::flock(self.as_raw_fd(), flags) }).map(drop) + } } // Safety: `File` is not [std::clone::Clone]. @@ -940,10 +1087,10 @@ pub fn copy_file_range( /// # See Also /// *[`splice`](https://man7.org/linux/man-pages/man2/splice.2.html) #[cfg(linux_android)] -pub fn splice( - fd_in: RawFd, +pub fn splice( + fd_in: Fd1, off_in: Option<&mut libc::loff_t>, - fd_out: RawFd, + fd_out: Fd2, off_out: Option<&mut libc::loff_t>, len: usize, flags: SpliceFFlags, @@ -956,7 +1103,7 @@ pub fn splice( .unwrap_or(ptr::null_mut()); let ret = unsafe { - libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) + libc::splice(fd_in.as_fd().as_raw_fd(), off_in, fd_out.as_fd().as_raw_fd(), off_out, len, flags.bits()) }; Errno::result(ret).map(|r| r as usize) } @@ -966,13 +1113,13 @@ pub fn splice( /// # See Also /// *[`tee`](https://man7.org/linux/man-pages/man2/tee.2.html) #[cfg(linux_android)] -pub fn tee( - fd_in: RawFd, - fd_out: RawFd, +pub fn tee( + fd_in: Fd1, + fd_out: Fd2, len: usize, flags: SpliceFFlags, ) -> Result { - let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) }; + let ret = unsafe { libc::tee(fd_in.as_fd().as_raw_fd(), fd_out.as_fd().as_raw_fd(), len, flags.bits()) }; Errno::result(ret).map(|r| r as usize) } @@ -981,14 +1128,14 @@ pub fn tee( /// # See Also /// *[`vmsplice`](https://man7.org/linux/man-pages/man2/vmsplice.2.html) #[cfg(linux_android)] -pub fn vmsplice( - fd: RawFd, +pub fn vmsplice( + fd: F, iov: &[std::io::IoSlice<'_>], flags: SpliceFFlags, ) -> Result { let ret = unsafe { libc::vmsplice( - fd, + fd.as_fd().as_raw_fd(), iov.as_ptr().cast(), iov.len(), flags.bits(), diff --git a/src/lib.rs b/src/lib.rs index dffac29b54..c4c0fa53cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -362,3 +362,21 @@ impl NixPath for PathBuf { self.as_os_str().with_nix_path(f) } } + +/// Like `NixPath::with_nix_path()`, but allow the `path` argument to be optional. +/// +/// A NULL pointer will be provided if `path.is_none()`. +#[cfg(any( + all(apple_targets, feature = "mount"), + all(linux_android, any(feature = "mount", feature = "fanotify")) +))] +pub(crate) fn with_opt_nix_path(path: Option<&P>, f: F) -> Result +where + P: ?Sized + NixPath, + F: FnOnce(*const libc::c_char) -> T, +{ + match path { + Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), + None => Ok(f(ptr::null())), + } +} diff --git a/src/mount/apple.rs b/src/mount/apple.rs new file mode 100644 index 0000000000..ce0ab1e9ca --- /dev/null +++ b/src/mount/apple.rs @@ -0,0 +1,111 @@ +use crate::{Errno, NixPath, Result}; +use libc::c_int; + +libc_bitflags!( + /// Used with [`mount()`] and [`unmount()`]. + pub struct MntFlags: c_int { + /// Do not interpret special files on the filesystem. + MNT_NODEV; + /// Enable data protection on the filesystem if the filesystem is configured for it. + MNT_CPROTECT; + /// file system is quarantined + MNT_QUARANTINE; + /// filesystem is stored locally + MNT_LOCAL; + /// quotas are enabled on filesystem + MNT_QUOTA; + /// identifies the root filesystem + MNT_ROOTFS; + /// file system is not appropriate path to user data + MNT_DONTBROWSE; + /// VFS will ignore ownership information on filesystem objects + MNT_IGNORE_OWNERSHIP; + /// filesystem was mounted by automounter + MNT_AUTOMOUNTED; + /// filesystem is journaled + MNT_JOURNALED; + /// Don't allow user extended attributes + MNT_NOUSERXATTR; + /// filesystem should defer writes + MNT_DEFWRITE; + /// don't block unmount if not responding + MNT_NOBLOCK; + /// file system is exported + MNT_EXPORTED; + /// file system written asynchronously + MNT_ASYNC; + /// Force a read-write mount even if the file system appears to be + /// unclean. + MNT_FORCE; + /// MAC support for objects. + MNT_MULTILABEL; + /// Do not update access times. + MNT_NOATIME; + /// Disallow program execution. + MNT_NOEXEC; + /// Do not honor setuid or setgid bits on files when executing them. + MNT_NOSUID; + /// Mount read-only. + MNT_RDONLY; + /// Causes the vfs subsystem to update its data structures pertaining to + /// the specified already mounted file system. + MNT_RELOAD; + /// Create a snapshot of the file system. + MNT_SNAPSHOT; + /// All I/O to the file system should be done synchronously. + MNT_SYNCHRONOUS; + /// Union with underlying fs. + MNT_UNION; + /// Indicates that the mount command is being applied to an already + /// mounted file system. + MNT_UPDATE; + } +); + +/// Mount a file system. +/// +/// # Arguments +/// - `source` - Specifies the file system. e.g. `/dev/sd0`. +/// - `target` - Specifies the destination. e.g. `/mnt`. +/// - `flags` - Optional flags controlling the mount. +/// - `data` - Optional file system specific data. +/// +/// # see also +/// [`mount`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mount.2.html) +pub fn mount< + P1: ?Sized + NixPath, + P2: ?Sized + NixPath, + P3: ?Sized + NixPath, +>( + source: &P1, + target: &P2, + flags: MntFlags, + data: Option<&P3>, +) -> Result<()> { + let res = source.with_nix_path(|s| { + target.with_nix_path(|t| { + crate::with_opt_nix_path(data, |d| unsafe { + libc::mount( + s.as_ptr(), + t.as_ptr(), + flags.bits(), + d.cast_mut().cast(), + ) + }) + }) + })???; + + Errno::result(res).map(drop) +} + +/// Umount the file system mounted at `target`. +pub fn unmount

(target: &P, flags: MntFlags) -> Result<()> +where + P: ?Sized + NixPath, +{ + let res = target.with_nix_path(|cstr| unsafe { + libc::unmount(cstr.as_ptr(), flags.bits()) + })?; + + Errno::result(res).map(drop) +} diff --git a/src/mount/bsd.rs b/src/mount/bsd_without_apple.rs similarity index 98% rename from src/mount/bsd.rs rename to src/mount/bsd_without_apple.rs index 248e0ab1d2..ae9eed7c0e 100644 --- a/src/mount/bsd.rs +++ b/src/mount/bsd_without_apple.rs @@ -30,7 +30,7 @@ libc_bitflags!( #[cfg(target_os = "freebsd")] MNT_GJOURNAL; /// MAC support for objects. - #[cfg(any(apple_targets, target_os = "freebsd"))] + #[cfg(target_os = "freebsd")] MNT_MULTILABEL; /// Disable read clustering. #[cfg(freebsdlike)] @@ -58,7 +58,7 @@ libc_bitflags!( /// Create a snapshot of the file system. /// /// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs) - #[cfg(any(apple_targets, target_os = "freebsd"))] + #[cfg(target_os = "freebsd")] MNT_SNAPSHOT; /// Using soft updates. #[cfg(any(freebsdlike, netbsdlike))] @@ -71,7 +71,6 @@ libc_bitflags!( MNT_SYNCHRONOUS; /// Union with underlying fs. #[cfg(any( - apple_targets, target_os = "freebsd", target_os = "netbsd" ))] diff --git a/src/mount/linux.rs b/src/mount/linux.rs index aa166bc9d3..3c27150761 100644 --- a/src/mount/linux.rs +++ b/src/mount/linux.rs @@ -113,21 +113,10 @@ pub fn mount< flags: MsFlags, data: Option<&P4>, ) -> Result<()> { - fn with_opt_nix_path(p: Option<&P>, f: F) -> Result - where - P: ?Sized + NixPath, - F: FnOnce(*const libc::c_char) -> T, - { - match p { - Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), - None => Ok(f(std::ptr::null())), - } - } - - let res = with_opt_nix_path(source, |s| { + let res = crate::with_opt_nix_path(source, |s| { target.with_nix_path(|t| { - with_opt_nix_path(fstype, |ty| { - with_opt_nix_path(data, |d| unsafe { + crate::with_opt_nix_path(fstype, |ty| { + crate::with_opt_nix_path(data, |d| unsafe { libc::mount( s, t.as_ptr(), diff --git a/src/mount/mod.rs b/src/mount/mod.rs index 8caf27f7df..41e7b3ec6d 100644 --- a/src/mount/mod.rs +++ b/src/mount/mod.rs @@ -5,8 +5,14 @@ mod linux; #[cfg(linux_android)] pub use self::linux::*; -#[cfg(bsd)] -mod bsd; +#[cfg(bsd_without_apple)] +mod bsd_without_apple; -#[cfg(bsd)] -pub use self::bsd::*; +#[cfg(bsd_without_apple)] +pub use self::bsd_without_apple::*; + +#[cfg(apple_targets)] +mod apple; + +#[cfg(apple_targets)] +pub use self::apple::*; diff --git a/src/net/if_.rs b/src/net/if_.rs index c66b5dc0b3..5b6c2e77a5 100644 --- a/src/net/if_.rs +++ b/src/net/if_.rs @@ -3,9 +3,9 @@ //! Uses Linux and/or POSIX functions to resolve interface names like "eth0" //! or "socan1" into device numbers. -use std::fmt; -use crate::{Error, NixPath, Result}; -use libc::c_uint; +use std::{ffi::{CStr, CString}, fmt}; +use crate::{errno::Errno, Error, NixPath, Result}; +use libc::{c_uint, IF_NAMESIZE}; #[cfg(not(solarish))] /// type alias for InterfaceFlags @@ -14,7 +14,7 @@ pub type IflagsType = libc::c_int; /// type alias for InterfaceFlags pub type IflagsType = libc::c_longlong; -/// Resolve an interface into a interface number. +/// Resolve an interface into an interface number. pub fn if_nametoindex(name: &P) -> Result { let if_index = name .with_nix_path(|name| unsafe { libc::if_nametoindex(name.as_ptr()) })?; @@ -26,6 +26,19 @@ pub fn if_nametoindex(name: &P) -> Result { } } +/// Resolve an interface number into an interface. +pub fn if_indextoname(index: c_uint) -> Result { + // We need to allocate this anyway, so doing it directly is faster. + let mut buf = vec![0u8; IF_NAMESIZE]; + + let return_buf = unsafe { + libc::if_indextoname(index, buf.as_mut_ptr().cast()) + }; + + Errno::result(return_buf.cast())?; + Ok(CStr::from_bytes_until_nul(buf.as_slice()).unwrap().to_owned()) +} + libc_bitflags!( /// Standard interface flags, used by `getifaddrs` pub struct InterfaceFlags: IflagsType { diff --git a/src/pty.rs b/src/pty.rs index 74f8ecf0df..171bbfa138 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -12,8 +12,6 @@ use std::os::unix::prelude::*; use crate::errno::Errno; #[cfg(not(target_os = "aix"))] use crate::sys::termios::Termios; -#[cfg(feature = "process")] -use crate::unistd::ForkResult; #[cfg(all(feature = "process", not(target_os = "aix")))] use crate::unistd::Pid; use crate::{fcntl, unistd, Result}; @@ -31,15 +29,19 @@ pub struct OpenptyResult { feature! { #![feature = "process"] -/// Representation of a master with a forked pty -/// -/// This is returned by [`forkpty`]. +/// A successful result of [`forkpty()`]. #[derive(Debug)] -pub struct ForkptyResult { - /// The master port in a virtual pty pair - pub master: OwnedFd, - /// Metadata about forked process - pub fork_result: ForkResult, +pub enum ForkptyResult { + /// This is the parent process of the underlying fork. + Parent { + /// The PID of the fork's child process + child: Pid, + /// A file descriptor referring to master side of the pseudoterminal of + /// the child process. + master: OwnedFd, + }, + /// This is the child process of the underlying fork. + Child, } } @@ -56,6 +58,12 @@ impl AsRawFd for PtyMaster { } } +impl AsFd for PtyMaster { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + impl IntoRawFd for PtyMaster { fn into_raw_fd(self) -> RawFd { let fd = self.0; @@ -300,9 +308,7 @@ pub fn openpty< feature! { #![feature = "process"] -/// Create a new pseudoterminal, returning the master file descriptor and forked pid. -/// in `ForkptyResult` -/// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)). +/// Create a new process operating in a pseudoterminal. /// /// If `winsize` is not `None`, the window size of the slave will be set to /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's @@ -319,6 +325,11 @@ feature! { /// special care must be taken to only invoke code you can control and audit. /// /// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html +/// +/// # Reference +/// +/// * [FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=forkpty) +/// * [Linux](https://man7.org/linux/man-pages/man3/forkpty.3.html) #[cfg(not(target_os = "aix"))] pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into>>( winsize: T, @@ -343,14 +354,23 @@ pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into ForkResult::Child, - res => ForkResult::Parent { child: Pid::from_raw(res) }, - })?; + let success_ret = Errno::result(res)?; + let forkpty_result = match success_ret { + // In the child process + 0 => ForkptyResult::Child, + // In the parent process + child_pid => { + // SAFETY: + // 1. The master buffer is guaranteed to be initialized in the parent process + // 2. OwnedFd::from_raw_fd won't panic as the fd is a valid file descriptor + let master = unsafe { OwnedFd::from_raw_fd( master.assume_init() ) }; + ForkptyResult::Parent { + master, + child: Pid::from_raw(child_pid), + } + } + }; - Ok(ForkptyResult { - master: unsafe { OwnedFd::from_raw_fd( master.assume_init() ) }, - fork_result, - }) + Ok(forkpty_result) } } diff --git a/src/sys/aio.rs b/src/sys/aio.rs index e9213c6434..c7ba40534c 100644 --- a/src/sys/aio.rs +++ b/src/sys/aio.rs @@ -30,7 +30,7 @@ use std::{ fmt::{self, Debug}, marker::{PhantomData, PhantomPinned}, mem, - os::unix::io::RawFd, + os::unix::io::{AsFd, AsRawFd, BorrowedFd}, pin::Pin, ptr, thread, }; @@ -55,6 +55,7 @@ libc_enum! { /// on supported operating systems only, do it like `fdatasync` #[cfg(any(apple_targets, target_os = "linux", + target_os = "freebsd", netbsdlike))] O_DSYNC } @@ -102,7 +103,7 @@ unsafe impl Sync for LibcAiocb {} // provide polymorphism at the wrong level. Instead, the best place for // polymorphism is at the level of `Futures`. #[repr(C)] -struct AioCb { +struct AioCb<'a> { aiocb: LibcAiocb, /// Could this `AioCb` potentially have any in-kernel state? // It would be really nice to perform the in-progress check entirely at @@ -112,9 +113,10 @@ struct AioCb { // that there's no way to write an AioCb constructor that neither boxes // the object itself, nor moves it during return. in_progress: bool, + _fd: PhantomData>, } -impl AioCb { +impl<'a> AioCb<'a> { pin_utils::unsafe_unpinned!(aiocb: LibcAiocb); fn aio_return(mut self: Pin<&mut Self>) -> Result { @@ -139,18 +141,23 @@ impl AioCb { } } - fn common_init(fd: RawFd, prio: i32, sigev_notify: SigevNotify) -> Self { + fn common_init( + fd: BorrowedFd<'a>, + prio: i32, + sigev_notify: SigevNotify, + ) -> Self { // Use mem::zeroed instead of explicitly zeroing each field, because the // number and name of reserved fields is OS-dependent. On some OSes, // some reserved fields are used the kernel for state, and must be // explicitly zeroed when allocated. let mut a = unsafe { mem::zeroed::() }; - a.aio_fildes = fd; + a.aio_fildes = fd.as_raw_fd(); a.aio_reqprio = prio; a.aio_sigevent = SigEvent::new(sigev_notify).sigevent(); AioCb { aiocb: LibcAiocb(a), in_progress: false, + _fd: PhantomData, } } @@ -186,7 +193,7 @@ impl AioCb { } } -impl Debug for AioCb { +impl<'a> Debug for AioCb<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("AioCb") .field("aiocb", &self.aiocb.0) @@ -195,7 +202,7 @@ impl Debug for AioCb { } } -impl Drop for AioCb { +impl<'a> Drop for AioCb<'a> { /// If the `AioCb` has no remaining state in the kernel, just drop it. /// Otherwise, dropping constitutes a resource leak, which is an error fn drop(&mut self) { @@ -243,11 +250,11 @@ pub trait Aio { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::Write; - /// # use std::os::unix::io::AsRawFd; + /// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); - /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// &wbuf[..], /// 0, //priority @@ -284,11 +291,11 @@ pub trait Aio { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; - /// # use std::os::unix::io::AsRawFd; + /// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); - /// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), + /// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// WBUF, /// 0, //priority @@ -306,7 +313,7 @@ pub trait Aio { fn error(self: Pin<&mut Self>) -> Result<()>; /// Returns the underlying file descriptor associated with the operation. - fn fd(&self) -> RawFd; + fn fd(&self) -> BorrowedFd; /// Does this operation currently have any in-kernel state? /// @@ -321,10 +328,10 @@ pub trait Aio { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify::SigevNone; /// # use std::{thread, time}; - /// # use std::os::unix::io::AsRawFd; + /// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let f = tempfile().unwrap(); - /// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, + /// let mut aiof = Box::pin(AioFsync::new(f.as_fd(), AioFsyncMode::O_SYNC, /// 0, SigevNone)); /// assert!(!aiof.as_mut().in_progress()); /// aiof.as_mut().submit().expect("aio_fsync failed early"); @@ -364,8 +371,10 @@ macro_rules! aio_methods { self.aiocb().error() } - fn fd(&self) -> RawFd { - self.aiocb.aiocb.0.aio_fildes + fn fd(&self) -> BorrowedFd<'a> { + // safe because self's lifetime is the same as the original file + // descriptor. + unsafe { BorrowedFd::borrow_raw(self.aiocb.aiocb.0.aio_fildes) } } fn in_progress(&self) -> bool { @@ -413,10 +422,10 @@ macro_rules! aio_methods { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify::SigevNone; /// # use std::{thread, time}; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let f = tempfile().unwrap(); -/// let mut aiof = Box::pin(AioFsync::new(f.as_raw_fd(), AioFsyncMode::O_SYNC, +/// let mut aiof = Box::pin(AioFsync::new(f.as_fd(), AioFsyncMode::O_SYNC, /// 0, SigevNone)); /// aiof.as_mut().submit().expect("aio_fsync failed early"); /// while (aiof.as_mut().error() == Err(Errno::EINPROGRESS)) { @@ -426,13 +435,13 @@ macro_rules! aio_methods { /// ``` #[derive(Debug)] #[repr(transparent)] -pub struct AioFsync { - aiocb: AioCb, +pub struct AioFsync<'a> { + aiocb: AioCb<'a>, _pin: PhantomPinned, } -impl AioFsync { - unsafe_pinned!(aiocb: AioCb); +impl<'a> AioFsync<'a> { + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the operation's fsync mode: data and metadata or data only? pub fn mode(&self) -> AioFsyncMode { @@ -451,7 +460,7 @@ impl AioFsync { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, mode: AioFsyncMode, prio: i32, sigev_notify: SigevNotify, @@ -469,7 +478,7 @@ impl AioFsync { } } -impl Aio for AioFsync { +impl<'a> Aio for AioFsync<'a> { type Output = (); aio_methods!(); @@ -490,7 +499,7 @@ impl Aio for AioFsync { // AioFsync does not need AsMut, since it can't be used with lio_listio -impl AsRef for AioFsync { +impl<'a> AsRef for AioFsync<'a> { fn as_ref(&self) -> &libc::aiocb { &self.aiocb.aiocb.0 } @@ -512,7 +521,7 @@ impl AsRef for AioFsync { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::Write; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const INITIAL: &[u8] = b"abcdef123456"; /// const LEN: usize = 4; @@ -522,7 +531,7 @@ impl AsRef for AioFsync { /// { /// let mut aior = Box::pin( /// AioRead::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// &mut rbuf, /// 0, //priority @@ -540,13 +549,13 @@ impl AsRef for AioFsync { #[derive(Debug)] #[repr(transparent)] pub struct AioRead<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [u8]>, _pin: PhantomPinned, } impl<'a> AioRead<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the requested length of the aio operation in bytes /// @@ -570,7 +579,7 @@ impl<'a> AioRead<'a> { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, offs: off_t, buf: &'a mut [u8], prio: i32, @@ -629,7 +638,7 @@ impl<'a> AsRef for AioRead<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::{IoSliceMut, Write}; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const INITIAL: &[u8] = b"abcdef123456"; /// let mut rbuf0 = vec![0; 4]; @@ -641,7 +650,7 @@ impl<'a> AsRef for AioRead<'a> { /// { /// let mut aior = Box::pin( /// AioReadv::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// &mut rbufs, /// 0, //priority @@ -661,14 +670,14 @@ impl<'a> AsRef for AioRead<'a> { #[derive(Debug)] #[repr(transparent)] pub struct AioReadv<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [&'a [u8]]>, _pin: PhantomPinned, } #[cfg(target_os = "freebsd")] impl<'a> AioReadv<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the number of buffers the operation will read into. pub fn iovlen(&self) -> usize { @@ -689,7 +698,7 @@ impl<'a> AioReadv<'a> { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, offs: off_t, bufs: &mut [IoSliceMut<'a>], prio: i32, @@ -750,13 +759,13 @@ impl<'a> AsRef for AioReadv<'a> { /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin( /// AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// WBUF, /// 0, //priority @@ -772,13 +781,13 @@ impl<'a> AsRef for AioReadv<'a> { #[derive(Debug)] #[repr(transparent)] pub struct AioWrite<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [u8]>, _pin: PhantomPinned, } impl<'a> AioWrite<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the requested length of the aio operation in bytes /// @@ -802,7 +811,7 @@ impl<'a> AioWrite<'a> { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, offs: off_t, buf: &'a [u8], prio: i32, @@ -864,7 +873,7 @@ impl<'a> AsRef for AioWrite<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::IoSlice; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const wbuf0: &[u8] = b"abcdef"; /// const wbuf1: &[u8] = b"123456"; @@ -873,7 +882,7 @@ impl<'a> AsRef for AioWrite<'a> { /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin( /// AioWritev::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, //offset /// &wbufs, /// 0, //priority @@ -890,14 +899,14 @@ impl<'a> AsRef for AioWrite<'a> { #[derive(Debug)] #[repr(transparent)] pub struct AioWritev<'a> { - aiocb: AioCb, + aiocb: AioCb<'a>, _data: PhantomData<&'a [&'a [u8]]>, _pin: PhantomPinned, } #[cfg(target_os = "freebsd")] impl<'a> AioWritev<'a> { - unsafe_pinned!(aiocb: AioCb); + unsafe_pinned!(aiocb: AioCb<'a>); /// Returns the number of buffers the operation will read into. pub fn iovlen(&self) -> usize { @@ -918,7 +927,7 @@ impl<'a> AioWritev<'a> { /// * `sigev_notify`: Determines how you will be notified of event /// completion. pub fn new( - fd: RawFd, + fd: BorrowedFd<'a>, offs: off_t, bufs: &[IoSlice<'a>], prio: i32, @@ -983,17 +992,17 @@ impl<'a> AsRef for AioWritev<'a> { /// # use nix::sys::signal::SigevNotify; /// # use std::{thread, time}; /// # use std::io::Write; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// let wbuf = b"CDEF"; /// let mut f = tempfile().unwrap(); -/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// &wbuf[..], /// 0, //priority /// SigevNotify::SigevNone)); /// aiocb.as_mut().submit().unwrap(); -/// let cs = aio_cancel_all(f.as_raw_fd()).unwrap(); +/// let cs = aio_cancel_all(f.as_fd()).unwrap(); /// if cs == AioCancelStat::AioNotCanceled { /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) { /// thread::sleep(time::Duration::from_millis(10)); @@ -1006,8 +1015,8 @@ impl<'a> AsRef for AioWritev<'a> { /// # References /// /// [`aio_cancel`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html) -pub fn aio_cancel_all(fd: RawFd) -> Result { - match unsafe { libc::aio_cancel(fd, ptr::null_mut()) } { +pub fn aio_cancel_all(fd: F) -> Result { + match unsafe { libc::aio_cancel(fd.as_fd().as_raw_fd(), ptr::null_mut()) } { libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled), libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled), libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone), @@ -1028,18 +1037,18 @@ pub fn aio_cancel_all(fd: RawFd) -> Result { /// ``` /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); -/// let mut aiocb = Box::pin(AioWrite::new(f.as_raw_fd(), +/// let mut aiocb = Box::pin(AioWrite::new(f.as_fd(), /// 2, //offset /// WBUF, /// 0, //priority /// SigevNotify::SigevNone)); /// aiocb.as_mut().submit().unwrap(); /// aio_suspend(&[&*aiocb], None).expect("aio_suspend failed"); -/// assert_eq!(aiocb.as_mut().aio_return().unwrap() as usize, WBUF.len()); +/// assert_eq!(aiocb.as_mut().aio_return().unwrap(), WBUF.len()); /// ``` /// # References /// @@ -1078,14 +1087,14 @@ pub fn aio_suspend( /// This mode is useful for otherwise-synchronous programs that want to execute /// a handful of I/O operations in parallel. /// ``` -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use nix::sys::aio::*; /// # use nix::sys::signal::SigevNotify; /// # use tempfile::tempfile; /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin(AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, // offset /// WBUF, /// 0, // priority @@ -1102,7 +1111,7 @@ pub fn aio_suspend( /// technique for reducing overall context-switch overhead, especially when /// combined with kqueue. /// ``` -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use std::thread; /// # use std::time; /// # use nix::errno::Errno; @@ -1112,7 +1121,7 @@ pub fn aio_suspend( /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin(AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, // offset /// WBUF, /// 0, // priority @@ -1136,7 +1145,7 @@ pub fn aio_suspend( /// possibly resubmit some. /// ``` /// # use libc::c_int; -/// # use std::os::unix::io::AsRawFd; +/// # use std::os::unix::io::AsFd; /// # use std::sync::atomic::{AtomicBool, Ordering}; /// # use std::thread; /// # use std::time; @@ -1158,7 +1167,7 @@ pub fn aio_suspend( /// const WBUF: &[u8] = b"abcdef123456"; /// let mut f = tempfile().unwrap(); /// let mut aiow = Box::pin(AioWrite::new( -/// f.as_raw_fd(), +/// f.as_fd(), /// 2, // offset /// WBUF, /// 0, // priority diff --git a/src/sys/fanotify.rs b/src/sys/fanotify.rs index e217406e02..e22c52753d 100644 --- a/src/sys/fanotify.rs +++ b/src/sys/fanotify.rs @@ -96,9 +96,22 @@ libc_bitflags! { /// final data. FAN_CLASS_PRE_CONTENT; - /// Remove the limit of 16384 events for the event queue. + /// Remove the limit on the number of events in the event queue. + /// + /// Prior to Linux kernel 5.13, this limit was hardcoded to 16384. After + /// 5.13, one can change it via file `/proc/sys/fs/fanotify/max_queued_events`. + /// + /// See `fanotify(7)` for details about this limit. Use of this flag + /// requires the `CAP_SYS_ADMIN` capability. FAN_UNLIMITED_QUEUE; - /// Remove the limit of 8192 marks. + /// Remove the limit on the number of fanotify marks per user. + /// + /// Prior to Linux kernel 5.13, this limit was hardcoded to 8192 (per + /// group, not per user). After 5.13, one can change it via file + /// `/proc/sys/fs/fanotify/max_user_marks`. + /// + /// See `fanotify(7)` for details about this limit. Use of this flag + /// requires the `CAP_SYS_ADMIN` capability. FAN_UNLIMITED_MARKS; /// Make `FanotifyEvent::pid` return pidfd. Since Linux 5.15. @@ -236,6 +249,9 @@ impl FanotifyEvent { impl Drop for FanotifyEvent { fn drop(&mut self) { + if self.0.fd == libc::FAN_NOFD { + return; + } let e = close(self.0.fd); if !std::thread::panicking() && e == Err(Errno::EBADF) { panic!("Closing an invalid file descriptor!"); @@ -313,18 +329,7 @@ impl Fanotify { dirfd: Option, path: Option<&P>, ) -> Result<()> { - fn with_opt_nix_path(p: Option<&P>, f: F) -> Result - where - P: ?Sized + NixPath, - F: FnOnce(*const libc::c_char) -> T, - { - match p { - Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())), - None => Ok(f(std::ptr::null())), - } - } - - let res = with_opt_nix_path(path, |p| unsafe { + let res = crate::with_opt_nix_path(path, |p| unsafe { libc::fanotify_mark( self.fd.as_raw_fd(), flags.bits(), diff --git a/src/sys/prctl.rs b/src/sys/prctl.rs index 42324beab2..35b1ce170f 100644 --- a/src/sys/prctl.rs +++ b/src/sys/prctl.rs @@ -9,9 +9,11 @@ use crate::errno::Errno; use crate::sys::signal::Signal; use crate::Result; -use libc::{c_int, c_ulong}; +use libc::{c_int, c_ulong, c_void}; use std::convert::TryFrom; use std::ffi::{CStr, CString}; +use std::num::NonZeroUsize; +use std::ptr::NonNull; libc_enum! { /// The type of hardware memory corruption kill policy for the thread. @@ -213,3 +215,14 @@ pub fn set_thp_disable(flag: bool) -> Result<()> { pub fn get_thp_disable() -> Result { prctl_get_bool(libc::PR_GET_THP_DISABLE) } + +/// Set an identifier (or reset it) to the address memory range. +pub fn set_vma_anon_name(addr: NonNull, length: NonZeroUsize, name: Option<&CStr>) -> Result<()> { + let nameref = match name { + Some(n) => n.as_ptr(), + _ => std::ptr::null() + }; + let res = unsafe { libc::prctl(libc::PR_SET_VMA, libc::PR_SET_VMA_ANON_NAME, addr.as_ptr(), length, nameref) }; + + Errno::result(res).map(drop) +} diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index 26544e134b..8abaf4d71b 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -17,8 +17,10 @@ pub type AddressType = *mut ::libc::c_void; target_arch = "x86_64", any(target_env = "gnu", target_env = "musl") ), - all(target_arch = "x86", target_env = "gnu") - ) + all(target_arch = "x86", target_env = "gnu"), + all(target_arch = "aarch64", target_env = "gnu"), + all(target_arch = "riscv64", target_env = "gnu"), + ), ))] use libc::user_regs_struct; @@ -170,6 +172,92 @@ libc_enum! { } } +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) +))] +libc_enum! { + #[repr(i32)] + /// Defines a specific register set, as used in `PTRACE_GETREGSET` and `PTRACE_SETREGSET`. + #[non_exhaustive] + pub enum RegisterSetValue { + NT_PRSTATUS, + NT_PRFPREG, + NT_PRPSINFO, + NT_TASKSTRUCT, + NT_AUXV, + } +} + +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) +))] +/// Represents register set areas, such as general-purpose registers or +/// floating-point registers. +/// +/// # Safety +/// +/// This trait is marked unsafe, since implementation of the trait must match +/// ptrace's request `VALUE` and return data type `Regs`. +pub unsafe trait RegisterSet { + /// Corresponding type of registers in the kernel. + const VALUE: RegisterSetValue; + + /// Struct representing the register space. + type Regs; +} + +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) +))] +/// Register sets used in [`getregset`] and [`setregset`] +pub mod regset { + use super::*; + + #[derive(Debug, Clone, Copy)] + /// General-purpose registers. + pub enum NT_PRSTATUS {} + + unsafe impl RegisterSet for NT_PRSTATUS { + const VALUE: RegisterSetValue = RegisterSetValue::NT_PRSTATUS; + type Regs = user_regs_struct; + } + + #[derive(Debug, Clone, Copy)] + /// Floating-point registers. + pub enum NT_PRFPREG {} + + unsafe impl RegisterSet for NT_PRFPREG { + const VALUE: RegisterSetValue = RegisterSetValue::NT_PRFPREG; + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + type Regs = libc::user_fpregs_struct; + #[cfg(target_arch = "aarch64")] + type Regs = libc::user_fpsimd_struct; + #[cfg(target_arch = "riscv64")] + type Regs = libc::__riscv_mc_d_ext_state; + } +} + libc_bitflags! { /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request. /// See `man ptrace` for more details. @@ -217,6 +305,12 @@ fn ptrace_peek( } /// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)` +/// +/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html #[cfg(all( target_os = "linux", any( @@ -231,7 +325,58 @@ pub fn getregs(pid: Pid) -> Result { ptrace_get_data::(Request::PTRACE_GETREGS, pid) } +/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)` +/// +/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "aarch64", target_arch = "riscv64") +))] +pub fn getregs(pid: Pid) -> Result { + getregset::(pid) +} + +/// Get a particular set of user registers, as with `ptrace(PTRACE_GETREGSET, ...)` +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) +))] +pub fn getregset(pid: Pid) -> Result { + let request = Request::PTRACE_GETREGSET; + let mut data = mem::MaybeUninit::::uninit(); + let mut iov = libc::iovec { + iov_base: data.as_mut_ptr().cast(), + iov_len: mem::size_of::(), + }; + unsafe { + ptrace_other( + request, + pid, + S::VALUE as i32 as AddressType, + (&mut iov as *mut libc::iovec).cast(), + )?; + }; + Ok(unsafe { data.assume_init() }) +} + /// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)` +/// +/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html #[cfg(all( target_os = "linux", any( @@ -248,12 +393,55 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { Request::PTRACE_SETREGS as RequestType, libc::pid_t::from(pid), ptr::null_mut::(), - ®s as *const _ as *const c_void, + ®s as *const user_regs_struct as *const c_void, ) }; Errno::result(res).map(drop) } +/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)` +/// +/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]), +/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect +/// on aarch64 and riscv64. +/// +/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "aarch64", target_arch = "riscv64") +))] +pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> { + setregset::(pid, regs) +} + +/// Set a particular set of user registers, as with `ptrace(PTRACE_SETREGSET, ...)` +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) +))] +pub fn setregset(pid: Pid, mut regs: S::Regs) -> Result<()> { + let mut iov = libc::iovec { + iov_base: (&mut regs as *mut S::Regs).cast(), + iov_len: mem::size_of::(), + }; + unsafe { + ptrace_other( + Request::PTRACE_SETREGSET, + pid, + S::VALUE as i32 as AddressType, + (&mut iov as *mut libc::iovec).cast(), + )?; + } + Ok(()) +} + /// Function for ptrace requests that return values from the data field. /// Some ptrace get requests populate structs or larger elements than `c_long` /// and therefore use the data field to return values. This function handles these @@ -543,17 +731,15 @@ pub fn read(pid: Pid, addr: AddressType) -> Result { /// Writes a word into the processes memory at the given address, as with /// ptrace(PTRACE_POKEDATA, ...) -/// -/// # Safety -/// -/// The `data` argument is passed directly to `ptrace(2)`. Read that man page -/// for guidance. -pub unsafe fn write( - pid: Pid, - addr: AddressType, - data: *mut c_void, -) -> Result<()> { - unsafe { ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(drop) } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn write(pid: Pid, addr: AddressType, data: c_long) -> Result<()> { + unsafe { + // Safety(not_unsafe_ptr_arg_deref): + // `ptrace_other` is a common abstract + // but in `PTRACE_POKEDATA` situation, `data` is exactly what will be wtitten + ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data as *mut c_void) + .map(drop) + } } /// Reads a word from a user area at `offset`, as with ptrace(PTRACE_PEEKUSER, ...). @@ -564,17 +750,13 @@ pub fn read_user(pid: Pid, offset: AddressType) -> Result { /// Writes a word to a user area at `offset`, as with ptrace(PTRACE_POKEUSER, ...). /// The user struct definition can be found in `/usr/include/sys/user.h`. -/// -/// # Safety -/// -/// The `data` argument is passed directly to `ptrace(2)`. Read that man page -/// for guidance. -pub unsafe fn write_user( - pid: Pid, - offset: AddressType, - data: *mut c_void, -) -> Result<()> { +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn write_user(pid: Pid, offset: AddressType, data: c_long) -> Result<()> { unsafe { - ptrace_other(Request::PTRACE_POKEUSER, pid, offset, data).map(drop) + // Safety(not_unsafe_ptr_arg_deref): + // `ptrace_other` is a common abstract + // but in `PTRACE_POKEDATA` situation, `data` is exactly what will be wtitten + ptrace_other(Request::PTRACE_POKEUSER, pid, offset, data as *mut c_void) + .map(drop) } } diff --git a/src/sys/resource.rs b/src/sys/resource.rs index 71315072d4..73d8a05e0f 100644 --- a/src/sys/resource.rs +++ b/src/sys/resource.rs @@ -293,7 +293,9 @@ impl Usage { TimeVal::from(self.0.ru_stime) } - /// The resident set size at its peak, in kilobytes. + /// The resident set size at its peak, + #[cfg_attr(apple_targets, doc = " in bytes.")] + #[cfg_attr(not(apple_targets), doc = " in kilobytes.")] pub fn max_rss(&self) -> c_long { self.0.ru_maxrss } diff --git a/src/sys/signal.rs b/src/sys/signal.rs index c9b593d0db..921fb28d6f 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -597,7 +597,7 @@ impl SigSet { target_os = "haiku", target_os = "hurd", target_os = "aix", - target_os = "fushsia" + target_os = "fuchsia" ))] #[doc(alias("sigsuspend"))] pub fn suspend(&self) -> Result<()> { @@ -753,11 +753,18 @@ pub enum SigHandler { } /// Action to take on receipt of a signal. Corresponds to `sigaction`. +#[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct SigAction { sigaction: libc::sigaction } +impl From for libc::sigaction { + fn from(value: SigAction) -> libc::sigaction { + value.sigaction + } +} + impl SigAction { /// Creates a new action. /// diff --git a/src/sys/signalfd.rs b/src/sys/signalfd.rs index ccba774d1a..4594f4deaa 100644 --- a/src/sys/signalfd.rs +++ b/src/sys/signalfd.rs @@ -105,11 +105,11 @@ impl SignalFd { Ok(SignalFd(fd)) } - pub fn set_mask(&mut self, mask: &SigSet) -> Result<()> { + pub fn set_mask(&self, mask: &SigSet) -> Result<()> { self.update(mask, SfdFlags::empty()) } - pub fn read_signal(&mut self) -> Result> { + pub fn read_signal(&self) -> Result> { let mut buffer = mem::MaybeUninit::::uninit(); let size = mem::size_of_val(&buffer); diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs index f6800aa5d0..aa89ba9723 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -919,6 +919,19 @@ impl From for net::SocketAddrV4 { } } +#[cfg(feature = "net")] +impl From for libc::sockaddr_in { + fn from(sin: SockaddrIn) -> libc::sockaddr_in { + sin.0 + } +} +#[cfg(feature = "net")] +impl From for SockaddrIn { + fn from(sin: libc::sockaddr_in) -> SockaddrIn { + SockaddrIn(sin) + } +} + #[cfg(feature = "net")] impl std::str::FromStr for SockaddrIn { type Err = net::AddrParseError; @@ -969,6 +982,20 @@ impl SockaddrIn6 { } } +#[cfg(feature = "net")] +impl From for libc::sockaddr_in6 { + fn from(sin6: SockaddrIn6) -> libc::sockaddr_in6 { + sin6.0 + } +} + +#[cfg(feature = "net")] +impl From for SockaddrIn6 { + fn from(sin6: libc::sockaddr_in6) -> SockaddrIn6 { + SockaddrIn6(sin6) + } +} + #[cfg(feature = "net")] impl private::SockaddrLikePriv for SockaddrIn6 {} #[cfg(feature = "net")] @@ -2150,9 +2177,8 @@ mod tests { } #[cfg(not(any(target_os = "hurd", target_os = "redox")))] + #[allow(clippy::cast_ptr_alignment)] mod link { - #![allow(clippy::cast_ptr_alignment)] - #[cfg(any(apple_targets, solarish))] use super::super::super::socklen_t; use super::*; diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 3d1651bd3f..1f1869e90d 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -13,6 +13,7 @@ use libc::{self, c_int, size_t, socklen_t}; #[cfg(all(feature = "uio", not(target_os = "redox")))] use libc::{ c_void, iovec, CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_NXTHDR, CMSG_SPACE, + MSG_CTRUNC, }; #[cfg(not(target_os = "redox"))] use std::io::{IoSlice, IoSliceMut}; @@ -599,13 +600,19 @@ pub struct RecvMsg<'a, 's, S> { } impl<'a, S> RecvMsg<'a, '_, S> { - /// Iterate over the valid control messages pointed to by this - /// msghdr. - pub fn cmsgs(&self) -> CmsgIterator { - CmsgIterator { + /// Iterate over the valid control messages pointed to by this msghdr. If + /// allocated space for CMSGs was too small it is not safe to iterate, + /// instead return an `Error::ENOBUFS` error. + pub fn cmsgs(&self) -> Result { + + if self.mhdr.msg_flags & MSG_CTRUNC == MSG_CTRUNC { + return Err(Errno::ENOBUFS); + } + + Ok(CmsgIterator { cmsghdr: self.cmsghdr, mhdr: &self.mhdr - } + }) } } @@ -700,7 +707,7 @@ pub enum ControlMessageOwned { /// let mut iov = [IoSliceMut::new(&mut buffer)]; /// let r = recvmsg::(in_socket.as_raw_fd(), &mut iov, Some(&mut cmsgspace), flags) /// .unwrap(); - /// let rtime = match r.cmsgs().next() { + /// let rtime = match r.cmsgs().unwrap().next() { /// Some(ControlMessageOwned::ScmTimestamp(rtime)) => rtime, /// Some(_) => panic!("Unexpected control message"), /// None => panic!("No control message") @@ -773,7 +780,7 @@ pub enum ControlMessageOwned { #[cfg(target_os = "linux")] #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] - UdpGroSegments(u16), + UdpGroSegments(i32), /// SO_RXQ_OVFL indicates that an unsigned 32 bit value /// ancilliary msg (cmsg) should be attached to recieved @@ -949,7 +956,7 @@ impl ControlMessageOwned { #[cfg(target_os = "linux")] #[cfg(feature = "net")] (libc::SOL_UDP, libc::UDP_GRO) => { - let gso_size: u16 = unsafe { ptr::read_unaligned(p as *const _) }; + let gso_size: i32 = unsafe { ptr::read_unaligned(p as *const _) }; ControlMessageOwned::UdpGroSegments(gso_size) }, #[cfg(any(linux_android, target_os = "fuchsia"))] diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 4357695f56..f66b54e1fa 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -5,7 +5,7 @@ use crate::sys::time::TimeVal; use crate::Result; use cfg_if::cfg_if; use libc::{self, c_int, c_void, socklen_t}; -use std::ffi::{OsStr, OsString}; +use std::ffi::{CStr, CString, OsStr, OsString}; use std::mem::{self, MaybeUninit}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsFd, AsRawFd}; @@ -270,6 +270,16 @@ sockopt_impl!( libc::SO_REUSEPORT, bool ); +#[cfg(target_os = "freebsd")] +sockopt_impl!( + /// Enables incoming connections to be distributed among N sockets (up to 256) + /// via a Load-Balancing hash based algorithm. + ReusePortLb, + Both, + libc::SOL_SOCKET, + libc::SO_REUSEPORT_LB, + bool +); #[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] @@ -1026,7 +1036,7 @@ sockopt_impl!( libc::IP_TTL, libc::c_int ); -#[cfg(any(linux_android, target_os = "freebsd"))] +#[cfg(any(apple_targets, linux_android, target_os = "freebsd"))] sockopt_impl!( /// Set the unicast hop limit for the socket. Ipv6Ttl, @@ -1065,6 +1075,17 @@ sockopt_impl!( libc::IPV6_DONTFRAG, bool ); +#[cfg(apple_targets)] +#[cfg(feature = "net")] +sockopt_impl!( + /// Get the utun interface name. + UtunIfname, + GetOnly, + libc::SYSPROTO_CONTROL, + libc::UTUN_OPT_IFNAME, + CString, + GetCString<[u8; libc::IFNAMSIZ]> +); #[allow(missing_docs)] // Not documented by Linux! @@ -1568,3 +1589,32 @@ impl<'a> Set<'a, OsString> for SetOsString<'a> { } } +/// Getter for a `CString` value. +struct GetCString> { + len: socklen_t, + val: MaybeUninit, +} + +impl> Get for GetCString { + fn uninit() -> Self { + GetCString { + len: mem::size_of::() as socklen_t, + val: MaybeUninit::uninit(), + } + } + + fn ffi_ptr(&mut self) -> *mut c_void { + self.val.as_mut_ptr().cast() + } + + fn ffi_len(&mut self) -> *mut socklen_t { + &mut self.len + } + + unsafe fn assume_init(self) -> CString { + let mut v = unsafe { self.val.assume_init() }; + CStr::from_bytes_until_nul(v.as_mut()) + .expect("string should be null-terminated") + .to_owned() + } +} diff --git a/src/sys/sysinfo.rs b/src/sys/sysinfo.rs index e8aa00b00d..a2bc093643 100644 --- a/src/sys/sysinfo.rs +++ b/src/sys/sysinfo.rs @@ -1,4 +1,4 @@ -use libc::{self, SI_LOAD_SHIFT}; +use libc::SI_LOAD_SHIFT; use std::time::Duration; use std::{cmp, mem}; diff --git a/src/unistd.rs b/src/unistd.rs index 4502766c5d..58ede6eb9b 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1377,7 +1377,7 @@ pub fn chroot(path: &P) -> Result<()> { /// Commit filesystem caches to disk /// /// See also [sync(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html) -#[cfg(any(freebsdlike, linux_android, netbsdlike))] +#[cfg(any(bsd, linux_android, solarish, target_os = "haiku", target_os = "aix", target_os = "hurd"))] pub fn sync() { unsafe { libc::sync() }; } @@ -1386,7 +1386,7 @@ pub fn sync() { /// descriptor `fd` to disk /// /// See also [syncfs(2)](https://man7.org/linux/man-pages/man2/sync.2.html) -#[cfg(linux_android)] +#[cfg(any(linux_android, target_os = "hurd"))] pub fn syncfs(fd: RawFd) -> Result<()> { let res = unsafe { libc::syncfs(fd) }; @@ -1411,13 +1411,27 @@ pub fn fsync(fd: RawFd) -> Result<()> { linux_android, solarish, netbsdlike, + apple_targets, target_os = "freebsd", target_os = "emscripten", target_os = "fuchsia", + target_os = "aix", + target_os = "hurd", ))] #[inline] pub fn fdatasync(fd: RawFd) -> Result<()> { - let res = unsafe { libc::fdatasync(fd) }; + cfg_if! { + // apple libc supports fdatasync too, albeit not being present in its headers + // [fdatasync](https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/vfs/vfs_syscalls.c#L7728) + if #[cfg(apple_targets)] { + extern "C" { + fn fdatasync(fd: libc::c_int) -> libc::c_int; + } + } else { + use libc::fdatasync as fdatasync; + } + } + let res = unsafe { fdatasync(fd) }; Errno::result(res).map(drop) } @@ -2031,6 +2045,19 @@ pub enum PathconfVar { /// queue; therefore, the maximum number of bytes a conforming application /// may require to be typed as input before reading them. MAX_INPUT = libc::_PC_MAX_INPUT, + #[cfg(any( + apple_targets, + solarish, + freebsdlike, + target_os = "netbsd", + ))] + /// If a file system supports the reporting of holes (see lseek(2)), + /// pathconf() and fpathconf() return a positive number that represents the + /// minimum hole size returned in bytes. The offsets of holes returned will + /// be aligned to this same value. A special value of 1 is returned if the + /// file system does not specify the minimum hole size but still reports + /// holes. + MIN_HOLE_SIZE = libc::_PC_MIN_HOLE_SIZE, /// Maximum number of bytes in a filename (not including the terminating /// null of a filename string). NAME_MAX = libc::_PC_NAME_MAX, diff --git a/test/common/mod.rs b/test/common/mod.rs index db4aed2598..ab0e746367 100644 --- a/test/common/mod.rs +++ b/test/common/mod.rs @@ -37,8 +37,8 @@ cfg_if! { #[macro_export] macro_rules! require_mount { ($name:expr) => { - use ::sysctl::{CtlValue, Sysctl}; use nix::unistd::Uid; + use sysctl::{CtlValue, Sysctl}; let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() @@ -65,7 +65,7 @@ macro_rules! skip_if_cirrus { #[macro_export] macro_rules! skip_if_jailed { ($name:expr) => { - use ::sysctl::{CtlValue, Sysctl}; + use sysctl::{CtlValue, Sysctl}; let ctl = ::sysctl::Ctl::new("security.jail.jailed").unwrap(); if let CtlValue::Int(1) = ctl.value().unwrap() { diff --git a/test/mount/mod.rs b/test/mount/mod.rs new file mode 100644 index 0000000000..2764b83f71 --- /dev/null +++ b/test/mount/mod.rs @@ -0,0 +1,6 @@ +#[cfg(target_os = "linux")] +mod test_mount; +#[cfg(apple_targets)] +mod test_mount_apple; +#[cfg(target_os = "freebsd")] +mod test_nmount; diff --git a/test/test_mount.rs b/test/mount/test_mount.rs similarity index 94% rename from test/test_mount.rs rename to test/mount/test_mount.rs index a4f0903dba..9cb7741796 100644 --- a/test/test_mount.rs +++ b/test/mount/test_mount.rs @@ -53,6 +53,8 @@ fn test_mount_tmpfs_without_flags_allows_rwx() { .unwrap_or_else(|e| panic!("read failed: {e}")); assert_eq!(buf, SCRIPT_CONTENTS); + // while forking and unmounting prevent other child processes + let _m = FORK_MTX.lock(); // Verify execute. assert_eq!( EXPECTED_STATUS, @@ -129,6 +131,8 @@ fn test_mount_noexec_disallows_exec() { &test_path ); + // while forking and unmounting prevent other child processes + let _m = FORK_MTX.lock(); // EACCES: Permission denied assert_eq!( EACCES, @@ -168,6 +172,8 @@ fn test_mount_bind() { .and_then(|mut f| f.write(SCRIPT_CONTENTS)) .unwrap_or_else(|e| panic!("write failed: {e}")); + // wait for child processes to prevent EBUSY + let _m = FORK_MTX.lock(); umount(mount_point.path()) .unwrap_or_else(|e| panic!("umount failed: {e}")); } diff --git a/test/mount/test_mount_apple.rs b/test/mount/test_mount_apple.rs new file mode 100644 index 0000000000..f2868500d0 --- /dev/null +++ b/test/mount/test_mount_apple.rs @@ -0,0 +1,8 @@ +use nix::errno::Errno; +use nix::mount::{mount, MntFlags}; + +#[test] +fn test_mount() { + let res = mount::("", "", MntFlags::empty(), None); + assert_eq!(res, Err(Errno::ENOENT)); +} diff --git a/test/test_nmount.rs b/test/mount/test_nmount.rs similarity index 100% rename from test/test_nmount.rs rename to test/mount/test_nmount.rs diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index ba5ad02ec3..2f4494facf 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -1,7 +1,7 @@ use std::{ io::{Read, Seek, Write}, ops::Deref, - os::unix::io::AsRawFd, + os::unix::io::{AsFd, AsRawFd, BorrowedFd}, pin::Pin, sync::atomic::{AtomicBool, Ordering}, thread, time, @@ -45,8 +45,9 @@ mod aio_fsync { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let aiocb = AioFsync::new( - 1001, + f.as_fd(), AioFsyncMode::O_SYNC, 42, SigevNotify::SigevSignal { @@ -54,7 +55,7 @@ mod aio_fsync { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(AioFsyncMode::O_SYNC, aiocb.mode()); assert_eq!(42, aiocb.priority()); let sev = aiocb.sigevent().sigevent(); @@ -67,21 +68,17 @@ mod aio_fsync { // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] - #[cfg(any(target_os = "freebsd", apple_targets))] + #[cfg_attr(any(target_os = "android", target_os = "linux"), ignore)] fn error() { use std::mem; const INITIAL: &[u8] = b"abcdef123456"; // Create an invalid AioFsyncMode - let mode = unsafe { mem::transmute(666) }; + let mode = unsafe { mem::transmute::(666) }; let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiof = Box::pin(AioFsync::new( - f.as_raw_fd(), - mode, - 0, - SigevNotify::SigevNone, - )); + let mut aiof = + Box::pin(AioFsync::new(f.as_fd(), mode, 0, SigevNotify::SigevNone)); let err = aiof.as_mut().submit(); err.expect_err("assertion failed"); } @@ -92,9 +89,8 @@ mod aio_fsync { const INITIAL: &[u8] = b"abcdef123456"; let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let fd = f.as_raw_fd(); let mut aiof = Box::pin(AioFsync::new( - fd, + f.as_fd(), AioFsyncMode::O_SYNC, 0, SigevNotify::SigevNone, @@ -110,9 +106,10 @@ mod aio_read { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let mut rbuf = vec![0; 4]; let aiocb = AioRead::new( - 1001, + f.as_fd(), 2, //offset &mut rbuf, 42, //priority @@ -121,7 +118,7 @@ mod aio_read { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(4, aiocb.nbytes()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -140,7 +137,7 @@ mod aio_read { let mut rbuf = vec![0; 4]; let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = Box::pin(AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone)); aior.as_mut().submit().unwrap(); @@ -164,7 +161,7 @@ mod aio_read { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); let mut aior = Box::pin(AioRead::new( - f.as_raw_fd(), + f.as_fd(), -1, //an invalid offset &mut rbuf, 0, //priority @@ -184,7 +181,7 @@ mod aio_read { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = Box::pin(AioRead::new( fd, 2, @@ -211,7 +208,7 @@ mod aio_read { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone); let mut aior = unsafe { Pin::new_unchecked(&mut aior) }; @@ -234,12 +231,13 @@ mod aio_readv { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let mut rbuf0 = vec![0; 4]; let mut rbuf1 = vec![0; 8]; let mut rbufs = [IoSliceMut::new(&mut rbuf0), IoSliceMut::new(&mut rbuf1)]; let aiocb = AioReadv::new( - 1001, + f.as_fd(), 2, //offset &mut rbufs, 42, //priority @@ -248,7 +246,7 @@ mod aio_readv { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(2, aiocb.iovlen()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -270,7 +268,7 @@ mod aio_readv { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); { - let fd = f.as_raw_fd(); + let fd = f.as_fd(); let mut aior = Box::pin(AioReadv::new( fd, 2, @@ -297,9 +295,10 @@ mod aio_write { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let wbuf = vec![0; 4]; let aiocb = AioWrite::new( - 1001, + f.as_fd(), 2, //offset &wbuf, 42, //priority @@ -308,7 +307,7 @@ mod aio_write { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(4, aiocb.nbytes()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -327,7 +326,7 @@ mod aio_write { let f = tempfile().unwrap(); let mut aiow = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 0, wbuf, 0, @@ -356,18 +355,20 @@ mod aio_write { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = Box::pin(AioWrite::new( - f.as_raw_fd(), - 2, - &wbuf, - 0, - SigevNotify::SigevNone, - )); - aiow.as_mut().submit().unwrap(); + { + let mut aiow = Box::pin(AioWrite::new( + f.as_fd(), + 2, + &wbuf, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); - let err = poll_aio!(&mut aiow); - assert_eq!(err, Ok(())); - assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + } f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); @@ -386,19 +387,21 @@ mod aio_write { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = AioWrite::new( - f.as_raw_fd(), - 2, //offset - &wbuf, - 0, //priority - SigevNotify::SigevNone, - ); - let mut aiow = unsafe { Pin::new_unchecked(&mut aiow) }; - aiow.as_mut().submit().unwrap(); + { + let mut aiow = AioWrite::new( + f.as_fd(), + 2, //offset + &wbuf, + 0, //priority + SigevNotify::SigevNone, + ); + let mut aiow = unsafe { Pin::new_unchecked(&mut aiow) }; + aiow.as_mut().submit().unwrap(); - let err = poll_aio!(&mut aiow); - assert_eq!(err, Ok(())); - assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wbuf.len()); + } f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); @@ -411,12 +414,14 @@ mod aio_write { // Skip on Linux, because Linux's AIO implementation can't detect errors // synchronously #[test] - #[cfg(any(target_os = "freebsd", apple_targets))] + #[cfg_attr(any(target_os = "android", target_os = "linux"), ignore)] fn error() { + // Not I/O safe! Deliberately create an invalid fd. + let fd = unsafe { BorrowedFd::borrow_raw(666) }; let wbuf = "CDEF".to_string().into_bytes(); let mut aiow = Box::pin(AioWrite::new( - 666, // An invalid file descriptor - 0, //offset + fd, + 0, //offset &wbuf, 0, //priority SigevNotify::SigevNone, @@ -435,11 +440,12 @@ mod aio_writev { #[test] fn test_accessors() { + let f = tempfile().unwrap(); let wbuf0 = vec![0; 4]; let wbuf1 = vec![0; 8]; let wbufs = [IoSlice::new(&wbuf0), IoSlice::new(&wbuf1)]; let aiocb = AioWritev::new( - 1001, + f.as_fd(), 2, //offset &wbufs, 42, //priority @@ -448,7 +454,7 @@ mod aio_writev { si_value: 99, }, ); - assert_eq!(1001, aiocb.fd()); + assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd()); assert_eq!(2, aiocb.iovlen()); assert_eq!(2, aiocb.offset()); assert_eq!(42, aiocb.priority()); @@ -472,18 +478,20 @@ mod aio_writev { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = Box::pin(AioWritev::new( - f.as_raw_fd(), - 1, - &wbufs, - 0, - SigevNotify::SigevNone, - )); - aiow.as_mut().submit().unwrap(); + { + let mut aiow = Box::pin(AioWritev::new( + f.as_fd(), + 1, + &wbufs, + 0, + SigevNotify::SigevNone, + )); + aiow.as_mut().submit().unwrap(); - let err = poll_aio!(&mut aiow); - assert_eq!(err, Ok(())); - assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen); + let err = poll_aio!(&mut aiow); + assert_eq!(err, Ok(())); + assert_eq!(aiow.as_mut().aio_return().unwrap(), wlen); + } f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); @@ -521,22 +529,25 @@ fn sigev_signal() { let mut f = tempfile().unwrap(); f.write_all(INITIAL).unwrap(); - let mut aiow = Box::pin(AioWrite::new( - f.as_raw_fd(), - 2, //offset - WBUF, - 0, //priority - SigevNotify::SigevSignal { - signal: Signal::SIGUSR2, - si_value: 0, //TODO: validate in sigfunc - }, - )); - aiow.as_mut().submit().unwrap(); - while !SIGNALED.load(Ordering::Relaxed) { - thread::sleep(time::Duration::from_millis(10)); + { + let mut aiow = Box::pin(AioWrite::new( + f.as_fd(), + 2, //offset + WBUF, + 0, //priority + SigevNotify::SigevSignal { + signal: Signal::SIGUSR2, + si_value: 0, //TODO: validate in sigfunc + }, + )); + aiow.as_mut().submit().unwrap(); + while !SIGNALED.load(Ordering::Relaxed) { + thread::sleep(time::Duration::from_millis(10)); + } + + assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); } - assert_eq!(aiow.as_mut().aio_return().unwrap(), WBUF.len()); f.rewind().unwrap(); let len = f.read_to_end(&mut rbuf).unwrap(); assert_eq!(len, EXPECT.len()); @@ -551,7 +562,7 @@ fn test_aio_cancel_all() { let f = tempfile().unwrap(); let mut aiocb = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 0, //offset wbuf, 0, //priority @@ -561,7 +572,7 @@ fn test_aio_cancel_all() { let err = aiocb.as_mut().error(); assert!(err == Ok(()) || err == Err(Errno::EINPROGRESS)); - aio_cancel_all(f.as_raw_fd()).unwrap(); + aio_cancel_all(f.as_fd()).unwrap(); // Wait for aiocb to complete, but don't care whether it succeeded let _ = poll_aio!(&mut aiocb); @@ -579,7 +590,7 @@ fn test_aio_suspend() { f.write_all(INITIAL).unwrap(); let mut wcb = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 2, //offset WBUF, 0, //priority @@ -587,7 +598,7 @@ fn test_aio_suspend() { )); let mut rcb = Box::pin(AioRead::new( - f.as_raw_fd(), + f.as_fd(), 8, //offset &mut rbuf, 0, //priority @@ -624,21 +635,23 @@ fn test_aio_suspend() { #[test] fn casting() { let sev = SigevNotify::SigevNone; - let aiof = AioFsync::new(666, AioFsyncMode::O_SYNC, 0, sev); + // Only safe because we'll never await the futures + let fd = unsafe { BorrowedFd::borrow_raw(666) }; + let aiof = AioFsync::new(fd, AioFsyncMode::O_SYNC, 0, sev); assert_eq!( aiof.as_ref() as *const libc::aiocb, &aiof as *const AioFsync as *const libc::aiocb ); let mut rbuf = []; - let aior = AioRead::new(666, 0, &mut rbuf, 0, sev); + let aior = AioRead::new(fd, 0, &mut rbuf, 0, sev); assert_eq!( aior.as_ref() as *const libc::aiocb, &aior as *const AioRead as *const libc::aiocb ); let wbuf = []; - let aiow = AioWrite::new(666, 0, &wbuf, 0, sev); + let aiow = AioWrite::new(fd, 0, &wbuf, 0, sev); assert_eq!( aiow.as_ref() as *const libc::aiocb, &aiow as *const AioWrite as *const libc::aiocb @@ -654,7 +667,9 @@ fn casting_vectored() { let mut rbuf = []; let mut rbufs = [IoSliceMut::new(&mut rbuf)]; - let aiorv = AioReadv::new(666, 0, &mut rbufs[..], 0, sev); + // Only safe because we'll never await the futures + let fd = unsafe { BorrowedFd::borrow_raw(666) }; + let aiorv = AioReadv::new(fd, 0, &mut rbufs[..], 0, sev); assert_eq!( aiorv.as_ref() as *const libc::aiocb, &aiorv as *const AioReadv as *const libc::aiocb @@ -662,7 +677,7 @@ fn casting_vectored() { let wbuf = []; let wbufs = [IoSlice::new(&wbuf)]; - let aiowv = AioWritev::new(666, 0, &wbufs, 0, sev); + let aiowv = AioWritev::new(fd, 0, &wbufs, 0, sev); assert_eq!( aiowv.as_ref() as *const libc::aiocb, &aiowv as *const AioWritev as *const libc::aiocb diff --git a/test/sys/test_aio_drop.rs b/test/sys/test_aio_drop.rs index 54106dd168..47660cf62c 100644 --- a/test/sys/test_aio_drop.rs +++ b/test/sys/test_aio_drop.rs @@ -16,7 +16,7 @@ fn test_drop() { use nix::sys::aio::*; use nix::sys::signal::*; - use std::os::unix::io::AsRawFd; + use std::os::unix::io::AsFd; use tempfile::tempfile; const WBUF: &[u8] = b"CDEF"; @@ -24,7 +24,7 @@ fn test_drop() { let f = tempfile().unwrap(); f.set_len(6).unwrap(); let mut aiocb = Box::pin(AioWrite::new( - f.as_raw_fd(), + f.as_fd(), 2, //offset WBUF, 0, //priority diff --git a/test/sys/test_fanotify.rs b/test/sys/test_fanotify.rs index 20226c272a..13ec945913 100644 --- a/test/sys/test_fanotify.rs +++ b/test/sys/test_fanotify.rs @@ -1,9 +1,10 @@ use crate::*; +use nix::errno::Errno; use nix::sys::fanotify::{ EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags, Response, }; -use std::fs::{read_link, File, OpenOptions}; +use std::fs::{read_link, read_to_string, File, OpenOptions}; use std::io::ErrorKind; use std::io::{Read, Write}; use std::os::fd::AsRawFd; @@ -16,6 +17,7 @@ pub fn test_fanotify() { test_fanotify_notifications(); test_fanotify_responses(); + test_fanotify_overflow(); } fn test_fanotify_notifications() { @@ -147,3 +149,71 @@ fn test_fanotify_responses() { file_thread.join().unwrap(); } + +fn test_fanotify_overflow() { + let max_events: usize = + read_to_string("/proc/sys/fs/fanotify/max_queued_events") + .unwrap() + .trim() + .parse() + .unwrap(); + + // make sure the kernel is configured with the default value, + // just so this test doesn't run forever + assert_eq!(max_events, 16384); + + let group = Fanotify::init( + InitFlags::FAN_CLASS_NOTIF + | InitFlags::FAN_REPORT_TID + | InitFlags::FAN_NONBLOCK, + EventFFlags::O_RDONLY, + ) + .unwrap(); + let tempdir = tempfile::tempdir().unwrap(); + let tempfile = tempdir.path().join("test"); + + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tempfile) + .unwrap(); + + group + .mark( + MarkFlags::FAN_MARK_ADD, + MaskFlags::FAN_OPEN, + None, + Some(&tempfile), + ) + .unwrap(); + + thread::scope(|s| { + // perform 10 more events to demonstrate some will be dropped + for _ in 0..(max_events + 10) { + s.spawn(|| { + File::open(&tempfile).unwrap(); + }); + } + }); + + // flush the queue until it's empty + let mut n = 0; + let mut last_event = None; + loop { + match group.read_events() { + Ok(events) => { + n += events.len(); + if let Some(event) = events.last() { + last_event = Some(event.mask()); + } + } + Err(e) if e == Errno::EWOULDBLOCK => break, + Err(e) => panic!("{e:?}"), + } + } + + // make sure we read all we expected. + // the +1 is for the overflow event. + assert_eq!(n, max_events + 1); + assert_eq!(last_event, Some(MaskFlags::FAN_Q_OVERFLOW)); +} diff --git a/test/sys/test_prctl.rs b/test/sys/test_prctl.rs index 351213b7ef..b409735af6 100644 --- a/test/sys/test_prctl.rs +++ b/test/sys/test_prctl.rs @@ -122,4 +122,38 @@ mod test_prctl { prctl::set_thp_disable(original).unwrap(); } + + #[test] + fn test_set_vma_anon_name() { + use nix::errno::Errno; + use nix::sys::mman; + use std::num::NonZeroUsize; + + const ONE_K: libc::size_t = 1024; + let sz = NonZeroUsize::new(ONE_K).unwrap(); + let ptr = unsafe { + mman::mmap_anonymous( + None, + sz, + mman::ProtFlags::PROT_READ, + mman::MapFlags::MAP_SHARED, + ) + .unwrap() + }; + let err = prctl::set_vma_anon_name( + ptr, + sz, + Some(CStr::from_bytes_with_nul(b"[,$\0").unwrap()), + ) + .unwrap_err(); + assert_eq!(err, Errno::EINVAL); + // `CONFIG_ANON_VMA_NAME` kernel config might not be set + prctl::set_vma_anon_name( + ptr, + sz, + Some(CStr::from_bytes_with_nul(b"Nix\0").unwrap()), + ) + .unwrap_or_default(); + prctl::set_vma_anon_name(ptr, sz, None).unwrap_or_default(); + } } diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index 246b35445d..c99c6762c3 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -1,7 +1,7 @@ #[cfg(all( target_os = "linux", - any(target_arch = "x86_64", target_arch = "x86"), - target_env = "gnu" + target_env = "gnu", + any(target_arch = "x86_64", target_arch = "x86") ))] use memoffset::offset_of; use nix::errno::Errno; @@ -179,8 +179,13 @@ fn test_ptrace_interrupt() { // ptrace::{setoptions, getregs} are only available in these platforms #[cfg(all( target_os = "linux", - any(target_arch = "x86_64", target_arch = "x86"), - target_env = "gnu" + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) ))] #[test] fn test_ptrace_syscall() { @@ -226,12 +231,21 @@ fn test_ptrace_syscall() { let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as libc::c_long; + #[cfg(target_arch = "aarch64")] + let get_syscall_id = + || ptrace::getregs(child).unwrap().regs[8] as libc::c_long; + + #[cfg(target_arch = "riscv64")] + let get_syscall_id = + || ptrace::getregs(child).unwrap().a7 as libc::c_long; + // this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`. #[cfg(target_arch = "x86_64")] let rax_offset = offset_of!(libc::user_regs_struct, orig_rax); #[cfg(target_arch = "x86")] let rax_offset = offset_of!(libc::user_regs_struct, orig_eax); + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] let get_syscall_from_user_area = || { // Find the offset of `user.regs.rax` (or `user.regs.eax` for x86) let rax_offset = offset_of!(libc::user, regs) + rax_offset; @@ -246,6 +260,7 @@ fn test_ptrace_syscall() { Ok(WaitStatus::PtraceSyscall(child)) ); assert_eq!(get_syscall_id(), ::libc::SYS_kill); + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill); // kill exit @@ -255,6 +270,7 @@ fn test_ptrace_syscall() { Ok(WaitStatus::PtraceSyscall(child)) ); assert_eq!(get_syscall_id(), ::libc::SYS_kill); + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill); // receive signal @@ -273,3 +289,85 @@ fn test_ptrace_syscall() { } } } + +#[cfg(all( + target_os = "linux", + target_env = "gnu", + any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "riscv64", + ) +))] +#[test] +fn test_ptrace_regsets() { + use nix::sys::ptrace::{self, getregset, regset, setregset}; + use nix::sys::signal::*; + use nix::sys::wait::{waitpid, WaitStatus}; + use nix::unistd::fork; + use nix::unistd::ForkResult::*; + + require_capability!("test_ptrace_regsets", CAP_SYS_PTRACE); + + let _m = crate::FORK_MTX.lock(); + + match unsafe { fork() }.expect("Error: Fork Failed") { + Child => { + ptrace::traceme().unwrap(); + // As recommended by ptrace(2), raise SIGTRAP to pause the child + // until the parent is ready to continue + loop { + raise(Signal::SIGTRAP).unwrap(); + } + } + + Parent { child } => { + assert_eq!( + waitpid(child, None), + Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)) + ); + let mut regstruct = + getregset::(child).unwrap(); + let mut fpregstruct = + getregset::(child).unwrap(); + + #[cfg(target_arch = "x86_64")] + let (reg, fpreg) = + (&mut regstruct.r15, &mut fpregstruct.st_space[5]); + #[cfg(target_arch = "x86")] + let (reg, fpreg) = + (&mut regstruct.edx, &mut fpregstruct.st_space[5]); + #[cfg(target_arch = "aarch64")] + let (reg, fpreg) = + (&mut regstruct.regs[16], &mut fpregstruct.vregs[5]); + #[cfg(target_arch = "riscv64")] + let (reg, fpreg) = (&mut regstruct.t1, &mut fpregstruct.__f[5]); + + *reg = 0xdeadbeefu32 as _; + *fpreg = 0xfeedfaceu32 as _; + let _ = setregset::(child, regstruct); + regstruct = getregset::(child).unwrap(); + let _ = setregset::(child, fpregstruct); + fpregstruct = getregset::(child).unwrap(); + + #[cfg(target_arch = "x86_64")] + let (reg, fpreg) = (regstruct.r15, fpregstruct.st_space[5]); + #[cfg(target_arch = "x86")] + let (reg, fpreg) = (regstruct.edx, fpregstruct.st_space[5]); + #[cfg(target_arch = "aarch64")] + let (reg, fpreg) = (regstruct.regs[16], fpregstruct.vregs[5]); + #[cfg(target_arch = "riscv64")] + let (reg, fpreg) = (regstruct.t1, fpregstruct.__f[5]); + assert_eq!(reg, 0xdeadbeefu32 as _); + assert_eq!(fpreg, 0xfeedfaceu32 as _); + + ptrace::cont(child, Some(Signal::SIGKILL)).unwrap(); + match waitpid(child, None) { + Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) + if pid == child => {} + _ => panic!("The process should have been killed"), + } + } + } +} diff --git a/test/sys/test_signal.rs b/test/sys/test_signal.rs index bf607497be..cd4bc3d9b9 100644 --- a/test/sys/test_signal.rs +++ b/test/sys/test_signal.rs @@ -283,6 +283,7 @@ fn test_from_and_into_iterator() { #[test] #[cfg(not(target_os = "redox"))] fn test_sigaction() { + let _m = crate::SIGNAL_MTX.lock(); thread::spawn(|| { extern "C" fn test_sigaction_handler(_: libc::c_int) {} extern "C" fn test_sigaction_action( @@ -349,7 +350,7 @@ fn test_sigwait() { target_os = "haiku", target_os = "hurd", target_os = "aix", - target_os = "fushsia" + target_os = "fuchsia" ))] #[test] fn test_sigsuspend() { diff --git a/test/sys/test_signalfd.rs b/test/sys/test_signalfd.rs index 4e0971aba7..d315848453 100644 --- a/test/sys/test_signalfd.rs +++ b/test/sys/test_signalfd.rs @@ -28,7 +28,7 @@ fn read_empty_signalfd() { }; let mask = SigSet::empty(); - let mut fd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); + let fd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); let res = fd.read_signal(); assert!(res.unwrap().is_none()); @@ -47,7 +47,7 @@ fn test_signalfd() { mask.add(signal::SIGUSR1); mask.thread_block().unwrap(); - let mut fd = SignalFd::new(&mask).unwrap(); + let fd = SignalFd::new(&mask).unwrap(); // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill` // because `kill` with `getpid` isn't correct during multi-threaded execution like during a @@ -72,7 +72,7 @@ fn test_signalfd_setmask() { // Block the SIGUSR1 signal from automatic processing for this thread let mut mask = SigSet::empty(); - let mut fd = SignalFd::new(&mask).unwrap(); + let fd = SignalFd::new(&mask).unwrap(); mask.add(signal::SIGUSR1); mask.thread_block().unwrap(); diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 90b8a6f528..79c97c8720 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -55,7 +55,7 @@ pub fn test_timestamping() { .unwrap(); let mut ts = None; - for c in recv.cmsgs() { + for c in recv.cmsgs().unwrap() { if let ControlMessageOwned::ScmTimestampsns(timestamps) = c { ts = Some(timestamps.system); } @@ -117,7 +117,7 @@ pub fn test_timestamping_realtime() { .unwrap(); let mut ts = None; - for c in recv.cmsgs() { + for c in recv.cmsgs().unwrap() { if let ControlMessageOwned::ScmRealtime(timeval) = c { ts = Some(timeval); } @@ -179,7 +179,7 @@ pub fn test_timestamping_monotonic() { .unwrap(); let mut ts = None; - for c in recv.cmsgs() { + for c in recv.cmsgs().unwrap() { if let ControlMessageOwned::ScmMonotonic(timeval) = c { ts = Some(timeval); } @@ -889,7 +889,7 @@ pub fn test_scm_rights() { ) .unwrap(); - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { if let ControlMessageOwned::ScmRights(fd) = cmsg { assert_eq!(received_r, None); assert_eq!(fd.len(), 1); @@ -1330,7 +1330,7 @@ fn test_scm_rights_single_cmsg_multiple_fds() { .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - let mut cmsgs = msg.cmsgs(); + let mut cmsgs = msg.cmsgs().unwrap(); match cmsgs.next() { Some(ControlMessageOwned::ScmRights(fds)) => { assert_eq!( @@ -1399,7 +1399,7 @@ pub fn test_sendmsg_empty_cmsgs() { ) .unwrap(); - if msg.cmsgs().next().is_some() { + if msg.cmsgs().unwrap().next().is_some() { panic!("unexpected cmsg"); } assert!(!msg @@ -1466,7 +1466,7 @@ fn test_scm_credentials() { .unwrap(); let mut received_cred = None; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { let cred = match cmsg { #[cfg(linux_android)] ControlMessageOwned::ScmCredentials(cred) => cred, @@ -1497,7 +1497,7 @@ fn test_scm_credentials() { #[test] fn test_scm_credentials_and_rights() { let space = cmsg_space!(libc::ucred, RawFd); - test_impl_scm_credentials_and_rights(space); + test_impl_scm_credentials_and_rights(space).unwrap(); } /// Ensure that passing a an oversized control message buffer to recvmsg @@ -1509,11 +1509,23 @@ fn test_scm_credentials_and_rights() { #[test] fn test_too_large_cmsgspace() { let space = vec![0u8; 1024]; - test_impl_scm_credentials_and_rights(space); + test_impl_scm_credentials_and_rights(space).unwrap(); } #[cfg(linux_android)] -fn test_impl_scm_credentials_and_rights(mut space: Vec) { +#[test] +fn test_too_small_cmsgspace() { + let space = vec![0u8; 4]; + assert_eq!( + test_impl_scm_credentials_and_rights(space), + Err(nix::errno::Errno::ENOBUFS) + ); +} + +#[cfg(linux_android)] +fn test_impl_scm_credentials_and_rights( + mut space: Vec, +) -> Result<(), nix::errno::Errno> { use libc::ucred; use nix::sys::socket::sockopt::PassCred; use nix::sys::socket::{ @@ -1573,9 +1585,9 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { .unwrap(); let mut received_cred = None; - assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs"); + assert_eq!(msg.cmsgs()?.count(), 2, "expected 2 cmsgs"); - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs()? { match cmsg { ControlMessageOwned::ScmRights(fds) => { assert_eq!(received_r, None, "already received fd"); @@ -1606,6 +1618,8 @@ fn test_impl_scm_credentials_and_rights(mut space: Vec) { read(received_r.as_raw_fd(), &mut buf).unwrap(); assert_eq!(&buf[..], b"world"); close(received_r).unwrap(); + + Ok(()) } // Test creating and using named unix domain sockets @@ -1837,7 +1851,7 @@ pub fn test_recv_ipv4pktinfo() { .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - let mut cmsgs = msg.cmsgs(); + let mut cmsgs = msg.cmsgs().unwrap(); if let Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) = cmsgs.next() { let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); @@ -1929,11 +1943,11 @@ pub fn test_recvif() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - assert_eq!(msg.cmsgs().count(), 2, "expected 2 cmsgs"); + assert_eq!(msg.cmsgs().unwrap().count(), 2, "expected 2 cmsgs"); let mut rx_recvif = false; let mut rx_recvdstaddr = false; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { match cmsg { ControlMessageOwned::Ipv4RecvIf(dl) => { rx_recvif = true; @@ -2027,10 +2041,10 @@ pub fn test_recvif_ipv4() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + assert_eq!(msg.cmsgs().unwrap().count(), 1, "expected 1 cmsgs"); let mut rx_recvorigdstaddr = false; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { match cmsg { ControlMessageOwned::Ipv4OrigDstAddr(addr) => { rx_recvorigdstaddr = true; @@ -2113,10 +2127,10 @@ pub fn test_recvif_ipv6() { assert!(!msg .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + assert_eq!(msg.cmsgs().unwrap().count(), 1, "expected 1 cmsgs"); let mut rx_recvorigdstaddr = false; - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs().unwrap() { match cmsg { ControlMessageOwned::Ipv6OrigDstAddr(addr) => { rx_recvorigdstaddr = true; @@ -2214,7 +2228,7 @@ pub fn test_recv_ipv6pktinfo() { .flags .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); - let mut cmsgs = msg.cmsgs(); + let mut cmsgs = msg.cmsgs().unwrap(); if let Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) = cmsgs.next() { let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); @@ -2357,7 +2371,7 @@ fn test_recvmsg_timestampns() { flags, ) .unwrap(); - let rtime = match r.cmsgs().next() { + let rtime = match r.cmsgs().unwrap().next() { Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, Some(_) => panic!("Unexpected control message"), None => panic!("No control message"), @@ -2407,7 +2421,7 @@ fn test_recvmmsg_timestampns() { // Receive the message let mut buffer = vec![0u8; message.len()]; let cmsgspace = nix::cmsg_space!(TimeSpec); - let mut iov = vec![[IoSliceMut::new(&mut buffer)]]; + let mut iov = [[IoSliceMut::new(&mut buffer)]]; let mut data = MultiHeaders::preallocate(1, Some(cmsgspace)); let r: Vec> = recvmmsg( in_socket.as_raw_fd(), @@ -2418,7 +2432,7 @@ fn test_recvmmsg_timestampns() { ) .unwrap() .collect(); - let rtime = match r[0].cmsgs().next() { + let rtime = match r[0].cmsgs().unwrap().next() { Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, Some(_) => panic!("Unexpected control message"), None => panic!("No control message"), @@ -2508,7 +2522,7 @@ fn test_recvmsg_rxq_ovfl() { MsgFlags::MSG_DONTWAIT, ) { Ok(r) => { - drop_counter = match r.cmsgs().next() { + drop_counter = match r.cmsgs().unwrap().next() { Some(ControlMessageOwned::RxqOvfl(drop_counter)) => { drop_counter } @@ -2687,7 +2701,7 @@ mod linux_errqueue { assert_eq!(msg.address, Some(sock_addr)); // Check for expected control message. - let ext_err = match msg.cmsgs().next() { + let ext_err = match msg.cmsgs().unwrap().next() { Some(cmsg) => testf(&cmsg), None => panic!("No control message"), }; @@ -2878,7 +2892,7 @@ fn test_recvmm2() -> nix::Result<()> { #[cfg(not(any(qemu, target_arch = "aarch64")))] let mut saw_time = false; let mut recvd = 0; - for cmsg in rmsg.cmsgs() { + for cmsg in rmsg.cmsgs().unwrap() { if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg { let ts = timestamps.system; diff --git a/test/sys/test_sockopt.rs b/test/sys/test_sockopt.rs index a99d4e39ed..1da3f6af38 100644 --- a/test/sys/test_sockopt.rs +++ b/test/sys/test_sockopt.rs @@ -828,3 +828,52 @@ fn test_ktls() { Err(err) => panic!("{err:?}"), } } + +#[test] +#[cfg(apple_targets)] +fn test_utun_ifname() { + skip_if_not_root!("test_utun_ifname"); + + use nix::sys::socket::connect; + use nix::sys::socket::SysControlAddr; + + let fd = socket( + AddressFamily::System, + SockType::Datagram, + SockFlag::empty(), + SockProtocol::KextControl, + ) + .unwrap(); + + let unit = 123; + let addr = SysControlAddr::from_name( + fd.as_raw_fd(), + "com.apple.net.utun_control", + unit, + ) + .unwrap(); + + connect(fd.as_raw_fd(), &addr).unwrap(); + + let name = getsockopt(&fd, sockopt::UtunIfname) + .expect("getting UTUN_OPT_IFNAME on a utun interface should succeed"); + + let expected_name = format!("utun{}", unit - 1); + assert_eq!(name.into_string(), Ok(expected_name)); +} + +#[test] +#[cfg(target_os = "freebsd")] +fn test_reuseport_lb() { + let fd = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .unwrap(); + setsockopt(&fd, sockopt::ReusePortLb, &false).unwrap(); + assert!(!getsockopt(&fd, sockopt::ReusePortLb).unwrap()); + setsockopt(&fd, sockopt::ReusePortLb, &true).unwrap(); + assert!(getsockopt(&fd, sockopt::ReusePortLb).unwrap()); +} diff --git a/test/sys/test_statfs.rs b/test/sys/test_statfs.rs index 66b3f2ce96..ca7934e6ed 100644 --- a/test/sys/test_statfs.rs +++ b/test/sys/test_statfs.rs @@ -44,7 +44,6 @@ fn check_statfs_strict(path: &str) { // The cast is not unnecessary on all platforms. #[allow(clippy::unnecessary_cast)] fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { - assert_eq!(fs.files() as u64, vfs.files() as u64); assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); } diff --git a/test/test.rs b/test/test.rs index c7231426c2..7401c95611 100644 --- a/test/test.rs +++ b/test/test.rs @@ -3,7 +3,9 @@ extern crate cfg_if; #[cfg_attr(not(any(target_os = "redox", target_os = "haiku")), macro_use)] extern crate nix; +#[macro_use] mod common; +mod mount; mod sys; #[cfg(not(target_os = "redox"))] mod test_dir; @@ -11,20 +13,11 @@ mod test_errno; mod test_fcntl; #[cfg(linux_android)] mod test_kmod; -#[cfg(target_os = "linux")] -mod test_mount; -#[cfg(any( - freebsdlike, - target_os = "fushsia", - target_os = "linux", - target_os = "netbsd" -))] +#[cfg(any(freebsdlike, target_os = "linux", target_os = "netbsd"))] mod test_mq; #[cfg(not(target_os = "redox"))] mod test_net; mod test_nix_path; -#[cfg(target_os = "freebsd")] -mod test_nmount; mod test_poll; #[cfg(not(any( target_os = "redox", @@ -59,9 +52,10 @@ fn read_exact(f: Fd, buf: &mut [u8]) { } } -/// Any test that creates child processes must grab this mutex, regardless -/// of what it does with those children. -pub static FORK_MTX: std::sync::Mutex<()> = std::sync::Mutex::new(()); +/// Any test that creates child processes or can be affected by child processes must grab this mutex, regardless +/// of what it does with those children. It must hold the mutex until the +/// child processes are waited upon. +pub static FORK_MTX: Mutex<()> = Mutex::new(()); /// Any test that changes the process's current working directory must grab /// the RwLock exclusively. Any process that cares about the current /// working directory must grab it shared. diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs index 6572e8af8d..5d320769d3 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -4,12 +4,15 @@ use nix::errno::*; use nix::fcntl::{open, readlink, OFlag}; #[cfg(not(target_os = "redox"))] use nix::fcntl::{openat, readlinkat, renameat}; + +#[cfg(target_os = "linux")] +use nix::fcntl::{openat2, OpenHow, ResolveFlag}; + #[cfg(all( target_os = "linux", target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -57,6 +60,64 @@ fn test_openat() { close(dirfd).unwrap(); } +#[test] +#[cfg(target_os = "linux")] +// QEMU does not handle openat well enough to satisfy this test +// https://gitlab.com/qemu-project/qemu/-/issues/829 +#[cfg_attr(qemu, ignore)] +fn test_openat2() { + const CONTENTS: &[u8] = b"abcd"; + let mut tmp = NamedTempFile::new().unwrap(); + tmp.write_all(CONTENTS).unwrap(); + + let dirfd = + open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty()) + .unwrap(); + + let fd = openat2( + dirfd, + tmp.path().file_name().unwrap(), + OpenHow::new() + .flags(OFlag::O_RDONLY) + .mode(Mode::empty()) + .resolve(ResolveFlag::RESOLVE_BENEATH), + ) + .unwrap(); + + let mut buf = [0u8; 1024]; + assert_eq!(4, read(fd, &mut buf).unwrap()); + assert_eq!(CONTENTS, &buf[0..4]); + + close(fd).unwrap(); + close(dirfd).unwrap(); +} + +#[test] +#[cfg(target_os = "linux")] +// QEMU does not handle openat well enough to satisfy this test +// https://gitlab.com/qemu-project/qemu/-/issues/829 +#[cfg_attr(qemu, ignore)] +fn test_openat2_forbidden() { + let mut tmp = NamedTempFile::new().unwrap(); + tmp.write_all(b"let me out").unwrap(); + + let dirfd = + open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty()) + .unwrap(); + + let escape_attempt = + tmp.path().parent().unwrap().join("../../../hello.txt"); + + let res = openat2( + dirfd, + &escape_attempt, + OpenHow::new() + .flags(OFlag::O_RDONLY) + .resolve(ResolveFlag::RESOLVE_BENEATH), + ); + assert_eq!(Err(Errno::EXDEV), res); +} + #[test] #[cfg(not(target_os = "redox"))] fn test_renameat() { @@ -84,7 +145,6 @@ fn test_renameat() { target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -128,7 +188,6 @@ fn test_renameat2_behaves_like_renameat_with_no_flags() { target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -176,7 +235,6 @@ fn test_renameat2_exchange() { target_env = "gnu", any( target_arch = "x86_64", - target_arch = "x32", target_arch = "powerpc", target_arch = "s390x" ) @@ -295,15 +353,9 @@ mod linux_android { let (rd, wr) = pipe().unwrap(); let mut offset: loff_t = 5; - let res = splice( - tmp.as_raw_fd(), - Some(&mut offset), - wr.as_raw_fd(), - None, - 2, - SpliceFFlags::empty(), - ) - .unwrap(); + let res = + splice(tmp, Some(&mut offset), wr, None, 2, SpliceFFlags::empty()) + .unwrap(); assert_eq!(2, res); @@ -319,9 +371,8 @@ mod linux_android { let (rd2, wr2) = pipe().unwrap(); write(wr1, b"abc").unwrap(); - let res = - tee(rd1.as_raw_fd(), wr2.as_raw_fd(), 2, SpliceFFlags::empty()) - .unwrap(); + let res = tee(rd1.try_clone().unwrap(), wr2, 2, SpliceFFlags::empty()) + .unwrap(); assert_eq!(2, res); @@ -344,8 +395,7 @@ mod linux_android { let buf2 = b"defghi"; let iovecs = [IoSlice::new(&buf1[0..3]), IoSlice::new(&buf2[0..3])]; - let res = vmsplice(wr.as_raw_fd(), &iovecs[..], SpliceFFlags::empty()) - .unwrap(); + let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap(); assert_eq!(6, res); @@ -636,7 +686,7 @@ mod test_flock { /// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop. #[test] - fn verify_lock_and_drop() { + fn lock_and_drop() { // Get 2 `File` handles to same underlying file. let file1 = NamedTempFile::new().unwrap(); let file2 = file1.reopen().unwrap(); @@ -660,9 +710,32 @@ mod test_flock { } } + /// An exclusive lock can be downgraded + #[test] + fn downgrade() { + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap(); + + // Attempt to lock second handle + let file2 = Flock::lock(file2, FlockArg::LockSharedNonblock) + .unwrap_err() + .0; + + // Downgrade the lock + lock1.relock(FlockArg::LockShared).unwrap(); + + // Attempt to lock second handle again (but successfully) + Flock::lock(file2, FlockArg::LockSharedNonblock) + .expect("Expected locking to be successful."); + } + /// Verify that `Flock::unlock()` correctly obtains unlocks. #[test] - fn verify_unlock() { + fn unlock() { // Get 2 `File` handles to same underlying file. let file1 = NamedTempFile::new().unwrap(); let file2 = file1.reopen().unwrap(); @@ -679,4 +752,29 @@ mod test_flock { panic!("Expected locking to be successful."); } } + + /// A shared lock can be upgraded + #[test] + fn upgrade() { + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file3 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockShared).unwrap(); + + // Attempt to lock second handle + { + Flock::lock(file2, FlockArg::LockSharedNonblock) + .expect("Locking should've succeeded"); + } + + // Upgrade the lock + lock1.relock(FlockArg::LockExclusive).unwrap(); + + // Acquiring an additional shared lock should fail + Flock::lock(file3, FlockArg::LockSharedNonblock) + .expect_err("Should not have been able to lock the file"); + } } diff --git a/test/test_net.rs b/test/test_net.rs index faba8503fe..46a3efd501 100644 --- a/test/test_net.rs +++ b/test/test_net.rs @@ -13,3 +13,14 @@ const LOOPBACK: &[u8] = b"loop"; fn test_if_nametoindex() { if_nametoindex(LOOPBACK).expect("assertion failed"); } + +#[test] +fn test_if_indextoname() { + let loopback_index = if_nametoindex(LOOPBACK).expect("assertion failed"); + assert_eq!( + if_indextoname(loopback_index) + .expect("assertion failed") + .as_bytes(), + LOOPBACK + ); +} diff --git a/test/test_pty.rs b/test/test_pty.rs index 368ec129b0..bc618e0bd8 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -8,6 +8,7 @@ use nix::fcntl::{open, OFlag}; use nix::pty::*; use nix::sys::stat; use nix::sys::termios::*; +use nix::sys::wait::WaitStatus; use nix::unistd::{pause, write}; /// Test equivalence of `ptsname` and `ptsname_r` @@ -16,9 +17,10 @@ use nix::unistd::{pause, write}; fn test_ptsname_equivalence() { let _m = crate::PTSNAME_MTX.lock(); - // Open a new PTTY master + // Open a new PTY master let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); assert!(master_fd.as_raw_fd() > 0); + assert!(master_fd.as_fd().as_raw_fd() == master_fd.as_raw_fd()); // Get the name of the slave let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); @@ -247,7 +249,6 @@ fn test_openpty_with_termios() { fn test_forkpty() { use nix::sys::signal::*; use nix::sys::wait::wait; - use nix::unistd::ForkResult::*; // forkpty calls openpty which uses ptname(3) internally. let _m0 = crate::PTSNAME_MTX.lock(); // forkpty spawns a child process @@ -255,21 +256,22 @@ fn test_forkpty() { let string = "naninani\n"; let echoed_string = "naninani\r\n"; - let pty = unsafe { forkpty(None, None).unwrap() }; - match pty.fork_result { - Child => { + let res = unsafe { forkpty(None, None).unwrap() }; + match res { + ForkptyResult::Child => { write(stdout(), string.as_bytes()).unwrap(); pause(); // we need the child to stay alive until the parent calls read unsafe { _exit(0); } } - Parent { child } => { + ForkptyResult::Parent { child, master } => { let mut buf = [0u8; 10]; assert!(child.as_raw() > 0); - crate::read_exact(&pty.master, &mut buf); + crate::read_exact(&master, &mut buf); kill(child, SIGTERM).unwrap(); - wait().unwrap(); // keep other tests using generic wait from getting our child + let status = wait().unwrap(); // keep other tests using generic wait from getting our child + assert_eq!(status, WaitStatus::Signaled(child, SIGTERM, false)); assert_eq!(&buf, echoed_string.as_bytes()); } } diff --git a/test/test_unistd.rs b/test/test_unistd.rs index aa2e5e56d7..6ccf59fb05 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -313,12 +313,15 @@ fn test_initgroups() { // groups that the user belongs to are also set. let user = CString::new("root").unwrap(); let group = Gid::from_raw(123); - let group_list = getgrouplist(&user, group).unwrap(); + let mut group_list = getgrouplist(&user, group).unwrap(); assert!(group_list.contains(&group)); initgroups(&user, group).unwrap(); - let new_groups = getgroups().unwrap(); + let mut new_groups = getgroups().unwrap(); + + new_groups.sort_by_key(|gid| gid.as_raw()); + group_list.sort_by_key(|gid| gid.as_raw()); assert_eq!(new_groups, group_list); // Revert back to the old groups