Subject: bug#9987: [PATCH] groups,id: add -0, --null option

From: Bernhard Voelker <[email-masked]>
Date: Thu, 19 Sep 2013 01:24:08 +0200

On 09/18/2013 09:56 PM, Pádraig Brady wrote:
> On 09/18/2013 07:46 PM, Bernhard Voelker wrote:
>> a) add the -z option only to id.c leaving groups(1) untouched.

> I'd vote for a) actually.

Great, that makes the change much smaller. ;-)

The patch is attached.

Thanks & have a nice day,
Berny
>From f8ee96fbbd577b3d6eb2e2a072936b71cac2b959 Mon Sep 17 00:00:00 2001
From: Bernhard Voelker <[email-masked]>
Date: Thu, 19 Sep 2013 01:23:00 +0200
Subject: [PATCH] id: add -z, --zero option

* src/group-list.h (print_group_list): Add a parameter for the
separator of type char.
* src/group-list.c (print_group_list): Likewise, and use it instead
of a white space character to separate the group entries.
* src/groups.c (main): Pass white space character to print_group_list().
* src/id.c (longopts):  Add array element for the new long option.
(usage): Document the new option.  While at it, fix the alignment
of the descriptions to match that of HELP_OPTION_DESCRIPTION.
(main): Define the bool flag opt_zero indicating the use of the
new option.  In the getopt_long loop, handle it.
Output an error diagnostic in the case the --zero option has been
specified together with the default format.
In the case of -gG, pass either a NUL or a white space character to
print_group_list() - depending on the above new flag.
Likewise change the printing of the final newline character: output
a NUL instead if the --zero option has been specified.
* doc/coreutils.texi (id invocation): Document the new option.
While at it, move the @exitstatus macro down after the macro
@primaryAndSupplementaryGroups in order to be consistent with
other info pages.
(groups invocation): Move @exitstatus down after the macro
@primaryAndSupplementaryGroups here, too.
* tests/misc/id-zero.sh: Add new test exercising the new option.
* tests/local.mk (all_tests): Reference it.
* NEWS (New features): Mention the new option.
Fixes http://bugs.gnu.org/9987
---
 NEWS                  |  3 +++
 doc/coreutils.texi    | 22 +++++++++++++++-----
 src/group-list.c      |  6 +++---
 src/group-list.h      |  2 +-
 src/groups.c          |  6 +++---
 src/id.c              | 36 ++++++++++++++++++++++----------
 tests/local.mk        |  1 +
 tests/misc/id-zero.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 110 insertions(+), 23 deletions(-)
 create mode 100755 tests/misc/id-zero.sh

diff --git a/NEWS b/NEWS
index d26722d..5891def 100644
--- a/NEWS
+++ b/NEWS
@@ -44,6 +44,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   du accepts a new option: --inodes to show the number of inodes instead
   of the blocks used.
 
+  id accepts a new option: --zero (-z) to separate the output entries by
+  a NUL instead of a white space character.
+
   id and ls with -Z report the SMACK security context where available.
   mkdir, mkfifo and mknod with -Z set the SMACK context where available.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 21216b4..327254b 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -14516,9 +14516,20 @@ Print only the security context of the current user.
 If SELinux is disabled then print a warning and
 set the exit status to 1.
 
-@end table
+@item -z
+@itemx --zero
+@opindex -z
+@opindex --zero
+Separate output items with NUL characters.
+This option is not permitted when using the default format.
 
-@exitstatus
+Example:
+@example
+$ id -Gn --zero
+users <NUL> devs
+@end example
+
+@end table
 
 @macro primaryAndSupplementaryGroups{cmd,arg}
 Primary and supplementary groups for a process are normally inherited
@@ -14530,6 +14541,8 @@ database to be consulted afresh, and so will give a different result.
 @end macro
 @primaryAndSupplementaryGroups{id,user argument}
 
+@exitstatus
+
 @node logname invocation
 @section @command{logname}: Print current login name
 
@@ -14587,13 +14600,12 @@ groups [@var{username}]@dots{}
 
 The group lists are equivalent to the output of the command @samp{id -Gn}.
 
-@primaryAndSupplementaryGroups{groups,list of users}
-
 The only options are @option{--help} and @option{--version}.  @xref{Common
 options}.
 
-@exitstatus
+@primaryAndSupplementaryGroups{groups,list of users}
 
+@exitstatus
 
 @node users invocation
 @section @command{users}: Print login names of users currently logged in
diff --git a/src/group-list.c b/src/group-list.c
index 7d4995b..a76ee00 100644
--- a/src/group-list.c
+++ b/src/group-list.c
@@ -35,7 +35,7 @@
 extern bool
 print_group_list (const char *username,
                   uid_t ruid, gid_t rgid, gid_t egid,
-                  bool use_names)
+                  bool use_names, char separator)
 {
   bool ok = true;
   struct passwd *pwd = NULL;
@@ -52,7 +52,7 @@ print_group_list (const char *username,
 
   if (egid != rgid)
     {
-      putchar (' ');
+      putchar (separator);
       if (!print_group (egid, use_names))
         ok = false;
     }
@@ -79,7 +79,7 @@ print_group_list (const char *username,
     for (i = 0; i < n_groups; i++)
       if (groups[i] != rgid && groups[i] != egid)
         {
-          putchar (' ');
+          putchar (separator);
           if (!print_group (groups[i], use_names))
             ok = false;
         }
diff --git a/src/group-list.h b/src/group-list.h
index 3fac887..573de1d 100644
--- a/src/group-list.h
+++ b/src/group-list.h
@@ -16,4 +16,4 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 bool print_group (gid_t, bool);
-bool print_group_list (const char *, uid_t, gid_t, gid_t, bool);
+bool print_group_list (const char *, uid_t, gid_t, gid_t, bool, char);
diff --git a/src/groups.c b/src/groups.c
index 53332d5..d30c9fb 100644
--- a/src/groups.c
+++ b/src/groups.c
@@ -114,13 +114,13 @@ main (int argc, char **argv)
       if (rgid == NO_GID && errno)
         error (EXIT_FAILURE, errno, _("cannot get real GID"));
 
-      if (!print_group_list (NULL, ruid, rgid, egid, true))
+      if (!print_group_list (NULL, ruid, rgid, egid, true, ' '))
         ok = false;
       putchar ('\n');
     }
   else
     {
-      /* At least one argument.  Divulge the details of the specified users. */
+      /* At least one argument.  Divulge the details of the specified users.  */
       while (optind < argc)
         {
           struct passwd *pwd = getpwnam (argv[optind]);
@@ -130,7 +130,7 @@ main (int argc, char **argv)
           rgid = egid = pwd->pw_gid;
 
           printf ("%s : ", argv[optind]);
-          if (!print_group_list (argv[optind++], ruid, rgid, egid, true))
+          if (!print_group_list (argv[optind++], ruid, rgid, egid, true, ' '))
             ok = false;
           putchar ('\n');
         }
diff --git a/src/id.c b/src/id.c
index 3e7016f..587dd3b 100644
--- a/src/id.c
+++ b/src/id.c
@@ -61,6 +61,7 @@ static security_context_t context = NULL;
 
 static struct option const longopts[] =
 {
+  {"zero", no_argument, NULL, 'z'},
   {"context", no_argument, NULL, 'Z'},
   {"group", no_argument, NULL, 'g'},
   {"groups", no_argument, NULL, 'G'},
@@ -83,14 +84,18 @@ usage (int status)
       fputs (_("\
 Print user and group information for the specified USERNAME,\n\
 or (when USERNAME omitted) for the current user.\n\
-\n\
-  -a              ignore, for compatibility with other versions\n\
-  -Z, --context   print only the security context of the current user\n\
-  -g, --group     print only the effective group ID\n\
-  -G, --groups    print all group IDs\n\
-  -n, --name      print a name instead of a number, for -ugG\n\
-  -r, --real      print the real ID instead of the effective ID, with -ugG\n\
-  -u, --user      print only the effective user ID\n\
+\n"),
+             stdout);
+      fputs (_("\
+  -a             ignore, for compatibility with other versions\n\
+  -Z, --context  print only the security context of the current user\n\
+  -g, --group    print only the effective group ID\n\
+  -G, --groups   print all group IDs\n\
+  -n, --name     print a name instead of a number, for -ugG\n\
+  -r, --real     print the real ID instead of the effective ID, with -ugG\n\
+  -u, --user     print only the effective user ID\n\
+  -z, --zero     separate entries by a NUL, not whitespace;\n\
+                   not permitted in default format\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -109,6 +114,7 @@ main (int argc, char **argv)
   int optc;
   int selinux_enabled = (is_selinux_enabled () > 0);
   bool smack_enabled = is_smack_enabled ();
+  bool opt_zero = false;
 
   /* If true, output the list of all group IDs. -G */
   bool just_group_list = false;
@@ -127,7 +133,7 @@ main (int argc, char **argv)
 
   atexit (close_stdout);
 
-  while ((optc = getopt_long (argc, argv, "agnruGZ", longopts, NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "agnruzGZ", longopts, NULL)) != -1)
     {
       switch (optc)
         {
@@ -162,6 +168,9 @@ main (int argc, char **argv)
         case 'u':
           just_user = true;
           break;
+        case 'z':
+          opt_zero = true;
+          break;
         case 'G':
           just_group_list = true;
           break;
@@ -193,6 +202,10 @@ main (int argc, char **argv)
     error (EXIT_FAILURE, 0,
            _("cannot print only names or real IDs in default format"));
 
+  if (default_format && opt_zero)
+    error (EXIT_FAILURE, 0,
+           _("option --zero not permitted in default format"));
+
   /* If we are on a SELinux/SMACK-enabled kernel, no user is specified, and
      either --context is specified or none of (-u,-g,-G) is specified,
      and we're not in POSIXLY_CORRECT mode, get our context.  Otherwise,
@@ -269,7 +282,8 @@ main (int argc, char **argv)
     }
   else if (just_group_list)
     {
-      if (!print_group_list (argv[optind], ruid, rgid, egid, use_name))
+      if (!print_group_list (argv[optind], ruid, rgid, egid, use_name,
+                             opt_zero ? '\0' : ' '))
         ok = false;
     }
   else if (just_context)
@@ -280,7 +294,7 @@ main (int argc, char **argv)
     {
       print_full_info (argv[optind]);
     }
-  putchar ('\n');
+  putchar (opt_zero ? '\0' : '\n');
 
   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }
diff --git a/tests/local.mk b/tests/local.mk
index b00ff59..8f76b23 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -277,6 +277,7 @@ all_tests =					\
   tests/misc/id-context.sh			\
   tests/misc/id-groups.sh			\
   tests/misc/id-setgid.sh			\
+  tests/misc/id-zero.sh			\
   tests/misc/md5sum.pl				\
   tests/misc/md5sum-bsd.sh			\
   tests/misc/md5sum-newline.pl			\
diff --git a/tests/misc/id-zero.sh b/tests/misc/id-zero.sh
new file mode 100755
index 0000000..15f6a53
--- /dev/null
+++ b/tests/misc/id-zero.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+# Exercise "id --zero".
+
+# Copyright (C) 2013 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ id
+
+u="$( id -nu )"
+id || fail=1
+id "$u" || fail=1
+id root || fail=1
+
+# Create a nice list of users (ignoring errors) ...
+getent passwd | head -n 100 | cut -d: -f1 > users
+# ... and ensure it contains at least 'root', "$u", and an
+# empty line for the tests without a user argument below.
+printf "%s\n" root "$u" '' >> users || framework_failure_
+
+# id(1) should refuse --zero in default format.
+id --zero > out 2>err && fail=1
+compare /dev/null out || fail=1
+grep 'option --zero not permitted in default format' err || fail=1
+
+# Exercise "id -z" with various options.
+printf "\n" > exp || framework_failure_
+cp /dev/null  out || framework_failure_
+
+while read u ; do
+  for o in g gr G Gr u ur ; do
+    for n in "" n ; do
+      printf   "%s: " "id -${o}${n}[z] $u" >> exp || framework_failure_
+      printf "\n%s: " "id -${o}${n}[z] $u" >> out || framework_failure_
+      id -${o}${n}  $u >> exp || fail=1
+      id -${o}${n}z $u  > tmp || fail=1
+      head -c-1 < tmp | tr '\0' ' ' >> out || framework_failure_
+    done
+  done
+done < users
+
+printf "\n" >> out || framework_failure_
+compare exp out || fail=1
+
+Exit $fail
-- 
1.8.3.1