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..7614b856460 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,18 @@ 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..4f88baafd26 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 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 propagated 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..74c27fcd4ed 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 memcpy set -e @@ -357,3 +358,14 @@ 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 + +# 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