Skip to content

Commit ef4f95c

Browse files
authored
Merge pull request #56 from cgwalters/add-mountpoint
dir: Add an `is_mountpoint` API
2 parents b81c502 + 744d894 commit ef4f95c

File tree

3 files changed

+44
-0
lines changed

3 files changed

+44
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ cap-primitives = "3"
1616

1717
[target.'cfg(not(windows))'.dependencies]
1818
rustix = { version = "0.38", features = ["fs", "procfs", "process", "pipe"] }
19+
libc = "0.2"
1920

2021
[dev-dependencies]
2122
anyhow = "1.0"

src/dirext.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ pub trait CapStdExtDirExt {
119119
contents: impl AsRef<[u8]>,
120120
perms: cap_std::fs::Permissions,
121121
) -> Result<()>;
122+
123+
#[cfg(any(target_os = "android", target_os = "linux"))]
124+
/// Returns `Some(true)` if the target is known to be a mountpoint, or
125+
/// `Some(false)` if the target is definitively known not to be a mountpoint.
126+
///
127+
/// In some scenarios (such as an older kernel) this currently may not be possible
128+
/// to determine, and `None` will be returned in those cases.
129+
fn is_mountpoint(&self, path: impl AsRef<Path>) -> Result<Option<bool>>;
122130
}
123131

124132
#[cfg(feature = "fs_utf8")]
@@ -300,6 +308,28 @@ fn subdir_of<'d, 'p>(d: &'d Dir, p: &'p Path) -> io::Result<(DirOwnedOrBorrowed<
300308
Ok((r, name))
301309
}
302310

311+
fn is_mountpoint_impl_statx(root: &Dir, path: &Path) -> Result<Option<bool>> {
312+
// https://github.com/systemd/systemd/blob/8fbf0a214e2fe474655b17a4b663122943b55db0/src/basic/mountpoint-util.c#L176
313+
use rustix::fs::{AtFlags, StatxFlags};
314+
use std::os::fd::AsFd;
315+
316+
// SAFETY(unwrap): We can infallibly convert an i32 into a u64.
317+
let mountroot_flag: u64 = libc::STATX_ATTR_MOUNT_ROOT.try_into().unwrap();
318+
match rustix::fs::statx(
319+
root.as_fd(),
320+
path,
321+
AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW,
322+
StatxFlags::empty(),
323+
) {
324+
Ok(r) => {
325+
let present = (r.stx_attributes_mask & mountroot_flag) > 0;
326+
Ok(present.then_some(r.stx_attributes & mountroot_flag > 0))
327+
}
328+
Err(e) if e == rustix::io::Errno::NOSYS => Ok(None),
329+
Err(e) => Err(e.into()),
330+
}
331+
}
332+
303333
impl CapStdExtDirExt for Dir {
304334
fn open_optional(&self, path: impl AsRef<Path>) -> Result<Option<File>> {
305335
map_optional(self.open(path.as_ref()))
@@ -452,6 +482,10 @@ impl CapStdExtDirExt for Dir {
452482
Ok(())
453483
})
454484
}
485+
486+
fn is_mountpoint(&self, path: impl AsRef<Path>) -> Result<Option<bool>> {
487+
is_mountpoint_impl_statx(self, path.as_ref()).map_err(Into::into)
488+
}
455489
}
456490

457491
// Implementation for the Utf8 variant of Dir. You shouldn't need to add

tests/it/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,12 @@ fn test_rootdir_entries() -> Result<()> {
383383
assert_eq!(ents.len(), 2);
384384
Ok(())
385385
}
386+
387+
#[test]
388+
fn test_mountpoint() -> Result<()> {
389+
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
390+
assert_eq!(root.is_mountpoint(".").unwrap(), Some(true));
391+
let td = &cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
392+
assert_eq!(td.is_mountpoint(".").unwrap(), Some(false));
393+
Ok(())
394+
}

0 commit comments

Comments
 (0)