From 0566dfc6effe441f5a0d80459bcc64653c012d83 Mon Sep 17 00:00:00 2001 From: cerdelen <95369756+cerdelen@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:38:14 +0100 Subject: [PATCH] rmdir: Remove all trailing slashes when checking for symlinks (#9983) * rmdir: Remove all trailing slashes when checking for symlinks rmdir: cargo fmt l * rmdir: Extract removal of trailing slashes to helper func * rmdir: Add regression test for removal of trailing slashes when checking for symlink * rmdir: add cfg flag to helper func which is only used on unix --- src/uu/rmdir/src/rmdir.rs | 14 ++++++++++++-- tests/by-util/test_rmdir.rs | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 4f13afcbf..e0c9f73bc 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -66,10 +66,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(path.metadata()?.file_type().is_dir()) } - let bytes = path.as_os_str().as_bytes(); + let mut bytes = path.as_os_str().as_bytes(); if error.raw_os_error() == Some(libc::ENOTDIR) && bytes.ends_with(b"/") { // Strip the trailing slash or .symlink_metadata() will follow the symlink - let no_slash: &Path = OsStr::from_bytes(&bytes[..bytes.len() - 1]).as_ref(); + bytes = strip_trailing_slashes_from_path(bytes); + let no_slash: &Path = OsStr::from_bytes(bytes).as_ref(); if no_slash.is_symlink() && points_to_directory(no_slash).unwrap_or(true) { show_error!( "{}", @@ -119,6 +120,15 @@ fn remove_single(path: &Path, opts: Opts) -> Result<(), Error<'_>> { remove_dir(path).map_err(|error| Error { error, path }) } +#[cfg(unix)] +fn strip_trailing_slashes_from_path(path: &[u8]) -> &[u8] { + let mut end = path.len(); + while end > 0 && path[end - 1] == b'/' { + end -= 1; + } + &path[..end] +} + // POSIX: https://pubs.opengroup.org/onlinepubs/009696799/functions/rmdir.html #[cfg(not(windows))] const NOT_EMPTY_CODES: &[i32] = &[libc::ENOTEMPTY, libc::EEXIST]; diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 0c52a2287..669884488 100644 --- a/tests/by-util/test_rmdir.rs +++ b/tests/by-util/test_rmdir.rs @@ -243,3 +243,18 @@ fn test_rmdir_remove_symlink_dangling() { .fails() .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed\n"); } + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_rmdir_remove_symlink_dir_with_trailing_slashes() { + // a symlink with trailing slashes should still be printing the 'Symbolic link not followed' + // message + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.symlink_dir("dir", "dl"); + + ucmd.arg("dl////") + .fails() + .stderr_is("rmdir: failed to remove 'dl////': Symbolic link not followed\n"); +}