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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy