1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 12:37:49 +00:00

rm: massive refactor

This commit is contained in:
Alex Lyon 2017-07-26 23:21:23 -07:00
parent 3d34d06a1e
commit b39689ac37
3 changed files with 137 additions and 120 deletions

12
Cargo.lock generated
View file

@ -914,12 +914,23 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "remove_dir_all"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rm"
version = "0.0.1"
dependencies = [
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.1",
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1490,6 +1501,7 @@ dependencies = [
"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
"checksum remove_dir_all 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0882bc41b0ba6131c7f0ce97233b62d8099e3f3abc60d4938185d3e35439c0cc"
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"

View file

@ -9,6 +9,8 @@ path = "rm.rs"
[dependencies]
getopts = "0.2.14"
walkdir = "1.0.7"
remove_dir_all = "0.2"
uucore = { path="../uucore" }
[[bin]]

View file

@ -10,6 +10,8 @@
*/
extern crate getopts;
extern crate remove_dir_all;
extern crate walkdir;
#[macro_use]
extern crate uucore;
@ -18,7 +20,9 @@ use std::collections::VecDeque;
use std::fs;
use std::io::{stdin, stderr, BufRead, Write};
use std::ops::BitOr;
use std::path::{Path, PathBuf};
use std::path::Path;
use remove_dir_all::remove_dir_all;
use walkdir::{DirEntry, WalkDir};
#[derive(Eq, PartialEq, Clone, Copy)]
enum InteractiveMode {
@ -27,6 +31,17 @@ enum InteractiveMode {
InteractiveAlways
}
struct Options {
force: bool,
interactive: InteractiveMode,
#[allow(dead_code)]
one_fs: bool,
preserve_root: bool,
recursive: bool,
dir: bool,
verbose: bool
}
static NAME: &'static str = "rm";
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -77,32 +92,36 @@ pub fn uumain(args: Vec<String>) -> i32 {
show_error!("for help, try '{0} --help'", NAME);
return 1;
} else {
let force = matches.opt_present("force");
let 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)
let options = Options {
force: matches.opt_present("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
}
} else {
InteractiveMode::InteractiveNone
};
let one_fs = matches.opt_present("one-file-system");
let preserve_root = !matches.opt_present("no-preserve-root");
let recursive = matches.opt_present("recursive");
let dir = matches.opt_present("dir");
let verbose = matches.opt_present("verbose");
if interactive == InteractiveMode::InteractiveOnce && (recursive || matches.free.len() > 3) {
},
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 recursive {
if options.recursive {
"Remove all arguments recursively? "
} else {
"Remove all arguments? "
@ -112,7 +131,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
}
}
if remove(matches.free, force, interactive, one_fs, preserve_root, recursive, dir, verbose) {
if remove(matches.free, options) {
return 1;
}
}
@ -120,118 +139,100 @@ pub fn uumain(args: Vec<String>) -> i32 {
0
}
// TODO: implement one-file-system
#[allow(unused_variables)]
fn remove(files: Vec<String>, force: bool, interactive: InteractiveMode, one_fs: bool, preserve_root: bool, recursive: bool, dir: bool, verbose: bool) -> bool {
// 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 filename = &filename[..];
let file = Path::new(filename);
match file.symlink_metadata() {
Ok(metadata) => if metadata.is_dir() {
if recursive && (filename != "/" || !preserve_root) {
if interactive != InteractiveMode::InteractiveAlways {
if let Err(e) = fs::remove_dir_all(file) {
had_err = true;
show_error!("could not remove '{}': {}", filename, e);
};
} else {
let mut dirs: VecDeque<PathBuf> = VecDeque::new();
let mut files: Vec<PathBuf> = Vec::new();
let mut rmdirstack: Vec<PathBuf> = Vec::new();
dirs.push_back(file.to_path_buf());
while !dirs.is_empty() {
let dir = dirs.pop_front().unwrap();
if !prompt(&(format!("rm: descend into directory '{}'? ", dir.display()))[..]) {
continue;
}
// iterate over items in this directory, adding to either file or
// directory queue
match fs::read_dir(dir.as_path()) {
Ok(rdir) => {
for ent in rdir {
match ent {
Ok(ref f) => match f.file_type() {
Ok(t) => {
if t.is_dir() {
dirs.push_back(f.path());
} else {
files.push(f.path());
}
},
Err(e) => {
had_err = true;
show_error!("reading '{}': {}", f.path().display(), e);
},
},
Err(ref e) => {
had_err = true;
show_error!("recursing into '{}': {}", filename, e);
},
};
}
},
Err(e) => {
had_err = true;
show_error!("could not recurse into '{}': {}", dir.display(), e);
continue;
},
};
for f in &files {
had_err = remove_file(f.as_path(), interactive, verbose).bitor(had_err);
}
files.clear();
rmdirstack.push(dir);
}
for d in rmdirstack.iter().rev() {
had_err = remove_dir(d.as_path(), interactive, verbose).bitor(had_err);
}
}
} else if dir && (filename != "/" || !preserve_root) {
had_err = remove_dir(&file, interactive, verbose).bitor(had_err);
had_err = match file.symlink_metadata() {
Ok(metadata) => {
if metadata.is_dir() {
handle_dir(file, &options)
} else {
if recursive {
show_error!("could not remove directory '{}'", filename);
had_err = true;
} else {
show_error!("could not remove directory '{}' (did you mean to pass '-r'?)", filename);
had_err = true;
}
remove_file(file, &options)
}
} else {
had_err = remove_file(&file, interactive, verbose).bitor(had_err);
},
Err(e) => {
}
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 !force {
had_err = true;
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, interactive: InteractiveMode, verbose: bool) -> bool {
fn remove_dir(path: &Path, options: &Options) -> bool {
let response =
if interactive == InteractiveMode::InteractiveAlways {
if options.interactive == InteractiveMode::InteractiveAlways {
prompt_file(path, true)
} else {
true
};
if response {
match fs::remove_dir(path) {
Ok(_) => if verbose { println!("removed '{}'", path.display()); },
Ok(_) => if options.verbose { println!("removed '{}'", path.display()); },
Err(e) => {
show_error!("removing '{}': {}", path.display(), e);
return true;
@ -242,16 +243,16 @@ fn remove_dir(path: &Path, interactive: InteractiveMode, verbose: bool) -> bool
false
}
fn remove_file(path: &Path, interactive: InteractiveMode, verbose: bool) -> bool {
fn remove_file(path: &Path, options: &Options) -> bool {
let response =
if interactive == InteractiveMode::InteractiveAlways {
if options.interactive == InteractiveMode::InteractiveAlways {
prompt_file(path, false)
} else {
true
};
if response {
match fs::remove_file(path) {
Ok(_) => if verbose { println!("removed '{}'", path.display()); },
Ok(_) => if options.verbose { println!("removed '{}'", path.display()); },
Err(e) => {
show_error!("removing '{}': {}", path.display(), e);
return true;
@ -264,22 +265,24 @@ fn remove_file(path: &Path, interactive: InteractiveMode, verbose: bool) -> bool
fn prompt_file(path: &Path, is_dir: bool) -> bool {
if is_dir {
prompt(&(format!("rm: remove directory '{}'? ", path.display()))[..])
prompt(&(format!("rm: remove directory '{}'? ", path.display())))
} else {
prompt(&(format!("rm: remove file '{}'? ", path.display()))[..])
prompt(&(format!("rm: remove file '{}'? ", path.display())))
}
}
fn prompt(msg: &str) -> bool {
stderr().write_all(msg.as_bytes()).unwrap_or(());
stderr().flush().unwrap_or(());
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] {
0x59 | 0x79 => true,
b'y' | b'Y' => true,
_ => false,
}
}