From 847a2bca4774e66db2ebc05371b7f1d851008730 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/linux.rs | 11 ++++++++++- src/uucore/src/lib/features/safe_traversal.rs | 14 ++++++++++++++ util/build-gnu.sh | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/locales/en-US.ftl b/src/uu/rm/locales/en-US.ftl index 12816693e69..4705f849d88 100644 --- a/src/uu/rm/locales/en-US.ftl +++ b/src/uu/rm/locales/en-US.ftl @@ -43,6 +43,7 @@ rm-error-dangerous-recursive-operation = it is dangerous to operate recursively 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-traversal-failed = traversal failed: {$path} # Verbose messages rm-verbose-removed = removed {$file} diff --git a/src/uu/rm/src/platform/linux.rs b/src/uu/rm/src/platform/linux.rs index 3e29bf85e7f..7be293bf58a 100644 --- a/src/uu/rm/src/platform/linux.rs +++ b/src/uu/rm/src/platform/linux.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; @@ -347,6 +347,7 @@ pub fn safe_remove_dir_recursive( 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 => { @@ -360,6 +361,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 6574910a95d..c6e5244d66a 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 15c72720fc8..ceb6b8f2ed3 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -357,3 +357,18 @@ test \$n_stat1 -ge \$n_stat2 \\' tests/ls/stat-free-color.sh # * 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 + +# 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 0611e8cc6c1d4f9ff26a0df9225628cd9ee3f15b 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/linux.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/src/platform/linux.rs b/src/uu/rm/src/platform/linux.rs index 7be293bf58a..7614b856460 100644 --- a/src/uu/rm/src/platform/linux.rs +++ b/src/uu/rm/src/platform/linux.rs @@ -364,7 +364,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 a1176ff5ed1673bc7740c4acd73a9766a7862cc1 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 c6e5244d66a..4f88baafd26 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 ceb6b8f2ed3..3a2f3a22ceb 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 @@ -358,17 +359,13 @@ test \$n_stat1 -ge \$n_stat2 \\' tests/ls/stat-free-color.sh # * 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 9466d557a032912f2f2c7eeecc5f01b760cfb262 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 3a2f3a22ceb..74c27fcd4ed 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