From a991f430a78607987ae3a8a5ce35524451d4ba86 Mon Sep 17 00:00:00 2001 From: favilances Date: Sat, 9 May 2026 22:18:53 +0300 Subject: [PATCH] mountpoint: check the target path parent mountpoint determines whether a path is a mount point by comparing it with its parent. The implementation was checking the process working directory's parent instead, which could make regular directories look like mount points. Use the target path parent for the comparison and return util-linux's non-mountpoint exit status so this behavior is covered by a focused regression test. Signed-off-by: favilances --- src/uu/mountpoint/src/mountpoint.rs | 28 +++++++++++++--------------- tests/by-util/test_mountpoint.rs | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/uu/mountpoint/src/mountpoint.rs b/src/uu/mountpoint/src/mountpoint.rs index 6186d908..9014511b 100644 --- a/src/uu/mountpoint/src/mountpoint.rs +++ b/src/uu/mountpoint/src/mountpoint.rs @@ -5,11 +5,13 @@ use clap::Arg; use clap::{crate_version, Command}; -use std::env; #[cfg(not(windows))] use std::fs; +use std::io; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; +#[cfg(not(windows))] +use std::path::Path; use std::process; use uucore::{error::UResult, format_usage, help_about, help_usage}; @@ -22,10 +24,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let path = matches.get_one::("path"); if let Some(path) = path { - if is_mountpoint(path) { + if is_mountpoint(path)? { println!("{path} is a mountpoint"); } else { println!("{path} is not a mountpoint"); + process::exit(32); } } else { // Handle the case where path is not provided @@ -36,27 +39,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[cfg(not(windows))] -fn is_mountpoint(path: &str) -> bool { - let metadata = match fs::metadata(path) { - Ok(metadata) => metadata, - Err(_) => return false, - }; +fn is_mountpoint(path: &str) -> io::Result { + let path = Path::new(path); + let metadata = fs::metadata(path)?; let dev = metadata.dev(); let inode = metadata.ino(); + let parent = path.parent().filter(|p| !p.as_os_str().is_empty()); + let parent_metadata = fs::metadata(parent.unwrap_or_else(|| Path::new(".")))?; - // Root inode (typically 2 in most Unix filesystems) indicates a mount point - inode == 2 - || match fs::metadata("..") { - Ok(parent_metadata) => parent_metadata.dev() != dev, - Err(_) => false, - } + Ok(parent_metadata.dev() != dev || parent_metadata.ino() == inode) } // TODO: implement for windows #[cfg(windows)] -fn is_mountpoint(_path: &str) -> bool { - false +fn is_mountpoint(_path: &str) -> io::Result { + Ok(false) } pub fn uu_app() -> Command { diff --git a/tests/by-util/test_mountpoint.rs b/tests/by-util/test_mountpoint.rs index ef8729f8..4970e26f 100644 --- a/tests/by-util/test_mountpoint.rs +++ b/tests/by-util/test_mountpoint.rs @@ -3,9 +3,25 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use uutests::new_ucmd; +use uutests::{at_and_ucmd, new_ucmd}; #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } + +#[cfg(target_os = "linux")] +#[test] +fn test_non_mountpoint_uses_path_parent() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("not-a-mountpoint"); + + let path = at.plus_as_string("not-a-mountpoint"); + + ucmd.current_dir("/proc/self") + .arg(&path) + .fails() + .code_is(32) + .stdout_is(format!("{path} is not a mountpoint\n")) + .no_stderr(); +}