Jim Meyering wrote: > It'd be great if someone would write a gnulib-style getgrouplist > replacement function that provides a poor-man's implementation (using > something like coreutils' existing code) for systems that lack a useful > function by that name.
Here is an implementation of the getgrouplist module you ask for. It is based on the getugroups.c file. About the glibc-2.3.2 bug workaround: - I don't know how to write an autoconf test for the bug (take e.g. a system where all users are only in their primary group). - The bug is unlikely to be present in other systems, therefore activating the workaround only on glibc-2.3.x seems acceptable. - Even on glibc-2.3.2 systems which have the bug, the getgrouplist function can be used if a protection against the destination overflow is installed. And if it's really faster than a getgrent() loop, it _should_ be used. The overhead added by the workaround is a small number of library calls (2 x sigaction, 1 x setjmp, 1 x memcpy). Notes: - The array element type is gid_t. I don't see why the GETGROUPS_T workaround should be applied to a function that is up to now only implemented by glibc. - The group argument can be any gid_t; if you pass (gid_t)(-1), you will have (gid_t)(-1) in the resulting array. This is how glibc's implementation works; therefore this implementation works the same. Whereas the getugroups() function treated (gid_t)(-1) specially. - The getgrent() loop in getugroups.c is O(n^2). I reduced this to O(n log n). You could also make it O(n) by use of a hash table. Proofreading welcome, as always! This is new, untested code. 2006-05-18 Bruno Haible <[EMAIL PROTECTED]> * m4/getgrouplist.m4: New file. * lib/getgrouplist.h: New file. * lib/getgrouplist.c: New file, based on lib/getugroups.c. * modules/getgrouplist: New file. ============================= modules/getgrouplist ============================= Description: getgrouplist() function: return all the group IDs of a given user. Files: lib/getgrouplist.h lib/getgrouplist.c m4/getgrouplist.m4 Depends-on: configure.ac: gl_FUNC_GETGROUPLIST Makefile.am: EXTRA_DIST += getgrouplist.h Include: #include "getgrouplist.h" License: GPL Maintainer: Jim Meyering, Bruno Haible ============================= lib/getgrouplist.h ============================= /* Lookup of groups of a user. Copyright (C) 2006 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 2, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _GETGROUPLIST_H #define _GETGROUPLIST_H #include <sys/types.h> #if HAVE_GETGROUPLIST && !(defined __GNU_LIBRARY__ && (__GLIBC__ == 2 && __GLIBC_MINOR__ == 3)) /* Use the system's getgrouplist() function. */ # include <grp.h> #else /* Return the groups to which the user with name USER belongs, including the given GROUP. GROUPS is a pointer to *NGROUPS elements. Up to *NGROUPS elements are stored at GROUPS. Upon return *NGROUPS is then set to the total number of elements that would have been stored if enough space had been given. Return the number of elements, if they all fit into the given space, or -1 if only some of the elements could be stored. */ extern int getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups); #endif #endif /* _GETGROUPLIST_H */ ============================= lib/getgrouplist.c ============================= /* Lookup of groups of a user. Copyright (C) 1990-1991, 1998-2000, 2003-2006 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 2, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Written by David MacKenzie and Bruno Haible. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif /* Specification. */ #include "getgrouplist.h" #undef getgrouplist #include <sys/types.h> #include <grp.h> #include <stdlib.h> #include <string.h> #if HAVE_GETGROUPLIST && defined __GNU_LIBRARY__ # include <setjmp.h> # include <signal.h> # include <unistd.h> # include <sys/mman.h> # include <sys/param.h> # define getgrouplist emulated_getgrouplist # define STATIC static #endif /* Emulate getgrouplist. */ /* Some old header files might not declare setgrent, getgrent, and endgrent. If you don't have them at all, we can't implement this function. You lose! */ extern struct group *getgrent (); static int group_compare (const void *p1, const void *p2) { gid_t group1 = *(const gid_t *)p1; gid_t group2 = *(const gid_t *)p2; if (group1 < group2) return -1; if (group1 > group2) return 1; return 0; } #ifdef STATIC STATIC #endif int getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups) { int maxcount = *ngroups; int count = 0; struct group *grp; /* Put the given group first. */ if (count < maxcount) groups[count] = group; count++; /* Scan all groups. */ setgrent (); while ((grp = getgrent ()) != NULL) /* No need to re-consider the given group. */ if (grp->gr_gid != group) { char **grp_members; for (grp_members = grp->gr_mem; *grp_members != NULL; grp_members++) if (strcmp (*grp_members, user) == 0) /* Found a group containing the given user. */ { if (count < maxcount) groups[count] = grp->gr_gid; count++; } } endgrent (); if (count <= maxcount && count > 2) { int i, j; /* Sort the array of supplementary groups. This is a preparation step, so that the next step, the removal of duplicates, is not O(n^2). */ qsort (groups + 1, count - 1, sizeof (gid_t), group_compare); /* Remove duplicates from the array of supplementary groups. Copy from i to j, keeping 0 <= j <= i. */ for (i = j = 1; i < count; i++) if (j == 1 || groups[i] != groups[j - 1]) { if (j < i) groups[j] = groups[i]; j++; } count = j; } *ngroups = count; return (count <= maxcount ? count : -1); } #if HAVE_GETGROUPLIST && defined __GNU_LIBRARY__ /* Use getgrouplist, but work around its bugs. On glibc-2.3.2, getgrouplist() computes the total list into a private malloc()ed and realloc()ed buffer and finally copies this buffer into the given one - writing past the end of the given buffer if there are more than *NGROUPS groups to be returned. We work around this problem by allocating a guard page behind the buffer and catching the SIGSEGV signal (or SIGBUS signal, in the case of Hurd) that occurs when the glibc function writes past the buffer's end. But since this leaks memory (glibc's private buffer is not freed), we do this only once, and fall back to the slower but memory-leak-free emulation afterwards. */ # undef getgrouplist static jmp_buf safe_point; static void my_sig_handler (int signum) { /* On glibc systems (unlike Solaris or *BSD systems), it is possible to longjmp from a signal handler to the main program. */ longjmp (safe_point, 1); } static size_t pagesize; static void *small_memory_with_guard_page; static void init_small_memory_with_guard_page (void) { size_t memsize = pagesize * sizeof (gid_t); void *mem; mem = mmap (NULL, memsize + pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0); if (mem == NULL) return; if (mprotect ((char *) mem + memsize, pagesize, PROT_NONE) < 0) { munmap (mem, memsize + pagesize); return; } small_memory_with_guard_page = mem; } static int caught_signal; int rpl_getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups) { /* Maximum number of groups specified by the caller. */ int maxcount; /* Temporary memory ending in a guard page. */ void *memory_with_guard_page; /* Amount of memory to cleanup at the end. */ size_t memory_size; /* Signal handlers to restore at the end. */ struct sigaction my_sig_action; struct sigaction old_sigsegv_action; # ifndef __linux__ struct sigaction old_sigbus_action; # endif /* Don't leak memory more than once. */ if (caught_signal) goto fail; /* Initialize the pagesize cache. */ if (pagesize == 0) pagesize = getpagesize (); maxcount = *ngroups; if (maxcount <= pagesize) { /* Use the preallocated small memory region with a guard page. */ if (small_memory_with_guard_page == NULL) { init_small_memory_with_guard_page (); if (small_memory_with_guard_page == NULL) goto fail; } memory_with_guard_page = small_memory_with_guard_page; memory_size = 0; } else { /* Allocate a one-time memory region with a guard page. */ size_t memsize = maxcount * sizeof (gid_t); /* Round up to a multiple of pagesize. */ memsize = ((memsize + pagesize - 1) / pagesize) * pagesize; memory_with_guard_page = mmap (NULL, memsize + pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0); if (memory_with_guard_page == NULL) goto fail; if (mprotect ((char *) memory_with_guard_page + memsize, pagesize, PROT_NONE) < 0) { munmap (memory_with_guard_page, memsize + pagesize); goto fail; } memory_size = memsize + pagesize; } if (setjmp (safe_point) == 0) { int result; /* Install handlers for SIGSEGV and SIGBUS. */ my_sig_action.sa_handler = my_sig_handler; sigemptyset (&my_sig_action.sa_mask); my_sig_action.sa_flags = SA_NOMASK; sigaction (SIGSEGV, &my_sig_action, &old_sigsegv_action); # ifndef __linux__ sigaction (SIGBUS, &my_sig_action, &old_sigbus_action); # endif /* Call the original, possibly buggy, getgrouplist() function. */ result = getgrouplist (user, group, memory_with_guard_page, ngroups); /* Uninstall the signal handlers. */ sigaction (SIGSEGV, &old_sigsegv_action, NULL); # ifndef __linux__ sigaction (SIGBUS, &old_sigbus_action, NULL); # endif /* The original function didn't crash, fine. */ memcpy (groups, memory_with_guard_page, MIN (maxcount, *ngroups) * sizeof (gid_t)); /* Clean up allocated memory. */ if (memory_size > 0) munmap (memory_with_guard_page, memory_size); return result; } else { /* The original function crashed. Use the emulation from now on. */ caught_signal = 1; /* Uninstall the signal handlers. */ sigaction (SIGSEGV, &old_sigsegv_action, NULL); # ifndef __linux__ sigaction (SIGBUS, &old_sigbus_action, NULL); # endif /* Clean up allocated memory. */ if (memory_size > 0) munmap (memory_with_guard_page, memory_size); } fail: return emulated_getgrouplist (user, group, groups, ngroups); } #endif ============================= m4/getgrouplist.m4 ============================= # getgrouplist.m4 serial 1 dnl Copyright (C) 2006 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. dnl From Bruno Haible. AC_DEFUN([gl_FUNC_GETGROUPLIST], [ dnl Persuade glibc <grp.h> to declare getgrouplist. AC_REQUIRE([AC_GNU_SOURCE]) AC_REQUIRE([AC_TYPE_UID_T]) AC_CHECK_FUNCS([getgrouplist]) AC_CACHE_CHECK(whether we are using the GNU C Library 2.3.x, gl_cv_gnu_library_2_3_x, [AC_EGREP_CPP([Potentially buggy], [ #include <features.h> #ifdef __GNU_LIBRARY__ #if __GLIBC__ == 2 && __GLIBC_MINOR__ == 3 Potentially buggy #endif #endif ], gl_cv_gnu_library_2_3_x=yes, gl_cv_gnu_library_2_3_x=no) ]) if test $ac_cv_func_getgrouplist = no; then AC_LIBOBJ([getgrouplist]) else if test $gl_cv_gnu_library_2_3_x = yes; then AC_LIBOBJ([getgrouplist]) AC_DEFINE(getgrouplist, rpl_getgrouplist, [Define to rpl_getgrouplist if the replacement function should be used.]) gl_PREREQ_GETGROUPLIST fi fi ]) # Prerequisites of lib/getgrouplist.c. AC_DEFUN([gl_PREREQ_GETGROUPLIST], [:]) ========================================================================