diff --git a/src/uu/shred/locales/en-US.ftl b/src/uu/shred/locales/en-US.ftl index 90245a08f..61e68772d 100644 --- a/src/uu/shred/locales/en-US.ftl +++ b/src/uu/shred/locales/en-US.ftl @@ -35,3 +35,33 @@ shred-after-help = Delete FILE(s) if --remove (-u) is specified. The default is In addition, file system backups and remote mirrors may contain copies of the file that cannot be removed, and that will allow a shredded file to be recovered later. + +# Error messages +shred-missing-file-operand = missing file operand +shred-invalid-number-of-passes = invalid number of passes: {$passes} +shred-cannot-open-random-source = cannot open random source: {$source} +shred-invalid-file-size = invalid file size: {$size} +shred-no-such-file-or-directory = {$file}: No such file or directory +shred-not-a-file = {$file}: Not a file + +# Option help text +shred-force-help = change permissions to allow writing if necessary +shred-iterations-help = overwrite N times instead of the default (3) +shred-size-help = shred this many bytes (suffixes like K, M, G accepted) +shred-deallocate-help = deallocate and remove file after overwriting +shred-remove-help = like -u but give control on HOW to delete; See below +shred-verbose-help = show progress +shred-exact-help = do not round file sizes up to the next full block; + this is the default for non-regular files +shred-zero-help = add a final overwrite with zeros to hide shredding +shred-random-source-help = take random bytes from FILE + +# Verbose messages +shred-removing = {$file}: removing +shred-removed = {$file}: removed +shred-renamed-to = renamed to +shred-pass-progress = {$file}: pass +shred-couldnt-rename = {$file}: Couldn't rename to {$new_name}: {$error} +shred-failed-to-open-for-writing = {$file}: failed to open for writing +shred-file-write-pass-failed = {$file}: File write pass failed +shred-failed-to-remove-file = {$file}: failed to remove file diff --git a/src/uu/shred/locales/fr-FR.ftl b/src/uu/shred/locales/fr-FR.ftl new file mode 100644 index 000000000..52491f0e0 --- /dev/null +++ b/src/uu/shred/locales/fr-FR.ftl @@ -0,0 +1,66 @@ +shred-about = Écrase les FICHIER(s) spécifiés de manière répétée, afin de rendre plus difficile + même pour du matériel de sondage très coûteux de récupérer les données. +shred-usage = shred [OPTION]... FICHIER... +shred-after-help = Supprime le ou les FICHIER(s) si --remove (-u) est spécifié. Par défaut, les fichiers + ne sont pas supprimés car il est courant d'opérer sur des fichiers de périphérique comme /dev/hda, + et ces fichiers ne doivent généralement pas être supprimés. + + ATTENTION : Notez que shred repose sur une hypothèse très importante : que le système + de fichiers écrase les données sur place. C'est la façon traditionnelle de procéder, mais + de nombreuses conceptions de systèmes de fichiers modernes ne satisfont pas cette hypothèse. + Voici des exemples de systèmes de fichiers sur lesquels shred n'est pas efficace, ou n'est pas + garanti d'être efficace dans tous les modes de système de fichiers : + + - systèmes de fichiers structurés en journal ou en log, tels que ceux fournis avec + AIX et Solaris (et JFS, ReiserFS, XFS, Ext3, etc.) + + - systèmes de fichiers qui écrivent des données redondantes et continuent même si certaines écritures + échouent, tels que les systèmes de fichiers basés sur RAID + + - systèmes de fichiers qui font des instantanés, tels que le serveur NFS de Network Appliance + + - systèmes de fichiers qui mettent en cache dans des emplacements temporaires, tels que NFS + version 3 clients + + - systèmes de fichiers compressés + + Dans le cas des systèmes de fichiers ext3, la clause de non-responsabilité ci-dessus s'applique (et shred est + donc d'efficacité limitée) seulement en mode data=journal, qui journalise les données de fichier + en plus des métadonnées seulement. Dans les modes data=ordered (par défaut) et + data=writeback, shred fonctionne comme d'habitude. Les modes de journal Ext3 peuvent être changés + en ajoutant l'option data=something aux options de montage pour un système de fichiers particulier + dans le fichier /etc/fstab, comme documenté dans la page de manuel mount (`man mount`). + + De plus, les sauvegardes de système de fichiers et les miroirs distants peuvent contenir des copies + du fichier qui ne peuvent pas être supprimées, et qui permettront à un fichier détruit d'être + récupéré plus tard. + +# Messages d'erreur +shred-missing-file-operand = opérande de fichier manquant +shred-invalid-number-of-passes = nombre de passes invalide : {$passes} +shred-cannot-open-random-source = impossible d'ouvrir la source aléatoire : {$source} +shred-invalid-file-size = taille de fichier invalide : {$size} +shred-no-such-file-or-directory = {$file} : Aucun fichier ou répertoire de ce type +shred-not-a-file = {$file} : N'est pas un fichier + +# Texte d'aide des options +shred-force-help = modifier les permissions pour permettre l'écriture si nécessaire +shred-iterations-help = écraser N fois au lieu de la valeur par défaut (3) +shred-size-help = détruire ce nombre d'octets (suffixes comme K, M, G acceptés) +shred-deallocate-help = désallouer et supprimer le fichier après écrasement +shred-remove-help = comme -u mais donne le contrôle sur COMMENT supprimer ; Voir ci-dessous +shred-verbose-help = afficher le progrès +shred-exact-help = ne pas arrondir les tailles de fichier au bloc complet suivant ; + c'est la valeur par défaut pour les fichiers non réguliers +shred-zero-help = ajouter un écrasement final avec des zéros pour cacher la destruction +shred-random-source-help = prendre des octets aléatoires du FICHIER + +# Messages verbeux +shred-removing = {$file} : suppression +shred-removed = {$file} : supprimé +shred-renamed-to = renommé en +shred-pass-progress = {$file}: passage +shred-couldnt-rename = {$file} : Impossible de renommer en {$new_name} : {$error} +shred-failed-to-open-for-writing = {$file} : impossible d'ouvrir pour l'écriture +shred-file-write-pass-failed = {$file} : Échec du passage d'écriture de fichier +shred-failed-to-remove-file = {$file} : impossible de supprimer le fichier diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 691e36bc2..493660fb2 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) wipesync prefill +// spell-checker:ignore (words) wipesync prefill couldnt use clap::{Arg, ArgAction, Command}; #[cfg(unix)] @@ -20,7 +20,8 @@ use uucore::parser::parse_size::parse_size_u64; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, show_error, show_if_err}; -use uucore::locale::get_message; +use std::collections::HashMap; +use uucore::locale::{get_message, get_message_with_args}; pub mod options { pub const FORCE: &str = "force"; @@ -242,7 +243,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; if !matches.contains_id(options::FILE) { - return Err(UUsageError::new(1, "missing file operand")); + return Err(UUsageError::new( + 1, + get_message("shred-missing-file-operand"), + )); } let iterations = match matches.get_one::(options::ITERATIONS) { @@ -251,7 +255,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Err(_) => { return Err(USimpleError::new( 1, - format!("invalid number of passes: {}", s.quote()), + get_message_with_args( + "shred-invalid-number-of-passes", + HashMap::from([("passes".to_string(), s.quote().to_string())]), + ), )); } }, @@ -262,7 +269,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Some(filepath) => RandomSource::Read(File::open(filepath).map_err(|_| { USimpleError::new( 1, - format!("cannot open random source: {}", filepath.quote()), + get_message_with_args( + "shred-cannot-open-random-source", + HashMap::from([("source".to_string(), filepath.quote().to_string())]), + ), ) })?), None => RandomSource::System, @@ -321,14 +331,14 @@ pub fn uu_app() -> Command { Arg::new(options::FORCE) .long(options::FORCE) .short('f') - .help("change permissions to allow writing if necessary") + .help(get_message("shred-force-help")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::ITERATIONS) .long(options::ITERATIONS) .short('n') - .help("overwrite N times instead of the default (3)") + .help(get_message("shred-iterations-help")) .value_name("NUMBER") .default_value("3"), ) @@ -337,12 +347,12 @@ pub fn uu_app() -> Command { .long(options::SIZE) .short('s') .value_name("N") - .help("shred this many bytes (suffixes like K, M, G accepted)"), + .help(get_message("shred-size-help")), ) .arg( Arg::new(options::WIPESYNC) .short('u') - .help("deallocate and remove file after overwriting") + .help(get_message("shred-deallocate-help")) .action(ArgAction::SetTrue), ) .arg( @@ -357,37 +367,34 @@ pub fn uu_app() -> Command { .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") + .help(get_message("shred-remove-help")) .action(ArgAction::Set), ) .arg( Arg::new(options::VERBOSE) .long(options::VERBOSE) .short('v') - .help("show progress") + .help(get_message("shred-verbose-help")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::EXACT) .long(options::EXACT) .short('x') - .help( - "do not round file sizes up to the next full block;\n\ - this is the default for non-regular files", - ) + .help(get_message("shred-exact-help")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::ZERO) .long(options::ZERO) .short('z') - .help("add a final overwrite with zeros to hide shredding") + .help(get_message("shred-zero-help")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::RANDOM_SOURCE) .long(options::RANDOM_SOURCE) - .help("take random bytes from FILE") + .help(get_message("shred-random-source-help")) .value_hint(clap::ValueHint::FilePath) .action(ArgAction::Set), ) @@ -405,7 +412,13 @@ fn get_size(size_str_opt: Option) -> Option { .and_then(|size| parse_size_u64(size.as_str()).ok()) .or_else(|| { if let Some(size) = size_str_opt { - show_error!("invalid file size: {}", size.quote()); + show_error!( + "{}", + get_message_with_args( + "shred-invalid-file-size", + HashMap::from([("size".to_string(), size.quote().to_string())]) + ) + ); // TODO: replace with our error management std::process::exit(1); } @@ -439,13 +452,19 @@ fn wipe_file( if !path.exists() { return Err(USimpleError::new( 1, - format!("{}: No such file or directory", path.maybe_quote()), + get_message_with_args( + "shred-no-such-file-or-directory", + HashMap::from([("file".to_string(), path.maybe_quote().to_string())]), + ), )); } if !path.is_file() { return Err(USimpleError::new( 1, - format!("{}: Not a file", path.maybe_quote()), + get_message_with_args( + "shred-not-a-file", + HashMap::from([("file".to_string(), path.maybe_quote().to_string())]), + ), )); } @@ -518,7 +537,12 @@ fn wipe_file( .write(true) .truncate(false) .open(path) - .map_err_context(|| format!("{}: failed to open for writing", path.maybe_quote()))?; + .map_err_context(|| { + get_message_with_args( + "shred-failed-to-open-for-writing", + HashMap::from([("file".to_string(), path.maybe_quote().to_string())]), + ) + })?; let size = match size { Some(size) => size, @@ -528,23 +552,35 @@ fn wipe_file( for (i, pass_type) in pass_sequence.into_iter().enumerate() { if verbose { let pass_name = pass_name(&pass_type); + let msg = get_message_with_args( + "shred-pass-progress", + HashMap::from([("file".to_string(), path.maybe_quote().to_string())]), + ); show_error!( - "{}: pass {}/{total_passes} ({pass_name})...", - path.maybe_quote(), - i + 1, + "{} {}/{total_passes} ({pass_name})...", + msg, + (i + 1).to_string() ); } // size is an optional argument for exactly how many bytes we want to shred // Ignore failed writes; just keep trying show_if_err!( - do_pass(&mut file, &pass_type, exact, random_source, size) - .map_err_context(|| format!("{}: File write pass failed", path.maybe_quote())) + do_pass(&mut file, &pass_type, exact, random_source, size).map_err_context(|| { + get_message_with_args( + "shred-file-write-pass-failed", + HashMap::from([("file".to_string(), path.maybe_quote().to_string())]), + ) + }) ); } if remove_method != RemoveMethod::None { - do_remove(path, path_str, verbose, remove_method) - .map_err_context(|| format!("{}: failed to remove file", path.maybe_quote()))?; + do_remove(path, path_str, verbose, remove_method).map_err_context(|| { + get_message_with_args( + "shred-failed-to-remove-file", + HashMap::from([("file".to_string(), path.maybe_quote().to_string())]), + ) + })?; } Ok(()) } @@ -615,9 +651,10 @@ fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Op Ok(()) => { if verbose { show_error!( - "{}: renamed to {}", - last_path.maybe_quote(), - new_path.display() + "{}: {} {}", + last_path.maybe_quote().to_string(), + get_message("shred-renamed-to",), + new_path.display().to_string() ); } @@ -634,11 +671,15 @@ fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Op break; } Err(e) => { - show_error!( - "{}: Couldn't rename to {}: {e}", - last_path.maybe_quote(), - new_path.quote(), + let msg = get_message_with_args( + "shred-couldnt-rename", + HashMap::from([ + ("file".to_string(), last_path.maybe_quote().to_string()), + ("new_name".to_string(), new_path.quote().to_string()), + ("error".to_string(), e.to_string()), + ]), ); + show_error!("{}", msg); // TODO: replace with our error management std::process::exit(1); } @@ -656,7 +697,13 @@ fn do_remove( remove_method: RemoveMethod, ) -> Result<(), io::Error> { if verbose { - show_error!("{}: removing", orig_filename.maybe_quote()); + show_error!( + "{}", + get_message_with_args( + "shred-removing", + HashMap::from([("file".to_string(), orig_filename.maybe_quote().to_string())]) + ) + ); } let remove_path = if remove_method == RemoveMethod::Unlink { @@ -670,7 +717,13 @@ fn do_remove( } if verbose { - show_error!("{}: removed", orig_filename.maybe_quote()); + show_error!( + "{}", + get_message_with_args( + "shred-removed", + HashMap::from([("file".to_string(), orig_filename.maybe_quote().to_string())]) + ) + ); } Ok(()) diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 9c810ae64..33248341a 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -292,3 +292,22 @@ fn test_random_source_dir() { .fails() .stderr_only("shred: foo.txt: pass 1/3 (random)...\nshred: foo.txt: File write pass failed: Is a directory\n"); } + +#[test] +fn test_shred_rename_exhaustion() { + // GNU: tests/shred/shred-remove.sh + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test"); + at.touch("000"); + + let result = scene.ucmd().arg("-vu").arg("test").succeeds(); + + result.stderr_contains("renamed to 0000"); + result.stderr_contains("renamed to 001"); + result.stderr_contains("renamed to 00"); + result.stderr_contains("removed"); + + assert!(!at.file_exists("test")); +}