1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2026-01-15 17:51:07 +00:00
uutils-coreutils/src/rm/rm.rs
2019-02-07 08:30:51 -06:00

326 lines
10 KiB
Rust

#![crate_name = "uu_rm"]
/*
* This file is part of the uutils coreutils package.
*
* (c) Alex Lyon <arcterus@mail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
extern crate getopts;
extern crate remove_dir_all;
extern crate walkdir;
#[macro_use]
extern crate uucore;
use std::collections::VecDeque;
use std::fs;
use std::io::{stderr, stdin, BufRead, Write};
use std::ops::BitOr;
use std::path::Path;
use remove_dir_all::remove_dir_all;
use walkdir::{DirEntry, WalkDir};
#[derive(Eq, PartialEq, Clone, Copy)]
enum InteractiveMode {
InteractiveNone,
InteractiveOnce,
InteractiveAlways,
}
struct Options {
force: bool,
interactive: InteractiveMode,
#[allow(dead_code)]
one_fs: bool,
preserve_root: bool,
recursive: bool,
dir: bool,
verbose: bool,
}
static NAME: &str = "rm";
static VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn uumain(args: Vec<String>) -> i32 {
// TODO: make getopts support -R in addition to -r
let mut opts = getopts::Options::new();
opts.optflag(
"f",
"force",
"ignore nonexistent files and arguments, never prompt",
);
opts.optflag("i", "", "prompt before every removal");
opts.optflag("I", "", "prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving some protection against most mistakes");
opts.optflagopt(
"",
"interactive",
"prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, prompts always",
"WHEN",
);
opts.optflag("", "one-file-system", "when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument (NOT IMPLEMENTED)");
opts.optflag("", "no-preserve-root", "do not treat '/' specially");
opts.optflag("", "preserve-root", "do not remove '/' (default)");
opts.optflag(
"r",
"recursive",
"remove directories and their contents recursively",
);
opts.optflag("d", "dir", "remove empty directories");
opts.optflag("v", "verbose", "explain what is being done");
opts.optflag("h", "help", "display this help and exit");
opts.optflag("V", "version", "output version information and exit");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => crash!(1, "{}", f),
};
let force = matches.opt_present("force");
if matches.opt_present("help") {
println!("{} {}", NAME, VERSION);
println!("");
println!("Usage:");
println!(" {0} [OPTION]... [FILE]...", NAME);
println!("");
println!("{}", opts.usage("Remove (unlink) the FILE(s)."));
println!("By default, rm does not remove directories. Use the --recursive (-r)");
println!("option to remove each listed directory, too, along with all of its contents");
println!("");
println!("To remove a file whose name starts with a '-', for example '-foo',");
println!("use one of these commands:");
println!("rm -- -foo");
println!("");
println!("rm ./-foo");
println!("");
println!("Note that if you use rm to remove a file, it might be possible to recover");
println!("some of its contents, given sufficient expertise and/or time. For greater");
println!("assurance that the contents are truly unrecoverable, consider using shred.");
} else if matches.opt_present("version") {
println!("{} {}", NAME, VERSION);
} else if matches.free.is_empty() && !force {
show_error!("missing an argument");
show_error!("for help, try '{0} --help'", NAME);
return 1;
} else {
let options = Options {
force: force,
interactive: {
if matches.opt_present("i") {
InteractiveMode::InteractiveAlways
} else if matches.opt_present("I") {
InteractiveMode::InteractiveOnce
} else if matches.opt_present("interactive") {
match &matches.opt_str("interactive").unwrap()[..] {
"none" => InteractiveMode::InteractiveNone,
"once" => InteractiveMode::InteractiveOnce,
"always" => InteractiveMode::InteractiveAlways,
val => crash!(1, "Invalid argument to interactive ({})", val),
}
} else {
InteractiveMode::InteractiveNone
}
},
one_fs: matches.opt_present("one-file-system"),
preserve_root: !matches.opt_present("no-preserve-root"),
recursive: matches.opt_present("recursive"),
dir: matches.opt_present("dir"),
verbose: matches.opt_present("verbose"),
};
if options.interactive == InteractiveMode::InteractiveOnce
&& (options.recursive || matches.free.len() > 3)
{
let msg = if options.recursive {
"Remove all arguments recursively? "
} else {
"Remove all arguments? "
};
if !prompt(msg) {
return 0;
}
}
if remove(matches.free, options) {
return 1;
}
}
0
}
// TODO: implement one-file-system (this may get partially implemented in walkdir)
fn remove(files: Vec<String>, options: Options) -> bool {
let mut had_err = false;
for filename in &files {
let file = Path::new(filename);
had_err = match file.symlink_metadata() {
Ok(metadata) => {
if metadata.is_dir() {
handle_dir(file, &options)
} else if is_symlink_dir(&metadata) {
remove_dir(file, &options)
} else {
remove_file(file, &options)
}
}
Err(_e) => {
// TODO: actually print out the specific error
// TODO: When the error is not about missing files
// (e.g., permission), even rm -f should fail with
// outputting the error, but there's no easy eay.
if !options.force {
show_error!("no such file or directory '{}'", filename);
true
} else {
false
}
}
}.bitor(had_err);
}
had_err
}
fn handle_dir(path: &Path, options: &Options) -> bool {
let mut had_err = false;
let is_root = path.has_root() && path.parent().is_none();
if options.recursive && (!is_root || !options.preserve_root) {
if options.interactive != InteractiveMode::InteractiveAlways {
// we need the extra crate because apparently fs::remove_dir_all() does not function
// correctly on Windows
if let Err(e) = remove_dir_all(path) {
had_err = true;
show_error!("could not remove '{}': {}", path.display(), e);
}
} else {
let mut dirs: VecDeque<DirEntry> = VecDeque::new();
for entry in WalkDir::new(path) {
match entry {
Ok(entry) => {
let file_type = entry.file_type();
if file_type.is_dir() {
dirs.push_back(entry);
} else {
had_err = remove_file(entry.path(), options).bitor(had_err);
}
}
Err(e) => {
had_err = true;
show_error!("recursing in '{}': {}", path.display(), e);
}
}
}
for dir in dirs.iter().rev() {
had_err = remove_dir(dir.path(), options).bitor(had_err);
}
}
} else if options.dir && (!is_root || !options.preserve_root) {
had_err = remove_dir(path, options).bitor(had_err);
} else {
if options.recursive {
show_error!("could not remove directory '{}'", path.display());
had_err = true;
} else {
show_error!(
"could not remove directory '{}' (did you mean to pass '-r'?)",
path.display()
);
had_err = true;
}
}
had_err
}
fn remove_dir(path: &Path, options: &Options) -> bool {
let response = if options.interactive == InteractiveMode::InteractiveAlways {
prompt_file(path, true)
} else {
true
};
if response {
match fs::remove_dir(path) {
Ok(_) => if options.verbose {
println!("removed '{}'", path.display());
},
Err(e) => {
show_error!("removing '{}': {}", path.display(), e);
return true;
}
}
}
false
}
fn remove_file(path: &Path, options: &Options) -> bool {
let response = if options.interactive == InteractiveMode::InteractiveAlways {
prompt_file(path, false)
} else {
true
};
if response {
match fs::remove_file(path) {
Ok(_) => if options.verbose {
println!("removed '{}'", path.display());
},
Err(e) => {
show_error!("removing '{}': {}", path.display(), e);
return true;
}
}
}
false
}
fn prompt_file(path: &Path, is_dir: bool) -> bool {
if is_dir {
prompt(&(format!("rm: remove directory '{}'? ", path.display())))
} else {
prompt(&(format!("rm: remove file '{}'? ", path.display())))
}
}
fn prompt(msg: &str) -> bool {
let _ = stderr().write_all(msg.as_bytes());
let _ = stderr().flush();
let mut buf = Vec::new();
let stdin = stdin();
let mut stdin = stdin.lock();
match stdin.read_until('\n' as u8, &mut buf) {
Ok(x) if x > 0 => match buf[0] {
b'y' | b'Y' => true,
_ => false,
},
_ => false,
}
}
#[cfg(not(windows))]
fn is_symlink_dir(_metadata: &fs::Metadata) -> bool {
false
}
#[cfg(windows)]
use std::os::windows::prelude::MetadataExt;
#[cfg(windows)]
fn is_symlink_dir(metadata: &fs::Metadata) -> bool {
use std::os::raw::c_ulong;
pub type DWORD = c_ulong;
pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10;
metadata.file_type().is_symlink() && ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0)
}