aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coreutils/test.c33
-rw-r--r--include/libbb.h9
-rw-r--r--libbb/bb_getgroups.c47
3 files changed, 63 insertions, 26 deletions
diff --git a/coreutils/test.c b/coreutils/test.c
index edc625f57..edcf2a2d8 100644
--- a/coreutils/test.c
+++ b/coreutils/test.c
@@ -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)
diff --git a/include/libbb.h b/include/libbb.h
index 557978e66..1c9de3af0 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -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
index 000000000..59ae53738
--- /dev/null
+++ b/libbb/bb_getgroups.c
@@ -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;
+}