diff --git a/CHANGELOG.md b/CHANGELOG.md index f085067a07..548a651dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Added `F_KINFO` FcntlFlags entry on FreeBSD for `::nix::fcntl`. ([#2152](https://github.com/nix-rust/nix/pull/2152)) +- Added `mount::open_tree()` helper on Linux. + ([#1958](https://github.com/nix-rust/nix/pull/1958)) + ### Changed - The MSRV is now 1.69 diff --git a/src/mount/linux.rs b/src/mount/linux.rs index e987603786..b2c10569ed 100644 --- a/src/mount/linux.rs +++ b/src/mount/linux.rs @@ -1,6 +1,8 @@ use crate::errno::Errno; +use crate::fcntl::at_rawfd; use crate::{NixPath, Result}; -use libc::{self, c_int, c_ulong}; +use libc::{self, c_int, c_uint, c_ulong}; +use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}; libc_bitflags!( /// Used with [`mount`]. @@ -85,7 +87,7 @@ libc_bitflags!( MNT_DETACH; /// Mark the mount point as expired. MNT_EXPIRE; - /// Don't dereference `target` if it is a symlink. + /// Don't dereference `target` if it is a symlink. UMOUNT_NOFOLLOW; } ); @@ -161,3 +163,41 @@ pub fn umount2(target: &P, flags: MntFlags) -> Result<()> { Errno::result(res).map(drop) } + +libc_bitflags!( + /// Flags for [`open_tree()`]. + pub struct OpenTreeFlags: c_uint { + /// Directly query the mount attached to the file descriptor. + AT_EMPTY_PATH as c_uint; + /// Do not trigger an automount target. + AT_NO_AUTOMOUNT as c_uint; + /// When cloning, also clone nested mount subtrees. + AT_RECURSIVE as c_uint; + /// If target is a symbolic link, do not dereference it. + AT_SYMLINK_NOFOLLOW as c_uint; + /// Clone the mount object. + OPEN_TREE_CLONE as c_uint; + /// Set the close-on-exec flag for the returned file descriptor. + OPEN_TREE_CLOEXEC as c_uint; + } +); + +/// Find the mount object for the target path, and return it as a file-descriptor. +/// +/// The returned FD behaves in the same way as those opened via `O_PATH`. +pub fn open_tree( + dirfd: Option, + pathname: &P, + flags: OpenTreeFlags, +) -> Result { + let res = pathname.with_nix_path(|cstr| unsafe { + let raw_dirfd = at_rawfd(dirfd.map(|v| v.as_fd().as_raw_fd())); + libc::syscall( + libc::SYS_open_tree, + raw_dirfd, + cstr.as_ptr(), + flags.bits(), + ) + })?; + Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r as RawFd) }) +} diff --git a/test/test_mount.rs b/test/test_mount.rs index 5cf00408e8..20e9243513 100644 --- a/test/test_mount.rs +++ b/test/test_mount.rs @@ -11,12 +11,13 @@ mod test_mount { use std::io::{self, Read, Write}; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::PermissionsExt; + use std::os::unix::io::{IntoRawFd, OwnedFd}; use std::process::{self, Command}; use libc::{EACCES, EROFS}; use nix::errno::Errno; - use nix::mount::{mount, umount, MsFlags}; + use nix::mount::{mount, open_tree, umount, MsFlags, OpenTreeFlags}; use nix::sched::{unshare, CloneFlags}; use nix::sys::stat::{self, Mode}; use nix::unistd::getuid; @@ -203,6 +204,31 @@ exit 23"; assert_eq!(buf, SCRIPT_CONTENTS); } + pub(crate) fn test_open_tree() { + let tempdir = tempfile::tempdir().unwrap(); + let abs_path = tempdir.path(); + let res = open_tree( + Option::::None, + abs_path, + OpenTreeFlags::empty(), + ); + let mount_fd = match res { + Ok(fd) => fd, + Err(e) if e == Errno::ENOSYS => { + let stderr = io::stderr(); + let mut handle = stderr.lock(); + writeln!( + handle, + "Detected kernel without `open_tree` syscall, skipping test." + ) + .unwrap(); + return; + } + Err(e) => panic!("{}", e), + }; + nix::unistd::close(mount_fd.into_raw_fd()).unwrap() + } + pub fn setup_namespaces() { // Hold on to the uid in the parent namespace. let uid = getuid(); @@ -250,12 +276,13 @@ fn main() { use test_mount::{ setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec, test_mount_rdonly_disallows_write, - test_mount_tmpfs_without_flags_allows_rwx, + test_mount_tmpfs_without_flags_allows_rwx, test_open_tree, }; skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351"); setup_namespaces(); run_tests!( + test_open_tree, test_mount_tmpfs_without_flags_allows_rwx, test_mount_rdonly_disallows_write, test_mount_noexec_disallows_exec,