Skip to main content

Modules/grpmodule.c

cpython 3.14 @ ab2d84fe1023/Modules/grpmodule.c

grpmodule.c implements the grp module, which wraps the POSIX group database routines defined in <grp.h>. The module exposes three functions: getgrgid, getgrnam, and getgrall. Each one calls the corresponding C library function, converts the returned struct group into a Python named-tuple, and returns it to the caller. On platforms without <grp.h> (Windows) the module is not built at all.

The Python-side representation is a struct_group named-tuple with five fields: gr_name (string), gr_passwd (string), gr_gid (integer), and gr_mem (list of strings). gr_mem is the most expensive field to build because it requires iterating a NULL-terminated char ** array and constructing a Python list. The other three fields map directly to PyUnicode_DecodeFSDefault or PyLong_FromLong calls.

The module is intentionally minimal. It defines no classes beyond the named tuple, holds no module state across calls, and has no interaction with the GIL other than the implicit hold that all C extension functions carry. The three functions are registered in the module's method table and the named-tuple type is created once during PyInit_grp.

Map

LinesSymbolRolegopy
1-35includes + struct_group named-tuple definitionmodule setup, type creationnot ported
36-80mkgrentconvert struct group * to Python struct_groupnot ported
81-120grp_getgrgid_impllook up a group by numeric GIDnot ported
121-160grp_getgrnam_impllook up a group by name stringnot ported
161-200grp_getgrall_impliterate all groups via setgrent/getgrent/endgrentnot ported
201-215method table + PyInit_grpmodule registrationnot ported

Reading

Named-tuple type creation (lines 1 to 35)

cpython 3.14 @ ab2d84fe1023/Modules/grpmodule.c#L1-35

The file opens with the standard POSIX includes and then constructs the struct_group named-tuple type by calling PyStructSequence_NewType with a descriptor table. The descriptor lists the five field names in order: gr_name, gr_passwd, gr_gid, gr_mem. The resulting type object is stored in a module-level static and reused for every conversion. Using a named-tuple rather than a plain tuple means field access by name works from Python without any extra __getattr__ overhead.

static PyStructSequence_Field struct_group_type_fields[] = {
{"gr_name", "group name"},
{"gr_passwd", "password"},
{"gr_gid", "group id"},
{"gr_mem", "group members"},
{NULL}
};

mkgrent: struct group to Python (lines 36 to 80)

cpython 3.14 @ ab2d84fe1023/Modules/grpmodule.c#L36-80

mkgrent is the single conversion helper used by all three public functions. It calls PyStructSequence_New to allocate a zero-filled named-tuple, then sets each field with PyStructSequence_SET_ITEM. The string fields go through PyUnicode_DecodeFSDefault so that non-UTF-8 system encodings are handled correctly. The gr_mem field is built by walking the NULL-terminated char **p array and appending each decoded string to a new list before handing ownership to the tuple slot.

static PyObject *
mkgrent(struct group *p)
{
PyObject *v = PyStructSequence_New(&StructGrpType);
...
PyStructSequence_SET_ITEM(v, 0, PyUnicode_DecodeFSDefault(p->gr_name));
PyStructSequence_SET_ITEM(v, 1, PyUnicode_DecodeFSDefault(p->gr_passwd));
PyStructSequence_SET_ITEM(v, 2, PyLong_FromLong((long)p->gr_gid));
PyStructSequence_SET_ITEM(v, 3, members_list);
return v;
}

getgrgid and getgrnam (lines 81 to 160)

cpython 3.14 @ ab2d84fe1023/Modules/grpmodule.c#L81-160

grp_getgrgid_impl accepts either an integer GID or an object that implements __index__. It converts the argument to gid_t with _Py_Uid_Converter, calls getgrgid(gid), and raises KeyError when the result is NULL. grp_getgrnam_impl follows the same pattern but accepts a str and calls getgrnam after encoding it through the filesystem codec. Both functions call mkgrent on success and return None if the entry is not found and the caller passed a non-existing key, matching the documented contract of raising KeyError.

static PyObject *
grp_getgrgid_impl(PyObject *module, PyObject *id)
{
gid_t gid;
struct group *p;
if (!_Py_Gid_Converter(id, &gid)) return NULL;
if ((p = getgrgid(gid)) == NULL) {
PyErr_Format(PyExc_KeyError, "getgrgid(): gid not found: %ld", (long)gid);
return NULL;
}
return mkgrent(p);
}

getgrall: full database scan (lines 161 to 200)

cpython 3.14 @ ab2d84fe1023/Modules/grpmodule.c#L161-200

grp_getgrall_impl opens the group database with setgrent, iterates with repeated getgrent calls until it returns NULL, converts each entry via mkgrent, and appends the result to a Python list. It closes the database with endgrent in all paths, including the error path, to avoid leaking the underlying file descriptor. If mkgrent or PyList_Append fails, the partially built list is released and NULL is returned after endgrent runs.

static PyObject *
grp_getgrall_impl(PyObject *module)
{
PyObject *d = PyList_New(0);
struct group *p;
setgrent();
while ((p = getgrent()) != NULL) {
PyObject *v = mkgrent(p);
if (v == NULL || PyList_Append(d, v) != 0) {
Py_XDECREF(v); Py_DECREF(d); endgrent(); return NULL;
}
Py_DECREF(v);
}
endgrent();
return d;
}

gopy mirror

Not yet ported.