From acb794a5e1b3557f2edfc348ad53d76480ee4fbc Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Mon, 8 Dec 2025 20:07:34 +0000 Subject: [PATCH 1/4] Adding sed example for overriding the LD when not the same function is used --- src/uu/rm/locales/en-US.ftl | 1 + src/uu/rm/src/platform/unix.rs | 11 ++++++++++- src/uucore/src/lib/features/safe_traversal.rs | 14 ++++++++++++++ util/build-gnu.sh | 17 ++++++++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/uu/rm/locales/en-US.ftl b/src/uu/rm/locales/en-US.ftl index 2d4486ce2fe..f0604d20573 100644 --- a/src/uu/rm/locales/en-US.ftl +++ b/src/uu/rm/locales/en-US.ftl @@ -44,6 +44,7 @@ rm-error-use-no-preserve-root = use --no-preserve-root to override this failsafe rm-error-refusing-to-remove-directory = refusing to remove '.' or '..' directory: skipping {$path} rm-error-cannot-remove = cannot remove {$file} rm-error-may-not-abbreviate-no-preserve-root = you may not abbreviate the --no-preserve-root option +rm-error-traversal-failed = traversal failed: {$path} # Verbose messages rm-verbose-removed = removed {$file} diff --git a/src/uu/rm/src/platform/unix.rs b/src/uu/rm/src/platform/unix.rs index e890ab15823..6a400377e19 100644 --- a/src/uu/rm/src/platform/unix.rs +++ b/src/uu/rm/src/platform/unix.rs @@ -16,7 +16,7 @@ use std::path::Path; use uucore::display::Quotable; use uucore::error::FromIo; use uucore::prompt_yes; -use uucore::safe_traversal::DirFd; +use uucore::safe_traversal::{DirFd, clear_errno, take_errno}; use uucore::show_error; use uucore::translate; @@ -348,6 +348,7 @@ pub fn safe_remove_dir_recursive( #[cfg(not(target_os = "redox"))] pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options) -> bool { // Read directory entries using safe traversal + clear_errno(); let entries = match dir_fd.read_dir() { Ok(entries) => entries, Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { @@ -361,6 +362,14 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt } }; + // Check if readdir failed partway through (partial read) + if let Some(err) = take_errno() { + if !entries.is_empty() { + show_error!("{}: {}", translate!("rm-error-traversal-failed", "path" => path.display()), err); + return true; + } + } + let mut error = false; // Process each entry diff --git a/src/uucore/src/lib/features/safe_traversal.rs b/src/uucore/src/lib/features/safe_traversal.rs index 3b1ac70678f..8a467a420e1 100644 --- a/src/uucore/src/lib/features/safe_traversal.rs +++ b/src/uucore/src/lib/features/safe_traversal.rs @@ -21,6 +21,7 @@ use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use std::path::{Path, PathBuf}; use nix::dir::Dir; +use nix::errno::Errno; use nix::fcntl::{OFlag, openat}; use nix::libc; use nix::sys::stat::{FchmodatFlags, FileStat, Mode, fchmodat, fstatat}; @@ -79,6 +80,19 @@ impl From for io::Error { } } +/// Clear errno and return any error that was set after an operation +/// This is used because the nix library does not propogate folder reading errors correctly +pub fn take_errno() -> Option { + let errno = Errno::last(); + Errno::clear(); + (errno != Errno::from_raw(0)).then(|| io::Error::from_raw_os_error(errno as i32)) +} + +/// Clear errno before an operation, required to read error messages not propogated by nix from reading folders +pub fn clear_errno() { + Errno::clear(); +} + // Helper function to read directory entries using nix fn read_dir_entries(fd: &OwnedFd) -> io::Result> { let mut entries = Vec::new(); diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 7a9f45c385b..51cf4dbadd2 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -362,4 +362,19 @@ sed -i 's/echo "changing security context/echo "chcon: changing security context # Disable this test, it is not relevant for us: # * the selinux crate is handling errors # * the test says "maybe we should not fail when no context available" -sed -i -e "s|returns_ 1||g" tests/cp/no-ctx.sh +"${SED}" -i -e "s|returns_ 1||g" tests/cp/no-ctx.sh + +# The rm-readdir-fail.sh test hooks readdir() but uutils rm uses nix library which calls readdir64_r(). +# readdir64_r has a different signature: int readdir64_r(DIR*, struct dirent64*, struct dirent64**) +"${SED}" -i \ + -e 's/struct dirent \*readdir (DIR \*dirp)/int readdir64_r(DIR *dirp, struct dirent64 *entry, struct dirent64 **result)/' \ + -e 's/struct dirent \*(\*real_readdir)(DIR \*dirp)/int (*real_func)(DIR *, struct dirent64 *, struct dirent64 **)/' \ + -e 's/real_readdir/real_func/g' \ + -e 's/dlsym (RTLD_NEXT, "readdir")/dlsym(RTLD_NEXT, "readdir64_r")/' \ + -e 's/struct dirent\* d;/int ret;/' \ + -e 's/! (d = real_func (dirp))/(ret = real_func(dirp, entry, result)) != 0 || !*result/' \ + -e 's/d->d_name/entry->d_name/g' \ + -e 's/d->d_namlen/entry->d_namlen/g' \ + -e 's/return d;/return 0;/' \ + -e 's/return NULL;/*result = NULL; return EIO;/' \ + tests/rm/rm-readdir-fail.sh From cd08eb09f437c56c10c35b179d2a203fbb38bb5b Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Mon, 8 Dec 2025 20:19:28 +0000 Subject: [PATCH 2/4] Cargo fmt --- src/uu/rm/src/platform/unix.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/src/platform/unix.rs b/src/uu/rm/src/platform/unix.rs index 6a400377e19..72e720f98df 100644 --- a/src/uu/rm/src/platform/unix.rs +++ b/src/uu/rm/src/platform/unix.rs @@ -365,7 +365,11 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt // Check if readdir failed partway through (partial read) if let Some(err) = take_errno() { if !entries.is_empty() { - show_error!("{}: {}", translate!("rm-error-traversal-failed", "path" => path.display()), err); + show_error!( + "{}: {}", + translate!("rm-error-traversal-failed", "path" => path.display()), + err + ); return true; } } From 5afa1b0fe30077b5643ec44ba421feda7636aa24 Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Mon, 8 Dec 2025 20:35:29 +0000 Subject: [PATCH 3/4] Simplified the approach by making a wrapper and did spelling fixes --- src/uucore/src/lib/features/safe_traversal.rs | 4 +-- util/build-gnu.sh | 25 ++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/uucore/src/lib/features/safe_traversal.rs b/src/uucore/src/lib/features/safe_traversal.rs index 8a467a420e1..34abb3be74f 100644 --- a/src/uucore/src/lib/features/safe_traversal.rs +++ b/src/uucore/src/lib/features/safe_traversal.rs @@ -81,14 +81,14 @@ impl From for io::Error { } /// Clear errno and return any error that was set after an operation -/// This is used because the nix library does not propogate folder reading errors correctly +/// This is used because the nix library does not propagate folder reading errors correctly pub fn take_errno() -> Option { let errno = Errno::last(); Errno::clear(); (errno != Errno::from_raw(0)).then(|| io::Error::from_raw_os_error(errno as i32)) } -/// Clear errno before an operation, required to read error messages not propogated by nix from reading folders +/// Clear errno before an operation, required to read error messages not propagated by nix from reading folders pub fn clear_errno() { Errno::clear(); } diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 51cf4dbadd2..c8faff622a3 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -5,6 +5,7 @@ # spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW # spell-checker:ignore baddecode submodules xstrtol distros ; (vars/env) SRCDIR vdir rcexp xpart dired OSTYPE ; (utils) greadlink gsed multihardlink texinfo CARGOFLAGS # spell-checker:ignore openat TOCTOU CFLAGS tmpfs gnproc +# spell-checker:ignore hfsplus casefold chattr dirp set -e @@ -364,17 +365,13 @@ sed -i 's/echo "changing security context/echo "chcon: changing security context # * the test says "maybe we should not fail when no context available" "${SED}" -i -e "s|returns_ 1||g" tests/cp/no-ctx.sh -# The rm-readdir-fail.sh test hooks readdir() but uutils rm uses nix library which calls readdir64_r(). -# readdir64_r has a different signature: int readdir64_r(DIR*, struct dirent64*, struct dirent64**) -"${SED}" -i \ - -e 's/struct dirent \*readdir (DIR \*dirp)/int readdir64_r(DIR *dirp, struct dirent64 *entry, struct dirent64 **result)/' \ - -e 's/struct dirent \*(\*real_readdir)(DIR \*dirp)/int (*real_func)(DIR *, struct dirent64 *, struct dirent64 **)/' \ - -e 's/real_readdir/real_func/g' \ - -e 's/dlsym (RTLD_NEXT, "readdir")/dlsym(RTLD_NEXT, "readdir64_r")/' \ - -e 's/struct dirent\* d;/int ret;/' \ - -e 's/! (d = real_func (dirp))/(ret = real_func(dirp, entry, result)) != 0 || !*result/' \ - -e 's/d->d_name/entry->d_name/g' \ - -e 's/d->d_namlen/entry->d_namlen/g' \ - -e 's/return d;/return 0;/' \ - -e 's/return NULL;/*result = NULL; return EIO;/' \ - tests/rm/rm-readdir-fail.sh +# uutils rm uses nix which calls readdir64_r, so add a wrapper that delegates to the readdir hook +"${SED}" -i '/^struct dirent \*readdir/i\ +int readdir64_r(DIR *dirp, struct dirent64 *entry, struct dirent64 **result) {\ + struct dirent *d = readdir(dirp);\ + if (!d) { *result = NULL; return errno ? EIO : 0; }\ + memcpy(entry, d, sizeof(*d));\ + *result = entry;\ + return 0;\ +} +' tests/rm/rm-readdir-fail.sh From 5ae2096622a54b304665b7f0b1bbc6049c1f9bab Mon Sep 17 00:00:00 2001 From: Chris Dryden Date: Mon, 8 Dec 2025 17:06:00 -0500 Subject: [PATCH 4/4] Update build-gnu.sh --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index c8faff622a3..616d026523a 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -5,7 +5,7 @@ # spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW # spell-checker:ignore baddecode submodules xstrtol distros ; (vars/env) SRCDIR vdir rcexp xpart dired OSTYPE ; (utils) greadlink gsed multihardlink texinfo CARGOFLAGS # spell-checker:ignore openat TOCTOU CFLAGS tmpfs gnproc -# spell-checker:ignore hfsplus casefold chattr dirp +# spell-checker:ignore hfsplus casefold chattr dirp memcpy set -e