From d8f7f0eba64ce0d3d84c8509abe70727d7852112 Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 25 Dec 2025 20:46:59 +0900 Subject: [PATCH 1/6] feat(rm): add localized prompt messages for interactive mode Add translations for rm command prompts in English and French, and update Linux-specific code to use localized strings instead of hardcoded messages, improving internationalization support. --- src/uu/rm/locales/en-US.ftl | 19 +++++++ src/uu/rm/locales/fr-FR.ftl | 19 +++++++ src/uu/rm/src/platform/unix.rs | 57 +++++++++++++++----- src/uu/rm/src/rm.rs | 99 +++++++++++++++++++++++----------- 4 files changed, 150 insertions(+), 44 deletions(-) diff --git a/src/uu/rm/locales/en-US.ftl b/src/uu/rm/locales/en-US.ftl index 2d4486ce2fe..f79c954f32a 100644 --- a/src/uu/rm/locales/en-US.ftl +++ b/src/uu/rm/locales/en-US.ftl @@ -33,6 +33,25 @@ rm-help-progress = display a progress bar. Note: this feature is not supported b # Progress messages rm-progress-removing = Removing +# Prompt messages +rm-prompt-remove-arguments = remove { $count -> + [one] { $count } argument? + *[other] { $count } arguments? +} +rm-prompt-remove-arguments-recursive = remove { $count -> + [one] { $count } argument recursively? + *[other] { $count } arguments recursively? +} +rm-prompt-remove-symbolic-link = remove symbolic link { $file }? +rm-prompt-remove-regular-empty-file = remove regular empty file { $file }? +rm-prompt-remove-file = remove file { $file }? +rm-prompt-remove-write-protected-regular-empty-file = remove write-protected regular empty file { $file }? +rm-prompt-remove-write-protected-regular-file = remove write-protected regular file { $file }? +rm-prompt-attempt-remove-inaccessible-directory = attempt removal of inaccessible directory { $path }? +rm-prompt-remove-write-protected-directory = remove write-protected directory { $path }? +rm-prompt-remove-directory = remove directory { $path }? +rm-prompt-descend-into-directory = descend into directory { $path }? + # Error messages rm-error-missing-operand = missing operand Try '{$util_name} --help' for more information. diff --git a/src/uu/rm/locales/fr-FR.ftl b/src/uu/rm/locales/fr-FR.ftl index 52d881d0266..728a069d3ba 100644 --- a/src/uu/rm/locales/fr-FR.ftl +++ b/src/uu/rm/locales/fr-FR.ftl @@ -33,6 +33,25 @@ rm-help-progress = afficher une barre de progression. Note : cette fonctionnalit # Messages de progression rm-progress-removing = Suppression +# Messages de confirmation +rm-prompt-remove-arguments = supprimer { $count -> + [one] { $count } argument ? + *[other] { $count } arguments ? +} +rm-prompt-remove-arguments-recursive = supprimer { $count -> + [one] { $count } argument récursivement ? + *[other] { $count } arguments récursivement ? +} +rm-prompt-remove-symbolic-link = supprimer le lien symbolique { $file } ? +rm-prompt-remove-regular-empty-file = supprimer le fichier ordinaire vide { $file } ? +rm-prompt-remove-file = supprimer le fichier { $file } ? +rm-prompt-remove-write-protected-regular-empty-file = supprimer le fichier ordinaire vide protégé en écriture { $file } ? +rm-prompt-remove-write-protected-regular-file = supprimer le fichier ordinaire protégé en écriture { $file } ? +rm-prompt-attempt-remove-inaccessible-directory = tenter de supprimer le répertoire inaccessible { $path } ? +rm-prompt-remove-write-protected-directory = supprimer le répertoire protégé en écriture { $path } ? +rm-prompt-remove-directory = supprimer le répertoire { $path } ? +rm-prompt-descend-into-directory = descendre dans le répertoire { $path } ? + # Messages d'erreur rm-error-missing-operand = opérande manquant Essayez '{$util_name} --help' pour plus d'informations. diff --git a/src/uu/rm/src/platform/unix.rs b/src/uu/rm/src/platform/unix.rs index e890ab15823..970f79cb885 100644 --- a/src/uu/rm/src/platform/unix.rs +++ b/src/uu/rm/src/platform/unix.rs @@ -52,13 +52,22 @@ fn prompt_file_with_stat(path: &Path, stat: &libc::stat, options: &Options) -> b // otherwise fall through to protected wording. if options.interactive == InteractiveMode::Always { if is_symlink { - return prompt_yes!("remove symbolic link {}?", path.quote()); + return prompt_yes!( + "{}", + translate!("rm-prompt-remove-symbolic-link", "file" => path.quote()) + ); } if writable { return if len == 0 { - prompt_yes!("remove regular empty file {}?", path.quote()) + prompt_yes!( + "{}", + translate!("rm-prompt-remove-regular-empty-file", "file" => path.quote()) + ) } else { - prompt_yes!("remove file {}?", path.quote()) + prompt_yes!( + "{}", + translate!("rm-prompt-remove-file", "file" => path.quote()) + ) }; } // Not writable: use protected wording below @@ -69,10 +78,19 @@ fn prompt_file_with_stat(path: &Path, stat: &libc::stat, options: &Options) -> b (false, _, _) if options.interactive == InteractiveMode::PromptProtected => true, (_, true, _) => true, (_, false, true) => prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() + "{}", + translate!( + "rm-prompt-remove-write-protected-regular-empty-file", + "file" => path.quote() + ) + ), + _ => prompt_yes!( + "{}", + translate!( + "rm-prompt-remove-write-protected-regular-file", + "file" => path.quote() + ) ), - _ => prompt_yes!("remove write-protected regular file {}?", path.quote()), } } @@ -90,17 +108,32 @@ fn prompt_dir_with_mode(path: &Path, mode: libc::mode_t, options: &Options) -> b (false, _, _, InteractiveMode::PromptProtected) => true, (false, false, false, InteractiveMode::Never) => true, (_, false, false, _) => prompt_yes!( - "attempt removal of inaccessible directory {}?", - path.quote() + "{}", + translate!( + "rm-prompt-attempt-remove-inaccessible-directory", + "path" => path.quote() + ) ), (_, false, true, InteractiveMode::Always) => { prompt_yes!( - "attempt removal of inaccessible directory {}?", - path.quote() + "{}", + translate!( + "rm-prompt-attempt-remove-inaccessible-directory", + "path" => path.quote() + ) ) } - (_, true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()), - (_, _, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()), + (_, true, false, _) => prompt_yes!( + "{}", + translate!( + "rm-prompt-remove-write-protected-directory", + "path" => path.quote() + ) + ), + (_, _, _, InteractiveMode::Always) => prompt_yes!( + "{}", + translate!("rm-prompt-remove-directory", "path" => path.quote()) + ), (_, _, _, _) => true, } } diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 252c723406b..eed5a8b4225 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -264,21 +264,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) { - let msg: String = format!( - "remove {} {}{}", - files.len(), - if files.len() > 1 { - "arguments" - } else { - "argument" - }, - if options.recursive { - " recursively?" - } else { - "?" - } - ); - if !prompt_yes!("{msg}") { + let prompt_key = if options.recursive { + "rm-prompt-remove-arguments-recursive" + } else { + "rm-prompt-remove-arguments" + }; + let msg = translate!(prompt_key, "count" => files.len()); + if !prompt_yes!("{}", msg) { return Ok(()); } } @@ -798,21 +790,29 @@ fn prompt_file(path: &Path, options: &Options) -> bool { if options.interactive == InteractiveMode::Never { return true; } - let Ok(metadata) = fs::symlink_metadata(path) else { return true; }; if metadata.is_symlink() { return options.interactive != InteractiveMode::Always - || prompt_yes!("remove symbolic link {}?", path.quote()); + || prompt_yes!( + "{}", + translate!("rm-prompt-remove-symbolic-link", "file" => path.quote()) + ); } if options.interactive == InteractiveMode::Always && is_writable_metadata(&metadata) { return if metadata.len() == 0 { - prompt_yes!("remove regular empty file {}?", path.quote()) + prompt_yes!( + "{}", + translate!("rm-prompt-remove-regular-empty-file", "file" => path.quote()) + ) } else { - prompt_yes!("remove file {}?", path.quote()) + prompt_yes!( + "{}", + translate!("rm-prompt-remove-file", "file" => path.quote()) + ) }; } @@ -825,10 +825,19 @@ fn prompt_file_permission_readonly(path: &Path, options: &Options, metadata: &Me (false, InteractiveMode::PromptProtected) => true, _ if is_writable_metadata(metadata) => true, _ if metadata.len() == 0 => prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() + "{}", + translate!( + "rm-prompt-remove-write-protected-regular-empty-file", + "file" => path.quote() + ) + ), + _ => prompt_yes!( + "{}", + translate!( + "rm-prompt-remove-write-protected-regular-file", + "file" => path.quote() + ) ), - _ => prompt_yes!("remove write-protected regular file {}?", path.quote()), } } @@ -861,15 +870,30 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata (false, _, _, InteractiveMode::PromptProtected) => true, (false, false, false, InteractiveMode::Never) => true, // Don't prompt when interactive is never (_, false, false, _) => prompt_yes!( - "attempt removal of inaccessible directory {}?", - path.quote() + "{}", + translate!( + "rm-prompt-attempt-remove-inaccessible-directory", + "path" => path.quote() + ) ), (_, false, true, InteractiveMode::Always) => prompt_yes!( - "attempt removal of inaccessible directory {}?", - path.quote() + "{}", + translate!( + "rm-prompt-attempt-remove-inaccessible-directory", + "path" => path.quote() + ) + ), + (_, true, false, _) => prompt_yes!( + "{}", + translate!( + "rm-prompt-remove-write-protected-directory", + "path" => path.quote() + ) + ), + (_, _, _, InteractiveMode::Always) => prompt_yes!( + "{}", + translate!("rm-prompt-remove-directory", "path" => path.quote()) ), - (_, true, false, _) => prompt_yes!("remove write-protected directory {}?", path.quote()), - (_, _, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()), (_, _, _, _) => true, } } @@ -883,8 +907,16 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal(); match (stdin_ok, not_user_writable, options.interactive) { (false, _, InteractiveMode::PromptProtected) => true, - (_, true, _) => prompt_yes!("remove write-protected directory {}?", path.quote()), - (_, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()), + (_, true, _) => prompt_yes!( + "{}", + translate!( + "rm-prompt-remove-write-protected-directory", + "path" => path.quote() + ) + ), + (_, _, InteractiveMode::Always) => { + prompt_yes!("{}", translate!("rm-prompt-remove-directory", "path" => path.quote())) + } (_, _, _) => true, } } @@ -894,7 +926,7 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata #[cfg(not(unix))] fn handle_writable_directory(path: &Path, options: &Options, _metadata: &Metadata) -> bool { if options.interactive == InteractiveMode::Always { - prompt_yes!("remove directory {}?", path.quote()) + prompt_yes!("{}", translate!("rm-prompt-remove-directory", "path" => path.quote())) } else { true } @@ -935,7 +967,10 @@ fn clean_trailing_slashes(path: &Path) -> &Path { } fn prompt_descend(path: &Path) -> bool { - prompt_yes!("descend into directory {}?", path.quote()) + prompt_yes!( + "{}", + translate!("rm-prompt-descend-into-directory", "path" => path.quote()) + ) } fn normalize(path: &Path) -> PathBuf { From e38de0fdfa2e2d138ed4f0c355808623e0ab97f6 Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 25 Dec 2025 21:10:44 +0900 Subject: [PATCH 2/6] refactor(rm): format prompt_yes! macro calls for readability Break long prompt_yes! macro invocations into multiple lines in the handle_writable_directory function to improve code readability and adhere to formatting standards. No functional changes. --- src/uu/rm/src/rm.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index eed5a8b4225..90b4ec2d923 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -915,7 +915,10 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata ) ), (_, _, InteractiveMode::Always) => { - prompt_yes!("{}", translate!("rm-prompt-remove-directory", "path" => path.quote())) + prompt_yes!( + "{}", + translate!("rm-prompt-remove-directory", "path" => path.quote()) + ) } (_, _, _) => true, } @@ -926,7 +929,10 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata #[cfg(not(unix))] fn handle_writable_directory(path: &Path, options: &Options, _metadata: &Metadata) -> bool { if options.interactive == InteractiveMode::Always { - prompt_yes!("{}", translate!("rm-prompt-remove-directory", "path" => path.quote())) + prompt_yes!( + "{}", + translate!("rm-prompt-remove-directory", "path" => path.quote()) + ) } else { true } From 18931e4d76347fe92dd8c4c42dabc042ff6b1972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Gomes?= Date: Wed, 14 Jan 2026 10:58:10 +0000 Subject: [PATCH 3/6] rm: don't treat symlinks as write-protected GNU rm does not check for write-protection on symbolic links; it instead prompts to "remove symbolic link" regardless of the link's permissions or its target's status. This change: - Ensures `prompt_file` checks for symlinks specifically using `symlink_metadata`, avoiding the incorrect "write-protected" prompt. - Refactors permission checks into `is_writable_metadata` to allow using the already-fetched metadata, which also optimizes performance by reducing redundant `stat` calls. - Updates `prompt_file_permission_readonly` to operate on metadata directly. --- tests/by-util/test_rm.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 20d4a935714..82c93f6352d 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -1236,7 +1236,6 @@ fn no_preserve_root_may_not_be_abbreviated() { assert!(at.file_exists(file)); } - #[cfg(unix)] #[test] fn test_symlink_to_readonly_no_prompt() { From 0d660b69679f9d0c57e3e0666e37cb92d056a4d5 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:18:03 +0900 Subject: [PATCH 4/6] coreutils: Remove limitation for prefix --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 7a9f45c385b..5febb7d8199 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -99,7 +99,7 @@ else do [ -e "${UU_BUILD_DIR}/${binary}" ] || ln -vf "${UU_BUILD_DIR}/coreutils" "${UU_BUILD_DIR}/${binary}" done fi -[ -e "${UU_BUILD_DIR}/ginstall" ] || ln -vf "${UU_BUILD_DIR}/install" "${UU_BUILD_DIR}/ginstall" # The GNU tests use ginstall +[ -e "${UU_BUILD_DIR}/ginstall" ] || ln -vf "${UU_BUILD_DIR}/install" "${UU_BUILD_DIR}/ginstall" # The GNU tests use renamed install to ginstall ## cd "${path_GNU}" && echo "[ pwd:'${PWD}' ]" From 1b3b8ee71733698c221eaa31fb0e893acb0f6f10 Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 25 Dec 2025 20:46:59 +0900 Subject: [PATCH 5/6] feat(rm): add localized prompt messages for interactive mode Add translations for rm command prompts in English and French, and update Linux-specific code to use localized strings instead of hardcoded messages, improving internationalization support. --- src/uu/rm/src/rm.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 90b4ec2d923..a13b842d3f6 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -551,11 +551,27 @@ fn is_writable_metadata(metadata: &Metadata) -> bool { (mode & 0o200) > 0 } +/// Whether the given file or directory is writable. +#[cfg(unix)] +fn is_writable(path: &Path) -> bool { + match fs::metadata(path) { + Err(_) => false, + Ok(metadata) => is_writable_metadata(&metadata), + } +} + #[cfg(not(unix))] fn is_writable_metadata(_metadata: &Metadata) -> bool { true } +/// Whether the given file or directory is writable. +#[cfg(not(unix))] +fn is_writable(_path: &Path) -> bool { + // TODO Not yet implemented. + true +} + /// Recursively remove the directory tree rooted at the given path. /// /// If `path` is a file or a symbolic link, just remove it. If it is a From 604925e66721943c171483c2b6a0f5196f6d61a6 Mon Sep 17 00:00:00 2001 From: mattsu Date: Sat, 24 Jan 2026 13:47:28 +0900 Subject: [PATCH 6/6] fix(rm): remove unused `is_writable` function and simplify logic The `is_writable` function was unused and has been removed to clean up the codebase. The logic has been simplified by keeping only the necessary `is_writable_metadata` function, which is now the single source of truth for checking writable permissions. This change reduces code duplication and improves maintainability. --- src/uu/rm/src/rm.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a13b842d3f6..90b4ec2d923 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -551,27 +551,11 @@ fn is_writable_metadata(metadata: &Metadata) -> bool { (mode & 0o200) > 0 } -/// Whether the given file or directory is writable. -#[cfg(unix)] -fn is_writable(path: &Path) -> bool { - match fs::metadata(path) { - Err(_) => false, - Ok(metadata) => is_writable_metadata(&metadata), - } -} - #[cfg(not(unix))] fn is_writable_metadata(_metadata: &Metadata) -> bool { true } -/// Whether the given file or directory is writable. -#[cfg(not(unix))] -fn is_writable(_path: &Path) -> bool { - // TODO Not yet implemented. - true -} - /// Recursively remove the directory tree rooted at the given path. /// /// If `path` is a file or a symbolic link, just remove it. If it is a