summaryrefslogtreecommitdiff
path: root/gl
diff options
context:
space:
mode:
Diffstat (limited to 'gl')
-rw-r--r--gl/lib/mgetgroups.c67
-rw-r--r--gl/m4/mgetgroups.m45
2 files changed, 61 insertions, 11 deletions
diff --git a/gl/lib/mgetgroups.c b/gl/lib/mgetgroups.c
index 6a4a42280..b63436abf 100644
--- a/gl/lib/mgetgroups.c
+++ b/gl/lib/mgetgroups.c
@@ -23,11 +23,27 @@
#include <unistd.h>
#include <stdint.h>
+#include <string.h>
#include <errno.h>
-
+#if HAVE_GETGROUPLIST
+#include <grp.h>
+#endif
#include "getugroups.h"
#include "xalloc.h"
+
+static void *
+allocate_groupbuf (int size)
+{
+ if (xalloc_oversized (size, sizeof (GETGROUPS_T)))
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return malloc (size * sizeof (GETGROUPS_T));
+}
+
/* Like getugroups, but store the result in malloc'd storage.
Set *GROUPS to the malloc'd list of all group IDs of which USERNAME
is a member. If GID is not -1, store it first. GID should be the
@@ -37,12 +53,51 @@
the number of groups. */
int
-mgetgroups (const char *username, gid_t gid, GETGROUPS_T **groups)
+mgetgroups (char const *username, gid_t gid, GETGROUPS_T **groups)
{
int max_n_groups;
int ng;
GETGROUPS_T *g;
+#if HAVE_GETGROUPLIST
+ /* We prefer to use getgrouplist if available, because it has better
+ performance characteristics.
+
+ In glibc 2.3.2, getgrouplist is buggy. If you pass a zero as the
+ size of the output buffer, getgrouplist will still write to the
+ buffer. Contrary to what some versions of the getgrouplist
+ manpage say, this doesn't happen with nonzero buffer sizes.
+ Therefore our usage here just avoids a zero sized buffer. */
+ if (username)
+ {
+ enum { INITIAL_GROUP_BUFSIZE = 1u };
+ /* INITIAL_GROUP_BUFSIZE is initially small to ensure good test coverage */
+ GETGROUPS_T smallbuf[INITIAL_GROUP_BUFSIZE];
+
+ max_n_groups = INITIAL_GROUP_BUFSIZE;
+ ng = getgrouplist (username, gid, smallbuf, &max_n_groups);
+
+ g = allocate_groupbuf (max_n_groups);
+ if (g == NULL)
+ return -1;
+
+ *groups = g;
+ if (INITIAL_GROUP_BUFSIZE < max_n_groups)
+ {
+ return getgrouplist (username, gid, g, &max_n_groups);
+ /* XXX: Ignoring the race with group size increase */
+ }
+ else
+ {
+ /* smallbuf was big enough, so we already have our data */
+ memcpy (g, smallbuf, max_n_groups * sizeof *g);
+ return 0;
+ }
+ /* getgrouplist failed, fall through and use getugroups instead. */
+ }
+ /* else no username, so fall through and use getgroups. */
+#endif
+
max_n_groups = (username
? getugroups (0, NULL, username, gid)
: getgroups (0, NULL));
@@ -52,13 +107,7 @@ mgetgroups (const char *username, gid_t gid, GETGROUPS_T **groups)
if (max_n_groups < 0)
max_n_groups = 5;
- if (xalloc_oversized (max_n_groups, sizeof *g))
- {
- errno = ENOMEM;
- return -1;
- }
-
- g = malloc (max_n_groups * sizeof *g);
+ g = allocate_groupbuf (max_n_groups);
if (g == NULL)
return -1;
diff --git a/gl/m4/mgetgroups.m4 b/gl/m4/mgetgroups.m4
index 81835415f..da557311b 100644
--- a/gl/m4/mgetgroups.m4
+++ b/gl/m4/mgetgroups.m4
@@ -1,10 +1,11 @@
-#serial 1
-dnl Copyright (C) 2007 Free Software Foundation, Inc.
+#serial 2
+dnl Copyright (C) 2007-2008 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
AC_DEFUN([gl_MGETGROUPS],
[
+ AC_CHECK_FUNCS(getgrouplist)
AC_LIBOBJ([mgetgroups])
])