libbb: new function bb_getgroups() - allocating wrapper around getgroups()
authorDenys Vlasenko <vda.linux@googlemail.com>
Tue, 4 Jul 2017 16:49:24 +0000 (18:49 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Tue, 4 Jul 2017 16:56:45 +0000 (18:56 +0200)
function                                             old     new   delta
bb_getgroups                                           -     111    +111
nexpr                                                843     757     -86
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 0/1 up/down: 111/-86)            Total: 25 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
coreutils/test.c
include/libbb.h
libbb/bb_getgroups.c [new file with mode: 0644]

index edc625f57a4ba8986f911c661b579f4420101c44..edcf2a2d840e161626fa04bd01ce25a742142fef 100644 (file)
@@ -563,26 +563,11 @@ static int binop(void)
        /*return 1; - NOTREACHED */
 }
 
-
 static void initialize_group_array(void)
 {
-       int n;
-
-       /* getgroups may be expensive, try to use it only once */
-       ngroups = 32;
-       do {
-               /* FIXME: ash tries so hard to not die on OOM,
-                * and we spoil it with just one xrealloc here */
-               /* We realloc, because test_main can be entered repeatedly by shell.
-                * Testcase (ash): 'while true; do test -x some_file; done'
-                * and watch top. (some_file must have owner != you) */
-               n = ngroups;
-               group_array = xrealloc(group_array, n * sizeof(gid_t));
-               ngroups = getgroups(n, group_array);
-       } while (ngroups > n);
+       group_array = bb_getgroups(&ngroups, NULL);
 }
 
-
 /* Return non-zero if GID is one that we have in our groups list. */
 //XXX: FIXME: duplicate of existing libbb function?
 // see toplevel TODO file:
@@ -610,14 +595,10 @@ static int is_a_group_member(gid_t gid)
 /* Do the same thing access(2) does, but use the effective uid and gid,
    and don't make the mistake of telling root that any file is
    executable. */
-static int test_eaccess(char *path, int mode)
+static int test_eaccess(struct stat *st, int mode)
 {
-       struct stat st;
        unsigned int euid = geteuid();
 
-       if (stat(path, &st) < 0)
-               return -1;
-
        if (euid == 0) {
                /* Root can read or write any file. */
                if (mode != X_OK)
@@ -625,16 +606,16 @@ static int test_eaccess(char *path, int mode)
 
                /* Root can execute any file that has any one of the execute
                 * bits set. */
-               if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
+               if (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
                        return 0;
        }
 
-       if (st.st_uid == euid)  /* owner */
+       if (st->st_uid == euid)  /* owner */
                mode <<= 6;
-       else if (is_a_group_member(st.st_gid))
+       else if (is_a_group_member(st->st_gid))
                mode <<= 3;
 
-       if (st.st_mode & mode)
+       if (st->st_mode & mode)
                return 0;
 
        return -1;
@@ -667,7 +648,7 @@ static int filstat(char *nm, enum token mode)
                        i = W_OK;
                if (mode == FILEX)
                        i = X_OK;
-               return test_eaccess(nm, i) == 0;
+               return test_eaccess(&s, i) == 0;
        }
        if (is_file_type(mode)) {
                if (mode == FILREG)
index 557978e66be9353cb0950f9a12eac367e83d17e6..1c9de3af0d9804c389459dd5d34db65a215171bf 100644 (file)
@@ -1033,6 +1033,15 @@ void die_if_bad_username(const char* name) FAST_FUNC;
 #else
 #define die_if_bad_username(name) ((void)(name))
 #endif
+/*
+ * Returns (-1) terminated malloced result of getgroups().
+ * Reallocs group_array (useful for repeated calls).
+ * ngroups is an initial size of array. It is rounded up to 32 for realloc.
+ * ngroups is updated on return.
+ * ngroups can be NULL: bb_getgroups(NULL, NULL) is valid usage.
+ * Dies on errors (on Linux, only xrealloc can cause this, not internal getgroups call).
+ */
+gid_t *bb_getgroups(int *ngroups, gid_t *group_array) FAST_FUNC;
 
 #if ENABLE_FEATURE_UTMP
 void FAST_FUNC write_new_utmp(pid_t pid, int new_type, const char *tty_name, const char *username, const char *hostname);
diff --git a/libbb/bb_getgroups.c b/libbb/bb_getgroups.c
new file mode 100644 (file)
index 0000000..59ae537
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2017 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+//kbuild:lib-y += bb_getgroups.o
+
+#include "libbb.h"
+
+gid_t* FAST_FUNC bb_getgroups(int *ngroups, gid_t *group_array)
+{
+       int n = ngroups ? *ngroups : 0;
+
+       /* getgroups may be a bit expensive, try to use it only once */
+       if (n < 32)
+               n = 32;
+
+       for (;;) {
+// FIXME: ash tries so hard to not die on OOM (when we are called from test),
+// and we spoil it with just one xrealloc here
+               group_array = xrealloc(group_array, (n+1) * sizeof(group_array[0]));
+               n = getgroups(n, group_array);
+               /*
+                * If buffer is too small, kernel does not return new_n > n.
+                * It returns -1 and EINVAL:
+                */
+               if (n >= 0) {
+                       /* Terminator for bb_getgroups(NULL, NULL) usage */
+                       group_array[n] = (gid_t) -1;
+                       break;
+               }
+               if (errno == EINVAL) { /* too small? */
+                       /* This is the way to ask kernel how big the array is */
+                       n = getgroups(0, group_array);
+                       continue;
+               }
+               /* Some other error (should never happen on Linux) */
+               bb_perror_msg_and_die("getgroups");
+       }
+
+       if (ngroups)
+               *ngroups = n;
+       return group_array;
+}