Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

nix

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nix - cargo Package Compare versions

Comparing version
0.28.0
to
0.29.0
+111
src/mount/apple.rs
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<P>(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)
}
#[cfg(target_os = "freebsd")]
use crate::Error;
use crate::{Errno, NixPath, Result};
use libc::c_int;
#[cfg(target_os = "freebsd")]
use libc::{c_char, c_uint, c_void};
#[cfg(target_os = "freebsd")]
use std::{
borrow::Cow,
ffi::{CStr, CString},
fmt, io,
marker::PhantomData,
};
libc_bitflags!(
/// Used with [`Nmount::nmount`].
pub struct MntFlags: c_int {
/// ACL support enabled.
#[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
MNT_ACLS;
/// All I/O to the file system should be done asynchronously.
MNT_ASYNC;
/// dir should instead be a file system ID encoded as “FSID:val0:val1”.
#[cfg(target_os = "freebsd")]
MNT_BYFSID;
/// Force a read-write mount even if the file system appears to be
/// unclean.
MNT_FORCE;
/// GEOM journal support enabled.
#[cfg(target_os = "freebsd")]
MNT_GJOURNAL;
/// MAC support for objects.
#[cfg(target_os = "freebsd")]
MNT_MULTILABEL;
/// Disable read clustering.
#[cfg(freebsdlike)]
MNT_NOCLUSTERR;
/// Disable write clustering.
#[cfg(freebsdlike)]
MNT_NOCLUSTERW;
/// Enable NFS version 4 ACLs.
#[cfg(target_os = "freebsd")]
MNT_NFS4ACLS;
/// Do not update access times.
MNT_NOATIME;
/// Disallow program execution.
MNT_NOEXEC;
/// Do not honor setuid or setgid bits on files when executing them.
MNT_NOSUID;
/// Do not follow symlinks.
#[cfg(freebsdlike)]
MNT_NOSYMFOLLOW;
/// Mount read-only.
MNT_RDONLY;
/// Causes the vfs subsystem to update its data structures pertaining to
/// the specified already mounted file system.
MNT_RELOAD;
/// Create a snapshot of the file system.
///
/// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs)
#[cfg(target_os = "freebsd")]
MNT_SNAPSHOT;
/// Using soft updates.
#[cfg(any(freebsdlike, netbsdlike))]
MNT_SOFTDEP;
/// Directories with the SUID bit set chown new files to their own
/// owner.
#[cfg(freebsdlike)]
MNT_SUIDDIR;
/// All I/O to the file system should be done synchronously.
MNT_SYNCHRONOUS;
/// Union with underlying fs.
#[cfg(any(
target_os = "freebsd",
target_os = "netbsd"
))]
MNT_UNION;
/// Indicates that the mount command is being applied to an already
/// mounted file system.
MNT_UPDATE;
/// Check vnode use counts.
#[cfg(target_os = "freebsd")]
MNT_NONBUSY;
}
);
/// The Error type of [`Nmount::nmount`].
///
/// It wraps an [`Errno`], but also may contain an additional message returned
/// by `nmount(2)`.
#[cfg(target_os = "freebsd")]
#[derive(Debug)]
pub struct NmountError {
errno: Error,
errmsg: Option<String>,
}
#[cfg(target_os = "freebsd")]
impl NmountError {
/// Returns the additional error string sometimes generated by `nmount(2)`.
pub fn errmsg(&self) -> Option<&str> {
self.errmsg.as_deref()
}
/// Returns the inner [`Error`]
pub const fn error(&self) -> Error {
self.errno
}
fn new(error: Error, errmsg: Option<&CStr>) -> Self {
Self {
errno: error,
errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned),
}
}
}
#[cfg(target_os = "freebsd")]
impl std::error::Error for NmountError {}
#[cfg(target_os = "freebsd")]
impl fmt::Display for NmountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(errmsg) = &self.errmsg {
write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc())
} else {
write!(f, "{:?}: {}", self.errno, self.errno.desc())
}
}
}
#[cfg(target_os = "freebsd")]
impl From<NmountError> for io::Error {
fn from(err: NmountError) -> Self {
err.errno.into()
}
}
/// Result type of [`Nmount::nmount`].
#[cfg(target_os = "freebsd")]
pub type NmountResult = std::result::Result<(), NmountError>;
/// Mount a FreeBSD file system.
///
/// The `nmount(2)` system call works similarly to the `mount(8)` program; it
/// takes its options as a series of name-value pairs. Most of the values are
/// strings, as are all of the names. The `Nmount` structure builds up an
/// argument list and then executes the syscall.
///
/// # Examples
///
/// To mount `target` onto `mountpoint` with `nullfs`:
/// ```
/// # use nix::unistd::Uid;
/// # use ::sysctl::{CtlValue, Sysctl};
/// # let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap();
/// # if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() {
/// # return;
/// # };
/// use nix::mount::{MntFlags, Nmount, unmount};
/// use std::ffi::CString;
/// use tempfile::tempdir;
///
/// let mountpoint = tempdir().unwrap();
/// let target = tempdir().unwrap();
///
/// let fstype = CString::new("fstype").unwrap();
/// let nullfs = CString::new("nullfs").unwrap();
/// Nmount::new()
/// .str_opt(&fstype, &nullfs)
/// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
/// .str_opt_owned("target", target.path().to_str().unwrap())
/// .nmount(MntFlags::empty()).unwrap();
///
/// unmount(mountpoint.path(), MntFlags::empty()).unwrap();
/// ```
///
/// # See Also
/// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount)
/// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs)
#[cfg(target_os = "freebsd")]
#[derive(Debug, Default)]
pub struct Nmount<'a> {
// n.b. notgull: In reality, this is a list that contains
// both mutable and immutable pointers.
// Be careful using this.
iov: Vec<libc::iovec>,
is_owned: Vec<bool>,
marker: PhantomData<&'a ()>,
}
#[cfg(target_os = "freebsd")]
impl<'a> Nmount<'a> {
/// Helper function to push a slice onto the `iov` array.
fn push_slice(&mut self, val: &'a [u8], is_owned: bool) {
self.iov.push(libc::iovec {
iov_base: val.as_ptr().cast_mut().cast(),
iov_len: val.len(),
});
self.is_owned.push(is_owned);
}
/// Helper function to push a pointer and its length onto the `iov` array.
fn push_pointer_and_length(
&mut self,
val: *const u8,
len: usize,
is_owned: bool,
) {
self.iov.push(libc::iovec {
iov_base: val as *mut _,
iov_len: len,
});
self.is_owned.push(is_owned);
}
/// Helper function to push a `nix` path as owned.
fn push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P) {
val.with_nix_path(|s| {
let len = s.to_bytes_with_nul().len();
let ptr = s.to_owned().into_raw() as *const u8;
self.push_pointer_and_length(ptr, len, true);
})
.unwrap();
}
/// Add an opaque mount option.
///
/// Some file systems take binary-valued mount options. They can be set
/// with this method.
///
/// # Safety
///
/// Unsafe because it will cause `Nmount::nmount` to dereference a raw
/// pointer. The user is responsible for ensuring that `val` is valid and
/// its lifetime outlives `self`! An easy way to do that is to give the
/// value a larger scope than `name`
///
/// # Examples
/// ```
/// use libc::c_void;
/// use nix::mount::Nmount;
/// use std::ffi::CString;
/// use std::mem;
///
/// // Note that flags outlives name
/// let mut flags: u32 = 0xdeadbeef;
/// let name = CString::new("flags").unwrap();
/// let p = &mut flags as *mut u32 as *mut c_void;
/// let len = mem::size_of_val(&flags);
/// let mut nmount = Nmount::new();
/// unsafe { nmount.mut_ptr_opt(&name, p, len) };
/// ```
pub unsafe fn mut_ptr_opt(
&mut self,
name: &'a CStr,
val: *mut c_void,
len: usize,
) -> &mut Self {
self.push_slice(name.to_bytes_with_nul(), false);
self.push_pointer_and_length(val.cast(), len, false);
self
}
/// Add a mount option that does not take a value.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::ffi::CString;
///
/// let read_only = CString::new("ro").unwrap();
/// Nmount::new()
/// .null_opt(&read_only);
/// ```
pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self {
self.push_slice(name.to_bytes_with_nul(), false);
self.push_slice(&[], false);
self
}
/// Add a mount option that does not take a value, but whose name must be
/// owned.
///
///
/// This has higher runtime cost than [`Nmount::null_opt`], but is useful
/// when the name's lifetime doesn't outlive the `Nmount`, or it's a
/// different string type than `CStr`.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
///
/// let read_only = "ro";
/// let mut nmount: Nmount<'static> = Nmount::new();
/// nmount.null_opt_owned(read_only);
/// ```
pub fn null_opt_owned<P: ?Sized + NixPath>(
&mut self,
name: &P,
) -> &mut Self {
self.push_nix_path(name);
self.push_slice(&[], false);
self
}
/// Add a mount option as a [`CStr`].
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::ffi::CString;
///
/// let fstype = CString::new("fstype").unwrap();
/// let nullfs = CString::new("nullfs").unwrap();
/// Nmount::new()
/// .str_opt(&fstype, &nullfs);
/// ```
pub fn str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self {
self.push_slice(name.to_bytes_with_nul(), false);
self.push_slice(val.to_bytes_with_nul(), false);
self
}
/// Add a mount option as an owned string.
///
/// This has higher runtime cost than [`Nmount::str_opt`], but is useful
/// when the value's lifetime doesn't outlive the `Nmount`, or it's a
/// different string type than `CStr`.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::path::Path;
///
/// let mountpoint = Path::new("/mnt");
/// Nmount::new()
/// .str_opt_owned("fspath", mountpoint.to_str().unwrap());
/// ```
pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self
where
P1: ?Sized + NixPath,
P2: ?Sized + NixPath,
{
self.push_nix_path(name);
self.push_nix_path(val);
self
}
/// Create a new `Nmount` struct with no options
pub fn new() -> Self {
Self::default()
}
/// Actually mount the file system.
pub fn nmount(&mut self, flags: MntFlags) -> NmountResult {
const ERRMSG_NAME: &[u8] = b"errmsg\0";
let mut errmsg = vec![0u8; 255];
// nmount can return extra error information via a "errmsg" return
// argument.
self.push_slice(ERRMSG_NAME, false);
// SAFETY: we are pushing a mutable iovec here, so we can't use
// the above method
self.iov.push(libc::iovec {
iov_base: errmsg.as_mut_ptr().cast(),
iov_len: errmsg.len(),
});
let niov = self.iov.len() as c_uint;
let iovp = self.iov.as_mut_ptr();
let res = unsafe { libc::nmount(iovp, niov, flags.bits()) };
match Errno::result(res) {
Ok(_) => Ok(()),
Err(error) => {
let errmsg = if errmsg[0] == 0 {
None
} else {
CStr::from_bytes_until_nul(&errmsg[..]).ok()
};
Err(NmountError::new(error, errmsg))
}
}
}
}
#[cfg(target_os = "freebsd")]
impl<'a> Drop for Nmount<'a> {
fn drop(&mut self) {
for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) {
if *is_owned {
// Free the owned string. Safe because we recorded ownership,
// and Nmount does not implement Clone.
unsafe {
drop(CString::from_raw(iov.iov_base as *mut c_char));
}
}
}
}
}
/// Unmount the file system mounted at `mountpoint`.
///
/// Useful flags include
/// * `MNT_FORCE` - Unmount even if still in use.
#[cfg_attr(
target_os = "freebsd",
doc = "
* `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID
encoded as `FSID:val0:val1`, where `val0` and `val1`
are the contents of the `fsid_t val[]` array in decimal.
The file system that has the specified file system ID
will be unmounted. See
[`statfs`](crate::sys::statfs::statfs) to determine the
`fsid`.
"
)]
pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()>
where
P: ?Sized + NixPath,
{
let res = mountpoint.with_nix_path(|cstr| unsafe {
libc::unmount(cstr.as_ptr(), flags.bits())
})?;
Errno::result(res).map(drop)
}
#[cfg(target_os = "linux")]
mod test_mount;
#[cfg(apple_targets)]
mod test_mount_apple;
#[cfg(target_os = "freebsd")]
mod test_nmount;
use nix::errno::Errno;
use nix::mount::{mount, MntFlags};
#[test]
fn test_mount() {
let res = mount::<str, str, str>("", "", MntFlags::empty(), None);
assert_eq!(res, Err(Errno::ENOENT));
}
use std::fs::{self, File};
use std::io::{Read, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
use libc::{EACCES, EROFS};
use nix::mount::{mount, umount, MsFlags};
use nix::sys::stat::{self, Mode};
use crate::*;
static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
exit 23";
const EXPECTED_STATUS: i32 = 23;
const NONE: Option<&'static [u8]> = None;
#[test]
fn test_mount_tmpfs_without_flags_allows_rwx() {
require_capability!(
"test_mount_tmpfs_without_flags_allows_rwx",
CAP_SYS_ADMIN
);
let tempdir = tempfile::tempdir().unwrap();
mount(
NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::empty(),
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
let test_path = tempdir.path().join("test");
// Verify write.
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(&test_path)
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {e}"));
// Verify read.
let mut buf = Vec::new();
File::open(&test_path)
.and_then(|mut f| f.read_to_end(&mut buf))
.unwrap_or_else(|e| panic!("read failed: {e}"));
assert_eq!(buf, SCRIPT_CONTENTS);
// while forking and unmounting prevent other child processes
let _m = FORK_MTX.lock();
// Verify execute.
assert_eq!(
EXPECTED_STATUS,
Command::new(&test_path)
.status()
.unwrap_or_else(|e| panic!("exec failed: {e}"))
.code()
.unwrap_or_else(|| panic!("child killed by signal"))
);
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
}
#[test]
fn test_mount_rdonly_disallows_write() {
require_capability!("test_mount_rdonly_disallows_write", CAP_SYS_ADMIN);
let tempdir = tempfile::tempdir().unwrap();
mount(
NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::MS_RDONLY,
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
// EROFS: Read-only file system
assert_eq!(
EROFS,
File::create(tempdir.path().join("test"))
.unwrap_err()
.raw_os_error()
.unwrap()
);
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
}
#[test]
fn test_mount_noexec_disallows_exec() {
require_capability!("test_mount_noexec_disallows_exec", CAP_SYS_ADMIN);
let tempdir = tempfile::tempdir().unwrap();
mount(
NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::MS_NOEXEC,
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
let test_path = tempdir.path().join("test");
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(&test_path)
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {e}"));
// Verify that we cannot execute despite a+x permissions being set.
let mode = stat::Mode::from_bits_truncate(
fs::metadata(&test_path)
.map(|md| md.permissions().mode())
.unwrap_or_else(|e| panic!("metadata failed: {e}")),
);
assert!(
mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
"{:?} did not have execute permissions",
&test_path
);
// while forking and unmounting prevent other child processes
let _m = FORK_MTX.lock();
// EACCES: Permission denied
assert_eq!(
EACCES,
Command::new(&test_path)
.status()
.unwrap_err()
.raw_os_error()
.unwrap()
);
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
}
#[test]
fn test_mount_bind() {
require_capability!("test_mount_bind", CAP_SYS_ADMIN);
let tempdir = tempfile::tempdir().unwrap();
let file_name = "test";
{
let mount_point = tempfile::tempdir().unwrap();
mount(
Some(tempdir.path()),
mount_point.path(),
NONE,
MsFlags::MS_BIND,
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(mount_point.path().join(file_name))
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {e}"));
// wait for child processes to prevent EBUSY
let _m = FORK_MTX.lock();
umount(mount_point.path())
.unwrap_or_else(|e| panic!("umount failed: {e}"));
}
// Verify the file written in the mount shows up in source directory, even
// after unmounting.
let mut buf = Vec::new();
File::open(tempdir.path().join(file_name))
.and_then(|mut f| f.read_to_end(&mut buf))
.unwrap_or_else(|e| panic!("read failed: {e}"));
assert_eq!(buf, SCRIPT_CONTENTS);
}
use crate::*;
use nix::{
errno::Errno,
mount::{unmount, MntFlags, Nmount},
};
use std::{ffi::CString, fs::File, path::Path};
use tempfile::tempdir;
#[test]
fn ok() {
require_mount!("nullfs");
let mountpoint = tempdir().unwrap();
let target = tempdir().unwrap();
let _sentry = File::create(target.path().join("sentry")).unwrap();
let fstype = CString::new("fstype").unwrap();
let nullfs = CString::new("nullfs").unwrap();
Nmount::new()
.str_opt(&fstype, &nullfs)
.str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
.str_opt_owned("target", target.path().to_str().unwrap())
.nmount(MntFlags::empty())
.unwrap();
// Now check that the sentry is visible through the mountpoint
let exists = Path::exists(&mountpoint.path().join("sentry"));
// Cleanup the mountpoint before asserting
unmount(mountpoint.path(), MntFlags::empty()).unwrap();
assert!(exists);
}
#[test]
fn bad_fstype() {
let mountpoint = tempdir().unwrap();
let target = tempdir().unwrap();
let _sentry = File::create(target.path().join("sentry")).unwrap();
let e = Nmount::new()
.str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
.str_opt_owned("target", target.path().to_str().unwrap())
.nmount(MntFlags::empty())
.unwrap_err();
assert_eq!(e.error(), Errno::EINVAL);
assert_eq!(e.errmsg(), Some("Invalid fstype"));
}
+1
-1
{
"git": {
"sha1": "21ab06ef23de214174ddb039be5b5f08750d19e6"
"sha1": "1dad4d8d04a2cd187fae87cb91c4f4e95ff0decd"
},
"path_in_vcs": ""
}

@@ -17,5 +17,9 @@ use cfg_aliases::cfg_aliases;

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) },

@@ -26,2 +30,13 @@ freebsdlike: { any(dragonfly, freebsd) },

}
// 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)");
}
+82
-154

@@ -13,5 +13,5 @@ # This file is automatically @generated by Cargo.

name = "autocfg"
version = "1.1.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"

@@ -26,5 +26,5 @@ [[package]]

name = "bitflags"
version = "2.4.2"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"

@@ -55,11 +55,11 @@ [[package]]

name = "cfg_aliases"
version = "0.1.1"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [

@@ -72,11 +72,11 @@ "libc",

name = "fastrand"
version = "2.0.1"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "getrandom"
version = "0.2.12"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [

@@ -90,17 +90,17 @@ "cfg-if",

name = "libc"
version = "0.2.153"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [

@@ -113,5 +113,5 @@ "autocfg",

name = "memoffset"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [

@@ -123,6 +123,6 @@ "autocfg",

name = "nix"
version = "0.28.0"
version = "0.29.0"
dependencies = [
"assert-impl",
"bitflags 2.4.2",
"bitflags 2.5.0",
"caps",

@@ -143,5 +143,5 @@ "cfg-if",

name = "parking_lot"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [

@@ -154,5 +154,5 @@ "lock_api",

name = "parking_lot_core"
version = "0.9.9"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [

@@ -163,3 +163,3 @@ "cfg-if",

"smallvec",
"windows-targets 0.48.5",
"windows-targets",
]

@@ -181,5 +181,5 @@

name = "proc-macro2"
version = "1.0.78"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
dependencies = [

@@ -191,5 +191,5 @@ "unicode-ident",

name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [

@@ -231,7 +231,7 @@ "proc-macro2",

name = "redox_syscall"
version = "0.4.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.5.0",
]

@@ -241,7 +241,7 @@

name = "rustix"
version = "0.38.31"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.5.0",
"errno",

@@ -270,17 +270,17 @@ "libc",

name = "semver"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "smallvec"
version = "1.13.1"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "syn"
version = "2.0.50"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [

@@ -307,5 +307,5 @@ "proc-macro2",

name = "tempfile"
version = "3.10.0"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [

@@ -320,5 +320,5 @@ "cfg-if",

name = "thiserror"
version = "1.0.57"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [

@@ -330,5 +330,5 @@ "thiserror-impl",

name = "thiserror-impl"
version = "1.0.57"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [

@@ -348,5 +348,5 @@ "proc-macro2",

name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [

@@ -364,33 +364,11 @@ "same-file",

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"winapi",
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"

@@ -401,3 +379,3 @@ version = "0.52.0"

dependencies = [
"windows-targets 0.52.3",
"windows-targets",
]

@@ -407,112 +385,62 @@

name = "windows-targets"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-targets"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
dependencies = [
"windows_aarch64_gnullvm 0.52.3",
"windows_aarch64_msvc 0.52.3",
"windows_i686_gnu 0.52.3",
"windows_i686_msvc 0.52.3",
"windows_x86_64_gnu 0.52.3",
"windows_x86_64_gnullvm 0.52.3",
"windows_x86_64_msvc 0.52.3",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnu"
version = "0.52.3"
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_i686_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO

name = "nix"
version = "0.28.0"
version = "0.29.0"
authors = ["The nix-rust Project Developers"]

@@ -76,3 +76,3 @@ include = [

[dependencies.libc]
version = "0.2.153"
version = "0.2.155"
features = ["extra_traits"]

@@ -104,3 +104,3 @@

[build-dependencies.cfg_aliases]
version = "0.1.1"
version = "0.2"

@@ -107,0 +107,0 @@ [features]

@@ -62,3 +62,3 @@ //! List directory contents

/// Converts from a file descriptor, closing it on success or failure.
/// Converts from a file descriptor, closing it on failure.
#[doc(alias("fdopendir"))]

@@ -65,0 +65,0 @@ pub fn from_fd(fd: RawFd) -> Result<Self> {

@@ -117,3 +117,3 @@ //! file control options

/// 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;

@@ -155,3 +155,3 @@ /// Error out if a file was not created.

/// 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;

@@ -169,4 +169,14 @@ /// Only allow reading.

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;

@@ -248,2 +258,115 @@ /// Open with a shared file lock.

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 <https://man7.org/linux/man-pages/man2/open_how.2type.html>
#[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<P: ?Sized + NixPath>(
dirfd: RawFd,
path: &P,
mut how: OpenHow,
) -> Result<RawFd> {
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::<libc::open_how>(),
)
})?;
Errno::result(fd as RawFd)
}
}
}
/// Change the name of a file.

@@ -839,2 +962,26 @@ ///

}
/// 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)
}
}

@@ -948,6 +1095,6 @@

#[cfg(linux_android)]
pub fn splice(
fd_in: RawFd,
pub fn splice<Fd1: AsFd, Fd2: AsFd>(
fd_in: Fd1,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
fd_out: Fd2,
off_out: Option<&mut libc::loff_t>,

@@ -965,3 +1112,3 @@ len: usize,

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())
};

@@ -976,9 +1123,9 @@ Errno::result(ret).map(|r| r as usize)

#[cfg(linux_android)]
pub fn tee(
fd_in: RawFd,
fd_out: RawFd,
pub fn tee<Fd1: AsFd, Fd2: AsFd>(
fd_in: Fd1,
fd_out: Fd2,
len: usize,
flags: SpliceFFlags,
) -> Result<usize> {
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)

@@ -992,4 +1139,4 @@ }

#[cfg(linux_android)]
pub fn vmsplice(
fd: RawFd,
pub fn vmsplice<F: AsFd>(
fd: F,
iov: &[std::io::IoSlice<'_>],

@@ -1000,3 +1147,3 @@ flags: SpliceFFlags,

libc::vmsplice(
fd,
fd.as_fd().as_raw_fd(),
iov.as_ptr().cast(),

@@ -1003,0 +1150,0 @@ iov.len(),

@@ -365,1 +365,19 @@ //! Rust friendly bindings to the various *nix system functions.

}
/// 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<P, T, F>(path: Option<&P>, f: F) -> Result<T>
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())),
}
}

@@ -116,17 +116,6 @@ use crate::errno::Errno;

) -> Result<()> {
fn with_opt_nix_path<P, T, F>(p: Option<&P>, f: F) -> Result<T>
where
P: ?Sized + NixPath,
F: FnOnce(*const libc::c_char) -> T,
{
match p {
Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
None => Ok(f(std::ptr::null())),
}
}
let res = with_opt_nix_path(source, |s| {
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(

@@ -133,0 +122,0 @@ s,

@@ -8,6 +8,12 @@ //! Mount file systems

#[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::*;

@@ -6,5 +6,5 @@ //! Network interface name resolution.

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};

@@ -18,3 +18,3 @@ #[cfg(not(solarish))]

/// Resolve an interface into a interface number.
/// Resolve an interface into an interface number.
pub fn if_nametoindex<P: ?Sized + NixPath>(name: &P) -> Result<c_uint> {

@@ -31,2 +31,15 @@ let if_index = name

/// Resolve an interface number into an interface.
pub fn if_indextoname(index: c_uint) -> Result<CString> {
// 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!(

@@ -33,0 +46,0 @@ /// Standard interface flags, used by `getifaddrs`

@@ -15,4 +15,2 @@ //! Create master and slave virtual pseudo-terminals (PTYs)

use crate::sys::termios::Termios;
#[cfg(feature = "process")]
use crate::unistd::ForkResult;
#[cfg(all(feature = "process", not(target_os = "aix")))]

@@ -35,11 +33,15 @@ use crate::unistd::Pid;

#![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,
}

@@ -61,2 +63,8 @@ }

impl AsFd for PtyMaster {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl IntoRawFd for PtyMaster {

@@ -306,5 +314,3 @@ fn into_raw_fd(self) -> RawFd {

#![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.
///

@@ -326,2 +332,7 @@ /// If `winsize` is not `None`, the window size of the slave will be set to

/// [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"))]

@@ -351,12 +362,21 @@ pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(

let fork_result = Errno::result(res).map(|res| match res {
0 => 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)
}
}

@@ -33,3 +33,3 @@ // vim: tw=80

mem,
os::unix::io::RawFd,
os::unix::io::{AsFd, AsRawFd, BorrowedFd},
pin::Pin,

@@ -59,2 +59,3 @@ ptr, thread,

target_os = "linux",
target_os = "freebsd",
netbsdlike))]

@@ -107,3 +108,3 @@ O_DSYNC

#[repr(C)]
struct AioCb {
struct AioCb<'a> {
aiocb: LibcAiocb,

@@ -118,5 +119,6 @@ /// Could this `AioCb` potentially have any in-kernel state?

in_progress: bool,
_fd: PhantomData<BorrowedFd<'a>>,
}
impl AioCb {
impl<'a> AioCb<'a> {
pin_utils::unsafe_unpinned!(aiocb: LibcAiocb);

@@ -146,3 +148,7 @@

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

@@ -153,3 +159,3 @@ // number and name of reserved fields is OS-dependent. On some OSes,

let mut a = unsafe { mem::zeroed::<libc::aiocb>() };
a.aio_fildes = fd;
a.aio_fildes = fd.as_raw_fd();
a.aio_reqprio = prio;

@@ -160,2 +166,3 @@ a.aio_sigevent = SigEvent::new(sigev_notify).sigevent();

in_progress: false,
_fd: PhantomData,
}

@@ -196,3 +203,3 @@ }

impl Debug for AioCb {
impl<'a> Debug for AioCb<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {

@@ -206,3 +213,3 @@ fmt.debug_struct("AioCb")

impl Drop for AioCb {
impl<'a> Drop for AioCb<'a> {
/// If the `AioCb` has no remaining state in the kernel, just drop it.

@@ -255,7 +262,7 @@ /// Otherwise, dropping constitutes a resource leak, which is an error

/// # 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

@@ -297,7 +304,7 @@ /// &wbuf[..],

/// # 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

@@ -320,3 +327,3 @@ /// WBUF,

/// Returns the underlying file descriptor associated with the operation.
fn fd(&self) -> RawFd;
fn fd(&self) -> BorrowedFd;

@@ -336,6 +343,6 @@ /// Does this operation currently have any in-kernel state?

/// # 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));

@@ -380,4 +387,6 @@ /// assert!(!aiof.as_mut().in_progress());

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) }
}

@@ -430,6 +439,6 @@

/// # 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));

@@ -444,9 +453,9 @@ /// aiof.as_mut().submit().expect("aio_fsync failed early");

#[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>);

@@ -470,3 +479,3 @@ /// Returns the operation's fsync mode: data and metadata or data only?

pub fn new(
fd: RawFd,
fd: BorrowedFd<'a>,
mode: AioFsyncMode,

@@ -489,3 +498,3 @@ prio: i32,

impl Aio for AioFsync {
impl<'a> Aio for AioFsync<'a> {
type Output = ();

@@ -511,3 +520,3 @@

impl AsRef<libc::aiocb> for AioFsync {
impl<'a> AsRef<libc::aiocb> for AioFsync<'a> {
fn as_ref(&self) -> &libc::aiocb {

@@ -534,3 +543,3 @@ &self.aiocb.aiocb.0

/// # use std::io::Write;
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use tempfile::tempfile;

@@ -545,3 +554,3 @@ /// const INITIAL: &[u8] = b"abcdef123456";

/// AioRead::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, //offset

@@ -564,3 +573,3 @@ /// &mut rbuf,

pub struct AioRead<'a> {
aiocb: AioCb,
aiocb: AioCb<'a>,
_data: PhantomData<&'a [u8]>,

@@ -571,3 +580,3 @@ _pin: PhantomPinned,

impl<'a> AioRead<'a> {
unsafe_pinned!(aiocb: AioCb);
unsafe_pinned!(aiocb: AioCb<'a>);

@@ -596,3 +605,3 @@ /// Returns the requested length of the aio operation in bytes

pub fn new(
fd: RawFd,
fd: BorrowedFd<'a>,
offs: off_t,

@@ -656,3 +665,3 @@ buf: &'a mut [u8],

/// # use std::io::{IoSliceMut, Write};
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use tempfile::tempfile;

@@ -669,3 +678,3 @@ /// const INITIAL: &[u8] = b"abcdef123456";

/// AioReadv::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, //offset

@@ -690,3 +699,3 @@ /// &mut rbufs,

pub struct AioReadv<'a> {
aiocb: AioCb,
aiocb: AioCb<'a>,
_data: PhantomData<&'a [&'a [u8]]>,

@@ -698,3 +707,3 @@ _pin: PhantomPinned,

impl<'a> AioReadv<'a> {
unsafe_pinned!(aiocb: AioCb);
unsafe_pinned!(aiocb: AioCb<'a>);

@@ -720,3 +729,3 @@ /// Returns the number of buffers the operation will read into.

pub fn new(
fd: RawFd,
fd: BorrowedFd<'a>,
offs: off_t,

@@ -782,3 +791,3 @@ bufs: &mut [IoSliceMut<'a>],

/// # use std::{thread, time};
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use tempfile::tempfile;

@@ -789,3 +798,3 @@ /// const WBUF: &[u8] = b"abcdef123456";

/// AioWrite::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, //offset

@@ -806,3 +815,3 @@ /// WBUF,

pub struct AioWrite<'a> {
aiocb: AioCb,
aiocb: AioCb<'a>,
_data: PhantomData<&'a [u8]>,

@@ -813,3 +822,3 @@ _pin: PhantomPinned,

impl<'a> AioWrite<'a> {
unsafe_pinned!(aiocb: AioCb);
unsafe_pinned!(aiocb: AioCb<'a>);

@@ -838,3 +847,3 @@ /// Returns the requested length of the aio operation in bytes

pub fn new(
fd: RawFd,
fd: BorrowedFd<'a>,
offs: off_t,

@@ -901,3 +910,3 @@ buf: &'a [u8],

/// # use std::io::IoSlice;
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use tempfile::tempfile;

@@ -911,3 +920,3 @@ /// const wbuf0: &[u8] = b"abcdef";

/// AioWritev::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, //offset

@@ -929,3 +938,3 @@ /// &wbufs,

pub struct AioWritev<'a> {
aiocb: AioCb,
aiocb: AioCb<'a>,
_data: PhantomData<&'a [&'a [u8]]>,

@@ -937,3 +946,3 @@ _pin: PhantomPinned,

impl<'a> AioWritev<'a> {
unsafe_pinned!(aiocb: AioCb);
unsafe_pinned!(aiocb: AioCb<'a>);

@@ -959,3 +968,3 @@ /// Returns the number of buffers the operation will read into.

pub fn new(
fd: RawFd,
fd: BorrowedFd<'a>,
offs: off_t,

@@ -1025,7 +1034,7 @@ bufs: &[IoSlice<'a>],

/// # 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

@@ -1036,3 +1045,3 @@ /// &wbuf[..],

/// 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 {

@@ -1050,4 +1059,4 @@ /// while (aiocb.as_mut().error() == Err(Errno::EINPROGRESS)) {

/// [`aio_cancel`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/aio_cancel.html)
pub fn aio_cancel_all(fd: RawFd) -> Result<AioCancelStat> {
match unsafe { libc::aio_cancel(fd, ptr::null_mut()) } {
pub fn aio_cancel_all<F: AsFd>(fd: F) -> Result<AioCancelStat> {
match unsafe { libc::aio_cancel(fd.as_fd().as_raw_fd(), ptr::null_mut()) } {
libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled),

@@ -1073,7 +1082,7 @@ libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled),

/// # 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

@@ -1085,3 +1094,3 @@ /// WBUF,

/// 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());
/// ```

@@ -1125,3 +1134,3 @@ /// # References

/// ```
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use nix::sys::aio::*;

@@ -1133,3 +1142,3 @@ /// # use nix::sys::signal::SigevNotify;

/// let mut aiow = Box::pin(AioWrite::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, // offset

@@ -1151,3 +1160,3 @@ /// WBUF,

/// ```
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use std::thread;

@@ -1162,3 +1171,3 @@ /// # use std::time;

/// let mut aiow = Box::pin(AioWrite::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, // offset

@@ -1187,3 +1196,3 @@ /// WBUF,

/// # use libc::c_int;
/// # use std::os::unix::io::AsRawFd;
/// # use std::os::unix::io::AsFd;
/// # use std::sync::atomic::{AtomicBool, Ordering};

@@ -1210,3 +1219,3 @@ /// # use std::thread;

/// let mut aiow = Box::pin(AioWrite::new(
/// f.as_raw_fd(),
/// f.as_fd(),
/// 2, // offset

@@ -1213,0 +1222,0 @@ /// WBUF,

@@ -99,5 +99,18 @@ //! Monitoring API for filesystem events.

/// 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;

@@ -240,2 +253,5 @@

fn drop(&mut self) {
if self.0.fd == libc::FAN_NOFD {
return;
}
let e = close(self.0.fd);

@@ -318,14 +334,3 @@ if !std::thread::panicking() && e == Err(Errno::EBADF) {

) -> Result<()> {
fn with_opt_nix_path<P, T, F>(p: Option<&P>, f: F) -> Result<T>
where
P: ?Sized + NixPath,
F: FnOnce(*const libc::c_char) -> T,
{
match p {
Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
None => Ok(f(std::ptr::null())),
}
}
let res = with_opt_nix_path(path, |p| unsafe {
let res = crate::with_opt_nix_path(path, |p| unsafe {
libc::fanotify_mark(

@@ -332,0 +337,0 @@ self.fd.as_raw_fd(),

@@ -12,5 +12,7 @@ //! prctl is a Linux-only API for performing operations on a process or thread.

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;

@@ -217,1 +219,12 @@ libc_enum! {

}
/// Set an identifier (or reset it) to the address memory range.
pub fn set_vma_anon_name(addr: NonNull<c_void>, 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)
}

@@ -20,4 +20,6 @@ //! For detailed description of the ptrace requests, consult `man ptrace`.

),
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"),
),
))]

@@ -174,2 +176,88 @@ use libc::user_regs_struct;

#[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! {

@@ -222,2 +310,8 @@ /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request.

/// 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(

@@ -237,3 +331,54 @@ target_os = "linux",

/// 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<user_regs_struct> {
getregset::<regset::NT_PRSTATUS>(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<S: RegisterSet>(pid: Pid) -> Result<S::Regs> {
let request = Request::PTRACE_GETREGSET;
let mut data = mem::MaybeUninit::<S::Regs>::uninit();
let mut iov = libc::iovec {
iov_base: data.as_mut_ptr().cast(),
iov_len: mem::size_of::<S::Regs>(),
};
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(

@@ -255,3 +400,3 @@ target_os = "linux",

ptr::null_mut::<c_void>(),
&regs as *const _ as *const c_void,
&regs as *const user_regs_struct as *const c_void,
)

@@ -262,2 +407,45 @@ };

/// 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::<regset::NT_PRSTATUS>(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<S: RegisterSet>(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::<S::Regs>(),
};
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.

@@ -552,13 +740,11 @@ /// Some ptrace get requests populate structs or larger elements than `c_long`

/// 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)
}
}

@@ -574,15 +760,11 @@

/// 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)
}
}

@@ -296,3 +296,5 @@ //! Configure the process resource limits.

/// 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 {

@@ -299,0 +301,0 @@ self.0.ru_maxrss

@@ -600,3 +600,3 @@ // Portions of this file are Copyright 2014 The Rust Project Developers.

target_os = "aix",
target_os = "fushsia"
target_os = "fuchsia"
))]

@@ -757,2 +757,3 @@ #[doc(alias("sigsuspend"))]

/// Action to take on receipt of a signal. Corresponds to `sigaction`.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]

@@ -763,2 +764,8 @@ pub struct SigAction {

impl From<SigAction> for libc::sigaction {
fn from(value: SigAction) -> libc::sigaction {
value.sigaction
}
}
impl SigAction {

@@ -765,0 +772,0 @@ /// Creates a new action.

@@ -108,7 +108,7 @@ //! Interface for the `signalfd` syscall.

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<Option<siginfo>> {
pub fn read_signal(&self) -> Result<Option<siginfo>> {
let mut buffer = mem::MaybeUninit::<siginfo>::uninit();

@@ -115,0 +115,0 @@

@@ -8,3 +8,3 @@ //! Socket options as used by `setsockopt` and `getsockopt`.

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};

@@ -274,2 +274,12 @@ use std::os::unix::ffi::OsStrExt;

);
#[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")]

@@ -1031,3 +1041,3 @@ sockopt_impl!(

);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(any(apple_targets, linux_android, target_os = "freebsd"))]
sockopt_impl!(

@@ -1071,2 +1081,13 @@ /// Set the unicast hop limit for the socket.

);
#[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]>
);

@@ -1575,1 +1596,30 @@ #[allow(missing_docs)]

/// Getter for a `CString` value.
struct GetCString<T: AsMut<[u8]>> {
len: socklen_t,
val: MaybeUninit<T>,
}
impl<T: AsMut<[u8]>> Get<CString> for GetCString<T> {
fn uninit() -> Self {
GetCString {
len: mem::size_of::<T>() 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()
}
}

@@ -1,2 +0,2 @@

use libc::{self, SI_LOAD_SHIFT};
use libc::SI_LOAD_SHIFT;
use std::time::Duration;

@@ -3,0 +3,0 @@ use std::{cmp, mem};

@@ -40,4 +40,4 @@ use cfg_if::cfg_if;

($name:expr) => {
use ::sysctl::{CtlValue, Sysctl};
use nix::unistd::Uid;
use sysctl::{CtlValue, Sysctl};

@@ -69,3 +69,3 @@ let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap();

($name:expr) => {
use ::sysctl::{CtlValue, Sysctl};
use sysctl::{CtlValue, Sysctl};

@@ -72,0 +72,0 @@ let ctl = ::sysctl::Ctl::new("security.jail.jailed").unwrap();

@@ -19,3 +19,3 @@ // Test dropping an AioCb that hasn't yet finished.

use nix::sys::signal::*;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::AsFd;
use tempfile::tempfile;

@@ -28,3 +28,3 @@

let mut aiocb = Box::pin(AioWrite::new(
f.as_raw_fd(),
f.as_fd(),
2, //offset

@@ -31,0 +31,0 @@ WBUF,

use std::{
io::{Read, Seek, Write},
ops::Deref,
os::unix::io::AsRawFd,
os::unix::io::{AsFd, AsRawFd, BorrowedFd},
pin::Pin,

@@ -48,4 +48,5 @@ sync::atomic::{AtomicBool, Ordering},

fn test_accessors() {
let f = tempfile().unwrap();
let aiocb = AioFsync::new(
1001,
f.as_fd(),
AioFsyncMode::O_SYNC,

@@ -58,3 +59,3 @@ 42,

);
assert_eq!(1001, aiocb.fd());
assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd());
assert_eq!(AioFsyncMode::O_SYNC, aiocb.mode());

@@ -72,3 +73,3 @@ assert_eq!(42, aiocb.priority());

#[test]
#[cfg(any(target_os = "freebsd", apple_targets))]
#[cfg_attr(any(target_os = "android", target_os = "linux"), ignore)]
fn error() {

@@ -79,11 +80,7 @@ use std::mem;

// Create an invalid AioFsyncMode
let mode = unsafe { mem::transmute(666) };
let mode = unsafe { mem::transmute::<i32, AioFsyncMode>(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();

@@ -99,5 +96,4 @@ err.expect_err("assertion failed");

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,

@@ -118,5 +114,6 @@ 0,

fn test_accessors() {
let f = tempfile().unwrap();
let mut rbuf = vec![0; 4];
let aiocb = AioRead::new(
1001,
f.as_fd(),
2, //offset

@@ -130,3 +127,3 @@ &mut rbuf,

);
assert_eq!(1001, aiocb.fd());
assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd());
assert_eq!(4, aiocb.nbytes());

@@ -150,3 +147,3 @@ assert_eq!(2, aiocb.offset());

f.write_all(INITIAL).unwrap();
let fd = f.as_raw_fd();
let fd = f.as_fd();
let mut aior =

@@ -175,3 +172,3 @@ Box::pin(AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone));

let mut aior = Box::pin(AioRead::new(
f.as_raw_fd(),
f.as_fd(),
-1, //an invalid offset

@@ -196,3 +193,3 @@ &mut rbuf,

{
let fd = f.as_raw_fd();
let fd = f.as_fd();
let mut aior = Box::pin(AioRead::new(

@@ -224,3 +221,3 @@ fd,

{
let fd = f.as_raw_fd();
let fd = f.as_fd();
let mut aior =

@@ -248,2 +245,3 @@ AioRead::new(fd, 2, &mut rbuf, 0, SigevNotify::SigevNone);

fn test_accessors() {
let f = tempfile().unwrap();
let mut rbuf0 = vec![0; 4];

@@ -254,3 +252,3 @@ let mut rbuf1 = vec![0; 8];

let aiocb = AioReadv::new(
1001,
f.as_fd(),
2, //offset

@@ -264,3 +262,3 @@ &mut rbufs,

);
assert_eq!(1001, aiocb.fd());
assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd());
assert_eq!(2, aiocb.iovlen());

@@ -287,3 +285,3 @@ assert_eq!(2, aiocb.offset());

{
let fd = f.as_raw_fd();
let fd = f.as_fd();
let mut aior = Box::pin(AioReadv::new(

@@ -315,5 +313,6 @@ fd,

fn test_accessors() {
let f = tempfile().unwrap();
let wbuf = vec![0; 4];
let aiocb = AioWrite::new(
1001,
f.as_fd(),
2, //offset

@@ -327,3 +326,3 @@ &wbuf,

);
assert_eq!(1001, aiocb.fd());
assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd());
assert_eq!(4, aiocb.nbytes());

@@ -347,3 +346,3 @@ assert_eq!(2, aiocb.offset());

let mut aiow = Box::pin(AioWrite::new(
f.as_raw_fd(),
f.as_fd(),
0,

@@ -377,14 +376,16 @@ wbuf,

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());
}

@@ -408,15 +409,17 @@ f.rewind().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());
}

@@ -434,8 +437,10 @@ f.rewind().unwrap();

#[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,

@@ -459,2 +464,3 @@ 0, //priority

fn test_accessors() {
let f = tempfile().unwrap();
let wbuf0 = vec![0; 4];

@@ -464,3 +470,3 @@ let wbuf1 = vec![0; 8];

let aiocb = AioWritev::new(
1001,
f.as_fd(),
2, //offset

@@ -474,3 +480,3 @@ &wbufs,

);
assert_eq!(1001, aiocb.fd());
assert_eq!(f.as_raw_fd(), aiocb.fd().as_raw_fd());
assert_eq!(2, aiocb.iovlen());

@@ -499,14 +505,16 @@ assert_eq!(2, aiocb.offset());

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);
}

@@ -549,18 +557,21 @@ f.rewind().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();

@@ -580,3 +591,3 @@ let len = f.read_to_end(&mut rbuf).unwrap();

let mut aiocb = Box::pin(AioWrite::new(
f.as_raw_fd(),
f.as_fd(),
0, //offset

@@ -591,3 +602,3 @@ wbuf,

aio_cancel_all(f.as_raw_fd()).unwrap();
aio_cancel_all(f.as_fd()).unwrap();

@@ -610,3 +621,3 @@ // Wait for aiocb to complete, but don't care whether it succeeded

let mut wcb = Box::pin(AioWrite::new(
f.as_raw_fd(),
f.as_fd(),
2, //offset

@@ -619,3 +630,3 @@ WBUF,

let mut rcb = Box::pin(AioRead::new(
f.as_raw_fd(),
f.as_fd(),
8, //offset

@@ -657,3 +668,5 @@ &mut rbuf,

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!(

@@ -665,3 +678,3 @@ aiof.as_ref() 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!(

@@ -673,3 +686,3 @@ aior.as_ref() 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!(

@@ -690,3 +703,5 @@ aiow.as_ref() as *const libc::aiocb,

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!(

@@ -699,3 +714,3 @@ aiorv.as_ref() as *const libc::aiocb,

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!(

@@ -702,0 +717,0 @@ aiowv.as_ref() as *const libc::aiocb,

use crate::*;
use nix::errno::Errno;
use nix::sys::fanotify::{

@@ -6,3 +7,3 @@ EventFFlags, Fanotify, FanotifyResponse, InitFlags, MarkFlags, MaskFlags,

};
use std::fs::{read_link, File, OpenOptions};
use std::fs::{read_link, read_to_string, File, OpenOptions};
use std::io::ErrorKind;

@@ -20,2 +21,3 @@ use std::io::{Read, Write};

test_fanotify_responses();
test_fanotify_overflow();
}

@@ -152,1 +154,69 @@

}
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));
}

@@ -125,2 +125,36 @@ #[cfg(target_os = "linux")]

}
#[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();
}
}
#[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")
))]

@@ -182,4 +182,9 @@ use memoffset::offset_of;

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",
)
))]

@@ -230,2 +235,10 @@ #[test]

#[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`.

@@ -237,2 +250,3 @@ #[cfg(target_arch = "x86_64")]

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
let get_syscall_from_user_area = || {

@@ -252,2 +266,3 @@ // Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)

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);

@@ -262,2 +277,3 @@

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);

@@ -281,1 +297,83 @@

}
#[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::<regset::NT_PRSTATUS>(child).unwrap();
let mut fpregstruct =
getregset::<regset::NT_PRFPREG>(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::<regset::NT_PRSTATUS>(child, regstruct);
regstruct = getregset::<regset::NT_PRSTATUS>(child).unwrap();
let _ = setregset::<regset::NT_PRFPREG>(child, fpregstruct);
fpregstruct = getregset::<regset::NT_PRFPREG>(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"),
}
}
}
}

@@ -286,2 +286,3 @@ use nix::errno::Errno;

fn test_sigaction() {
let _m = crate::SIGNAL_MTX.lock();
thread::spawn(|| {

@@ -353,3 +354,3 @@ extern "C" fn test_sigaction_handler(_: libc::c_int) {}

target_os = "aix",
target_os = "fushsia"
target_os = "fuchsia"
))]

@@ -356,0 +357,0 @@ #[test]

@@ -31,3 +31,3 @@ use std::convert::TryFrom;

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();

@@ -51,3 +51,3 @@ let res = fd.read_signal();

let mut fd = SignalFd::new(&mask).unwrap();
let fd = SignalFd::new(&mask).unwrap();

@@ -77,3 +77,3 @@ // Send a SIGUSR1 signal to the current process. Note that this uses `raise` instead of `kill`

let mut fd = SignalFd::new(&mask).unwrap();
let fd = SignalFd::new(&mask).unwrap();

@@ -80,0 +80,0 @@ mask.add(signal::SIGUSR1);

@@ -831,1 +831,50 @@ #[cfg(linux_android)]

}
#[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());
}

@@ -47,3 +47,2 @@ use nix::sys::statfs::*;

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);

@@ -50,0 +49,0 @@ assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64);

@@ -7,2 +7,6 @@ #[cfg(not(target_os = "redox"))]

use nix::fcntl::{openat, readlinkat, renameat};
#[cfg(target_os = "linux")]
use nix::fcntl::{openat2, OpenHow, ResolveFlag};
#[cfg(all(

@@ -13,3 +17,2 @@ target_os = "linux",

target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",

@@ -63,2 +66,60 @@ target_arch = "s390x"

#[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"))]

@@ -90,3 +151,2 @@ fn test_renameat() {

target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",

@@ -135,3 +195,2 @@ target_arch = "s390x"

target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",

@@ -184,3 +243,2 @@ target_arch = "s390x"

target_arch = "x86_64",
target_arch = "x32",
target_arch = "powerpc",

@@ -304,11 +362,5 @@ target_arch = "s390x"

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();

@@ -329,5 +381,4 @@ assert_eq!(2, res);

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();

@@ -355,4 +406,3 @@ assert_eq!(2, res);

let res = vmsplice(wr.as_raw_fd(), &iovecs[..], SpliceFFlags::empty())
.unwrap();
let res = vmsplice(wr, &iovecs[..], SpliceFFlags::empty()).unwrap();

@@ -648,3 +698,3 @@ assert_eq!(6, res);

#[test]
fn verify_lock_and_drop() {
fn lock_and_drop() {
// Get 2 `File` handles to same underlying file.

@@ -673,5 +723,28 @@ let file1 = NamedTempFile::new().unwrap();

/// 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.

@@ -693,2 +766,27 @@ let file1 = NamedTempFile::new().unwrap();

}
/// 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");
}
}

@@ -16,1 +16,12 @@ use nix::net::if_::*;

}
#[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
);
}

@@ -11,2 +11,3 @@ use std::fs::File;

use nix::sys::termios::*;
use nix::sys::wait::WaitStatus;
use nix::unistd::{pause, write};

@@ -20,5 +21,6 @@

// 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());

@@ -252,3 +254,2 @@ // Get the name of the slave

use nix::sys::wait::wait;
use nix::unistd::ForkResult::*;
// forkpty calls openpty which uses ptname(3) internally.

@@ -261,5 +262,5 @@ let _m0 = crate::PTSNAME_MTX.lock();

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();

@@ -271,8 +272,9 @@ pause(); // we need the child to stay alive until the parent calls read

}
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());

@@ -279,0 +281,0 @@ }

@@ -316,3 +316,3 @@ use libc::{_exit, mode_t, off_t};

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));

@@ -322,3 +322,6 @@

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);

@@ -325,0 +328,0 @@

@@ -6,3 +6,5 @@ #[macro_use]

#[macro_use]
mod common;
mod mount;
mod sys;

@@ -15,10 +17,3 @@ #[cfg(not(target_os = "redox"))]

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;

@@ -28,4 +23,2 @@ #[cfg(not(target_os = "redox"))]

mod test_nix_path;
#[cfg(target_os = "freebsd")]
mod test_nmount;
mod test_poll;

@@ -65,5 +58,6 @@ #[cfg(not(any(

/// 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

@@ -70,0 +64,0 @@ /// the RwLock exclusively. Any process that cares about the current

#[cfg(target_os = "freebsd")]
use crate::Error;
use crate::{Errno, NixPath, Result};
use libc::c_int;
#[cfg(target_os = "freebsd")]
use libc::{c_char, c_uint, c_void};
#[cfg(target_os = "freebsd")]
use std::{
borrow::Cow,
ffi::{CStr, CString},
fmt, io,
marker::PhantomData,
};
libc_bitflags!(
/// Used with [`Nmount::nmount`].
pub struct MntFlags: c_int {
/// ACL support enabled.
#[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
MNT_ACLS;
/// All I/O to the file system should be done asynchronously.
MNT_ASYNC;
/// dir should instead be a file system ID encoded as “FSID:val0:val1”.
#[cfg(target_os = "freebsd")]
MNT_BYFSID;
/// Force a read-write mount even if the file system appears to be
/// unclean.
MNT_FORCE;
/// GEOM journal support enabled.
#[cfg(target_os = "freebsd")]
MNT_GJOURNAL;
/// MAC support for objects.
#[cfg(any(apple_targets, target_os = "freebsd"))]
MNT_MULTILABEL;
/// Disable read clustering.
#[cfg(freebsdlike)]
MNT_NOCLUSTERR;
/// Disable write clustering.
#[cfg(freebsdlike)]
MNT_NOCLUSTERW;
/// Enable NFS version 4 ACLs.
#[cfg(target_os = "freebsd")]
MNT_NFS4ACLS;
/// Do not update access times.
MNT_NOATIME;
/// Disallow program execution.
MNT_NOEXEC;
/// Do not honor setuid or setgid bits on files when executing them.
MNT_NOSUID;
/// Do not follow symlinks.
#[cfg(freebsdlike)]
MNT_NOSYMFOLLOW;
/// Mount read-only.
MNT_RDONLY;
/// Causes the vfs subsystem to update its data structures pertaining to
/// the specified already mounted file system.
MNT_RELOAD;
/// Create a snapshot of the file system.
///
/// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs)
#[cfg(any(apple_targets, target_os = "freebsd"))]
MNT_SNAPSHOT;
/// Using soft updates.
#[cfg(any(freebsdlike, netbsdlike))]
MNT_SOFTDEP;
/// Directories with the SUID bit set chown new files to their own
/// owner.
#[cfg(freebsdlike)]
MNT_SUIDDIR;
/// All I/O to the file system should be done synchronously.
MNT_SYNCHRONOUS;
/// Union with underlying fs.
#[cfg(any(
apple_targets,
target_os = "freebsd",
target_os = "netbsd"
))]
MNT_UNION;
/// Indicates that the mount command is being applied to an already
/// mounted file system.
MNT_UPDATE;
/// Check vnode use counts.
#[cfg(target_os = "freebsd")]
MNT_NONBUSY;
}
);
/// The Error type of [`Nmount::nmount`].
///
/// It wraps an [`Errno`], but also may contain an additional message returned
/// by `nmount(2)`.
#[cfg(target_os = "freebsd")]
#[derive(Debug)]
pub struct NmountError {
errno: Error,
errmsg: Option<String>,
}
#[cfg(target_os = "freebsd")]
impl NmountError {
/// Returns the additional error string sometimes generated by `nmount(2)`.
pub fn errmsg(&self) -> Option<&str> {
self.errmsg.as_deref()
}
/// Returns the inner [`Error`]
pub const fn error(&self) -> Error {
self.errno
}
fn new(error: Error, errmsg: Option<&CStr>) -> Self {
Self {
errno: error,
errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned),
}
}
}
#[cfg(target_os = "freebsd")]
impl std::error::Error for NmountError {}
#[cfg(target_os = "freebsd")]
impl fmt::Display for NmountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(errmsg) = &self.errmsg {
write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc())
} else {
write!(f, "{:?}: {}", self.errno, self.errno.desc())
}
}
}
#[cfg(target_os = "freebsd")]
impl From<NmountError> for io::Error {
fn from(err: NmountError) -> Self {
err.errno.into()
}
}
/// Result type of [`Nmount::nmount`].
#[cfg(target_os = "freebsd")]
pub type NmountResult = std::result::Result<(), NmountError>;
/// Mount a FreeBSD file system.
///
/// The `nmount(2)` system call works similarly to the `mount(8)` program; it
/// takes its options as a series of name-value pairs. Most of the values are
/// strings, as are all of the names. The `Nmount` structure builds up an
/// argument list and then executes the syscall.
///
/// # Examples
///
/// To mount `target` onto `mountpoint` with `nullfs`:
/// ```
/// # use nix::unistd::Uid;
/// # use ::sysctl::{CtlValue, Sysctl};
/// # let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap();
/// # if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() {
/// # return;
/// # };
/// use nix::mount::{MntFlags, Nmount, unmount};
/// use std::ffi::CString;
/// use tempfile::tempdir;
///
/// let mountpoint = tempdir().unwrap();
/// let target = tempdir().unwrap();
///
/// let fstype = CString::new("fstype").unwrap();
/// let nullfs = CString::new("nullfs").unwrap();
/// Nmount::new()
/// .str_opt(&fstype, &nullfs)
/// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
/// .str_opt_owned("target", target.path().to_str().unwrap())
/// .nmount(MntFlags::empty()).unwrap();
///
/// unmount(mountpoint.path(), MntFlags::empty()).unwrap();
/// ```
///
/// # See Also
/// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount)
/// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs)
#[cfg(target_os = "freebsd")]
#[derive(Debug, Default)]
pub struct Nmount<'a> {
// n.b. notgull: In reality, this is a list that contains
// both mutable and immutable pointers.
// Be careful using this.
iov: Vec<libc::iovec>,
is_owned: Vec<bool>,
marker: PhantomData<&'a ()>,
}
#[cfg(target_os = "freebsd")]
impl<'a> Nmount<'a> {
/// Helper function to push a slice onto the `iov` array.
fn push_slice(&mut self, val: &'a [u8], is_owned: bool) {
self.iov.push(libc::iovec {
iov_base: val.as_ptr().cast_mut().cast(),
iov_len: val.len(),
});
self.is_owned.push(is_owned);
}
/// Helper function to push a pointer and its length onto the `iov` array.
fn push_pointer_and_length(
&mut self,
val: *const u8,
len: usize,
is_owned: bool,
) {
self.iov.push(libc::iovec {
iov_base: val as *mut _,
iov_len: len,
});
self.is_owned.push(is_owned);
}
/// Helper function to push a `nix` path as owned.
fn push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P) {
val.with_nix_path(|s| {
let len = s.to_bytes_with_nul().len();
let ptr = s.to_owned().into_raw() as *const u8;
self.push_pointer_and_length(ptr, len, true);
})
.unwrap();
}
/// Add an opaque mount option.
///
/// Some file systems take binary-valued mount options. They can be set
/// with this method.
///
/// # Safety
///
/// Unsafe because it will cause `Nmount::nmount` to dereference a raw
/// pointer. The user is responsible for ensuring that `val` is valid and
/// its lifetime outlives `self`! An easy way to do that is to give the
/// value a larger scope than `name`
///
/// # Examples
/// ```
/// use libc::c_void;
/// use nix::mount::Nmount;
/// use std::ffi::CString;
/// use std::mem;
///
/// // Note that flags outlives name
/// let mut flags: u32 = 0xdeadbeef;
/// let name = CString::new("flags").unwrap();
/// let p = &mut flags as *mut u32 as *mut c_void;
/// let len = mem::size_of_val(&flags);
/// let mut nmount = Nmount::new();
/// unsafe { nmount.mut_ptr_opt(&name, p, len) };
/// ```
pub unsafe fn mut_ptr_opt(
&mut self,
name: &'a CStr,
val: *mut c_void,
len: usize,
) -> &mut Self {
self.push_slice(name.to_bytes_with_nul(), false);
self.push_pointer_and_length(val.cast(), len, false);
self
}
/// Add a mount option that does not take a value.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::ffi::CString;
///
/// let read_only = CString::new("ro").unwrap();
/// Nmount::new()
/// .null_opt(&read_only);
/// ```
pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self {
self.push_slice(name.to_bytes_with_nul(), false);
self.push_slice(&[], false);
self
}
/// Add a mount option that does not take a value, but whose name must be
/// owned.
///
///
/// This has higher runtime cost than [`Nmount::null_opt`], but is useful
/// when the name's lifetime doesn't outlive the `Nmount`, or it's a
/// different string type than `CStr`.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
///
/// let read_only = "ro";
/// let mut nmount: Nmount<'static> = Nmount::new();
/// nmount.null_opt_owned(read_only);
/// ```
pub fn null_opt_owned<P: ?Sized + NixPath>(
&mut self,
name: &P,
) -> &mut Self {
self.push_nix_path(name);
self.push_slice(&[], false);
self
}
/// Add a mount option as a [`CStr`].
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::ffi::CString;
///
/// let fstype = CString::new("fstype").unwrap();
/// let nullfs = CString::new("nullfs").unwrap();
/// Nmount::new()
/// .str_opt(&fstype, &nullfs);
/// ```
pub fn str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self {
self.push_slice(name.to_bytes_with_nul(), false);
self.push_slice(val.to_bytes_with_nul(), false);
self
}
/// Add a mount option as an owned string.
///
/// This has higher runtime cost than [`Nmount::str_opt`], but is useful
/// when the value's lifetime doesn't outlive the `Nmount`, or it's a
/// different string type than `CStr`.
///
/// # Examples
/// ```
/// use nix::mount::Nmount;
/// use std::path::Path;
///
/// let mountpoint = Path::new("/mnt");
/// Nmount::new()
/// .str_opt_owned("fspath", mountpoint.to_str().unwrap());
/// ```
pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self
where
P1: ?Sized + NixPath,
P2: ?Sized + NixPath,
{
self.push_nix_path(name);
self.push_nix_path(val);
self
}
/// Create a new `Nmount` struct with no options
pub fn new() -> Self {
Self::default()
}
/// Actually mount the file system.
pub fn nmount(&mut self, flags: MntFlags) -> NmountResult {
const ERRMSG_NAME: &[u8] = b"errmsg\0";
let mut errmsg = vec![0u8; 255];
// nmount can return extra error information via a "errmsg" return
// argument.
self.push_slice(ERRMSG_NAME, false);
// SAFETY: we are pushing a mutable iovec here, so we can't use
// the above method
self.iov.push(libc::iovec {
iov_base: errmsg.as_mut_ptr().cast(),
iov_len: errmsg.len(),
});
let niov = self.iov.len() as c_uint;
let iovp = self.iov.as_mut_ptr();
let res = unsafe { libc::nmount(iovp, niov, flags.bits()) };
match Errno::result(res) {
Ok(_) => Ok(()),
Err(error) => {
let errmsg = if errmsg[0] == 0 {
None
} else {
CStr::from_bytes_until_nul(&errmsg[..]).ok()
};
Err(NmountError::new(error, errmsg))
}
}
}
}
#[cfg(target_os = "freebsd")]
impl<'a> Drop for Nmount<'a> {
fn drop(&mut self) {
for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) {
if *is_owned {
// Free the owned string. Safe because we recorded ownership,
// and Nmount does not implement Clone.
unsafe {
drop(CString::from_raw(iov.iov_base as *mut c_char));
}
}
}
}
}
/// Unmount the file system mounted at `mountpoint`.
///
/// Useful flags include
/// * `MNT_FORCE` - Unmount even if still in use.
#[cfg_attr(
target_os = "freebsd",
doc = "
* `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID
encoded as `FSID:val0:val1`, where `val0` and `val1`
are the contents of the `fsid_t val[]` array in decimal.
The file system that has the specified file system ID
will be unmounted. See
[`statfs`](crate::sys::statfs::statfs) to determine the
`fsid`.
"
)]
pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()>
where
P: ?Sized + NixPath,
{
let res = mountpoint.with_nix_path(|cstr| unsafe {
libc::unmount(cstr.as_ptr(), flags.bits())
})?;
Errno::result(res).map(drop)
}
use std::fs::{self, File};
use std::io::{Read, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
use libc::{EACCES, EROFS};
use nix::mount::{mount, umount, MsFlags};
use nix::sys::stat::{self, Mode};
use crate::*;
static SCRIPT_CONTENTS: &[u8] = b"#!/bin/sh
exit 23";
const EXPECTED_STATUS: i32 = 23;
const NONE: Option<&'static [u8]> = None;
#[test]
fn test_mount_tmpfs_without_flags_allows_rwx() {
require_capability!(
"test_mount_tmpfs_without_flags_allows_rwx",
CAP_SYS_ADMIN
);
let tempdir = tempfile::tempdir().unwrap();
mount(
NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::empty(),
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
let test_path = tempdir.path().join("test");
// Verify write.
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(&test_path)
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {e}"));
// Verify read.
let mut buf = Vec::new();
File::open(&test_path)
.and_then(|mut f| f.read_to_end(&mut buf))
.unwrap_or_else(|e| panic!("read failed: {e}"));
assert_eq!(buf, SCRIPT_CONTENTS);
// Verify execute.
assert_eq!(
EXPECTED_STATUS,
Command::new(&test_path)
.status()
.unwrap_or_else(|e| panic!("exec failed: {e}"))
.code()
.unwrap_or_else(|| panic!("child killed by signal"))
);
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
}
#[test]
fn test_mount_rdonly_disallows_write() {
require_capability!("test_mount_rdonly_disallows_write", CAP_SYS_ADMIN);
let tempdir = tempfile::tempdir().unwrap();
mount(
NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::MS_RDONLY,
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
// EROFS: Read-only file system
assert_eq!(
EROFS,
File::create(tempdir.path().join("test"))
.unwrap_err()
.raw_os_error()
.unwrap()
);
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
}
#[test]
fn test_mount_noexec_disallows_exec() {
require_capability!("test_mount_noexec_disallows_exec", CAP_SYS_ADMIN);
let tempdir = tempfile::tempdir().unwrap();
mount(
NONE,
tempdir.path(),
Some(b"tmpfs".as_ref()),
MsFlags::MS_NOEXEC,
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
let test_path = tempdir.path().join("test");
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(&test_path)
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {e}"));
// Verify that we cannot execute despite a+x permissions being set.
let mode = stat::Mode::from_bits_truncate(
fs::metadata(&test_path)
.map(|md| md.permissions().mode())
.unwrap_or_else(|e| panic!("metadata failed: {e}")),
);
assert!(
mode.contains(Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IXOTH),
"{:?} did not have execute permissions",
&test_path
);
// EACCES: Permission denied
assert_eq!(
EACCES,
Command::new(&test_path)
.status()
.unwrap_err()
.raw_os_error()
.unwrap()
);
umount(tempdir.path()).unwrap_or_else(|e| panic!("umount failed: {e}"));
}
#[test]
fn test_mount_bind() {
require_capability!("test_mount_bind", CAP_SYS_ADMIN);
let tempdir = tempfile::tempdir().unwrap();
let file_name = "test";
{
let mount_point = tempfile::tempdir().unwrap();
mount(
Some(tempdir.path()),
mount_point.path(),
NONE,
MsFlags::MS_BIND,
NONE,
)
.unwrap_or_else(|e| panic!("mount failed: {e}"));
fs::OpenOptions::new()
.create(true)
.write(true)
.mode((Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO).bits())
.open(mount_point.path().join(file_name))
.and_then(|mut f| f.write(SCRIPT_CONTENTS))
.unwrap_or_else(|e| panic!("write failed: {e}"));
umount(mount_point.path())
.unwrap_or_else(|e| panic!("umount failed: {e}"));
}
// Verify the file written in the mount shows up in source directory, even
// after unmounting.
let mut buf = Vec::new();
File::open(tempdir.path().join(file_name))
.and_then(|mut f| f.read_to_end(&mut buf))
.unwrap_or_else(|e| panic!("read failed: {e}"));
assert_eq!(buf, SCRIPT_CONTENTS);
}
use crate::*;
use nix::{
errno::Errno,
mount::{unmount, MntFlags, Nmount},
};
use std::{ffi::CString, fs::File, path::Path};
use tempfile::tempdir;
#[test]
fn ok() {
require_mount!("nullfs");
let mountpoint = tempdir().unwrap();
let target = tempdir().unwrap();
let _sentry = File::create(target.path().join("sentry")).unwrap();
let fstype = CString::new("fstype").unwrap();
let nullfs = CString::new("nullfs").unwrap();
Nmount::new()
.str_opt(&fstype, &nullfs)
.str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
.str_opt_owned("target", target.path().to_str().unwrap())
.nmount(MntFlags::empty())
.unwrap();
// Now check that the sentry is visible through the mountpoint
let exists = Path::exists(&mountpoint.path().join("sentry"));
// Cleanup the mountpoint before asserting
unmount(mountpoint.path(), MntFlags::empty()).unwrap();
assert!(exists);
}
#[test]
fn bad_fstype() {
let mountpoint = tempdir().unwrap();
let target = tempdir().unwrap();
let _sentry = File::create(target.path().join("sentry")).unwrap();
let e = Nmount::new()
.str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
.str_opt_owned("target", target.path().to_str().unwrap())
.nmount(MntFlags::empty())
.unwrap_err();
assert_eq!(e.error(), Errno::EINVAL);
assert_eq!(e.errmsg(), Some("Invalid fstype"));
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display