diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 9fc0cb2ff..f93fff646 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -19,6 +19,24 @@ use std::os::unix::fs::MetadataExt; const ABOUT: &str = help_about!("chgrp.md"); const USAGE: &str = help_usage!("chgrp.md"); +fn parse_gid_from_str(group: &str) -> Result { + if let Some(gid_str) = group.strip_prefix(':') { + // Handle :gid format + gid_str + .parse::() + .map_err(|_| format!("invalid group id: '{}'", gid_str)) + } else { + // Try as group name first + match entries::grp2gid(group) { + Ok(g) => Ok(g), + // If group name lookup fails, try parsing as raw number + Err(_) => group + .parse::() + .map_err(|_| format!("invalid group: '{}'", group)), + } + } +} + fn parse_gid_and_uid(matches: &ArgMatches) -> UResult { let mut raw_group: String = String::new(); let dest_gid = if let Some(file) = matches.get_one::(options::REFERENCE) { @@ -38,26 +56,21 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult { if group.is_empty() { None } else { - match entries::grp2gid(group) { + match parse_gid_from_str(group) { Ok(g) => Some(g), - _ => { - return Err(USimpleError::new( - 1, - format!("invalid group: {}", group.quote()), - )) - } + Err(e) => return Err(USimpleError::new(1, e)), } } }; // Handle --from option let filter = if let Some(from_group) = matches.get_one::(options::FROM) { - match entries::grp2gid(from_group) { + match parse_gid_from_str(from_group) { Ok(g) => IfFrom::Group(g), - _ => { + Err(_) => { return Err(USimpleError::new( 1, - format!("invalid group: {}", from_group.quote()), + format!("invalid user: '{}'", from_group), )) } } diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 086f21cb0..c39824907 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -547,3 +547,49 @@ fn test_from_with_reference() { let ref_gid = at.plus("ref_file").metadata().unwrap().gid(); assert_eq!(new_gid, ref_gid); } + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_numeric_group_formats() { + use std::os::unix::fs::MetadataExt; + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let groups = nix::unistd::getgroups().unwrap(); + if groups.len() < 2 { + return; + } + let (first_group, second_group) = (groups[0], groups[1]); + + at.touch("test_file"); + + scene + .ucmd() + .arg(first_group.to_string()) + .arg("test_file") + .succeeds(); + + // Test :gid format in --from + scene + .ucmd() + .arg(format!("--from=:{}", first_group.as_raw())) + .arg(second_group.to_string()) + .arg("test_file") + .succeeds() + .no_stderr(); + + let new_gid = at.plus("test_file").metadata().unwrap().gid(); + assert_eq!(new_gid, second_group.as_raw()); + + // Test :gid format in target group + scene + .ucmd() + .arg(format!("--from={}", second_group.as_raw())) + .arg(format!(":{}", first_group.as_raw())) + .arg("test_file") + .succeeds() + .no_stderr(); + + let final_gid = at.plus("test_file").metadata().unwrap().gid(); + assert_eq!(final_gid, first_group.as_raw()); +}