coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v2] id: add feature to accept multiple usernames


From: Achilles Gaikwad
Subject: [PATCH v2] id: add feature to accept multiple usernames
Date: Tue, 24 Jul 2018 01:09:13 +0530
User-agent: Mutt/1.10.0 (2018-05-17)

Adds a feature to id command to accept multiple usernames and/or uid
as arguments.

$ 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 varibles 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 consequent NULs.
Restructure the code in the file to have global variables followed by
functions to make the file look pretty.
* tests/id/zero.sh: Add test cases to check the usage of -z option with
multiple users.
* doc/coreutils.texi: add minor documentation change to reflect the
number of inputs that may be given.

Signed-off-by: Achilles Gaikwad <address@hidden>
---
 doc/coreutils.texi |   2 +-
 src/id.c           | 171 +++++++++++++++++++++++++--------------------
 tests/id/zero.sh   |  34 +++++++++
 3 files changed, 131 insertions(+), 76 deletions(-)

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 10fd023d8..f2d58b46d 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -15200,7 +15200,7 @@ logins, groups, and so forth.
 running it if no user is specified.  Synopsis:
 
 @example
-id address@hidden@dots{} address@hidden
+id address@hidden@dots{} address@hidden@dots{}
 @end example
 
 @var{user} can be either a user ID or a name, with name look-up
diff --git a/src/id.c b/src/id.c
index be0758059..e60803894 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 */
+bool opt_zero = false;
+/* 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;
+/* 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 the 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,29 +224,44 @@ main (int argc, char **argv)
         die (EXIT_FAILURE, 0, _("can't get process context"));
     }
 
-  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)
-        {
-          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)
-        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;
-    }
+  if (n_ids >= 1)
+  {
+      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++)
+      {
+          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)
+          {
+            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);
+          IF_LINT (free (pw_name));
+      }
+  }
   else
     {
       /* POSIX says identification functions (getuid, getgid, and
@@ -289,34 +300,10 @@ main (int argc, char **argv)
           if (rgid == NO_GID && errno)
             die (EXIT_FAILURE, errno, _("cannot get real GID"));
         }
+        print_stuff(pw_name);
+        IF_LINT (free (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,37 @@ 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/zero.sh b/tests/id/zero.sh
index f183e18f8..7a94edde1 100755
--- a/tests/id/zero.sh
+++ b/tests/id/zero.sh
@@ -63,4 +63,38 @@ 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
+
+# Seperate checks when we are testing for multiple users && -G.
+# This is done because we terminate the records with two consequent
+# NULs instead of the regular single NUL.
+
+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
+    # consequent spaces instead of two consequent 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/ \+ /\n/g' >> gtmp3
+  done
+done
+compare gtmp1 gtmp3 || fail=1
+
 Exit $fail
-- 
2.17.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]