From 1b633fa607fca70a36937135253a215f671d6d35 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:46:31 +0530 Subject: [PATCH 1/2] useradd: honor -U when USERGROUPS_ENAB is no, reject -U with -g USERGROUPS_ENAB only sets the default. With it set to no, -U was ignored and the user got the default group. -U with -g was accepted and quietly created a new group instead of using the one given; GNU rejects the pair. --- src/uu/useradd/src/useradd.rs | 11 ++++++++++- tests/by-util/test_useradd.rs | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/uu/useradd/src/useradd.rs b/src/uu/useradd/src/useradd.rs index af76918..26fee8e 100644 --- a/src/uu/useradd/src/useradd.rs +++ b/src/uu/useradd/src/useradd.rs @@ -511,7 +511,9 @@ fn parse_options(matches: &clap::ArgMatches) -> Result Command { .short('U') .long("user-group") .action(ArgAction::SetTrue) + .conflicts_with(options::GID) .help("Also create a matching user-private group (default)"), ) .arg( @@ -1403,6 +1406,12 @@ mod tests { assert!(result.is_err()); } + #[test] + fn test_clap_user_group_conflicts_with_gid() { + let result = uu_app().try_get_matches_from(["useradd", "-U", "-g", "100", "user"]); + assert!(result.is_err()); + } + #[test] fn test_clap_non_unique_requires_uid() { let result = uu_app().try_get_matches_from(["useradd", "-o", "user"]); diff --git a/tests/by-util/test_useradd.rs b/tests/by-util/test_useradd.rs index 2888165..ad7f896 100644 --- a/tests/by-util/test_useradd.rs +++ b/tests/by-util/test_useradd.rs @@ -510,6 +510,28 @@ fn test_create_user_with_key_long_option() { ); } +#[test] +fn test_user_group_flag_wins_over_usergroups_enab_no() { + if crate::common::skip_unless_root() { + return; + } + + let dir = setup_root_dir(); + let defs = dir.path().join("etc/login.defs"); + let content = std::fs::read_to_string(&defs).expect("read login.defs"); + std::fs::write( + &defs, + content.replace("USERGROUPS_ENAB yes", "USERGROUPS_ENAB no"), + ) + .expect("write login.defs"); + + // USERGROUPS_ENAB only sets the default; -U asks for the group explicitly + assert_eq!(run_with_root(&dir, &["-U", "ugu"]), 0); + let group = read_group(&dir); + assert!(group.contains("ugu:x:1000:"), "got: {group}"); + assert!(read_passwd(&dir).contains("ugu:x:1000:1000:")); +} + #[test] fn test_create_system_user_with_key_sys_uid_range() { if crate::common::skip_unless_root() { From 2545a28eeccedb4508dd2528f50cc11682e6ee87 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Sun, 25 Oct 2026 13:11:02 +0530 Subject: [PATCH 2/2] useradd: check the -U -g error is an argument conflict --- src/uu/useradd/src/useradd.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/useradd/src/useradd.rs b/src/uu/useradd/src/useradd.rs index 26fee8e..5432915 100644 --- a/src/uu/useradd/src/useradd.rs +++ b/src/uu/useradd/src/useradd.rs @@ -1409,7 +1409,10 @@ mod tests { #[test] fn test_clap_user_group_conflicts_with_gid() { let result = uu_app().try_get_matches_from(["useradd", "-U", "-g", "100", "user"]); - assert!(result.is_err()); + assert_eq!( + result.unwrap_err().kind(), + clap::error::ErrorKind::ArgumentConflict + ); } #[test]