From aab474017515be867485541eba805be04772fac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 31 Jul 2026 12:45:14 +0200 Subject: [PATCH 1/2] l10n: Add translations for UIoError --- src/uucore/locales/en-US.ftl | 19 ++++++++++++++ src/uucore/locales/fr-FR.ftl | 19 ++++++++++++++ src/uucore/src/lib/mods/error.rs | 45 ++++++++++++++++---------------- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl index f408b4ee69e..129a2e3ef2b 100644 --- a/src/uucore/locales/en-US.ftl +++ b/src/uucore/locales/en-US.ftl @@ -32,6 +32,25 @@ error-file-not-found = No such file or directory error-invalid-argument = Invalid argument error-is-a-directory = { $file }: Is a directory +uio-err-not-found = No such file or directory +uio-err-permission-denied = Permission denied +uio-err-conn-refused = Connection refused +uio-err-conn-reset = Connection reset +uio-err-conn-abort = Connection aborted +uio-err-not-connected = Not connected +uio-err-addr-in-use = Address in use +uio-err-addr-not-avail = Address not available +uio-err-broken-pipe = Broken pipe +uio-err-already-exists = Already exists +uio-err-would-block = Would block +uio-err-invalid-input = Invalid input +uio-err-invalid-data = Invalid data +uio-err-timed-out = Timed out +uio-err-write-zero = Write zero +uio-err-interrupted = Interrupted +uio-err-unexpected-eof = Unexpected end of file +uio-err-is-a-directory = Is a directory + # Common actions action-copying = copying action-moving = moving diff --git a/src/uucore/locales/fr-FR.ftl b/src/uucore/locales/fr-FR.ftl index 507a536cc6e..ae99d13a08b 100644 --- a/src/uucore/locales/fr-FR.ftl +++ b/src/uucore/locales/fr-FR.ftl @@ -32,6 +32,25 @@ error-file-not-found = Aucun fichier ou répertoire de ce type error-invalid-argument = Argument invalide error-is-a-directory = { $file }: Est un répertoire +uio-err-not-found = Aucun fichier ou répertoire de ce type +uio-err-permission-denied = Permission refusée +uio-err-conn-refused = Connexion refusée +uio-err-conn-reset = Connexion réinitialisée +uio-err-conn-abort = Connexion interrompue +uio-err-not-connected = Déconnecté +uio-err-addr-in-use = Address in use but in french +uio-err-addr-not-avail = Address indisponible +uio-err-broken-pipe = Broken pipe but in french +uio-err-already-exists = Already exists but in french +uio-err-would-block = Would block but in french +uio-err-invalid-input = Invalid input but in french +uio-err-invalid-data = Invalid data but in french +uio-err-timed-out = Timed out but in french +uio-err-write-zero = Write zero but in french +uio-err-interrupted = Interrupted but in french +uio-err-unexpected-eof = Unexpected end of file but in french +uio-err-is-a-directory = Est un répertoire + # Actions communes action-copying = copie action-moving = déplacement diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index b4e35928c68..52979d26f9d 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -62,6 +62,8 @@ use std::{ sync::atomic::{AtomicI32, Ordering}, }; +use crate::translate; + static EXIT_CODE: AtomicI32 = AtomicI32::new(0); /// Get the last exit code set with [`set_exit_code`]. @@ -409,36 +411,34 @@ impl Display for UIoError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { use std::io::ErrorKind::*; - let message; let message = if self.inner.raw_os_error().is_some() { // These are errors that come directly from the OS. // We want to normalize their messages across systems, // and we want to strip the "(os error X)" suffix. match self.inner.kind() { - NotFound => "No such file or directory", - PermissionDenied => "Permission denied", - ConnectionRefused => "Connection refused", - ConnectionReset => "Connection reset", - ConnectionAborted => "Connection aborted", - NotConnected => "Not connected", - AddrInUse => "Address in use", - AddrNotAvailable => "Address not available", - BrokenPipe => "Broken pipe", - AlreadyExists => "Already exists", - WouldBlock => "Would block", - InvalidInput => "Invalid input", - InvalidData => "Invalid data", - TimedOut => "Timed out", - WriteZero => "Write zero", - Interrupted => "Interrupted", - UnexpectedEof => "Unexpected end of file", - IsADirectory => "Is a directory", + NotFound => translate!("uio-err-not-found"), + PermissionDenied => translate!("uio-err-permission-denied"), + ConnectionRefused => translate!("uio-err-conn-refused"), + ConnectionReset => translate!("uio-err-conn-reset"), + ConnectionAborted => translate!("uio-err-conn-abort"), + NotConnected => translate!("uio-err-not-connected"), + AddrInUse => translate!("uio-err-addr-in-use"), + AddrNotAvailable => translate!("uio-err-addr-not-avail"), + BrokenPipe => translate!("uio-err-broken-pipe"), + AlreadyExists => translate!("uio-err-already-exists"), + WouldBlock => translate!("uio-err-would-block"), + InvalidInput => translate!("uio-err-invalid-input"), + InvalidData => translate!("uio-err-invalid-data"), + TimedOut => translate!("uio-err-timed-out"), + WriteZero => translate!("uio-err-write-zero"), + Interrupted => translate!("uio-err-interrupted"), + UnexpectedEof => translate!("uio-err-unexpected-eof"), + IsADirectory => translate!("uio-err-is-a-directory"), _ => { // TODO: When the new error variants // (https://github.com/rust-lang/rust/issues/86442) // are stabilized, we should add them to the match statement. - message = strip_errno(&self.inner); - &message + strip_errno(&self.inner) } } } else { @@ -447,8 +447,7 @@ impl Display for UIoError { // For example, ErrorKind::NotFound doesn't necessarily mean it was // a file that was not found. // There are also errors with entirely custom messages. - message = self.inner.to_string(); - &message + self.inner.to_string() }; if let Some(ctx) = &self.context { write!(f, "{ctx}: {message}") From 0420d11f26eeac0bc0ac1d874c00fbf688d533b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 31 Jul 2026 23:16:18 +0200 Subject: [PATCH 2/2] uucore(error): Normalize error messages --- src/uu/du/src/du.rs | 3 +- src/uu/mv/src/mv.rs | 3 +- .../src/lib/features/checksum/validate.rs | 14 ++--- src/uucore/src/lib/features/fs.rs | 7 +-- src/uucore/src/lib/mods/error.rs | 63 ++++++++----------- tests/by-util/test_cp.rs | 2 +- 6 files changed, 36 insertions(+), 56 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index f30127ef7b0..035744a8d56 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -642,8 +642,7 @@ fn du_regular( // Check symlink depth limit if current_symlink_depth > MAX_SYMLINK_DEPTH { - print_tx.send(Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, + print_tx.send(Err(std::io::Error::other( "Too many levels of symbolic links", ).map_err_context( || translate!("du-error-cannot-access", "path" => entry_path.quote()), diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 1beab05af35..a152485935b 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -367,8 +367,7 @@ fn parse_paths(files: &[OsString], opts: &Options) -> Vec { fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> { // `mv` never follows a symlink source, so the guard must not either. if backup_would_destroy_source(source, target, &opts.suffix, opts.backup, false) { - return Err(io::Error::new( - io::ErrorKind::NotFound, + return Err(io::Error::other( translate!("mv-error-backup-might-destroy-source", "target" => target.quote(), "source" => source.quote()), ) .into()); diff --git a/src/uucore/src/lib/features/checksum/validate.rs b/src/uucore/src/lib/features/checksum/validate.rs index d10d9588e06..59e662a5354 100644 --- a/src/uucore/src/lib/features/checksum/validate.rs +++ b/src/uucore/src/lib/features/checksum/validate.rs @@ -595,20 +595,14 @@ fn get_input_file(filename: &OsStr) -> UResult> { match File::open(filename) { Ok(f) => { if f.metadata()?.is_dir() { - Err(io::Error::other( - translate!("error-is-a-directory", "file" => filename.maybe_quote()), - ) - .into()) + Err(io::Error::from(io::ErrorKind::IsADirectory) + .map_err_context(|| filename.maybe_quote().to_string())) } else { Ok(Box::new(f)) } } - Err(_) => Err(io::Error::other(format!( - "{}: {}", - filename.maybe_quote(), - translate!("error-file-not-found") - )) - .into()), + Err(_) => Err(io::Error::from(io::ErrorKind::NotFound) + .map_err_context(|| filename.maybe_quote().to_string())), } } diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 199d1ca6cbb..5bf8f61a2d3 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -17,7 +17,7 @@ use std::fs; use std::fs::read_dir; use std::hash::Hash; use std::io::Stdin; -use std::io::{Error, ErrorKind, Result as IOResult}; +use std::io::{Error, Result as IOResult}; #[cfg(any(unix, all(target_os = "wasi", target_env = "p2")))] use std::os::fd::AsFd; #[cfg(unix)] @@ -435,10 +435,7 @@ pub fn canonicalize>( path_to_follow.push(part.as_os_str()); } if !visited_files.insert((file_info, path_to_follow)) { - return Err(Error::new( - ErrorKind::InvalidInput, - "Too many levels of symbolic links", - )); // TODO use ErrorKind::FilesystemLoop when stable + return Err(Error::other("Too many levels of symbolic links")); // TODO use ErrorKind::FilesystemLoop when stable } } result.pop(); diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 52979d26f9d..2f7fe8bb06e 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -411,43 +411,34 @@ impl Display for UIoError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { use std::io::ErrorKind::*; - let message = if self.inner.raw_os_error().is_some() { - // These are errors that come directly from the OS. - // We want to normalize their messages across systems, - // and we want to strip the "(os error X)" suffix. - match self.inner.kind() { - NotFound => translate!("uio-err-not-found"), - PermissionDenied => translate!("uio-err-permission-denied"), - ConnectionRefused => translate!("uio-err-conn-refused"), - ConnectionReset => translate!("uio-err-conn-reset"), - ConnectionAborted => translate!("uio-err-conn-abort"), - NotConnected => translate!("uio-err-not-connected"), - AddrInUse => translate!("uio-err-addr-in-use"), - AddrNotAvailable => translate!("uio-err-addr-not-avail"), - BrokenPipe => translate!("uio-err-broken-pipe"), - AlreadyExists => translate!("uio-err-already-exists"), - WouldBlock => translate!("uio-err-would-block"), - InvalidInput => translate!("uio-err-invalid-input"), - InvalidData => translate!("uio-err-invalid-data"), - TimedOut => translate!("uio-err-timed-out"), - WriteZero => translate!("uio-err-write-zero"), - Interrupted => translate!("uio-err-interrupted"), - UnexpectedEof => translate!("uio-err-unexpected-eof"), - IsADirectory => translate!("uio-err-is-a-directory"), - _ => { - // TODO: When the new error variants - // (https://github.com/rust-lang/rust/issues/86442) - // are stabilized, we should add them to the match statement. - strip_errno(&self.inner) - } + // These are errors that come directly from the OS. + // We want to normalize their messages across systems, + // and we want to strip the "(os error X)" suffix. + let message = match self.inner.kind() { + NotFound => translate!("uio-err-not-found"), + PermissionDenied => translate!("uio-err-permission-denied"), + ConnectionRefused => translate!("uio-err-conn-refused"), + ConnectionReset => translate!("uio-err-conn-reset"), + ConnectionAborted => translate!("uio-err-conn-abort"), + NotConnected => translate!("uio-err-not-connected"), + AddrInUse => translate!("uio-err-addr-in-use"), + AddrNotAvailable => translate!("uio-err-addr-not-avail"), + BrokenPipe => translate!("uio-err-broken-pipe"), + AlreadyExists => translate!("uio-err-already-exists"), + WouldBlock => translate!("uio-err-would-block"), + InvalidInput => translate!("uio-err-invalid-input"), + InvalidData => translate!("uio-err-invalid-data"), + TimedOut => translate!("uio-err-timed-out"), + WriteZero => translate!("uio-err-write-zero"), + Interrupted => translate!("uio-err-interrupted"), + UnexpectedEof => translate!("uio-err-unexpected-eof"), + IsADirectory => translate!("uio-err-is-a-directory"), + _ => { + // TODO: When the new error variants + // (https://github.com/rust-lang/rust/issues/86442) + // are stabilized, we should add them to the match statement. + strip_errno(&self.inner) } - } else { - // These messages don't need as much normalization, and the above - // messages wouldn't always be a good substitute. - // For example, ErrorKind::NotFound doesn't necessarily mean it was - // a file that was not found. - // There are also errors with entirely custom messages. - self.inner.to_string() }; if let Some(ctx) = &self.context { write!(f, "{ctx}: {message}") diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 9ad4ef75c4d..373373fc06d 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -4157,7 +4157,7 @@ fn test_copy_dir_preserve_permissions_inaccessible_file() { // V V V V ucmd.args(&["-p", "-R", "d1", "d2"]) .fails_with_code(1) - .stderr_only("cp: cannot open 'd1/f' for reading: permission denied\n"); + .stderr_only("cp: cannot open 'd1/f' for reading: Permission denied\n"); assert!(at.dir_exists("d2")); assert!(!at.file_exists("d2/f"));