diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index b32988ebc..f44d77c5f 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -13,8 +13,10 @@ // This is not based on coreutils (8.32) GNU's `id`. // This is based on BSD's `id` (noticeable in functionality, usage text, options text, etc.) // +// Option '--zero' does not exist for BSD's `id`, therefor '--zero' is only allowed together +// with other options that are available on GNU's `id`. -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -85,6 +87,7 @@ mod options { pub const OPT_NAME: &str = "name"; pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this pub const OPT_REAL_ID: &str = "real"; + pub const OPT_ZERO: &str = "zero"; // BSD's id does not have this pub const ARG_USERS: &str = "USER"; } @@ -102,26 +105,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::OPT_AUDIT) .short("A") + .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_GROUPS, options::OPT_ZERO]) .help("Display the process audit user ID and other process audit properties, which requires privilege (not available on Linux)."), ) .arg( Arg::with_name(options::OPT_EFFECTIVE_USER) .short("u") .long(options::OPT_EFFECTIVE_USER) - .help("Display the effective user ID as a number."), + .conflicts_with(options::OPT_GROUP) + .help("Display only the effective user ID as a number."), ) .arg( Arg::with_name(options::OPT_GROUP) .short("g") .long(options::OPT_GROUP) - .help("Display the effective group ID as a number"), + .help("Display only the effective group ID as a number"), ) .arg( Arg::with_name(options::OPT_GROUPS) .short("G") .long(options::OPT_GROUPS) .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT]) - .help("Display the different group IDs as white-space separated numbers, in no particular order."), + .help("Display only the different group IDs as white-space separated numbers, in no particular order."), ) .arg( Arg::with_name(options::OPT_HUMAN_READABLE) @@ -145,6 +150,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::OPT_REAL_ID) .help("Display the real ID for the -g and -u options instead of the effective ID."), ) + .arg( + Arg::with_name(options::OPT_ZERO) + .short("z") + .long(options::OPT_ZERO) + .help("delimit entries with NUL characters, not whitespace;\nnot permitted in default format"), + ) .arg( Arg::with_name(options::ARG_USERS) .multiple(true) @@ -158,11 +169,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let gflag = matches.is_present(options::OPT_GROUP); let gsflag = matches.is_present(options::OPT_GROUPS); let rflag = matches.is_present(options::OPT_REAL_ID); + let zflag = matches.is_present(options::OPT_ZERO); - // -ugG + // "default format" is when none of '-ugG' was used + // could not implement these "required" rules with just clap if (nflag || rflag) && !(uflag || gflag || gsflag) { crash!(1, "cannot print only names or real IDs in default format"); } + if (zflag) && !(uflag || gflag || gsflag) { + crash!(1, "option --zero not permitted in default format"); + } let users: Vec = matches .values_of(options::ARG_USERS) @@ -183,17 +199,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } }; + let line_ending = if zflag { '\0' } else { '\n' }; + if gflag { let id = possible_pw .map(|p| p.gid()) .unwrap_or(if rflag { getgid() } else { getegid() }); - println!( - "{}", + print!( + "{}{}", if nflag { entries::gid2grp(id).unwrap_or_else(|_| id.to_string()) } else { id.to_string() - } + }, + line_ending ); return 0; } @@ -202,20 +221,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let id = possible_pw .map(|p| p.uid()) .unwrap_or(if rflag { getuid() } else { geteuid() }); - println!( - "{}", + print!( + "{}{}", if nflag { entries::uid2usr(id).unwrap_or_else(|_| id.to_string()) } else { id.to_string() - } + }, + line_ending ); return 0; } if gsflag { - println!( - "{}", + let delimiter = if zflag { "" } else { " " }; + print!( + "{}{}", if nflag { possible_pw .map(|p| p.belongs_to()) @@ -223,7 +244,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .iter() .map(|&id| entries::gid2grp(id).unwrap()) .collect::>() - .join(" ") + .join(delimiter) } else { possible_pw .map(|p| p.belongs_to()) @@ -231,8 +252,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .iter() .map(|&id| id.to_string()) .collect::>() - .join(" ") - } + .join(delimiter) + }, + line_ending ); return 0; } @@ -398,3 +420,5 @@ fn id_print(possible_pw: Option, p_euid: bool, p_egid: bool) { .join(",") ); } + +fn get_groups() -> diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 1f8249aab..a8ad37190 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -96,11 +96,19 @@ fn test_id_group() { let mut result = scene.ucmd().arg("-g").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); result = scene.ucmd().arg("--group").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); + + #[cfg(any(target_vendor = "apple", target_os = "linux"))] + for flag in &["-g", "--group"] { + new_ucmd!() + .arg(flag) + .succeeds() + .stdout_is(expected_result(&[flag], false)); + } } #[test] @@ -110,13 +118,22 @@ fn test_id_groups() { let result = scene.ucmd().arg("-G").succeeds(); let groups = result.stdout_str().trim().split_whitespace(); for s in groups { - assert!(s.parse::().is_ok()); + assert!(s.parse::().is_ok()); } let result = scene.ucmd().arg("--groups").succeeds(); let groups = result.stdout_str().trim().split_whitespace(); for s in groups { - assert!(s.parse::().is_ok()); + assert!(s.parse::().is_ok()); + } + + #[cfg(any(target_vendor = "apple", target_os = "linux"))] + for args in &["-G", "--groups"] { + let expect = expected_result(&[args], false); + let actual = new_ucmd!().arg(&args).succeeds().stdout_move_str(); + let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); + let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); + assert_eq!(v_actual.sort_unstable(), v_expect.sort_unstable()); } } @@ -126,11 +143,19 @@ fn test_id_user() { let result = scene.ucmd().arg("-u").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); let result = scene.ucmd().arg("--user").succeeds(); let s1 = result.stdout_str().trim(); - assert!(s1.parse::().is_ok()); + assert!(s1.parse::().is_ok()); + + #[cfg(any(target_vendor = "apple", target_os = "linux"))] + for flag in &["-u", "--user"] { + new_ucmd!() + .arg(flag) + .succeeds() + .stdout_is(expected_result(&[flag], false)); + } } #[test] @@ -167,3 +192,89 @@ fn test_id_password_style() { assert!(result.stdout_str().starts_with(&username)); } + +#[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn test_id_default_format() { + // -ugG + for flag in &["--name", "--real"] { + new_ucmd!() + .arg(flag) + .fails() + .stderr_is(expected_result(&[flag], true)); + for &opt in &["--user", "--group", "--groups"] { + if is_ci() && *flag == "--name" { + // '--name' does not work in CI: + // id: cannot find name for user ID 1001 + // id: cannot find name for group ID 116 + println!("test skipped:"); + continue; + } + let args = [opt, flag]; + let expect = expected_result(&args, false); + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); + let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); + let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); + assert_eq!(v_actual.sort_unstable(), v_expect.sort_unstable()); + } + } +} + +#[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn test_id_zero() { + for z_flag in &["-z", "--zero"] { + for &opt in &["-n", "--name", "-r", "--real"] { + let args = [opt, z_flag]; + new_ucmd!() + .args(&args) + .fails() + .stderr_is(expected_result(&args, true)); + } + for &opt in &["-u", "--user", "-g", "--group"] { + let args = [opt, z_flag]; + new_ucmd!() + .args(&args) + .succeeds() + .stdout_is(expected_result(&args, false)); + } + // '--groups' ids are in no particular order and when paired with '--zero' there's no + // delimiter which makes the split_whitespace-collect-into-vector comparison impossible. + for opt in &["-G", "--groups"] { + let args = [opt, z_flag]; + let result = new_ucmd!().args(&args).succeeds().stdout_move_str(); + assert!(!result.contains(" ")); + assert!(result.ends_with('\0')); + } + } +} + +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn expected_result(args: &[&str], exp_fail: bool) -> String { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + let result = if !exp_fail { + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .succeeds() + .stdout_move_str() + } else { + TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .args(args) + .fails() + .stderr_move_str() + }; + // #[cfg(target_vendor = "apple")] + return if cfg!(target_os = "macos") && result.starts_with("gid") { + result[1..].to_string() + } else { + result + }; +}