Skip to content

Commit cb75846

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 0f4feed commit cb75846

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1313
([#1912](https://github.com/nix-rust/nix/pull/1912))
1414
- Added `mq_timedreceive` to `::nix::mqueue`.
1515
([#1966])(https://github.com/nix-rust/nix/pull/1966)
16+
- Added `mount::open_tree()` helper on Linux.
17+
([#1958](https://github.com/nix-rust/nix/pull/1958))
1618

1719
### Changed
1820

src/mount/linux.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd};
12
use crate::errno::Errno;
23
use crate::{NixPath, Result};
3-
use libc::{self, c_int, c_ulong};
4+
use libc::{self, c_int, c_uint, c_ulong};
45

56
libc_bitflags!(
67
/// Used with [`mount`].
@@ -161,3 +162,36 @@ pub fn umount2<P: ?Sized + NixPath>(target: &P, flags: MntFlags) -> Result<()> {
161162

162163
Errno::result(res).map(drop)
163164
}
165+
166+
libc_bitflags!(
167+
/// Flags for [`open_tree()`].
168+
pub struct OpenTreeFlags: c_uint {
169+
/// Directly query the mount attached to the file descriptor.
170+
AT_EMPTY_PATH as c_uint;
171+
/// Do not trigger an automount target.
172+
AT_NO_AUTOMOUNT as c_uint;
173+
/// When cloning, also clone nested mount subtrees.
174+
AT_RECURSIVE as c_uint;
175+
/// If target is a symbolic link, do not dereference it.
176+
AT_SYMLINK_NOFOLLOW as c_uint;
177+
/// Clone the mount object.
178+
OPEN_TREE_CLONE as c_uint;
179+
/// Set the close-on-exec flag for the returned file descriptor.
180+
OPEN_TREE_CLOEXEC as c_uint;
181+
}
182+
);
183+
184+
/// Find the mount object for the target path, and return it as a file-descriptor.
185+
///
186+
/// The returned FD behaves in the same way as those opened via `O_PATH`.
187+
pub fn open_tree<Fd: AsFd, P: ?Sized + NixPath>(
188+
dirfd: Option<Fd>,
189+
pathname: &P,
190+
flags: OpenTreeFlags,
191+
) -> Result<OwnedFd> {
192+
let res = pathname.with_nix_path(|cstr| unsafe {
193+
let fd = dirfd.map(|v| v.as_fd().as_raw_fd()).unwrap_or(-1);
194+
libc::syscall(libc::SYS_open_tree, fd, cstr.as_ptr(), flags.bits())
195+
})?;
196+
Errno::result(res).map(|r| unsafe { OwnedFd::from_raw_fd(r as RawFd) })
197+
}

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)