diff --git a/Cargo.lock b/Cargo.lock index 1e5cf14b9..9e587c67e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2976,6 +2976,7 @@ version = "0.1.0" dependencies = [ "clap", "libc", + "thiserror 2.0.12", "uucore", ] diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 09f1c531a..9049987d5 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -20,6 +20,7 @@ path = "src/chmod.rs" [dependencies] clap = { workspace = true } libc = { workspace = true } +thiserror = { workspace = true } uucore = { workspace = true, features = ["entries", "fs", "mode", "perms"] } [[bin]] diff --git a/src/uu/chmod/locales/en-US.ftl b/src/uu/chmod/locales/en-US.ftl index c0d4d39ab..52447f263 100644 --- a/src/uu/chmod/locales/en-US.ftl +++ b/src/uu/chmod/locales/en-US.ftl @@ -4,3 +4,28 @@ chmod-usage = chmod [OPTION]... MODE[,MODE]... FILE... chmod [OPTION]... OCTAL-MODE FILE... chmod [OPTION]... --reference=RFILE FILE... chmod-after-help = Each MODE is of the form [ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+. +chmod-error-cannot-stat = cannot stat attributes of {$file} +chmod-error-dangling-symlink = cannot operate on dangling symlink {$file} +chmod-error-no-such-file = cannot access {$file}: No such file or directory +chmod-error-preserve-root = it is dangerous to operate recursively on {$file} + chmod: use --no-preserve-root to override this failsafe +chmod-error-permission-denied = {$file}: Permission denied +chmod-error-new-permissions = {$file}: new permissions are {$actual}, not {$expected} +chmod-error-missing-operand = missing operand + +# Help messages +chmod-help-print-help = Print help information. +chmod-help-changes = like verbose but report only when a change is made +chmod-help-quiet = suppress most error messages +chmod-help-verbose = output a diagnostic for every file processed +chmod-help-no-preserve-root = do not treat '/' specially (the default) +chmod-help-preserve-root = fail to operate recursively on '/' +chmod-help-recursive = change files and directories recursively +chmod-help-reference = use RFILE's mode instead of MODE values + +# Verbose messages +chmod-verbose-failed-dangling = failed to change mode of {$file} from 0000 (---------) to 1500 (r-x-----T) +chmod-verbose-neither-changed = neither symbolic link {$file} nor referent has been changed +chmod-verbose-mode-retained = mode of {$file} retained as {$mode_octal} ({$mode_display}) +chmod-verbose-failed-change = failed to change mode of file {$file} from {$old_mode} ({$old_mode_display}) to {$new_mode} ({$new_mode_display}) +chmod-verbose-mode-changed = mode of {$file} changed from {$old_mode} ({$old_mode_display}) to {$new_mode} ({$new_mode_display}) diff --git a/src/uu/chmod/locales/fr-FR.ftl b/src/uu/chmod/locales/fr-FR.ftl new file mode 100644 index 000000000..051835628 --- /dev/null +++ b/src/uu/chmod/locales/fr-FR.ftl @@ -0,0 +1,33 @@ +chmod-about = Changer le mode de chaque FICHIER vers MODE. + Avec --reference, changer le mode de chaque FICHIER vers celui de RFICHIER. +chmod-usage = chmod [OPTION]... MODE[,MODE]... FICHIER... + chmod [OPTION]... MODE-OCTAL FICHIER... + chmod [OPTION]... --reference=RFICHIER FICHIER... +chmod-after-help = Chaque MODE est de la forme [ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+. + +# Messages d'aide +chmod-help-print-help = Afficher les informations d'aide. +chmod-help-changes = comme verbeux mais rapporter seulement lors d'un changement +chmod-help-quiet = supprimer la plupart des messages d'erreur +chmod-help-verbose = afficher un diagnostic pour chaque fichier traité +chmod-help-no-preserve-root = ne pas traiter '/' spécialement (par défaut) +chmod-help-preserve-root = échouer à opérer récursivement sur '/' +chmod-help-recursive = changer les fichiers et répertoires récursivement +chmod-help-reference = utiliser le mode de RFICHIER au lieu des valeurs de MODE + +# Messages d'erreur +chmod-error-cannot-stat = impossible d'obtenir les attributs de {$file} +chmod-error-dangling-symlink = impossible d'opérer sur le lien symbolique pendouillant {$file} +chmod-error-no-such-file = impossible d'accéder à {$file} : Aucun fichier ou dossier de ce type +chmod-error-preserve-root = il est dangereux d'opérer récursivement sur {$file} + chmod: utiliser --no-preserve-root pour outrepasser cette protection +chmod-error-permission-denied = {$file} : Permission refusée +chmod-error-new-permissions = {$file} : les nouvelles permissions sont {$actual}, pas {$expected} +chmod-error-missing-operand = opérande manquant + +# Messages verbeux/de statut +chmod-verbose-failed-dangling = échec du changement de mode de {$file} de 0000 (---------) vers 1500 (r-x-----T) +chmod-verbose-neither-changed = ni le lien symbolique {$file} ni la référence n'ont été changés +chmod-verbose-mode-retained = mode de {$file} conservé comme {$mode_octal} ({$mode_display}) +chmod-verbose-failed-change = échec du changement de mode du fichier {$file} de {$old_mode} ({$old_mode_display}) vers {$new_mode} ({$new_mode_display}) +chmod-verbose-mode-changed = mode de {$file} changé de {$old_mode} ({$old_mode_display}) vers {$new_mode} ({$new_mode_display}) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 7367d4b76..2566c7c29 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -6,12 +6,14 @@ // spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's use clap::{Arg, ArgAction, Command}; +use std::collections::HashMap; use std::ffi::OsString; use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; +use thiserror::Error; use uucore::display::Quotable; -use uucore::error::{ExitCode, UResult, USimpleError, UUsageError, set_exit_code}; +use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError, set_exit_code}; use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] @@ -19,7 +21,25 @@ use uucore::mode; use uucore::perms::{TraverseSymlinks, configure_symlink_and_recursion}; use uucore::{format_usage, show, show_error}; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; + +#[derive(Debug, Error)] +enum ChmodError { + #[error("{}", get_message_with_args("chmod-error-cannot-stat", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + CannotStat(String), + #[error("{}", get_message_with_args("chmod-error-dangling-symlink", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + DanglingSymlink(String), + #[error("{}", get_message_with_args("chmod-error-no-such-file", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + NoSuchFile(String), + #[error("{}", get_message_with_args("chmod-error-preserve-root", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + PreserveRoot(String), + #[error("{}", get_message_with_args("chmod-error-permission-denied", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + PermissionDenied(String), + #[error("{}", get_message_with_args("chmod-error-new-permissions", HashMap::from([("file".to_string(), _0.clone()), ("actual".to_string(), _1.clone()), ("expected".to_string(), _2.clone())])))] + NewPermissions(String, String, String), +} + +impl UError for ChmodError {} mod options { pub const HELP: &str = "help"; @@ -103,11 +123,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let fmode = match matches.get_one::(options::REFERENCE) { Some(fref) => match fs::metadata(fref) { Ok(meta) => Some(meta.mode() & 0o7777), - Err(err) => { - return Err(USimpleError::new( - 1, - format!("cannot stat attributes of {}: {err}", fref.quote()), - )); + Err(_) => { + return Err(ChmodError::CannotStat(fref.to_string()).into()); } }, None => None, @@ -135,7 +152,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; if files.is_empty() { - return Err(UUsageError::new(1, "missing operand".to_string())); + return Err(UUsageError::new( + 1, + get_message("chmod-error-missing-operand"), + )); } let (recursive, dereference, traverse_symlinks) = @@ -168,14 +188,14 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::HELP) .long(options::HELP) - .help("Print help information.") + .help(get_message("chmod-help-print-help")) .action(ArgAction::Help), ) .arg( Arg::new(options::CHANGES) .long(options::CHANGES) .short('c') - .help("like verbose but report only when a change is made") + .help(get_message("chmod-help-changes")) .action(ArgAction::SetTrue), ) .arg( @@ -183,40 +203,40 @@ pub fn uu_app() -> Command { .long(options::QUIET) .visible_alias("silent") .short('f') - .help("suppress most error messages") + .help(get_message("chmod-help-quiet")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::VERBOSE) .long(options::VERBOSE) .short('v') - .help("output a diagnostic for every file processed") + .help(get_message("chmod-help-verbose")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::NO_PRESERVE_ROOT) .long(options::NO_PRESERVE_ROOT) - .help("do not treat '/' specially (the default)") + .help(get_message("chmod-help-no-preserve-root")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::PRESERVE_ROOT) .long(options::PRESERVE_ROOT) - .help("fail to operate recursively on '/'") + .help(get_message("chmod-help-preserve-root")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::RECURSIVE) .long(options::RECURSIVE) .short('R') - .help("change files and directories recursively") + .help(get_message("chmod-help-recursive")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::REFERENCE) .long("reference") .value_hint(clap::ValueHint::FilePath) - .help("use RFILE's mode instead of MODE values"), + .help(get_message("chmod-help-reference")), ) .arg( Arg::new(options::MODE).required_unless_present(options::REFERENCE), @@ -265,27 +285,21 @@ impl Chmoder { } if !self.quiet { - show!(USimpleError::new( - 1, - format!("cannot operate on dangling symlink {}", filename.quote()), - )); + show!(ChmodError::DanglingSymlink(filename.to_string())); set_exit_code(1); } if self.verbose { println!( - "failed to change mode of {} from 0000 (---------) to 1500 (r-x-----T)", - filename.quote() + "{}", + get_message_with_args( + "chmod-verbose-failed-dangling", + HashMap::from([("file".to_string(), filename.quote().to_string())]) + ) ); } } else if !self.quiet { - show!(USimpleError::new( - 1, - format!( - "cannot access {}: No such file or directory", - filename.quote() - ) - )); + show!(ChmodError::NoSuchFile(filename.to_string())); } // GNU exits with exit code 1 even if -q or --quiet are passed // So we set the exit code, because it hasn't been set yet if `self.quiet` is true. @@ -298,13 +312,7 @@ impl Chmoder { continue; } if self.recursive && self.preserve_root && filename == "/" { - return Err(USimpleError::new( - 1, - format!( - "it is dangerous to operate recursively on {}\nchmod: use --no-preserve-root to override this failsafe", - filename.quote() - ), - )); + return Err(ChmodError::PreserveRoot(filename.to_string()).into()); } if self.recursive { r = self.walk_dir(file); @@ -368,12 +376,9 @@ impl Chmoder { } else if err.kind() == std::io::ErrorKind::PermissionDenied { // These two filenames would normally be conditionally // quoted, but GNU's tests expect them to always be quoted - Err(USimpleError::new( - 1, - format!("{}: Permission denied", file.quote()), - )) + Err(ChmodError::PermissionDenied(file.to_string_lossy().to_string()).into()) } else { - Err(USimpleError::new(1, format!("{}: {err}", file.quote()))) + Err(ChmodError::CannotStat(file.to_string_lossy().to_string()).into()) }; } }; @@ -420,15 +425,12 @@ impl Chmoder { self.change_file(fperm, new_mode, file)?; // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail if (new_mode & !naively_expected_new_mode) != 0 { - return Err(USimpleError::new( - 1, - format!( - "{}: new permissions are {}, not {}", - file.maybe_quote(), - display_permissions_unix(new_mode as mode_t, false), - display_permissions_unix(naively_expected_new_mode as mode_t, false) - ), - )); + return Err(ChmodError::NewPermissions( + file.to_string_lossy().to_string(), + display_permissions_unix(new_mode as mode_t, false), + display_permissions_unix(naively_expected_new_mode as mode_t, false), + ) + .into()); } } }