mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
shred: implemented "--remove" arg (#5790)
This commit is contained in:
parent
247f2e55bd
commit
c867d6bfb1
2 changed files with 158 additions and 24 deletions
|
@ -28,10 +28,17 @@ pub mod options {
|
||||||
pub const FILE: &str = "file";
|
pub const FILE: &str = "file";
|
||||||
pub const ITERATIONS: &str = "iterations";
|
pub const ITERATIONS: &str = "iterations";
|
||||||
pub const SIZE: &str = "size";
|
pub const SIZE: &str = "size";
|
||||||
|
pub const WIPESYNC: &str = "u";
|
||||||
pub const REMOVE: &str = "remove";
|
pub const REMOVE: &str = "remove";
|
||||||
pub const VERBOSE: &str = "verbose";
|
pub const VERBOSE: &str = "verbose";
|
||||||
pub const EXACT: &str = "exact";
|
pub const EXACT: &str = "exact";
|
||||||
pub const ZERO: &str = "zero";
|
pub const ZERO: &str = "zero";
|
||||||
|
|
||||||
|
pub mod remove {
|
||||||
|
pub const UNLINK: &str = "unlink";
|
||||||
|
pub const WIPE: &str = "wipe";
|
||||||
|
pub const WIPESYNC: &str = "wipesync";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This block size seems to match GNU (2^16 = 65536)
|
// This block size seems to match GNU (2^16 = 65536)
|
||||||
|
@ -81,6 +88,14 @@ enum PassType {
|
||||||
Random,
|
Random,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
enum RemoveMethod {
|
||||||
|
None, // Default method. Only obfuscate the file data
|
||||||
|
Unlink, // The same as 'None' + unlink the file
|
||||||
|
Wipe, // The same as 'Unlink' + obfuscate the file name before unlink
|
||||||
|
WipeSync, // The same as 'Wipe' sync the file name changes
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterates over all possible filenames of a certain length using NAME_CHARSET as an alphabet
|
/// Iterates over all possible filenames of a certain length using NAME_CHARSET as an alphabet
|
||||||
struct FilenameIter {
|
struct FilenameIter {
|
||||||
// Store the indices of the letters of our filename in NAME_CHARSET
|
// Store the indices of the letters of our filename in NAME_CHARSET
|
||||||
|
@ -219,17 +234,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: implement --remove HOW
|
|
||||||
// The optional HOW parameter indicates how to remove a directory entry:
|
|
||||||
// - 'unlink' => use a standard unlink call.
|
|
||||||
// - 'wipe' => also first obfuscate bytes in the name.
|
|
||||||
// - 'wipesync' => also sync each obfuscated byte to disk.
|
|
||||||
// The default mode is 'wipesync', but note it can be expensive.
|
|
||||||
|
|
||||||
// TODO: implement --random-source
|
// TODO: implement --random-source
|
||||||
|
|
||||||
|
let remove_method = if matches.get_flag(options::WIPESYNC) {
|
||||||
|
RemoveMethod::WipeSync
|
||||||
|
} else if matches.contains_id(options::REMOVE) {
|
||||||
|
match matches
|
||||||
|
.get_one::<String>(options::REMOVE)
|
||||||
|
.map(AsRef::as_ref)
|
||||||
|
{
|
||||||
|
Some(options::remove::UNLINK) => RemoveMethod::Unlink,
|
||||||
|
Some(options::remove::WIPE) => RemoveMethod::Wipe,
|
||||||
|
Some(options::remove::WIPESYNC) => RemoveMethod::WipeSync,
|
||||||
|
_ => unreachable!("should be caught by clap"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RemoveMethod::None
|
||||||
|
};
|
||||||
|
|
||||||
let force = matches.get_flag(options::FORCE);
|
let force = matches.get_flag(options::FORCE);
|
||||||
let remove = matches.get_flag(options::REMOVE);
|
|
||||||
let size_arg = matches
|
let size_arg = matches
|
||||||
.get_one::<String>(options::SIZE)
|
.get_one::<String>(options::SIZE)
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
|
@ -240,7 +263,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
for path_str in matches.get_many::<String>(options::FILE).unwrap() {
|
for path_str in matches.get_many::<String>(options::FILE).unwrap() {
|
||||||
show_if_err!(wipe_file(
|
show_if_err!(wipe_file(
|
||||||
path_str, iterations, remove, size, exact, zero, verbose, force,
|
path_str,
|
||||||
|
iterations,
|
||||||
|
remove_method,
|
||||||
|
size,
|
||||||
|
exact,
|
||||||
|
zero,
|
||||||
|
verbose,
|
||||||
|
force,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -276,12 +306,26 @@ pub fn uu_app() -> Command {
|
||||||
.help("shred this many bytes (suffixes like K, M, G accepted)"),
|
.help("shred this many bytes (suffixes like K, M, G accepted)"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::REMOVE)
|
Arg::new(options::WIPESYNC)
|
||||||
.short('u')
|
.short('u')
|
||||||
.long(options::REMOVE)
|
.help("deallocate and remove file after overwriting")
|
||||||
.help("truncate and remove file after overwriting; See below")
|
|
||||||
.action(ArgAction::SetTrue),
|
.action(ArgAction::SetTrue),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::REMOVE)
|
||||||
|
.long(options::REMOVE)
|
||||||
|
.value_name("HOW")
|
||||||
|
.value_parser([
|
||||||
|
options::remove::UNLINK,
|
||||||
|
options::remove::WIPE,
|
||||||
|
options::remove::WIPESYNC,
|
||||||
|
])
|
||||||
|
.num_args(0..=1)
|
||||||
|
.require_equals(true)
|
||||||
|
.default_missing_value(options::remove::WIPESYNC)
|
||||||
|
.help("like -u but give control on HOW to delete; See below")
|
||||||
|
.action(ArgAction::Set),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::VERBOSE)
|
Arg::new(options::VERBOSE)
|
||||||
.long(options::VERBOSE)
|
.long(options::VERBOSE)
|
||||||
|
@ -340,7 +384,7 @@ fn pass_name(pass_type: &PassType) -> String {
|
||||||
fn wipe_file(
|
fn wipe_file(
|
||||||
path_str: &str,
|
path_str: &str,
|
||||||
n_passes: usize,
|
n_passes: usize,
|
||||||
remove: bool,
|
remove_method: RemoveMethod,
|
||||||
size: Option<u64>,
|
size: Option<u64>,
|
||||||
exact: bool,
|
exact: bool,
|
||||||
zero: bool,
|
zero: bool,
|
||||||
|
@ -457,8 +501,8 @@ fn wipe_file(
|
||||||
.map_err_context(|| format!("{}: File write pass failed", path.maybe_quote())));
|
.map_err_context(|| format!("{}: File write pass failed", path.maybe_quote())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if remove {
|
if remove_method != RemoveMethod::None {
|
||||||
do_remove(path, path_str, verbose)
|
do_remove(path, path_str, verbose, remove_method)
|
||||||
.map_err_context(|| format!("{}: failed to remove file", path.maybe_quote()))?;
|
.map_err_context(|| format!("{}: failed to remove file", path.maybe_quote()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -501,7 +545,7 @@ fn get_file_size(path: &Path) -> Result<u64, io::Error> {
|
||||||
|
|
||||||
// Repeatedly renames the file with strings of decreasing length (most likely all 0s)
|
// Repeatedly renames the file with strings of decreasing length (most likely all 0s)
|
||||||
// Return the path of the file after its last renaming or None if error
|
// Return the path of the file after its last renaming or None if error
|
||||||
fn wipe_name(orig_path: &Path, verbose: bool) -> Option<PathBuf> {
|
fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Option<PathBuf> {
|
||||||
let file_name_len = orig_path.file_name().unwrap().to_str().unwrap().len();
|
let file_name_len = orig_path.file_name().unwrap().to_str().unwrap().len();
|
||||||
|
|
||||||
let mut last_path = PathBuf::from(orig_path);
|
let mut last_path = PathBuf::from(orig_path);
|
||||||
|
@ -526,12 +570,14 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option<PathBuf> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync every file rename
|
if remove_method == RemoveMethod::WipeSync {
|
||||||
let new_file = OpenOptions::new()
|
// Sync every file rename
|
||||||
.write(true)
|
let new_file = OpenOptions::new()
|
||||||
.open(new_path.clone())
|
.write(true)
|
||||||
.expect("Failed to open renamed file for syncing");
|
.open(new_path.clone())
|
||||||
new_file.sync_all().expect("Failed to sync renamed file");
|
.expect("Failed to open renamed file for syncing");
|
||||||
|
new_file.sync_all().expect("Failed to sync renamed file");
|
||||||
|
}
|
||||||
|
|
||||||
last_path = new_path;
|
last_path = new_path;
|
||||||
break;
|
break;
|
||||||
|
@ -552,12 +598,23 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option<PathBuf> {
|
||||||
Some(last_path)
|
Some(last_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_remove(path: &Path, orig_filename: &str, verbose: bool) -> Result<(), io::Error> {
|
fn do_remove(
|
||||||
|
path: &Path,
|
||||||
|
orig_filename: &str,
|
||||||
|
verbose: bool,
|
||||||
|
remove_method: RemoveMethod,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
if verbose {
|
if verbose {
|
||||||
show_error!("{}: removing", orig_filename.maybe_quote());
|
show_error!("{}: removing", orig_filename.maybe_quote());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rp) = wipe_name(path, verbose) {
|
let remove_path = if remove_method == RemoveMethod::Unlink {
|
||||||
|
Some(path.with_file_name(orig_filename))
|
||||||
|
} else {
|
||||||
|
wipe_name(path, verbose, remove_method)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rp) = remove_path {
|
||||||
fs::remove_file(rp)?;
|
fs::remove_file(rp)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
|
// spell-checker:ignore wipesync
|
||||||
|
|
||||||
use crate::common::util::TestScenario;
|
use crate::common::util::TestScenario;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -9,8 +12,82 @@ fn test_invalid_arg() {
|
||||||
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_remove_arg() {
|
||||||
|
new_ucmd!().arg("--remove=unknown").fails().code_is(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shred() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file = "test_shred";
|
||||||
|
let file_original_content = "test_shred file content";
|
||||||
|
|
||||||
|
at.write(file, file_original_content);
|
||||||
|
|
||||||
|
ucmd.arg(file).succeeds();
|
||||||
|
|
||||||
|
// File exists
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
// File is obfuscated
|
||||||
|
assert!(at.read_bytes(file) != file_original_content.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shred_remove() {
|
fn test_shred_remove() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file = "test_shred_remove";
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
ucmd.arg("--remove").arg(file).succeeds();
|
||||||
|
|
||||||
|
// File was deleted
|
||||||
|
assert!(!at.file_exists(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shred_remove_unlink() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file = "test_shred_remove_unlink";
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
ucmd.arg("--remove=unlink").arg(file).succeeds();
|
||||||
|
|
||||||
|
// File was deleted
|
||||||
|
assert!(!at.file_exists(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shred_remove_wipe() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file = "test_shred_remove_wipe";
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
ucmd.arg("--remove=wipe").arg(file).succeeds();
|
||||||
|
|
||||||
|
// File was deleted
|
||||||
|
assert!(!at.file_exists(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shred_remove_wipesync() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file = "test_shred_remove_wipesync";
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
ucmd.arg("--remove=wipesync").arg(file).succeeds();
|
||||||
|
|
||||||
|
// File was deleted
|
||||||
|
assert!(!at.file_exists(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shred_u() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue