Index: configure.in =================================================================== --- configure.in (revision 87729) +++ configure.in (working copy) @@ -2536,7 +2536,7 @@ # checks for library functions AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \ - gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \ + gai_strerror getgrouplist getgroups getlogin getloadavg getpeername getpgid getpid \ getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \ initgroups kill killpg lchmod lchown lstat mbrtowc mkfifo mknod mktime \ mremap nice pathconf pause plock poll pthread_init \ Index: Doc/library/os.rst =================================================================== --- Doc/library/os.rst (revision 87729) +++ Doc/library/os.rst (working copy) @@ -219,6 +219,17 @@ Availability: Unix. +.. function:: getgrouplist(user, group) + + Return list of group ids that *user* belongs to. If *group* is not in the + list, it is included; typically, *group* is specified as the group ID + field from the password record for *user*. + + Availability: Unix. + + .. versionadded:: 3.3 + + .. function:: getgroups() Return list of supplemental group ids associated with the current process. Index: Lib/test/test_posix.py =================================================================== --- Lib/test/test_posix.py (revision 87729) +++ Lib/test/test_posix.py (working copy) @@ -373,6 +373,21 @@ os.chdir(curdir) support.rmtree(base_path) + @unittest.skipUnless(hasattr(posix, 'getgrouplist'), "test needs posix.getgrouplist()") + @unittest.skipUnless(hasattr(pwd, 'getpwuid'), "test needs pwd.getpwuid()") + @unittest.skipUnless(hasattr(os, 'getuid'), "test needs os.getuid()") + def test_getgrouplist(self): + with os.popen('id -G') as idg: + groups = idg.read().strip() + + if not groups: + raise unittest.SkipTest("need working 'id -G'") + + self.assertEqual( + set([int(x) for x in groups.split()]), + set(posix.getgrouplist(pwd.getpwuid(os.getuid())[0], + pwd.getpwuid(os.getuid())[3]))) + def test_getgroups(self): with os.popen('id -G') as idg: groups = idg.read().strip() Index: Modules/posixmodule.c =================================================================== --- Modules/posixmodule.c (revision 87729) +++ Modules/posixmodule.c (working copy) @@ -4240,7 +4240,71 @@ return PyLong_FromPid(getpid()); } +#ifdef HAVE_GETGROUPLIST +PyDoc_STRVAR(posix_getgrouplist__doc__, +"getgrouplist(user, group) -> list of groups to which a user belongs\n\n\ +Returns a list of groups to which a user belongs.\n\n\ + user: username to lookup\n\ + group: base group id of the user"); +static PyObject * +posix_getgrouplist(PyObject *self, PyObject *args) +{ +#ifdef NGROUPS_MAX +#define MAX_GROUPS NGROUPS_MAX +#else + /* defined to be 16 on Solaris7, so this should be a small number */ +#define MAX_GROUPS 64 +#endif + + const char *user; + int i, ngroups; + PyObject *list; +#ifdef __APPLE__ + int *groups, basegid; +#else + gid_t *groups, basegid; +#endif + ngroups = MAX_GROUPS; + + if (!PyArg_ParseTuple(args, "si", &user, &basegid)) + return NULL; + +#ifdef __APPLE__ + groups = PyMem_Malloc(ngroups * sizeof (int)); +#else + groups = PyMem_Malloc(ngroups * sizeof (gid_t)); +#endif + if (groups == NULL) + return PyErr_NoMemory(); + + if (getgrouplist(user, basegid, groups, &ngroups) == -1) { + PyMem_Del(groups); + return posix_error(); + } + + list = PyList_New(ngroups); + if (list == NULL) { + PyMem_Del(groups); + return NULL; + } + + for (i = 0; i < ngroups; i++) { + PyObject *o = PyLong_FromUnsignedLong((unsigned long)groups[i]); + if (o == NULL) { + Py_DECREF(list); + PyMem_Del(groups); + return NULL; + } + PyList_SET_ITEM(list, i, o); + } + + PyMem_Del(groups); + + return list; +} +#endif + #ifdef HAVE_GETGROUPS PyDoc_STRVAR(posix_getgroups__doc__, "getgroups() -> list of group IDs\n\n\ @@ -7872,6 +7936,9 @@ #ifdef HAVE_GETGID {"getgid", posix_getgid, METH_NOARGS, posix_getgid__doc__}, #endif /* HAVE_GETGID */ +#ifdef HAVE_GETGROUPLIST + {"getgrouplist", posix_getgrouplist, METH_VARARGS, posix_getgrouplist__doc__}, +#endif #ifdef HAVE_GETGROUPS {"getgroups", posix_getgroups, METH_NOARGS, posix_getgroups__doc__}, #endif Index: pyconfig.h.in =================================================================== --- pyconfig.h.in (revision 87729) +++ pyconfig.h.in (working copy) @@ -278,6 +278,9 @@ /* Define this if you have flockfile(), getc_unlocked(), and funlockfile() */ #undef HAVE_GETC_UNLOCKED +/* Define to 1 if you have the `getgrouplist' function. */ +#undef HAVE_GETGROUPLIST + /* Define to 1 if you have the `getgroups' function. */ #undef HAVE_GETGROUPS