Reading /etc/passwd and /etc/group directly with fopen and fscanf would work, but it is fragile and does not handle NIS or LDAP. The right way is to use the standard POSIX library functions. These functions abstract away the storage backend and always give you the right data regardless of whether users are stored locally or across a network.
In this part you will learn how to look up user info by name and by UID, how to iterate through all users, how to read group records, and critical pitfalls like the static buffer problem and the non-reentrant nature of these functions.
You might wonder: if /etc/passwd is a plain text file, why not just open it and parse it yourself? There are three good reasons not to.
The Name Service Switch (NSS) layer sits between your program and the actual storage backend. When you call getpwnam(“alice”), NSS decides whether to look in /etc/passwd, query an LDAP server, or check NIS — based on configuration in /etc/nsswitch.conf. Your code does not need to know.
/* /etc/nsswitch.conf tells NSS where to look */
$ cat /etc/nsswitch.conf
passwd: files ldap # Check /etc/passwd first, then LDAP
group: files ldap
shadow: files ldap
/* Your code just calls getpwnam — NSS handles the rest */
struct passwd *p = getpwnam("alice");
/* Works whether alice is in /etc/passwd or an LDAP server */
The most common operation: given a username string, get all the account information for that user.
#include <pwd.h>
#include <stdio.h>
#include <errno.h>
int main() {
struct passwd *pwd;
errno = 0;
pwd = getpwnam("alice");
if (pwd == NULL) {
if (errno == 0)
printf("User 'alice' not found\n");
else
perror("getpwnam");
return 1;
}
/* Print all fields from the passwd structure */
printf("Login name : %s\n", pwd->pw_name);
printf("UID : %d\n", pwd->pw_uid);
printf("GID : %d\n", pwd->pw_gid);
printf("Full name : %s\n", pwd->pw_gecos);
printf("Home dir : %s\n", pwd->pw_dir);
printf("Login shell : %s\n", pwd->pw_shell);
return 0;
}
/* Output when run */
Login name : alice
UID : 1001
GID : 1001
Full name : Alice Kumar
Home dir : /home/alice
Login shell : /bin/bash
Link with no special flags — this function is in the standard C library (glibc).
$ gcc -o lookup lookup.c
$ ./lookup
Here is the complete structure for reference:
/* Defined in <pwd.h> */
struct passwd {
char *pw_name; /* Login name */
char *pw_passwd; /* Encrypted password (valid only if NO shadow) */
uid_t pw_uid; /* Numeric User ID */
gid_t pw_gid; /* Numeric primary Group ID */
char *pw_gecos; /* Comment / full name */
char *pw_dir; /* Home directory path */
char *pw_shell; /* Login shell path */
};
Important: pw_passwd contains valid data ONLY if shadow passwords are NOT enabled. On modern Linux (shadow enabled), pw_passwd will just be “x”. To get the actual hash you need getspnam() from <shadow.h>.
Sometimes you have a numeric UID (perhaps from stat() on a file) and need to find the human-readable username. getpwuid() does exactly this.
#include <pwd.h>
#include <sys/stat.h>
#include <stdio.h>
/* A simple program that prints the owner name of a file */
int main(int argc, char *argv[]) {
struct stat sb;
struct passwd *pwd;
if (argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
return 1;
}
/* Get file metadata */
if (stat(argv[1], &sb) == -1) {
perror("stat");
return 1;
}
/* UID is in sb.st_uid — look up the name */
pwd = getpwuid(sb.st_uid);
if (pwd == NULL)
printf("Owner UID: %d (name unknown)\n", sb.st_uid);
else
printf("Owner: %s (UID=%d)\n", pwd->pw_name, pwd->pw_uid);
return 0;
}
$ ./owner /etc/hosts
Owner: root (UID=0)
$ ./owner /home/alice/notes.txt
Owner: alice (UID=1001)
This is essentially what the ls -l command does internally for every file it displays.
Both getpwnam() and getpwuid() return a pointer to a statically allocated structure. This means the same memory location is reused on every call. If you call either function twice, the second call overwrites the data from the first call.
/* BUG: This prints the same UID twice — both will be bob's UID */
struct passwd *p1 = getpwnam("alice");
struct passwd *p2 = getpwnam("bob");
/* p1 now points to the SAME struct as p2 — it was overwritten! */
printf("alice UID = %d\n", p1->pw_uid); /* WRONG: prints bob's UID */
printf("bob UID = %d\n", p2->pw_uid); /* Correct */
This is exactly what exercise 8-1 from the chapter is asking about. The fix is to copy the data you need before calling again:
/* CORRECT: Save the data you need before the next call */
struct passwd *p;
uid_t alice_uid, bob_uid;
p = getpwnam("alice");
alice_uid = p->pw_uid; /* Copy the UID before next call */
p = getpwnam("bob");
bob_uid = p->pw_uid;
printf("alice UID = %d\n", alice_uid); /* Now correct */
printf("bob UID = %d\n", bob_uid); /* Correct */
For multi-threaded programs or reentrant code, use the thread-safe variants:
#include <pwd.h>
/* Reentrant versions — you supply the buffer */
int getpwnam_r(const char *name,
struct passwd *pwd, /* caller-provided struct */
char *buf, /* caller-provided string buffer */
size_t buflen, /* size of buf */
struct passwd **result); /* points to pwd on success, NULL if not found */
/* Usage example */
struct passwd pwd_store;
char buf[4096];
struct passwd *result;
if (getpwnam_r("alice", &pwd_store, buf, sizeof(buf), &result) == 0) {
if (result != NULL)
printf("alice UID = %d\n", result->pw_uid);
}
A very common requirement in real programs is to convert a username string to a UID (or vice versa). Here are clean, reusable helper functions that handle the edge cases properly:
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
/* Returns username for a given UID, or NULL if not found */
char *username_from_uid(uid_t uid) {
struct passwd *pwd = getpwuid(uid);
return (pwd == NULL) ? NULL : pwd->pw_name;
}
/* Returns UID for a given username, or -1 if not found.
Accepts either a name ("alice") or a numeric string ("1001"). */
uid_t uid_from_username(const char *name) {
struct passwd *pwd;
char *endptr;
uid_t u;
if (name == NULL || *name == '\0')
return (uid_t) -1;
/* Allow caller to pass a numeric string directly */
u = (uid_t) strtol(name, &endptr, 10);
if (*endptr == '\0') /* entire string was a valid number */
return u;
pwd = getpwnam(name);
if (pwd == NULL)
return (uid_t) -1;
return pwd->pw_uid;
}
/* Example usage */
int main() {
printf("UID of alice = %d\n", uid_from_username("alice"));
printf("UID of 1001 = %d\n", uid_from_username("1001")); /* numeric string */
printf("Name of 0 = %s\n", username_from_uid(0));
printf("Name of 1001 = %s\n", username_from_uid(1001));
return 0;
}
/* Output */
UID of alice = 1001
UID of 1001 = 1001
Name of 0 = root
Name of 1001 = alice
The group equivalents work identically to the password functions, but they use <grp.h> and return a struct group.
#include <grp.h>
#include <stdio.h>
int main() {
struct group *grp;
/* Look up group by name */
grp = getgrnam("developers");
if (grp == NULL) {
printf("Group 'developers' not found\n");
return 1;
}
printf("Group name : %s\n", grp->gr_name);
printf("Group GID : %d\n", grp->gr_gid);
printf("Members : ");
/* gr_mem is a NULL-terminated array of strings */
char **member = grp->gr_mem;
while (*member != NULL) {
printf("%s ", *member);
member++;
}
printf("\n");
return 0;
}
/* Output */
Group name : developers
Group GID : 200
Members : alice carol dave
/* Look up group by GID */
grp = getgrgid(200);
if (grp != NULL)
printf("GID 200 is group: %s\n", grp->gr_name);
The same static buffer pitfall applies to group functions. Successive calls overwrite the returned structure.
Sometimes you need to iterate over every user on the system — for example, to build a list of all home directories, or to find all accounts that have not logged in for 90 days. The getpwent family does this.
#include <pwd.h>
#include <stdio.h>
/* Print all usernames and their UIDs */
int main() {
struct passwd *pwd;
/* No need to open the file manually — getpwent does it */
while ((pwd = getpwent()) != NULL) {
printf("%-20s UID=%-6d Home=%s\n",
pwd->pw_name,
pwd->pw_uid,
pwd->pw_dir);
}
/* ALWAYS call endpwent when done — closes the file */
endpwent();
return 0;
}
/* Sample output */
root UID=0 Home=/root
daemon UID=1 Home=/usr/sbin
alice UID=1001 Home=/home/alice
bob UID=1002 Home=/home/bob
carol UID=1003 Home=/home/carol
The three functions and their roles:
setpwent(); /* Rewind to the beginning of the password database */
getpwent(); /* Return next entry, NULL when exhausted */
endpwent(); /* Close the database (free resources) */
/* To restart iteration from the beginning mid-way */
setpwent();
/* Example: Find all users with UID >= 1000 (regular users) */
while ((pwd = getpwent()) != NULL) {
if (pwd->pw_uid >= 1000)
printf("Regular user: %s\n", pwd->pw_name);
}
endpwent();
The same pattern applies to groups. Useful for finding all groups a user belongs to, or listing all groups on the system.
#include <grp.h>
#include <stdio.h>
#include <string.h>
/* Print all groups that 'alice' belongs to (via the group file) */
void print_groups_for_user(const char *username) {
struct group *grp;
setgrent(); /* Start from beginning */
while ((grp = getgrent()) != NULL) {
char **member = grp->gr_mem;
while (*member != NULL) {
if (strcmp(*member, username) == 0) {
printf(" %s (GID=%d)\n", grp->gr_name, grp->gr_gid);
break;
}
member++;
}
}
endgrent(); /* Clean up */
}
int main() {
printf("Groups for alice:\n");
print_groups_for_user("alice");
return 0;
}
/* Output */
Groups for alice:
developers (GID=200)
docker (GID=999)
sudo (GID=27)
If you are writing an authentication program (like a custom login screen or a PAM module), you may need to access the shadow password. The getspnam() function retrieves a shadow record by username.
#include <shadow.h>
#include <stdio.h>
#include <errno.h>
/* Must run as root or with appropriate privilege */
int main() {
struct spwd *sp;
errno = 0;
sp = getspnam("alice");
if (sp == NULL) {
if (errno == EACCES)
fprintf(stderr, "Permission denied: run as root\n");
else
fprintf(stderr, "User not found in shadow file\n");
return 1;
}
printf("Login name : %s\n", sp->sp_namp);
printf("Encrypted pw : %s\n", sp->sp_pwdp); /* the actual hash */
printf("Last changed : %ld days since Jan 1 1970\n", sp->sp_lstchg);
printf("Max days : %ld\n", sp->sp_max);
printf("Warning days : %ld\n", sp->sp_warn);
return 0;
}
$ sudo ./shadow_read
Login name : alice
Encrypted pw : $6$rAnD0mSaLt$VeryLongHashString...
Last changed : 19500 days since Jan 1 1970
Max days : 90
Warning days : 7
There are also setspent(), getspent(), and endspent() for iterating all shadow records, following the same pattern as the password functions.
Here is a small but complete program combining getpwuid and getgrgid to print detailed ownership info for a file, similar to what ls -l does:
#include <stdio.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
int main(int argc, char *argv[]) {
struct stat sb;
struct passwd *pwd;
struct group *grp;
const char *filename;
filename = (argc > 1) ? argv[1] : "/etc/hosts";
if (stat(filename, &sb) == -1) {
perror("stat");
return 1;
}
/* Look up owner name */
pwd = getpwuid(sb.st_uid);
const char *owner = (pwd != NULL) ? pwd->pw_name : "unknown";
/* Look up group name */
grp = getgrgid(sb.st_gid);
const char *group = (grp != NULL) ? grp->gr_name : "unknown";
printf("File : %s\n", filename);
printf("Owner : %s (UID=%d)\n", owner, sb.st_uid);
printf("Group : %s (GID=%d)\n", group, sb.st_gid);
printf("Size : %ld bytes\n", (long)sb.st_size);
return 0;
}
$ gcc -o fileinfo fileinfo.c
$ ./fileinfo /etc/passwd
File : /etc/passwd
Owner : root (UID=0)
Group : root (GID=0)
Size : 2345 bytes
/* Headers required */
#include <pwd.h> /* getpwnam, getpwuid, getpwent, setpwent, endpwent */
#include <grp.h> /* getgrnam, getgrgid, getgrent, setgrent, endgrent */
#include <shadow.h> /* getspnam, getspent, setspent, endspent */
/* Compile */
$ gcc -o myprogram myprogram.c
/* No special link flags needed for pwd/grp functions */
/* Shadow functions also require no extra -l flag on Linux */
In the final part, we learn how Linux encrypts passwords using crypt() and how to write a program that authenticates a user against the shadow password file.
