From a32a756a58fec134836f85c2c8bfd58063ebc4c4 Mon Sep 17 00:00:00 2001 From: Achilles Gaikwad Date: Tue, 24 Jul 2018 01:09:13 +0530 Subject: [PATCH] id: support multiple specified users $ id root nobody uid=0(root) gid=0(root) groups=0(root) uid=99(nobody) gid=99(nobody) groups=99(nobody) * src/id.c (main): Make variables opt_zero, just_group_list, just_group, use_real, just_user global to be used in a new function. (print_stuff): New function that will print user and group information for the specified USER. When using -G option delimit each record with two NULs. Restructure the code in the file to have global variables followed by functions. * tests/id/zero.sh: Add test cases to check the usage of -z option with multiple users. * tests/id/uid.sh: Add a test case to ensure all users are queried in the presence of errors. * doc/coreutils.texi: Document the interface changes. * NEWS: Mention the new feature. --- NEWS | 4 ++ doc/coreutils.texi | 7 ++- src/id.c | 162 ++++++++++++++++++++++++++++++----------------------- tests/id/uid.sh | 5 ++ tests/id/zero.sh | 37 ++++++++++++ 5 files changed, 144 insertions(+), 71 deletions(-) diff --git a/NEWS b/NEWS index aa3b4f994..77c26df4d 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,10 @@ GNU coreutils NEWS -*- outline -*- df no longer corrupts displayed multibyte characters on macOS. +** New features + + id now supports specifying multiple users. + * Noteworthy changes in release 8.30 (2018-07-01) [stable] diff --git a/doc/coreutils.texi b/doc/coreutils.texi index f147beb78..16768cbca 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -15207,7 +15207,7 @@ logins, groups, and so forth. running it if no user is specified. Synopsis: @example -id [@var{option}]@dots{} [@var{user}] +id [@var{option}]@dots{} [@var{user}]@dots{} @end example @var{user} can be either a user ID or a name, with name look-up @@ -15276,8 +15276,11 @@ set the exit status to 1. @itemx --zero @opindex -z @opindex --zero -Delimit output items with NUL characters. +Delimit output items with ASCII NUL characters. This option is not permitted when using the default format. +When multiple users are specified, and the @option{--groups} option +is also in effect, groups are delimited with a single NUL character, +while users are delimited with two NUL characters. Example: @example diff --git a/src/id.c b/src/id.c index be0758059..2f89736b1 100644 --- a/src/id.c +++ b/src/id.c @@ -43,10 +43,20 @@ /* If nonzero, output only the SELinux context. */ static bool just_context = 0; - -static void print_user (uid_t uid); -static void print_full_info (const char *username); - +/* If true, delimit entries with NUL characters, not whitespace */ +static bool opt_zero = false; +/* If true, output the list of all group IDs. -G */ +static bool just_group_list = false; +/* If true, output only the group ID(s). -g */ +static bool just_group = false; +/* If true, output real UID/GID instead of default effective UID/GID. -r */ +static bool use_real = false; +/* If true, output only the user ID(s). -u */ +static bool just_user = false; +/* True unless errors have been encountered. */ +static bool ok = true; +/* If true, we are using multiple users. Terminate -G with double NUL. */ +static bool multiple_users = false; /* If true, output user/group name instead of ID number. -n */ static bool use_name = false; @@ -54,13 +64,14 @@ static bool use_name = false; static uid_t ruid, euid; static gid_t rgid, egid; -/* True unless errors have been encountered. */ -static bool ok = true; - /* The SELinux context. Start with a known invalid value so print_full_info knows when 'context' has not been set to a meaningful value. */ static char *context = NULL; +static void print_user (uid_t uid); +static void print_full_info (const char *username); +static void print_stuff (const char *pw_name); + static struct option const longopts[] = { {"context", no_argument, NULL, 'Z'}, @@ -82,9 +93,9 @@ usage (int status) emit_try_help (); else { - printf (_("Usage: %s [OPTION]... [USER]\n"), program_name); + printf (_("Usage: %s [OPTION]... [USER]...\n"), program_name); fputs (_("\ -Print user and group information for the specified USER,\n\ +Print user and group information for each specified USER,\n\ or (when USER omitted) for the current user.\n\ \n"), stdout); @@ -116,18 +127,8 @@ main (int argc, char **argv) int optc; int selinux_enabled = (is_selinux_enabled () > 0); bool smack_enabled = is_smack_enabled (); - bool opt_zero = false; char *pw_name = NULL; - /* If true, output the list of all group IDs. -G */ - bool just_group_list = false; - /* If true, output only the group ID(s). -g */ - bool just_group = false; - /* If true, output real UID/GID instead of default effective UID/GID. -r */ - bool use_real = false; - /* If true, output only the user ID(s). -u */ - bool just_user = false; - initialize_main (&argc, &argv); set_program_name (argv[0]); setlocale (LC_ALL, ""); @@ -185,11 +186,6 @@ main (int argc, char **argv) } size_t n_ids = argc - optind; - if (1 < n_ids) - { - error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); - usage (EXIT_FAILURE); - } if (n_ids && just_context) die (EXIT_FAILURE, 0, @@ -228,28 +224,44 @@ main (int argc, char **argv) die (EXIT_FAILURE, 0, _("can't get process context")); } - if (n_ids == 1) + if (n_ids >= 1) { - struct passwd *pwd = NULL; - const char *spec = argv[optind]; - /* Disallow an empty spec here as parse_user_spec() doesn't - give an error for that as it seems it's a valid way to - specify a noop or "reset special bits" depending on the system. */ - if (*spec) + multiple_users = n_ids > 1 ? true : false; + /* Changing the value of n_ids to the last index in the array where we + have the last possible user id. This helps us because we don't have + to declare a different variable to keep a track of where the + last username lies in argv[]. */ + n_ids += optind; + /* For each username/userid to get its pw_name field */ + for (; optind < n_ids; optind++) { - if (parse_user_spec (spec, &euid, NULL, NULL, NULL) == NULL) + struct passwd *pwd = NULL; + const char *spec = argv[optind]; + /* Disallow an empty spec here as parse_user_spec() doesn't + give an error for that as it seems it's a valid way to + specify a noop or "reset special bits" depending on the system. */ + if (*spec) + { + if (parse_user_spec (spec, &euid, NULL, NULL, NULL) == NULL) + { + /* parse_user_spec will only extract a numeric spec, + so we lookup that here to verify and also retrieve + the PW_NAME used subsequently in group lookup. */ + pwd = getpwuid (euid); + } + } + if (pwd == NULL) { - /* parse_user_spec will only extract a numeric spec, - so we lookup that here to verify and also retrieve - the PW_NAME used subsequently in group lookup. */ - pwd = getpwuid (euid); + error (0, errno, _("%s: no such user"), quote (argv[optind])); + ok &= false; + continue; } + pw_name = xstrdup (pwd->pw_name); + ruid = euid = pwd->pw_uid; + rgid = egid = pwd->pw_gid; + print_stuff (pw_name); + free (pw_name); } - if (pwd == NULL) - die (EXIT_FAILURE, 0, _("%s: no such user"), quote (spec)); - pw_name = xstrdup (pwd->pw_name); - ruid = euid = pwd->pw_uid; - rgid = egid = pwd->pw_gid; } else { @@ -289,34 +301,9 @@ main (int argc, char **argv) if (rgid == NO_GID && errno) die (EXIT_FAILURE, errno, _("cannot get real GID")); } + print_stuff (pw_name); } - if (just_user) - { - print_user (use_real ? ruid : euid); - } - else if (just_group) - { - if (!print_group (use_real ? rgid : egid, use_name)) - ok = false; - } - else if (just_group_list) - { - if (!print_group_list (pw_name, ruid, rgid, egid, use_name, - opt_zero ? '\0' : ' ')) - ok = false; - } - else if (just_context) - { - fputs (context, stdout); - } - else - { - print_full_info (pw_name); - } - putchar (opt_zero ? '\0' : '\n'); - - IF_LINT (free (pw_name)); return ok ? EXIT_SUCCESS : EXIT_FAILURE; } @@ -356,7 +343,7 @@ print_user (uid_t uid) { error (0, 0, _("cannot find name for user ID %s"), uidtostr (uid)); - ok = false; + ok &= false; } } @@ -415,7 +402,7 @@ print_full_info (const char *username) quote (username)); else error (0, errno, _("failed to get groups for the current process")); - ok = false; + ok &= false; return; } @@ -438,3 +425,40 @@ print_full_info (const char *username) if (context) printf (_(" context=%s"), context); } + +/* Print information about the user based on the arguments passed. */ + +static void +print_stuff (const char *pw_name) +{ + if (just_user) + print_user (use_real ? ruid : euid); + + /* print_group and print_group_lists functions return true on successful + execution but false if something goes wrong. We then AND this value with + the current value of 'ok' because we want to know if one of the previous + users faced a problem in these functions. This value of 'ok' is later used + to understand what status program should exit with. */ + else if (just_group) + ok &= print_group (use_real ? rgid : egid, use_name); + else if (just_group_list) + ok &= print_group_list (pw_name, ruid, rgid, egid, + use_name, opt_zero ? '\0' : ' '); + else if (just_context) + fputs (context, stdout); + else + print_full_info (pw_name); + + /* When printing records for more than 1 user, at the end of groups + of each user terminate the record with two consequent NUL characters + to make parsing and distinguishing between two records possible. */ + if (opt_zero && just_group_list && multiple_users) + { + putchar ('\0'); + putchar ('\0'); + } + else + { + putchar (opt_zero ? '\0' : '\n'); + } +} diff --git a/tests/id/uid.sh b/tests/id/uid.sh index 61d313778..9d856433d 100755 --- a/tests/id/uid.sh +++ b/tests/id/uid.sh @@ -24,6 +24,11 @@ user=$(id -nu) || fail=1 # Ensure the empty user spec is discarded returns_ 1 id '' || fail=1 +# Ensure we don't exit early, and process all users +id $user > user_out || fail=1 +returns_ 1 id '' $user >multi_user_out || fail=1 +compare user_out multi_user_out || fail=1 + for mode in '' '-G' '-g'; do id $mode $user > user_out || fail=1 # lookup name for comparison diff --git a/tests/id/zero.sh b/tests/id/zero.sh index f183e18f8..d0cf44c53 100755 --- a/tests/id/zero.sh +++ b/tests/id/zero.sh @@ -63,4 +63,41 @@ printf '\n' >> out || framework_failure_ tr '\0' ' ' < out > out2 || framework_failure_ compare exp out2 || fail=1 +# multiuser testing with -z +# test if the options work, these tests should pass if the above tests +# do. + +for o in g gr u ur ; do + for n in '' n ; do + id -${o}${n} $users >> tmp1 || + { test $? -ne 1 || test -z "$n" && fail=1; } + id -${o}${n}z $users > tmp2 || + { test $? -ne 1 || test -z "$n" && fail=1; } + tr '\0' '\n' < tmp2 >> tmp3 + done +done +compare tmp1 tmp3 || fail=1 + +# Separate checks when we are testing for multiple users && -G. +# This is done because we terminate the records with two NULs +# instead of a regular single NUL. + +NL=' +' + +for o in G Gr ; do + for n in '' n ; do + id -${o}${n} $users >> gtmp1 || + { test $? -ne 1 || test -z "$n" && fail=1; } + id -${o}${n}z $users > gtmp2 || + { test $? -ne 1 || test -z "$n" && fail=1; } + # we replace all NULs with spaces, the result we get is there are two + # consecutive spaces instead of two NUL's, we pass this to sed + # to replace more than 1 space with a newline. This is ideally where a new + # line should be. This should make the output similar to without -z. + tr '\0' ' ' < gtmp2 | sed "s/ /\\$NL/g" >> gtmp3 + done +done +compare gtmp1 gtmp3 || fail=1 + Exit $fail -- 2.11.4.GIT