From 2b4b6df9f6568395ffc8a1ac450a8865d668f27a Mon Sep 17 00:00:00 2001 From: cerdelen Date: Fri, 2 Jan 2026 11:13:15 +0100 Subject: [PATCH 1/4] rmdir: Remove all trailing slashes when checking for symlinks rmdir: cargo fmt l --- src/uu/rmdir/src/rmdir.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 4f13afcbf83..72d577e0473 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -66,10 +66,13 @@ 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(); + while bytes.ends_with(b"/") { + bytes = &bytes[..bytes.len() - 1]; + } + 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!( "{}", From 7f0fbd505e1ca69eda6776ec8165cd74cbb57824 Mon Sep 17 00:00:00 2001 From: cerdelen Date: Fri, 2 Jan 2026 14:40:12 +0100 Subject: [PATCH 2/4] rmdir: Extract removal of trailing slashes to helper func --- src/uu/rmdir/src/rmdir.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 72d577e0473..b6e6ea29748 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -69,9 +69,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { 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 - while bytes.ends_with(b"/") { - bytes = &bytes[..bytes.len() - 1]; - } + 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!( @@ -122,6 +120,14 @@ fn remove_single(path: &Path, opts: Opts) -> Result<(), Error<'_>> { remove_dir(path).map_err(|error| Error { error, path }) } +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]; From df6288f370c11836ae6c1dd5678c710138968df2 Mon Sep 17 00:00:00 2001 From: cerdelen Date: Fri, 2 Jan 2026 14:40:43 +0100 Subject: [PATCH 3/4] rmdir: Add regression test for removal of trailing slashes when checking for symlink --- tests/by-util/test_rmdir.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 0c52a22878e..669884488a0 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"); +} From 984b7a3867b5ff00fecfc7cd41e40be0379a67df Mon Sep 17 00:00:00 2001 From: cerdelen Date: Fri, 2 Jan 2026 15:38:19 +0100 Subject: [PATCH 4/4] rmdir: add cfg flag to helper func which is only used on unix --- src/uu/rmdir/src/rmdir.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index b6e6ea29748..e0c9f73bcec 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -120,6 +120,7 @@ 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'/' {