Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -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}
Expand Down
15 changes: 14 additions & 1 deletion src/uu/rm/src/platform/linux.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 @@ -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 => {
Expand All @@ -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
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
12 changes: 12 additions & 0 deletions 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

set -e

Expand Down Expand Up @@ -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
Loading