I want to list all groups for users stored in /etc/passwd. I tried using getpwent() and getgrouplist() in C:
struct passwd* user;
int nGroups = 20;
gid_t* groups = malloc(nGroups * sizeof(gid_t));
struct group* gr;
while(user = getpwent()){
printf("%s : ", user->pw_name);
getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups);
for(int i = 0; i < nGroups; ++i){
gr = getgrgid(groups[i]);
if(gr){
printf("%s ", gr->gr_name);
}
}
printf("\n");
}
free(groups);
But the output is incorrect, repeating some groups multiple times. For example, for user ricenwind, the output shows repeated root entries instead of the actual groups listed by the groups ricenwind command.
How can I fix this code to correctly print all groups for each local user?
I ran into the same issue when using getgrouplist(). The problem is that getgrouplist expects the ngroups parameter to be set to the size of your groups array on input, and it updates it with the actual number of groups on output.
In your code, you’re reusing nGroups without resetting it for each user, so some calls fail silently or return fewer groups than expected, leading to repeated or wrong entries.
Try this pattern:
int nGroups;
gid_t* groups;
struct passwd* user;
struct group* gr;
setpwent();
while ((user = getpwent()) != NULL) {
// start with a reasonable size
nGroups = 16;
groups = malloc(nGroups * sizeof(gid_t));
if (getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups) == -1) {
// nGroups now contains required size
groups = realloc(groups, nGroups * sizeof(gid_t));
getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups);
}
printf("%s : ", user->pw_name);
for (int i = 0; i < nGroups; i++) {
gr = getgrgid(groups[i]);
if (gr) printf("%s ", gr->gr_name);
}
printf("\n");
free(groups);
}
endpwent();
Notice how I reset nGroups and allocate fresh memory for each user. That fixed the repeated root issue for me.
I faced a similar problem, and what helped was remembering to reinitialize the groups array and ngroups for each user.
getgrouplist() modifies ngroups, so if you reuse it without resetting, you end up with garbage or repeated entries.
Also, be careful with malloc/realloc: start with a reasonable array size, and if getgrouplist returns -1, realloc to the new nGroups size.
nGroups = 16; // starting size
groups = malloc(nGroups * sizeof(gid_t));
if (getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups) == -1) {
groups = realloc(groups, nGroups * sizeof(gid_t));
getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups);
}
After that, printing the groups using getgrgid(groups[i])->gr_name worked exactly like the groups username command.
From my experience, the repeated root entries usually happen because nGroups is not reset for each call to getgrouplist(), and sometimes the initial array isn’t big enough.
The function doesn’t automatically expand your array, it just tells you the needed size via nGroups.
What I do:
-
Set nGroups to an initial guess (like 16).
-
Allocate the groups array.
-
Call getgrouplist().
-
If it returns -1, realloc to the new nGroups and call again.
This guarantees you get all groups, and no duplicates. Also, don’t forget to call setpwent() and endpwent() around your loop, it helped me avoid missing users.
setpwent();
while ((user = getpwent()) != NULL) {
nGroups = 16;
groups = malloc(nGroups * sizeof(gid_t));
if (getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups) == -1) {
groups = realloc(groups, nGroups * sizeof(gid_t));
getgrouplist(user->pw_name, user->pw_gid, groups, &nGroups);
}
// print groups...
free(groups);
}
endpwent();
With this approach, the output matches groups <username> exactly.