From c45bbe3d1c5ff95d7b56fd6b8dd19cd7c9d43aef Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Jan 2025 20:48:33 +0100 Subject: [PATCH] chgrp: add option --from --- src/uu/chgrp/src/chgrp.rs | 24 ++++++- tests/by-util/test_chgrp.rs | 130 ++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index fe5aee872..9fc0cb2ff 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -49,11 +49,27 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult { } } }; + + // Handle --from option + let filter = if let Some(from_group) = matches.get_one::(options::FROM) { + match entries::grp2gid(from_group) { + Ok(g) => IfFrom::Group(g), + _ => { + return Err(USimpleError::new( + 1, + format!("invalid group: {}", from_group.quote()), + )) + } + } + } else { + IfFrom::All + }; + Ok(GidUidOwnerFilter { dest_gid, dest_uid: None, raw_owner: raw_group, - filter: IfFrom::All, + filter, }) } @@ -120,6 +136,12 @@ pub fn uu_app() -> Command { .value_hint(clap::ValueHint::FilePath) .help("use RFILE's group rather than specifying GROUP values"), ) + .arg( + Arg::new(options::FROM) + .long(options::FROM) + .value_name("GROUP") + .help("change the group only if its current group matches GROUP"), + ) .arg( Arg::new(options::RECURSIVE) .short('R') diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index eca5ba0ed..086f21cb0 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -417,3 +417,133 @@ fn test_traverse_symlinks() { ); } } + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_from_option() { + use std::os::unix::fs::MetadataExt; + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let groups = nix::unistd::getgroups().unwrap(); + // Skip test if we don't have at least two different groups to work with + 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 successful group change with --from + scene + .ucmd() + .arg("--from") + .arg(first_group.to_string()) + .arg(second_group.to_string()) + .arg("test_file") + .succeeds() + .no_stderr(); + + // Verify the group was changed + let new_gid = at.plus("test_file").metadata().unwrap().gid(); + assert_eq!(new_gid, second_group.as_raw()); + + scene + .ucmd() + .arg("--from") + .arg(first_group.to_string()) + .arg(first_group.to_string()) + .arg("test_file") + .succeeds() + .no_stderr(); + + let unchanged_gid = at.plus("test_file").metadata().unwrap().gid(); + assert_eq!(unchanged_gid, second_group.as_raw()); +} + +#[test] +#[cfg(not(any(target_os = "android", target_os = "macos")))] +fn test_from_with_invalid_group() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("test_file"); + #[cfg(not(target_os = "android"))] + let err_msg = "chgrp: invalid user: 'nonexistent_group'\n"; + #[cfg(target_os = "android")] + let err_msg = "chgrp: invalid user: 'staff'\n"; + + ucmd.arg("--from") + .arg("nonexistent_group") + .arg("staff") + .arg("test_file") + .fails() + .stderr_is(err_msg); +} + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_verbosity_messages() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let groups = nix::unistd::getgroups().unwrap(); + // Skip test if we don't have at least one group to work with + if groups.is_empty() { + return; + } + + at.touch("ref_file"); + at.touch("target_file"); + + scene + .ucmd() + .arg("-v") + .arg("--reference=ref_file") + .arg("target_file") + .succeeds() + .stderr_contains("group of 'target_file' retained as "); +} + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_from_with_reference() { + 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("ref_file"); + at.touch("test_file"); + + scene + .ucmd() + .arg(first_group.to_string()) + .arg("test_file") + .succeeds(); + + scene + .ucmd() + .arg(second_group.to_string()) + .arg("ref_file") + .succeeds(); + + // Test --from with --reference + scene + .ucmd() + .arg("--from") + .arg(first_group.to_string()) + .arg("--reference=ref_file") + .arg("test_file") + .succeeds() + .no_stderr(); + + let new_gid = at.plus("test_file").metadata().unwrap().gid(); + let ref_gid = at.plus("ref_file").metadata().unwrap().gid(); + assert_eq!(new_gid, ref_gid); +}