Skip to content

Commit b465bf6

Browse files
authored
Merge pull request #62 from cgwalters/noxdev
dirext: Add open_dir_noxdev
2 parents 8737440 + 7868617 commit b465bf6

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

src/dirext.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1111
use cap_std::fs::{Dir, File, Metadata};
1212
use cap_tempfile::cap_std;
13+
use rustix::path::Arg;
1314
use std::ffi::OsStr;
1415
use std::io::Result;
1516
use std::io::{self, Write};
@@ -36,6 +37,10 @@ pub trait CapStdExtDirExt {
3637
#[cfg(any(target_os = "android", target_os = "linux"))]
3738
fn open_dir_rooted_ext(&self, path: impl AsRef<Path>) -> Result<crate::RootDir>;
3839

40+
/// Open the target directory, but return Ok(None) if this would cross a mount point.
41+
#[cfg(any(target_os = "android", target_os = "linux"))]
42+
fn open_dir_noxdev(&self, path: impl AsRef<Path>) -> Result<Option<Dir>>;
43+
3944
/// Create the target directory, but do nothing if a directory already exists at that path.
4045
/// The return value will be `true` if the directory was created. An error will be
4146
/// returned if the path is a non-directory. Symbolic links will be followed.
@@ -308,6 +313,33 @@ fn subdir_of<'d, 'p>(d: &'d Dir, p: &'p Path) -> io::Result<(DirOwnedOrBorrowed<
308313
Ok((r, name))
309314
}
310315

316+
/// A thin wrapper for [`openat2`] but that retries on interruption.
317+
fn openat2_with_retry(
318+
dirfd: impl std::os::fd::AsFd,
319+
path: impl AsRef<Path>,
320+
oflags: rustix::fs::OFlags,
321+
mode: rustix::fs::Mode,
322+
resolve: rustix::fs::ResolveFlags,
323+
) -> rustix::io::Result<std::os::fd::OwnedFd> {
324+
let dirfd = dirfd.as_fd();
325+
let path = path.as_ref();
326+
// We loop forever on EAGAIN right now. The cap-std version loops just 4 times,
327+
// which seems really arbitrary.
328+
path.into_with_c_str(|path_c_str| 'start: loop {
329+
match rustix::fs::openat2(dirfd, path_c_str, oflags, mode, resolve) {
330+
Ok(file) => {
331+
return Ok(file);
332+
}
333+
Err(rustix::io::Errno::AGAIN | rustix::io::Errno::INTR) => {
334+
continue 'start;
335+
}
336+
Err(e) => {
337+
return Err(e);
338+
}
339+
}
340+
})
341+
}
342+
311343
fn is_mountpoint_impl_statx(root: &Dir, path: &Path) -> Result<Option<bool>> {
312344
// https://github.com/systemd/systemd/blob/8fbf0a214e2fe474655b17a4b663122943b55db0/src/basic/mountpoint-util.c#L176
313345
use rustix::fs::{AtFlags, StatxFlags};
@@ -344,6 +376,23 @@ impl CapStdExtDirExt for Dir {
344376
crate::RootDir::new(self, path)
345377
}
346378

379+
/// Open the target directory, but return Ok(None) if this would cross a mount point.
380+
#[cfg(any(target_os = "android", target_os = "linux"))]
381+
fn open_dir_noxdev(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<Option<Dir>> {
382+
use rustix::fs::{Mode, OFlags, ResolveFlags};
383+
match openat2_with_retry(
384+
self,
385+
path,
386+
OFlags::CLOEXEC | OFlags::DIRECTORY | OFlags::NOFOLLOW,
387+
Mode::empty(),
388+
ResolveFlags::NO_XDEV | ResolveFlags::BENEATH,
389+
) {
390+
Ok(r) => Ok(Some(Dir::reopen_dir(&r)?)),
391+
Err(e) if e == rustix::io::Errno::XDEV => Ok(None),
392+
Err(e) => Err(e.into()),
393+
}
394+
}
395+
347396
fn ensure_dir_with(
348397
&self,
349398
p: impl AsRef<Path>,

tests/it/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,3 +392,15 @@ fn test_mountpoint() -> Result<()> {
392392
assert_eq!(td.is_mountpoint(".").unwrap(), Some(false));
393393
Ok(())
394394
}
395+
396+
#[test]
397+
#[cfg(any(target_os = "android", target_os = "linux"))]
398+
fn test_open_noxdev() -> Result<()> {
399+
let root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
400+
// This hard requires the host setup to have /usr/bin on the same filesystem as /
401+
let usr = Dir::open_ambient_dir("/usr", cap_std::ambient_authority())?;
402+
assert!(usr.open_dir_noxdev("bin").unwrap().is_some());
403+
// Requires a mounted /proc, but that also seems ane.
404+
assert!(root.open_dir_noxdev("proc").unwrap().is_none());
405+
Ok(())
406+
}

0 commit comments

Comments
 (0)