mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-31 04:57:45 +00:00
rm: massive refactor
This commit is contained in:
parent
3d34d06a1e
commit
b39689ac37
3 changed files with 137 additions and 120 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -914,12 +914,23 @@ dependencies = [
|
||||||
"uucore 0.0.1",
|
"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]]
|
[[package]]
|
||||||
name = "rm"
|
name = "rm"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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",
|
"uucore 0.0.1",
|
||||||
|
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1490,6 +1501,7 @@ dependencies = [
|
||||||
"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
|
"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.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 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 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-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"
|
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
||||||
|
|
|
@ -9,6 +9,8 @@ path = "rm.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "0.2.14"
|
getopts = "0.2.14"
|
||||||
|
walkdir = "1.0.7"
|
||||||
|
remove_dir_all = "0.2"
|
||||||
uucore = { path="../uucore" }
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
243
src/rm/rm.rs
243
src/rm/rm.rs
|
@ -10,6 +10,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
|
extern crate remove_dir_all;
|
||||||
|
extern crate walkdir;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
@ -18,7 +20,9 @@ use std::collections::VecDeque;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{stdin, stderr, BufRead, Write};
|
use std::io::{stdin, stderr, BufRead, Write};
|
||||||
use std::ops::BitOr;
|
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)]
|
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||||
enum InteractiveMode {
|
enum InteractiveMode {
|
||||||
|
@ -27,6 +31,17 @@ enum InteractiveMode {
|
||||||
InteractiveAlways
|
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 NAME: &'static str = "rm";
|
||||||
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
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);
|
show_error!("for help, try '{0} --help'", NAME);
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
let force = matches.opt_present("force");
|
let options = Options {
|
||||||
let interactive =
|
force: matches.opt_present("force"),
|
||||||
if matches.opt_present("i") {
|
interactive: {
|
||||||
InteractiveMode::InteractiveAlways
|
if matches.opt_present("i") {
|
||||||
} else if matches.opt_present("I") {
|
InteractiveMode::InteractiveAlways
|
||||||
InteractiveMode::InteractiveOnce
|
} else if matches.opt_present("I") {
|
||||||
} else if matches.opt_present("interactive") {
|
InteractiveMode::InteractiveOnce
|
||||||
match &matches.opt_str("interactive").unwrap()[..] {
|
} else if matches.opt_present("interactive") {
|
||||||
"none" => InteractiveMode::InteractiveNone,
|
match &matches.opt_str("interactive").unwrap()[..] {
|
||||||
"once" => InteractiveMode::InteractiveOnce,
|
"none" => InteractiveMode::InteractiveNone,
|
||||||
"always" => InteractiveMode::InteractiveAlways,
|
"once" => InteractiveMode::InteractiveOnce,
|
||||||
val => {
|
"always" => InteractiveMode::InteractiveAlways,
|
||||||
crash!(1, "Invalid argument to interactive ({})", val)
|
val => {
|
||||||
|
crash!(1, "Invalid argument to interactive ({})", val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
InteractiveMode::InteractiveNone
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
InteractiveMode::InteractiveNone
|
one_fs: matches.opt_present("one-file-system"),
|
||||||
};
|
preserve_root: !matches.opt_present("no-preserve-root"),
|
||||||
let one_fs = matches.opt_present("one-file-system");
|
recursive: matches.opt_present("recursive"),
|
||||||
let preserve_root = !matches.opt_present("no-preserve-root");
|
dir: matches.opt_present("dir"),
|
||||||
let recursive = matches.opt_present("recursive");
|
verbose: matches.opt_present("verbose")
|
||||||
let dir = matches.opt_present("dir");
|
};
|
||||||
let verbose = matches.opt_present("verbose");
|
if options.interactive == InteractiveMode::InteractiveOnce
|
||||||
if interactive == InteractiveMode::InteractiveOnce && (recursive || matches.free.len() > 3) {
|
&& (options.recursive || matches.free.len() > 3) {
|
||||||
let msg =
|
let msg =
|
||||||
if recursive {
|
if options.recursive {
|
||||||
"Remove all arguments recursively? "
|
"Remove all arguments recursively? "
|
||||||
} else {
|
} else {
|
||||||
"Remove all arguments? "
|
"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;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,118 +139,100 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement one-file-system
|
// TODO: implement one-file-system (this may get partially implemented in walkdir)
|
||||||
#[allow(unused_variables)]
|
fn remove(files: Vec<String>, options: Options) -> bool {
|
||||||
fn remove(files: Vec<String>, force: bool, interactive: InteractiveMode, one_fs: bool, preserve_root: bool, recursive: bool, dir: bool, verbose: bool) -> bool {
|
|
||||||
let mut had_err = false;
|
let mut had_err = false;
|
||||||
|
|
||||||
for filename in &files {
|
for filename in &files {
|
||||||
let filename = &filename[..];
|
|
||||||
let file = Path::new(filename);
|
let file = Path::new(filename);
|
||||||
match file.symlink_metadata() {
|
had_err = match file.symlink_metadata() {
|
||||||
Ok(metadata) => if metadata.is_dir() {
|
Ok(metadata) => {
|
||||||
if recursive && (filename != "/" || !preserve_root) {
|
if metadata.is_dir() {
|
||||||
if interactive != InteractiveMode::InteractiveAlways {
|
handle_dir(file, &options)
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
if recursive {
|
remove_file(file, &options)
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
had_err = remove_file(&file, interactive, verbose).bitor(had_err);
|
Err(_e) => {
|
||||||
},
|
// TODO: actually print out the specific error
|
||||||
Err(e) => {
|
|
||||||
// TODO: When the error is not about missing files
|
// TODO: When the error is not about missing files
|
||||||
// (e.g., permission), even rm -f should fail with
|
// (e.g., permission), even rm -f should fail with
|
||||||
// outputting the error, but there's no easy eay.
|
// outputting the error, but there's no easy eay.
|
||||||
if !force {
|
if !options.force {
|
||||||
had_err = true;
|
|
||||||
show_error!("no such file or directory '{}'", filename);
|
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
|
had_err
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_dir(path: &Path, interactive: InteractiveMode, verbose: bool) -> bool {
|
fn remove_dir(path: &Path, options: &Options) -> bool {
|
||||||
let response =
|
let response =
|
||||||
if interactive == InteractiveMode::InteractiveAlways {
|
if options.interactive == InteractiveMode::InteractiveAlways {
|
||||||
prompt_file(path, true)
|
prompt_file(path, true)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
if response {
|
if response {
|
||||||
match fs::remove_dir(path) {
|
match fs::remove_dir(path) {
|
||||||
Ok(_) => if verbose { println!("removed '{}'", path.display()); },
|
Ok(_) => if options.verbose { println!("removed '{}'", path.display()); },
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
show_error!("removing '{}': {}", path.display(), e);
|
show_error!("removing '{}': {}", path.display(), e);
|
||||||
return true;
|
return true;
|
||||||
|
@ -242,16 +243,16 @@ fn remove_dir(path: &Path, interactive: InteractiveMode, verbose: bool) -> bool
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_file(path: &Path, interactive: InteractiveMode, verbose: bool) -> bool {
|
fn remove_file(path: &Path, options: &Options) -> bool {
|
||||||
let response =
|
let response =
|
||||||
if interactive == InteractiveMode::InteractiveAlways {
|
if options.interactive == InteractiveMode::InteractiveAlways {
|
||||||
prompt_file(path, false)
|
prompt_file(path, false)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
if response {
|
if response {
|
||||||
match fs::remove_file(path) {
|
match fs::remove_file(path) {
|
||||||
Ok(_) => if verbose { println!("removed '{}'", path.display()); },
|
Ok(_) => if options.verbose { println!("removed '{}'", path.display()); },
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
show_error!("removing '{}': {}", path.display(), e);
|
show_error!("removing '{}': {}", path.display(), e);
|
||||||
return true;
|
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 {
|
fn prompt_file(path: &Path, is_dir: bool) -> bool {
|
||||||
if is_dir {
|
if is_dir {
|
||||||
prompt(&(format!("rm: remove directory '{}'? ", path.display()))[..])
|
prompt(&(format!("rm: remove directory '{}'? ", path.display())))
|
||||||
} else {
|
} else {
|
||||||
prompt(&(format!("rm: remove file '{}'? ", path.display()))[..])
|
prompt(&(format!("rm: remove file '{}'? ", path.display())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(msg: &str) -> bool {
|
fn prompt(msg: &str) -> bool {
|
||||||
stderr().write_all(msg.as_bytes()).unwrap_or(());
|
let _ = stderr().write_all(msg.as_bytes());
|
||||||
stderr().flush().unwrap_or(());
|
let _ = stderr().flush();
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
let mut stdin = stdin.lock();
|
let mut stdin = stdin.lock();
|
||||||
|
|
||||||
match stdin.read_until('\n' as u8, &mut buf) {
|
match stdin.read_until('\n' as u8, &mut buf) {
|
||||||
Ok(x) if x > 0 => {
|
Ok(x) if x > 0 => {
|
||||||
match buf[0] {
|
match buf[0] {
|
||||||
0x59 | 0x79 => true,
|
b'y' | b'Y' => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue