diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index d5f910297..7e0e77184 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -259,6 +259,7 @@ static AFTER_HELP: &str = "; pub mod options { + pub const FORCE: &str = "force"; pub const FILE: &str = "file"; pub const ITERATIONS: &str = "iterations"; pub const SIZE: &str = "size"; @@ -278,6 +279,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .after_help(AFTER_HELP) .usage(&usage[..]) + .arg( + Arg::with_name(options::FORCE) + .long(options::FORCE) + .short("f") + .help("change permissions to allow writing if necessary"), + ) .arg( Arg::with_name(options::ITERATIONS) .long(options::ITERATIONS) @@ -354,8 +361,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // TODO: implement --random-source - // TODO: implement --force - + let force = matches.is_present(options::FORCE); let remove = matches.is_present(options::REMOVE); let size_arg = match matches.value_of(options::SIZE) { Some(s) => Some(s.to_string()), @@ -375,7 +381,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } for path_str in matches.values_of(options::FILE).unwrap() { - wipe_file(&path_str, iterations, remove, size, exact, zero, verbose); + wipe_file( + &path_str, iterations, remove, size, exact, zero, verbose, force, + ); } 0 @@ -439,18 +447,40 @@ fn wipe_file( exact: bool, zero: bool, verbose: bool, + force: bool, ) { // Get these potential errors out of the way first let path: &Path = Path::new(path_str); if !path.exists() { - println!("{}: {}: No such file or directory", NAME, path.display()); + show_error!("{}: No such file or directory", path.display()); return; } if !path.is_file() { - println!("{}: {}: Not a file", NAME, path.display()); + show_error!("{}: Not a file", path.display()); return; } + // If force is true, set file permissions to not-readonly. + if force { + let metadata = match fs::metadata(path) { + Ok(m) => m, + Err(e) => { + show_error!("{}", e); + return; + } + }; + + let mut perms = metadata.permissions(); + perms.set_readonly(false); + match fs::set_permissions(path, perms) { + Err(e) => { + show_error!("{}", e); + return; + } + _ => {} + } + } + // Fill up our pass sequence let mut pass_sequence: Vec = Vec::new(); @@ -489,11 +519,13 @@ fn wipe_file( { let total_passes: usize = pass_sequence.len(); - let mut file: File = OpenOptions::new() - .write(true) - .truncate(false) - .open(path) - .expect("Failed to open file for writing"); + let mut file: File = match OpenOptions::new().write(true).truncate(false).open(path) { + Ok(f) => f, + Err(e) => { + show_error!("{}: failed to open for writing: {}", path.display(), e); + return; + } + }; // NOTE: it does not really matter what we set for total_bytes and gen_type here, so just // use bogus values @@ -523,14 +555,23 @@ fn wipe_file( } } // size is an optional argument for exactly how many bytes we want to shred - do_pass(&mut file, path, &mut generator, *pass_type, size) - .expect("File write pass failed"); + match do_pass(&mut file, path, &mut generator, *pass_type, size) { + Ok(_) => {} + Err(e) => { + show_error!("{}: File write pass failed: {}", path.display(), e); + } + } // Ignore failed writes; just keep trying } } if remove { - do_remove(path, path_str, verbose).expect("Failed to remove file"); + match do_remove(path, path_str, verbose) { + Ok(_) => {} + Err(e) => { + show_error!("{}: failed to remove file: {}", path.display(), e); + } + } } } diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 651491045..de54fae5b 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -1 +1,51 @@ -// ToDO: add tests +use crate::common::util::*; + +#[test] +fn test_shred_remove() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_shred_remove_a"; + let file_b = "test_shred_remove_b"; + + // Create file_a and file_b. + at.touch(file_a); + at.touch(file_b); + + // Shred file_a. + scene.ucmd().arg("-u").arg(file_a).run(); + + // file_a was deleted, file_b exists. + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[cfg(not(target_os = "freebsd"))] +#[test] +fn test_shred_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_shred_force"; + + // Create file_a. + at.touch(file); + assert!(at.file_exists(file)); + + // Make file_a readonly. + at.set_readonly(file); + + // Try shred -u. + let result = scene.ucmd().arg("-u").arg(file).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + + // file_a was not deleted because it is readonly. + assert!(at.file_exists(file)); + + // Try shred -u -f. + scene.ucmd().arg("-u").arg("-f").arg(file).run(); + + // file_a was deleted. + assert!(!at.file_exists(file)); +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 8a09b71c1..13c58747d 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -351,6 +351,13 @@ impl AtPath { String::from(self.minus(name).to_str().unwrap()) } + pub fn set_readonly(&self, name: &str) { + let metadata = fs::metadata(self.plus(name)).unwrap(); + let mut permissions = metadata.permissions(); + permissions.set_readonly(true); + fs::set_permissions(self.plus(name), permissions).unwrap(); + } + pub fn open(&self, name: &str) -> File { log_info("open", self.plus_as_string(name)); File::open(self.plus(name)).unwrap()