1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

chgrp: support the --from=:X syntax

This commit is contained in:
Sylvestre Ledru 2025-01-12 21:17:04 +01:00
parent 8e9a4b5f9a
commit d76c561516
2 changed files with 69 additions and 10 deletions

View file

@ -19,6 +19,24 @@ use std::os::unix::fs::MetadataExt;
const ABOUT: &str = help_about!("chgrp.md"); const ABOUT: &str = help_about!("chgrp.md");
const USAGE: &str = help_usage!("chgrp.md"); const USAGE: &str = help_usage!("chgrp.md");
fn parse_gid_from_str(group: &str) -> Result<u32, String> {
if let Some(gid_str) = group.strip_prefix(':') {
// Handle :gid format
gid_str
.parse::<u32>()
.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::<u32>()
.map_err(|_| format!("invalid group: '{}'", group)),
}
}
}
fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<GidUidOwnerFilter> { fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<GidUidOwnerFilter> {
let mut raw_group: String = String::new(); let mut raw_group: String = String::new();
let dest_gid = if let Some(file) = matches.get_one::<String>(options::REFERENCE) { let dest_gid = if let Some(file) = matches.get_one::<String>(options::REFERENCE) {
@ -38,26 +56,21 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<GidUidOwnerFilter> {
if group.is_empty() { if group.is_empty() {
None None
} else { } else {
match entries::grp2gid(group) { match parse_gid_from_str(group) {
Ok(g) => Some(g), Ok(g) => Some(g),
_ => { Err(e) => return Err(USimpleError::new(1, e)),
return Err(USimpleError::new(
1,
format!("invalid group: {}", group.quote()),
))
}
} }
} }
}; };
// Handle --from option // Handle --from option
let filter = if let Some(from_group) = matches.get_one::<String>(options::FROM) { let filter = if let Some(from_group) = matches.get_one::<String>(options::FROM) {
match entries::grp2gid(from_group) { match parse_gid_from_str(from_group) {
Ok(g) => IfFrom::Group(g), Ok(g) => IfFrom::Group(g),
_ => { Err(_) => {
return Err(USimpleError::new( return Err(USimpleError::new(
1, 1,
format!("invalid group: {}", from_group.quote()), format!("invalid user: '{}'", from_group),
)) ))
} }
} }

View file

@ -547,3 +547,49 @@ fn test_from_with_reference() {
let ref_gid = at.plus("ref_file").metadata().unwrap().gid(); let ref_gid = at.plus("ref_file").metadata().unwrap().gid();
assert_eq!(new_gid, ref_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());
}