From 1b32857a59c4c03a8806e3061e15a1d721d8c5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Sat, 21 Mar 2026 06:06:42 +0300 Subject: [PATCH 1/4] chown: warn when '.' is used as owner:group separator When the spec contains '.' but no ':', emit a warning matching GNU coreutils behavior: "warning: '.' should be ':': ''" Closes #11352 --- src/uu/chown/src/chown.rs | 23 ++++++++++----- tests/by-util/test_chown.rs | 58 ++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 6f0599ce811..0b050b970d2 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -9,6 +9,7 @@ use uucore::display::Quotable; pub use uucore::entries::{self, Group, Locate, Passwd}; use uucore::format_usage; use uucore::perms::{GidUidOwnerFilter, IfFrom, chown_base, options}; +use uucore::show_warning; use uucore::translate; use uucore::error::{FromIo, UResult, USimpleError}; @@ -151,7 +152,7 @@ pub fn uu_app() -> Command { } /// Parses the user string to extract the UID. -fn parse_uid(user: &str, spec: &str, sep: char) -> UResult> { +fn parse_uid(user: &str, spec: &str) -> UResult> { if user.is_empty() { return Ok(None); } @@ -160,11 +161,6 @@ fn parse_uid(user: &str, spec: &str, sep: char) -> UResult> { return Ok(Some(u.uid)); } - // Handle `username.groupname` syntax (e.g. when sep is ':' but spec contains '.') - if spec.contains('.') && !spec.contains(':') && sep == ':' { - return parse_spec(spec, '.').map(|(uid, _)| uid); - } - // Fallback: `user` string contains a numeric user ID user.parse().map(Some).map_err(|_| { USimpleError::new( @@ -209,7 +205,20 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let user = args.next().unwrap_or(""); let group = args.next().unwrap_or(""); - let uid = parse_uid(user, spec, sep)?; + // Handle `owner.group` syntax: when the separator is ':' but the spec + // contains '.' and not ':', the full spec is treated as the user field. + // If that user lookup fails, re-parse with '.' as separator (like GNU does) + // so that both owner and group are applied. + if sep == ':' && !spec.contains(':') && spec.contains('.') { + if let Ok(uid) = parse_uid(user, spec) { + let gid = parse_gid(group, spec)?; + return Ok((uid, gid)); + } + show_warning!("'.' should be ':': {}", spec.quote()); + return parse_spec(spec, '.'); + } + + let uid = parse_uid(user, spec)?; let gid = parse_gid(group, spec)?; if user.chars().next().is_some_and(char::is_numeric) && group.is_empty() && spec != user { diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 2602aabde06..5b6498361d4 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -149,7 +149,8 @@ fn test_chown_only_owner_colon() { .arg("--verbose") .arg(file1) .succeeds() - .stderr_contains("retained as"); + .stderr_contains("retained as") + .stderr_contains("warning: '.' should be ':'"); scene .ucmd() @@ -160,6 +161,61 @@ fn test_chown_only_owner_colon() { .stderr_contains("failed to change"); } +#[test] +fn test_chown_dot_separator_warning() { + // test that using '.' as separator emits a warning + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let result = scene.cmd("whoami").run(); + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { + return; + } + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); + + let file1 = "test_chown_dot_warn"; + at.touch(file1); + + let result = scene.cmd("id").arg("-gn").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { + return; + } + let group_name = String::from(result.stdout_str().trim()); + assert!(!group_name.is_empty()); + + // chown user. file should warn about '.' separator + scene + .ucmd() + .arg(format!("{user_name}.")) + .arg(file1) + .succeeds() + .stderr_contains("warning: '.' should be ':'"); + + // chown user.group file should warn AND apply both owner and group + let result = scene + .ucmd() + .arg(format!("{user_name}.{group_name}")) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "chown: invalid group:") { + return; + } + result.stderr_contains("warning: '.' should be ':'"); + // "retained as" in verbose output confirms both owner and group were applied + result.stderr_contains("retained as"); + + // chown user: file should NOT warn + scene + .ucmd() + .arg(format!("{user_name}:")) + .arg(file1) + .succeeds() + .stderr_does_not_contain("warning"); +} + #[test] fn test_chown_only_colon() { // test chown : file.txt From 4b82e302ec87d964b6b589c13e2d9be1e63f673a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Sun, 22 Mar 2026 14:13:47 +0300 Subject: [PATCH 2/4] tests/chown: fix dot separator warning test on OpenBSD On BSDs, new files inherit the group from the parent directory (e.g. /tmp -> wheel) rather than the user's primary group. Accept both "retained as" and "changed ownership" in the verbose output since either confirms the dot separator was parsed correctly. --- tests/by-util/test_chown.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 5b6498361d4..3ac8a6d7c78 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -204,8 +204,15 @@ fn test_chown_dot_separator_warning() { return; } result.stderr_contains("warning: '.' should be ':'"); - // "retained as" in verbose output confirms both owner and group were applied - result.stderr_contains("retained as"); + // verbose output confirms both owner and group were processed; + // "retained as" on Linux (file group matches id -gn), "changed ownership" + // on BSDs where files inherit group from parent dir (e.g. /tmp -> wheel) + assert!( + result.stderr_str().contains("retained as") + || result.stderr_str().contains("changed ownership"), + "expected verbose ownership output, got: {}", + result.stderr_str() + ); // chown user: file should NOT warn scene From 50921a9015c74659907160f0dc2ef3c3b1db0aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Mon, 23 Mar 2026 16:05:33 +0300 Subject: [PATCH 3/4] chown: use translate!() macro for dot separator warning Move the warning string to locale .ftl files so it follows the project's i18n pattern. --- src/uu/chown/locales/en-US.ftl | 3 +++ src/uu/chown/locales/fr-FR.ftl | 3 +++ src/uu/chown/src/chown.rs | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uu/chown/locales/en-US.ftl b/src/uu/chown/locales/en-US.ftl index 0dfe8301e9d..9bcb725a6c3 100644 --- a/src/uu/chown/locales/en-US.ftl +++ b/src/uu/chown/locales/en-US.ftl @@ -21,3 +21,6 @@ chown-error-failed-to-get-attributes = failed to get attributes of { $file } chown-error-invalid-user = invalid user: { $user } chown-error-invalid-group = invalid group: { $group } chown-error-invalid-spec = invalid spec: { $spec } + +# Warning messages +chown-warning-dot-separator = '.' should be ':': { $spec } diff --git a/src/uu/chown/locales/fr-FR.ftl b/src/uu/chown/locales/fr-FR.ftl index 48e39853a3e..deaa7a620e9 100644 --- a/src/uu/chown/locales/fr-FR.ftl +++ b/src/uu/chown/locales/fr-FR.ftl @@ -21,3 +21,6 @@ chown-error-failed-to-get-attributes = échec de l'obtention des attributs de { chown-error-invalid-user = utilisateur invalide : { $user } chown-error-invalid-group = groupe invalide : { $group } chown-error-invalid-spec = spécification invalide : { $spec } + +# Messages d'avertissement +chown-warning-dot-separator = '.' devrait être ':' : { $spec } diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 0b050b970d2..633fe4d0949 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -214,7 +214,7 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let gid = parse_gid(group, spec)?; return Ok((uid, gid)); } - show_warning!("'.' should be ':': {}", spec.quote()); + show_warning!("{}", translate!("chown-warning-dot-separator", "spec" => spec.quote())); return parse_spec(spec, '.'); } From 958bd70da181859c2ba32a256c5a8aaca55b7e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Mon, 23 Mar 2026 16:43:40 +0300 Subject: [PATCH 4/4] chown: fix rustfmt formatting for translate!() macro call --- src/uu/chown/src/chown.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 633fe4d0949..b3de9eec6fa 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -214,7 +214,10 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let gid = parse_gid(group, spec)?; return Ok((uid, gid)); } - show_warning!("{}", translate!("chown-warning-dot-separator", "spec" => spec.quote())); + show_warning!( + "{}", + translate!("chown-warning-dot-separator", "spec" => spec.quote()) + ); return parse_spec(spec, '.'); }