]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
id: support multiple specified users
authorAchilles Gaikwad <agaikwad@redhat.com>
Mon, 23 Jul 2018 19:39:13 +0000 (01:09 +0530)
committerPádraig Brady <P@draigBrady.com>
Sun, 30 Sep 2018 05:39:26 +0000 (22:39 -0700)
  $ 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
doc/coreutils.texi
src/id.c
tests/id/uid.sh
tests/id/zero.sh

diff --git a/NEWS b/NEWS
index aa3b4f9947f60ae9f3691530ee8ee90a41c106f7..77c26df4d7f2e6fdbb3a67ff1c67749810d5330c 100644 (file)
--- 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]
 
index f147beb78e320b176a375cd93cdb767ce99ef0aa..16768cbca4ad1da9ad6db19d0582c9d5e5086611 100644 (file)
@@ -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
index be0758059c5d28eafa629ebb6bfee8b753a270ea..2f89736b13c8a69de2eb9be12aee9ca1c82dfa46 100644 (file)
--- a/src/id.c
+++ b/src/id.c
 
 /* 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');
+    }
+}
index 61d313778e29924279f123a2819d5f13c6e413b8..9d856433dbe1c24c4d90db8f24c67184f6b56e12 100755 (executable)
@@ -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
 
index f183e18f868cc4e322970e5970c3e3ad64d71875..d0cf44c538e86e8004a5af66b2c1ebecba70bfc3 100755 (executable)
@@ -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