Skip to content
Merged
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
31 changes: 21 additions & 10 deletions src/uu/rmdir/src/rmdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use clap::builder::ValueParser;
use clap::{Arg, ArgAction, Command};
use std::ffi::OsString;
use std::fs::{read_dir, remove_dir};
use std::io;
use std::io::{self, Write};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{UResult, set_exit_code, strip_errno};
use uucore::error::{FromIo, UResult, set_exit_code, strip_errno};
use uucore::translate;

use uucore::{format_usage, show_error};
Expand All @@ -32,13 +32,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
parents: matches.get_flag(OPT_PARENTS),
verbose: matches.get_flag(OPT_VERBOSE),
};
let mut verbose_result = Ok(());

for path in matches
.get_many::<OsString>(ARG_DIRS)
.unwrap_or_default()
.map(Path::new)
{
if let Err(error) = remove(path, opts) {
if let Err(error) = remove(path, opts, &mut verbose_result) {
let Error { error, path } = error;

if opts.ignore && dir_not_empty(&error, path) {
Expand Down Expand Up @@ -88,31 +89,41 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
}

Ok(())
verbose_result.map_err_context(|| translate!("common-write-error"))
}

struct Error<'a> {
error: io::Error,
path: &'a Path,
}

fn remove(mut path: &Path, opts: Opts) -> Result<(), Error<'_>> {
remove_single(path, opts)?;
fn remove<'a>(
mut path: &'a Path,
opts: Opts,
verbose_result: &mut io::Result<()>,
) -> Result<(), Error<'a>> {
remove_single(path, opts, verbose_result)?;
if opts.parents {
while let Some(new) = path.parent() {
path = new;
if path.as_os_str().is_empty() {
break;
}
remove_single(path, opts)?;
remove_single(path, opts, verbose_result)?;
}
}
Ok(())
}

fn remove_single(path: &Path, opts: Opts) -> Result<(), Error<'_>> {
if opts.verbose {
println!(
fn remove_single<'a>(
path: &'a Path,
opts: Opts,
verbose_result: &mut io::Result<()>,
) -> Result<(), Error<'a>> {
// Defer reporting a write error until all directory removals have been attempted.
if opts.verbose && verbose_result.is_ok() {
*verbose_result = writeln!(
io::stdout(),
"{}",
translate!("rmdir-verbose-removing-directory", "util_name" => "rmdir", "path" => path.quote())
);
Expand Down
49 changes: 49 additions & 0 deletions tests/by-util/test_rmdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,55 @@ fn test_rmdir_empty_directory_with_parents() {
assert!(!at.dir_exists(DIR));
}

#[test]
#[cfg(target_os = "linux")]
fn test_rmdir_verbose_write_error() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all(NESTED_DIR);
at.mkdir("other");
at.mkdir_all("nonempty/child");
let dev_full = std::fs::OpenOptions::new()
.write(true)
.open("/dev/full")
.unwrap();

ucmd.args(&[
"-pv",
"--ignore-fail-on-non-empty",
"nonempty",
NESTED_DIR,
"other",
])
.set_stdout(dev_full)
.fails_with_code(1)
.stderr_is("rmdir: write error: No space left on device\n");

assert!(!at.dir_exists(DIR));
assert!(!at.dir_exists("other"));
assert!(at.dir_exists("nonempty/child"));
}

#[test]
#[cfg(target_os = "linux")]
fn test_rmdir_verbose_write_error_with_removal_error() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir(DIR);
let dev_full = std::fs::OpenOptions::new()
.write(true)
.open("/dev/full")
.unwrap();

ucmd.args(&["-v", "missing", DIR])
.set_stdout(dev_full)
.fails_with_code(1)
.stderr_is(
"rmdir: failed to remove 'missing': No such file or directory\n\
rmdir: write error: No space left on device\n",
);

assert!(!at.dir_exists(DIR));
}

#[test]
fn test_rmdir_nonempty_directory_no_parents() {
let (at, mut ucmd) = at_and_ucmd!();
Expand Down
Loading