diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index d2c768afca4..3f6e11bb363 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -6,6 +6,7 @@ from test import test_support posix = test_support.import_module('posix') import errno +import sys import time import os import pwd @@ -363,9 +364,59 @@ class PosixTester(unittest.TestCase): os.chdir(curdir) shutil.rmtree(base_path) + def test_getgroups(self): + with os.popen('id -G') as idg: + groups = idg.read().strip() + + if not groups: + raise unittest.SkipTest("need working 'id -G'") + + self.assertEqual([int(x) for x in groups.split()], posix.getgroups()) + +class PosixGroupsTester(unittest.TestCase): + + def setUp(self): + if posix.getuid() != 0: + raise unittest.SkipTest("not enough privileges") + if not hasattr(posix, 'getgroups'): + raise unittest.SkipTest("need posix.getgroups") + if sys.platform == 'darwin': + raise unittest.SkipTest("getgroups(2) is broken on OSX") + self.saved_groups = posix.getgroups() + + def tearDown(self): + if hasattr(posix, 'setgroups'): + posix.setgroups(self.saved_groups) + elif hasattr(posix, 'initgroups'): + name = pwd.getpwuid(posix.getuid()).pw_name + posix.initgroups(name, self.saved_groups[0]) + + @unittest.skipUnless(hasattr(posix, 'initgroups'), + "test needs posix.initgroups()") + def test_initgroups(self): + # find missing group + + groups = sorted(self.saved_groups) + for g1,g2 in zip(groups[:-1], groups[1:]): + g = g1 + 1 + if g < g2: + break + else: + g = g2 + 1 + name = pwd.getpwuid(posix.getuid()).pw_name + posix.initgroups(name, g) + self.assertIn(g, posix.getgroups()) + + @unittest.skipUnless(hasattr(posix, 'setgroups'), + "test needs posix.setgroups()") + def test_setgroups(self): + for groups in [[0], range(16)]: + posix.setgroups(groups) + self.assertListEqual(groups, posix.getgroups()) + def test_main(): - test_support.run_unittest(PosixTester) + test_support.run_unittest(PosixTester, PosixGroupsTester) if __name__ == '__main__': test_main() diff --git a/Misc/NEWS b/Misc/NEWS index 915913743e4..6fce1bc71c4 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -56,6 +56,13 @@ Library Extension Modules ----------------- +- Issue #7900: The getgroups(2) system call on MacOSX behaves rather oddly + compared to other unix systems. In particular, os.getgroups() does + not reflect any changes made using os.setgroups() but basicly always + returns the same information as the id command. + + os.getgroups() can now return more than 16 groups on MacOSX. + - Issue #9277: Fix bug in struct.pack for bools in standard mode (e.g., struct.pack('>?')): if conversion to bool raised an exception then that exception wasn't properly propagated on machines where diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c365bc8c9c3..d0d68c9e3a3 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3877,17 +3877,49 @@ posix_getgroups(PyObject *self, PyObject *noargs) #define MAX_GROUPS 64 #endif gid_t grouplist[MAX_GROUPS]; + + /* On MacOSX getgroups(2) can return more than MAX_GROUPS results + * This is a helper variable to store the intermediate result when + * that happens. + * + * To keep the code readable the OSX behaviour is unconditional, + * according to the POSIX spec this should be safe on all unix-y + * systems. + */ + gid_t* alt_grouplist = grouplist; int n; n = getgroups(MAX_GROUPS, grouplist); - if (n < 0) - posix_error(); - else { - result = PyList_New(n); - if (result != NULL) { + if (n < 0) { + if (errno == EINVAL) { + n = getgroups(0, NULL); + if (n == -1) { + return posix_error(); + } + if (n == 0) { + /* Avoid malloc(0) */ + alt_grouplist = grouplist; + } else { + alt_grouplist = PyMem_Malloc(n * sizeof(gid_t)); + if (alt_grouplist == NULL) { + errno = EINVAL; + return posix_error(); + } + n = getgroups(n, alt_grouplist); + if (n == -1) { + PyMem_Free(alt_grouplist); + return posix_error(); + } + } + } else { + return posix_error(); + } + } + result = PyList_New(n); + if (result != NULL) { int i; for (i = 0; i < n; ++i) { - PyObject *o = PyInt_FromLong((long)grouplist[i]); + PyObject *o = PyInt_FromLong((long)alt_grouplist[i]); if (o == NULL) { Py_DECREF(result); result = NULL; @@ -3895,7 +3927,10 @@ posix_getgroups(PyObject *self, PyObject *noargs) } PyList_SET_ITEM(result, i, o); } - } + } + + if (alt_grouplist != grouplist) { + PyMem_Free(alt_grouplist); } return result;