Skip to content

Commit aae0d32

Browse files
committed
mount/linux: add a safe wrapper for open_tree(2)
This adds an ergonomic wrapper for the Linux-specific `open_tree(2)` syscall, using safe-io file descriptors. The syscall has been introduced in kernel version 5.2 and allows FD-based manipulation of mount hierarchies.
1 parent 197250c commit aae0d32

File tree

3 files changed

+58
-3
lines changed

3 files changed

+58
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1111
(#[1662](https://github.com/nix-rust/nix/pull/1662))
1212
- Added `CanRaw` to `SockProtocol` and `CanBcm` as a separate `SocProtocol` constant.
1313
([#1912](https://github.com/nix-rust/nix/pull/1912))
14+
- Added `mount::open_tree()` helper on Linux.
15+
([#1958](https://github.com/nix-rust/nix/pull/1958))
1416

1517
### Changed
1618

src/mount/linux.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#![allow(missing_docs)]
2+
use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd};
3+
24
use crate::errno::Errno;
35
use crate::{NixPath, Result};
4-
use libc::{self, c_int, c_ulong};
6+
use libc::{self, c_int, c_uint, c_ulong};
57

68
libc_bitflags!(
79
pub struct MsFlags: c_ulong {
@@ -120,3 +122,27 @@ pub fn umount2<P: ?Sized + NixPath>(target: &P, flags: MntFlags) -> Result<()> {
120122

121123
Errno::result(res).map(drop)
122124
}
125+
126+
libc_bitflags!(
127+
/// Flags for [`open_tree()`].
128+
pub struct OpenTreeFlags: c_uint {
129+
AT_EMPTY_PATH as c_uint;
130+
AT_NO_AUTOMOUNT as c_uint;
131+
AT_SYMLINK_NOFOLLOW as c_uint;
132+
}
133+
);
134+
135+
/// Find the mount object for the target path, and return it as a file-descriptor.
136+
///
137+
/// The returned FD behaves in the same way as those opened via `O_PATH`.
138+
pub fn open_tree<Fd: AsFd, P: ?Sized + NixPath>(
139+
dirfd: Option<Fd>,
140+
pathname: &P,
141+
flags: OpenTreeFlags,
142+
) -> Result<OwnedFd> {
143+
let res = pathname.with_nix_path(|cstr| unsafe {
144+
let fd = dirfd.map(|v| v.as_fd().as_raw_fd()).unwrap_or(-1);
145+
libc::syscall(libc::SYS_open_tree, fd, cstr.as_ptr(), flags.bits())
146+
})?;
147+
Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r as RawFd) })
148+
}

test/test_mount.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ mod test_mount {
1111
use std::io::{self, Read, Write};
1212
use std::os::unix::fs::OpenOptionsExt;
1313
use std::os::unix::fs::PermissionsExt;
14+
use std::os::unix::io::{IntoRawFd, OwnedFd};
1415
use std::process::{self, Command};
1516

1617
use libc::{EACCES, EROFS};
1718

1819
use nix::errno::Errno;
19-
use nix::mount::{mount, umount, MsFlags};
20+
use nix::mount::{mount, open_tree, umount, MsFlags, OpenTreeFlags};
2021
use nix::sched::{unshare, CloneFlags};
2122
use nix::sys::stat::{self, Mode};
2223
use nix::unistd::getuid;
@@ -203,6 +204,31 @@ exit 23";
203204
assert_eq!(buf, SCRIPT_CONTENTS);
204205
}
205206

207+
pub(crate) fn test_open_tree() {
208+
let tempdir = tempfile::tempdir().unwrap();
209+
let abs_path = tempdir.path();
210+
let res = open_tree(
211+
Option::<OwnedFd>::None,
212+
abs_path,
213+
OpenTreeFlags::empty(),
214+
);
215+
let mount_fd = match res {
216+
Ok(fd) => fd,
217+
Err(e) if e == Errno::ENOSYS => {
218+
let stderr = io::stderr();
219+
let mut handle = stderr.lock();
220+
writeln!(
221+
handle,
222+
"Detected kernel without `open_tree` syscall, skipping test."
223+
)
224+
.unwrap();
225+
return;
226+
}
227+
Err(e) => panic!("{}", e),
228+
};
229+
nix::unistd::close(mount_fd.into_raw_fd()).unwrap()
230+
}
231+
206232
pub fn setup_namespaces() {
207233
// Hold on to the uid in the parent namespace.
208234
let uid = getuid();
@@ -250,12 +276,13 @@ fn main() {
250276
use test_mount::{
251277
setup_namespaces, test_mount_bind, test_mount_noexec_disallows_exec,
252278
test_mount_rdonly_disallows_write,
253-
test_mount_tmpfs_without_flags_allows_rwx,
279+
test_mount_tmpfs_without_flags_allows_rwx, test_open_tree,
254280
};
255281
skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1351");
256282
setup_namespaces();
257283

258284
run_tests!(
285+
test_open_tree,
259286
test_mount_tmpfs_without_flags_allows_rwx,
260287
test_mount_rdonly_disallows_write,
261288
test_mount_noexec_disallows_exec,

0 commit comments

Comments
 (0)