Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/uu/rm/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
15 changes: 14 additions & 1 deletion src/uu/rm/src/platform/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 => {
Expand All @@ -361,6 +362,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
Expand Down
14 changes: 14 additions & 0 deletions src/uucore/src/lib/features/safe_traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -79,6 +80,19 @@ impl From<SafeTraversalError> 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<io::Error> {
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<Vec<OsString>> {
let mut entries = Vec::new();
Expand Down
14 changes: 13 additions & 1 deletion util/build-gnu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is hfsplus really used?


set -e

Expand Down Expand Up @@ -362,4 +363,15 @@ 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have

command -v gsed && sed(){ gsed "$@";}
SED=$(command -v gsed||command -v sed) # for find...exec...


# 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
Loading