getgroups(2)
okay bestie, pull up a chair (or a beanbag, no judgment) because today we're reading getgroups(2) โ yes it lives in the System Calls Manual, section 2, and yes it's lowkey iconic. this is the manual page for two little syscalls that handle the whole "which groups am I in" situation. let's get into it. ๐
โจ NAME โจ
getgroups, setgroups โ get/set list of supplementary group IDs
๐ LIBRARY ๐
Standard C library (libc, -lc). the classics. ๐
๐ฆ SYNOPSIS ๐ฆ
#include <unistd.h>
int getgroups(int size, gid_t list[_Nullable size]);
#include <grp.h>
int setgroups(size_t size, const gid_t list[size]);
๐ Feature Test Macro Requirements for glibc ๐
(peep feature_test_macros(7) for the whole lore, but here's the tea for setgroups() specifically):
- Since glibc 2.19:
_DEFAULT_SOURCE - glibc 2.19 and earlier:
_BSD_SOURCE
๐ DESCRIPTION ๐
getgroups() โ the "hey what groups am I in" call
okay so getgroups() returns the supplementary group IDs of the calling process, dropping them into list. the size argument? set it to the maximum number of items that can be stored in the buffer that list points at. and if your calling process is a member of more than size supplementary groups โ boom, error. ๐ญ
it is unspecified whether the effective group ID of the calling process is included in the returned list. (so like, an application should also call getegid(2) and add or remove the resulting value itself โ don't trust the list to do it for you, bestie.)
if size is zero, then list is not modified, but the total number of supplementary group IDs for the process gets returned to you. this is THE move โ it lets you figure out how big your dynamically allocated list needs to be before you call getgroups() again. main character planning energy. ๐
setgroups() โ the "okay, setting my groups now" call
setgroups() sets the supplementary group IDs for the calling process. appropriate privileges are required for this (go read the EPERM bit below, it's important fr). the size argument specifies the number of supplementary group IDs sitting in the buffer pointed to by list.
and if a process wants to drop all of its supplementary groups? one call does it:
setgroups(0, (gid_t [0]){});
slay, goodbye groups. ๐ธ
๐ RETURN VALUE ๐
On success, getgroups() returns the number of supplementary group IDs. On error, -1 is returned, and errno is set to indicate the error.
On success, setgroups() returns 0. On error, -1 is returned, and errno is set to indicate the error.
๐ ERRORS ๐
EFAULTโlisthas an invalid address. not it.
getgroups() can additionally fail with this one:
EINVALโsizeis less than the number of supplementary group IDs, but is not zero.
setgroups() can additionally fail with these:
EINVALโsizeis greater thanNGROUPS_MAX(which was 32 before Linux 2.6.4, and has been 65536 since Linux 2.6.4).ENOMEMโ out of memory. rip. ๐ชฆEPERMโ the calling process has insufficient privilege (the caller does not have theCAP_SETGIDcapability in the user namespace in which it resides).EPERM(since Linux 3.19) โ the use ofsetgroups()is denied in this user namespace. see the description of/proc/pid/setgroupsinuser_namespaces(7).
bestie please clock that there are two separate EPERM entries here โ the second one only applies since Linux 3.19 and it's about user namespaces. don't merge them in your head. ๐
๐ VERSIONS ๐
C library/kernel differences
At the kernel level, user IDs and group IDs are a per-thread attribute. however, POSIX requires that all threads in a process share the same credentials. the NPTL threading implementation handles the POSIX requirements by providing wrapper functions for the various system calls that change process UIDs and GIDs. these wrapper functions (including the one for setgroups()) employ a signal-based technique to ensure that when one thread changes credentials, all of the other threads in the process also change their credentials too. for the details, see nptl(7). group chat rules: when one changes, everybody changes. ๐ซถ
โญ STANDARDS โญ
getgroups()โ POSIX.1-2024.setgroups()โ None.
(as in, nothing. POSIX said not my problem. ๐)
๐ HISTORY ๐
getgroups()โ 4.3BSD, SVr4, POSIX.1-1988.setgroups()โ SVr4, 4.3BSD. sincesetgroups()requires privilege, it is not covered by POSIX.1.
The original Linux getgroups() system call supported only 16-bit group IDs. subsequently, Linux 2.4 added getgroups32(), supporting 32-bit IDs. the glibc getgroups() wrapper function transparently deals with the variation across kernel versions โ so you don't gotta worry about it. โจ
๐ง NOTES ๐ง
A process can have up to NGROUPS_MAX supplementary group IDs in addition to the effective group ID. the constant NGROUPS_MAX is defined in <limits.h>. the set of supplementary group IDs is inherited from the parent process, and preserved across an execve(2).
you can find the maximum number of supplementary group IDs at run time using sysconf(3):
long ngroups_max;
ngroups_max = sysconf(_SC_NGROUPS_MAX);
the maximum return value of getgroups() cannot be larger than one more than this value. and since Linux 2.6.4, the maximum number of supplementary group IDs is also exposed via the Linux-specific read-only file /proc/sys/kernel/ngroups_max. ๐
๐ฅ EXAMPLES ๐ฅ
okay here's the full code, verbatim, no edits, go off:
#include <err.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define MALLOC(n, T) ((T *) reallocarray(NULL, n, sizeof(T)))
static gid_t *agetgroups(size_t *ngids);
int
main(void)
{
gid_t *gids;
size_t n;
gids = agetgroups(&n);
if (gids == NULL)
err(EXIT_FAILURE, "agetgroups");
if (n != 0) {
printf("%jd", (intmax_t) gids[0]);
for (size_t i = 1; i < n; i++)
printf(" %jd", (intmax_t) gids[i]);
}
puts("");
free(gids);
exit(EXIT_SUCCESS);
}
static gid_t *
agetgroups(size_t *ngids)
{
int n;
gid_t *gids;
n = getgroups(0, NULL);
if (n == -1)
return NULL;
gids = MALLOC(n, gid_t);
if (gids == NULL)
return NULL;
n = getgroups(n, gids);
if (n == -1) {
free(gids);
return NULL;
}
*ngids = n;
return gids;
}
see how it calls getgroups(0, NULL) first to get the count, then allocates, then calls getgroups() for real? that's the whole two-step the NOTES section was talking about. obsessed. ๐
๐ SEE ALSO ๐
getgid(2), setgid(2), getgrouplist(3), group_member(3), initgroups(3), capabilities(7), credentials(7)
and that's the page, bestie!! ๐ this whole thing is from Linux man-pages 6.20, dated 2026-02-08 โ go forth and check your groups responsibly. mwah. ๐ โจ๐
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: Linux-man-pages-copyleft
- Copyright 1993, Rickard E. Faith <faith@cs.unc.edu>
- Copyright 2008-2015, Michael Kerrisk <mtk.manpages@gmail.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms