use indicatif::ProgressBar;
use std::ffi::OsStr;
use std::fs;
use std::io::{IsTerminal, stdin};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::FromIo;
use uucore::prompt_yes;
use uucore::safe_traversal::{DirFd, SymlinkBehavior};
use uucore::show_error;
use uucore::translate;
use super::super::{
InteractiveMode, Options, is_dir_empty, is_readable_metadata, prompt_descend, remove_file,
show_permission_denied_error, show_removal_error, verbose_removed_directory,
verbose_removed_file,
};
#[inline]
fn mode_readable(mode: libc::mode_t) -> bool {
(mode & libc::S_IRUSR) != 0
}
#[inline]
fn mode_writable(mode: libc::mode_t) -> bool {
(mode & libc::S_IWUSR) != 0
}
fn prompt_file_with_stat(path: &Path, stat: &libc::stat, options: &Options) -> bool {
if options.interactive == InteractiveMode::Never {
return true;
}
let is_symlink = ((stat.st_mode as libc::mode_t) & libc::S_IFMT) == libc::S_IFLNK;
let writable = mode_writable(stat.st_mode as libc::mode_t);
let len = stat.st_size as u64;
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
if options.interactive == InteractiveMode::Always {
if is_symlink {
return prompt_yes!("remove symbolic link {}?", path.quote());
}
if writable {
return if len == 0 {
prompt_yes!("remove regular empty file {}?", path.quote())
} else {
prompt_yes!("remove file {}?", path.quote())
};
}
}
match (stdin_ok, writable, len == 0) {
(false, _, _) if options.interactive == InteractiveMode::PromptProtected => true,
(_, true, _) => true,
(_, false, true) => prompt_yes!(
"remove write-protected regular empty file {}?",
path.quote()
),
_ => prompt_yes!("remove write-protected regular file {}?", path.quote()),
}
}
fn prompt_dir_with_mode(path: &Path, mode: libc::mode_t, options: &Options) -> bool {
if options.interactive == InteractiveMode::Never {
return true;
}
let readable = mode_readable(mode as libc::mode_t);
let writable = mode_writable(mode as libc::mode_t);
let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
match (stdin_ok, readable, writable, options.interactive) {
(false, _, _, InteractiveMode::PromptProtected) => true,
(false, false, false, InteractiveMode::Never) => true,
(_, false, false, _) => prompt_yes!(
"attempt removal of inaccessible directory {}?",
path.quote()
),
(_, false, true, InteractiveMode::Always) => {
prompt_yes!(
"attempt removal of inaccessible directory {}?",
path.quote()
)
}
(_, true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()),
(_, _, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()),
(_, _, _, _) => true,
}
}
pub fn is_readable(path: &Path) -> bool {
fs::metadata(path).is_ok_and(|metadata| is_readable_metadata(&metadata))
}
pub fn safe_remove_file(
path: &Path,
options: &Options,
progress_bar: Option<&ProgressBar>,
) -> Option<bool> {
let parent = path.parent().unwrap_or(Path::new("."));
let file_name = path.file_name()?;
let dir_fd = DirFd::open(parent, SymlinkBehavior::Follow).ok()?;
match dir_fd.unlink_at(file_name, false) {
Ok(_) => {
if let Some(pb) = progress_bar {
pb.inc(1);
}
verbose_removed_file(path, options);
Some(false)
}
Err(e) => {
if e.kind() == std::io::ErrorKind::PermissionDenied {
show_error!("cannot remove {}: Permission denied", path.quote());
} else {
let _ = show_removal_error(e, path);
}
Some(true)
}
}
}
pub fn safe_remove_empty_dir(
path: &Path,
options: &Options,
progress_bar: Option<&ProgressBar>,
) -> Option<bool> {
let parent = path.parent().unwrap_or(Path::new("."));
let dir_name = path.file_name()?;
let dir_fd = DirFd::open(parent, SymlinkBehavior::Follow).ok()?;
match dir_fd.unlink_at(dir_name, true) {
Ok(_) => {
if let Some(pb) = progress_bar {
pb.inc(1);
}
verbose_removed_directory(path, options);
Some(false)
}
Err(e) => {
let e =
e.map_err_context(|| translate!("rm-error-cannot-remove", "file" => path.quote()));
show_error!("{e}");
Some(true)
}
}
}
fn handle_error_with_force(e: std::io::Error, path: &Path, options: &Options) -> bool {
if e.kind() == std::io::ErrorKind::PermissionDenied {
show_permission_denied_error(path);
return true;
}
if !options.force {
let e = e.map_err_context(|| translate!("rm-error-cannot-remove", "file" => path.quote()));
show_error!("{e}");
}
!options.force
}
fn handle_permission_denied(
dir_fd: &DirFd,
entry_name: &OsStr,
entry_path: &Path,
options: &Options,
) -> bool {
if let Err(_remove_err) = dir_fd.unlink_at(entry_name, true) {
show_permission_denied_error(entry_path);
return true;
}
verbose_removed_directory(entry_path, options);
false
}
fn handle_unlink(
dir_fd: &DirFd,
entry_name: &OsStr,
entry_path: &Path,
is_dir: bool,
options: &Options,
) -> bool {
if let Err(e) = dir_fd.unlink_at(entry_name, is_dir) {
let e = e
.map_err_context(|| translate!("rm-error-cannot-remove", "file" => entry_path.quote()));
show_error!("{e}");
true
} else {
if is_dir {
verbose_removed_directory(entry_path, options);
} else {
verbose_removed_file(entry_path, options);
}
false
}
}
pub fn remove_dir_with_special_cases(path: &Path, options: &Options, error_occurred: bool) -> bool {
match fs::remove_dir(path) {
Err(_) if !error_occurred && !is_readable(path) => {
show_permission_denied_error(path);
true
}
Err(_) if !error_occurred && path.read_dir().is_err() => {
show_permission_denied_error(path);
true
}
Err(e) if !error_occurred => show_removal_error(e, path),
Err(_) => {
error_occurred
}
Ok(_) => {
verbose_removed_directory(path, options);
false
}
}
}
pub fn safe_remove_dir_recursive(
path: &Path,
options: &Options,
progress_bar: Option<&ProgressBar>,
) -> bool {
let initial_mode = match fs::symlink_metadata(path) {
Ok(metadata) if !metadata.is_dir() => {
return remove_file(path, options, progress_bar);
}
Ok(metadata) => metadata.permissions().mode(),
Err(e) => {
return show_removal_error(e, path);
}
};
let dir_fd = match DirFd::open(path, SymlinkBehavior::Follow) {
Ok(fd) => fd,
Err(e) => {
if e.kind() == std::io::ErrorKind::PermissionDenied {
if fs::remove_dir(path).is_ok() {
verbose_removed_directory(path, options);
return false;
}
return show_permission_denied_error(path);
}
return show_removal_error(e, path);
}
};
let error = safe_remove_dir_recursive_impl(path, &dir_fd, options);
if error {
error
} else {
if options.interactive == InteractiveMode::Always
&& !prompt_dir_with_mode(path, initial_mode as libc::mode_t, options)
{
return false;
}
if !is_dir_empty(path) {
if options.interactive == InteractiveMode::Always {
return false;
}
return remove_dir_with_special_cases(path, options, false);
}
if let Some(result) = safe_remove_empty_dir(path, options, progress_bar) {
result
} else {
remove_dir_with_special_cases(path, options, error)
}
}
}
#[cfg(not(target_os = "redox"))]
pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options) -> bool {
let entries = match dir_fd.read_dir() {
Ok(entries) => entries,
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
if !options.force {
show_permission_denied_error(path);
}
return !options.force;
}
Err(e) => {
return handle_error_with_force(e, path, options);
}
};
let mut error = false;
for entry_name in entries {
let entry_path = path.join(&entry_name);
let entry_stat = match dir_fd.stat_at(&entry_name, SymlinkBehavior::NoFollow) {
Ok(stat) => stat,
Err(e) => {
error |= handle_error_with_force(e, &entry_path, options);
continue;
}
};
let is_dir = ((entry_stat.st_mode as libc::mode_t) & libc::S_IFMT) == libc::S_IFDIR;
if is_dir {
if options.interactive == InteractiveMode::Always
&& !is_dir_empty(&entry_path)
&& !prompt_descend(&entry_path)
{
continue;
}
let child_dir_fd = match dir_fd.open_subdir(&entry_name, SymlinkBehavior::Follow) {
Ok(fd) => fd,
Err(e) => {
if e.kind() == std::io::ErrorKind::PermissionDenied {
error |= handle_permission_denied(
dir_fd,
entry_name.as_ref(),
&entry_path,
options,
);
} else {
error |= handle_error_with_force(e, &entry_path, options);
}
continue;
}
};
let child_error = safe_remove_dir_recursive_impl(&entry_path, &child_dir_fd, options);
error |= child_error;
if !child_error
&& options.interactive == InteractiveMode::Always
&& !prompt_dir_with_mode(&entry_path, entry_stat.st_mode as libc::mode_t, options)
{
continue;
}
if !child_error {
error |= handle_unlink(dir_fd, entry_name.as_ref(), &entry_path, true, options);
}
} else {
if prompt_file_with_stat(&entry_path, &entry_stat, options) {
error |= handle_unlink(dir_fd, entry_name.as_ref(), &entry_path, false, options);
}
}
}
error
}
#[cfg(target_os = "redox")]
pub fn safe_remove_dir_recursive_impl(_path: &Path, _dir_fd: &DirFd, _options: &Options) -> bool {
true }