From 744d8948f3a78f4819e04185f3a8435de9a634ae Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 23 Jul 2024 13:09:37 -0400 Subject: [PATCH] dir: Add an `is_mountpoint` API Copy the code from https://github.com/ostreedev/ostree-rs-ext/pull/551 as it's better here. --- Cargo.toml | 1 + src/dirext.rs | 34 ++++++++++++++++++++++++++++++++++ tests/it/main.rs | 9 +++++++++ 3 files changed, 44 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 0d9fd9b..6d8e10f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ cap-primitives = "3" [target.'cfg(not(windows))'.dependencies] rustix = { version = "0.38", features = ["fs", "procfs", "process", "pipe"] } +libc = "0.2" [dev-dependencies] anyhow = "1.0" diff --git a/src/dirext.rs b/src/dirext.rs index c65f061..94670c1 100644 --- a/src/dirext.rs +++ b/src/dirext.rs @@ -119,6 +119,14 @@ pub trait CapStdExtDirExt { contents: impl AsRef<[u8]>, perms: cap_std::fs::Permissions, ) -> Result<()>; + + #[cfg(any(target_os = "android", target_os = "linux"))] + /// Returns `Some(true)` if the target is known to be a mountpoint, or + /// `Some(false)` if the target is definitively known not to be a mountpoint. + /// + /// In some scenarios (such as an older kernel) this currently may not be possible + /// to determine, and `None` will be returned in those cases. + fn is_mountpoint(&self, path: impl AsRef) -> Result>; } #[cfg(feature = "fs_utf8")] @@ -300,6 +308,28 @@ fn subdir_of<'d, 'p>(d: &'d Dir, p: &'p Path) -> io::Result<(DirOwnedOrBorrowed< Ok((r, name)) } +fn is_mountpoint_impl_statx(root: &Dir, path: &Path) -> Result> { + // https://github.com/systemd/systemd/blob/8fbf0a214e2fe474655b17a4b663122943b55db0/src/basic/mountpoint-util.c#L176 + use rustix::fs::{AtFlags, StatxFlags}; + use std::os::fd::AsFd; + + // SAFETY(unwrap): We can infallibly convert an i32 into a u64. + let mountroot_flag: u64 = libc::STATX_ATTR_MOUNT_ROOT.try_into().unwrap(); + match rustix::fs::statx( + root.as_fd(), + path, + AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW, + StatxFlags::empty(), + ) { + Ok(r) => { + let present = (r.stx_attributes_mask & mountroot_flag) > 0; + Ok(present.then_some(r.stx_attributes & mountroot_flag > 0)) + } + Err(e) if e == rustix::io::Errno::NOSYS => Ok(None), + Err(e) => Err(e.into()), + } +} + impl CapStdExtDirExt for Dir { fn open_optional(&self, path: impl AsRef) -> Result> { map_optional(self.open(path.as_ref())) @@ -452,6 +482,10 @@ impl CapStdExtDirExt for Dir { Ok(()) }) } + + fn is_mountpoint(&self, path: impl AsRef) -> Result> { + is_mountpoint_impl_statx(self, path.as_ref()).map_err(Into::into) + } } // Implementation for the Utf8 variant of Dir. You shouldn't need to add diff --git a/tests/it/main.rs b/tests/it/main.rs index f07c533..ac977ab 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -383,3 +383,12 @@ fn test_rootdir_entries() -> Result<()> { assert_eq!(ents.len(), 2); Ok(()) } + +#[test] +fn test_mountpoint() -> Result<()> { + let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?; + assert_eq!(root.is_mountpoint(".").unwrap(), Some(true)); + let td = &cap_tempfile::TempDir::new(cap_std::ambient_authority())?; + assert_eq!(td.is_mountpoint(".").unwrap(), Some(false)); + Ok(()) +}