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

id: implement '--zero' flag

* add tests for '--zero' flag
* add a bunch of requires/conflicts rules for flags (incl. tests)
This commit is contained in:
Jan Scheer 2021-06-06 00:03:53 +02:00
parent 74a7da7b52
commit 98225105af
2 changed files with 157 additions and 22 deletions

View file

@ -13,8 +13,10 @@
// This is not based on coreutils (8.32) GNU's `id`. // 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.) // 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(non_camel_case_types)]
#![allow(dead_code)] #![allow(dead_code)]
@ -85,6 +87,7 @@ mod options {
pub const OPT_NAME: &str = "name"; pub const OPT_NAME: &str = "name";
pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this pub const OPT_PASSWORD: &str = "password"; // GNU's id does not have this
pub const OPT_REAL_ID: &str = "real"; 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"; pub const ARG_USERS: &str = "USER";
} }
@ -102,26 +105,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg( .arg(
Arg::with_name(options::OPT_AUDIT) Arg::with_name(options::OPT_AUDIT)
.short("A") .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)."), .help("Display the process audit user ID and other process audit properties, which requires privilege (not available on Linux)."),
) )
.arg( .arg(
Arg::with_name(options::OPT_EFFECTIVE_USER) Arg::with_name(options::OPT_EFFECTIVE_USER)
.short("u") .short("u")
.long(options::OPT_EFFECTIVE_USER) .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(
Arg::with_name(options::OPT_GROUP) Arg::with_name(options::OPT_GROUP)
.short("g") .short("g")
.long(options::OPT_GROUP) .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(
Arg::with_name(options::OPT_GROUPS) Arg::with_name(options::OPT_GROUPS)
.short("G") .short("G")
.long(options::OPT_GROUPS) .long(options::OPT_GROUPS)
.conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT]) .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(
Arg::with_name(options::OPT_HUMAN_READABLE) Arg::with_name(options::OPT_HUMAN_READABLE)
@ -145,6 +150,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::OPT_REAL_ID) .long(options::OPT_REAL_ID)
.help("Display the real ID for the -g and -u options instead of the effective 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(
Arg::with_name(options::ARG_USERS) Arg::with_name(options::ARG_USERS)
.multiple(true) .multiple(true)
@ -158,11 +169,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let gflag = matches.is_present(options::OPT_GROUP); let gflag = matches.is_present(options::OPT_GROUP);
let gsflag = matches.is_present(options::OPT_GROUPS); let gsflag = matches.is_present(options::OPT_GROUPS);
let rflag = matches.is_present(options::OPT_REAL_ID); 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) { if (nflag || rflag) && !(uflag || gflag || gsflag) {
crash!(1, "cannot print only names or real IDs in default format"); 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<String> = matches let users: Vec<String> = matches
.values_of(options::ARG_USERS) .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 { if gflag {
let id = possible_pw let id = possible_pw
.map(|p| p.gid()) .map(|p| p.gid())
.unwrap_or(if rflag { getgid() } else { getegid() }); .unwrap_or(if rflag { getgid() } else { getegid() });
println!( print!(
"{}", "{}{}",
if nflag { if nflag {
entries::gid2grp(id).unwrap_or_else(|_| id.to_string()) entries::gid2grp(id).unwrap_or_else(|_| id.to_string())
} else { } else {
id.to_string() id.to_string()
} },
line_ending
); );
return 0; return 0;
} }
@ -202,20 +221,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let id = possible_pw let id = possible_pw
.map(|p| p.uid()) .map(|p| p.uid())
.unwrap_or(if rflag { getuid() } else { geteuid() }); .unwrap_or(if rflag { getuid() } else { geteuid() });
println!( print!(
"{}", "{}{}",
if nflag { if nflag {
entries::uid2usr(id).unwrap_or_else(|_| id.to_string()) entries::uid2usr(id).unwrap_or_else(|_| id.to_string())
} else { } else {
id.to_string() id.to_string()
} },
line_ending
); );
return 0; return 0;
} }
if gsflag { if gsflag {
println!( let delimiter = if zflag { "" } else { " " };
"{}", print!(
"{}{}",
if nflag { if nflag {
possible_pw possible_pw
.map(|p| p.belongs_to()) .map(|p| p.belongs_to())
@ -223,7 +244,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.iter() .iter()
.map(|&id| entries::gid2grp(id).unwrap()) .map(|&id| entries::gid2grp(id).unwrap())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(delimiter)
} else { } else {
possible_pw possible_pw
.map(|p| p.belongs_to()) .map(|p| p.belongs_to())
@ -231,8 +252,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.iter() .iter()
.map(|&id| id.to_string()) .map(|&id| id.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(delimiter)
} },
line_ending
); );
return 0; return 0;
} }
@ -398,3 +420,5 @@ fn id_print(possible_pw: Option<Passwd>, p_euid: bool, p_egid: bool) {
.join(",") .join(",")
); );
} }
fn get_groups() ->

View file

@ -96,11 +96,19 @@ fn test_id_group() {
let mut result = scene.ucmd().arg("-g").succeeds(); let mut result = scene.ucmd().arg("-g").succeeds();
let s1 = result.stdout_str().trim(); let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<u64>().is_ok());
result = scene.ucmd().arg("--group").succeeds(); result = scene.ucmd().arg("--group").succeeds();
let s1 = result.stdout_str().trim(); let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<u64>().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] #[test]
@ -110,13 +118,22 @@ fn test_id_groups() {
let result = scene.ucmd().arg("-G").succeeds(); let result = scene.ucmd().arg("-G").succeeds();
let groups = result.stdout_str().trim().split_whitespace(); let groups = result.stdout_str().trim().split_whitespace();
for s in groups { for s in groups {
assert!(s.parse::<f64>().is_ok()); assert!(s.parse::<u64>().is_ok());
} }
let result = scene.ucmd().arg("--groups").succeeds(); let result = scene.ucmd().arg("--groups").succeeds();
let groups = result.stdout_str().trim().split_whitespace(); let groups = result.stdout_str().trim().split_whitespace();
for s in groups { for s in groups {
assert!(s.parse::<f64>().is_ok()); assert!(s.parse::<u64>().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 result = scene.ucmd().arg("-u").succeeds();
let s1 = result.stdout_str().trim(); let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<u64>().is_ok());
let result = scene.ucmd().arg("--user").succeeds(); let result = scene.ucmd().arg("--user").succeeds();
let s1 = result.stdout_str().trim(); let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<u64>().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] #[test]
@ -167,3 +192,89 @@ fn test_id_password_style() {
assert!(result.stdout_str().starts_with(&username)); 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
};
}