The utmpx API gives you five functions for reading records from the utmp file (or any utmpx-format file). Three are sequential/search readers, one controls file position, and one closes the file. Together they let you scan through all records or jump to a specific one.
All these functions share a single “current position” pointer inside the file. Every read advances this pointer.
#include <utmpx.h>
/* Rewind the utmp file to the beginning */
void setutxent(void);
/* Close the utmp file when done */
void endutxent(void);
/* Read next record sequentially */
struct utmpx *getutxent(void);
/* Search for record by type (ut_type) and id (ut_id) */
struct utmpx *getutxid(const struct utmpx *ut);
/* Search for record by terminal line (ut_line) */
struct utmpx *getutxline(const struct utmpx *ut);
/* All getutx*() functions return:
* pointer to statically allocated struct utmpx on success
* NULL if no matching record found OR end-of-file reached
*/
โ ๏ธ Static buffer warning: All three getutx*() functions return a pointer to a single shared static buffer. Each call overwrites this buffer. If you need to keep a record for later, copy the whole struct: struct utmpx saved = *getutxent();
Moves the file position back to the beginning of the utmp file. Call this before starting any scan. Also opens the file if it isn’t already open.
setutxent(); /* Always call this before getutxent() loop */
Closes the utmp file. Call this when done to release the file descriptor. Good practice even though the OS will close it on program exit.
endutxent(); /* Always call this after done reading */
Reads the next record from the current file position. Returns NULL at end-of-file. Use this to scan all records in order.
struct utmpx *ut;
setutxent();
while ((ut = getutxent()) != NULL) {
/* Process each record */
}
endutxent();
Searches forward from current position for a matching record. The match criteria depend on ut_type:
- If
ut_typeisRUN_LVL,BOOT_TIME,NEW_TIME, orOLD_TIMEโ match records with the sameut_typeonly - If
ut_typeisINIT_PROCESS,LOGIN_PROCESS,USER_PROCESS, orDEAD_PROCESSโ match any of these four types AND the sameut_id
Searches forward for a record where ut_type is LOGIN_PROCESS or USER_PROCESS AND ut_line matches. Useful for finding the login record for a specific terminal.
On some UNIX implementations, getutxid() and getutxline() use the static result buffer as a cache. If the buffer already contains a record that matches the search criteria, they skip reading the file and just return the same cached record again.
This can trap you in an infinite loop! To avoid this, zero out the result after each call when searching in a loop:
/* Correct pattern for searching in a loop */
struct utmpx search;
struct utmpx *result = NULL;
memset(&search, 0, sizeof(struct utmpx));
strncpy(search.ut_line, "pts/0", sizeof(search.ut_line));
search.ut_type = USER_PROCESS;
setutxent();
while (1) {
/* IMPORTANT: clear cache before each call */
if (result != NULL)
memset(result, 0, sizeof(struct utmpx));
result = getutxline(&search);
if (result == NULL)
break; /* not found or end of file */
printf("Found: %s on %s\n", result->ut_user, result->ut_line);
}
endutxent();
This is a clean reimplementation of the dump_utmpx example from the book โ reads and displays all records from any utmpx-format file.
/* dump_utmpx.c
* Display all records in a utmpx-format file (utmp or wtmp)
* Compile: gcc dump_utmpx.c -o dump_utmpx
* Usage: ./dump_utmpx [filepath]
* Default: reads /var/run/utmp
* Example: ./dump_utmpx /var/log/wtmp
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utmpx.h>
#include <time.h>
#include <paths.h>
static const char *type_str(short type)
{
switch (type) {
case EMPTY: return "EMPTY ";
case RUN_LVL: return "RUN_LVL ";
case BOOT_TIME: return "BOOT_TIME";
case NEW_TIME: return "NEW_TIME ";
case OLD_TIME: return "OLD_TIME ";
case INIT_PROCESS: return "INIT_PR ";
case LOGIN_PROCESS: return "LOGIN_PR ";
case USER_PROCESS: return "USER_PR ";
case DEAD_PROCESS: return "DEAD_PR ";
default: return "??? ";
}
}
int main(int argc, char *argv[])
{
struct utmpx *ut;
char timebuf[32];
if (argc > 1 && strcmp(argv[1], "--help") == 0) {
fprintf(stderr, "Usage: %s [utmpx-file]\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Switch file if a path was provided */
if (argc > 1) {
if (utmpxname(argv[1]) == -1) {
perror("utmpxname");
exit(EXIT_FAILURE);
}
}
setutxent();
/* Print header */
printf("%-10s %-9s %5s %-10s %-4s %-20s %s\n",
"USER", "TYPE", "PID", "LINE", "ID", "HOST", "DATE/TIME");
printf("%-10s %-9s %5s %-10s %-4s %-20s %s\n",
"----", "----", "---", "----", "--", "----", "---------");
while ((ut = getutxent()) != NULL) {
/* Format time */
strftime(timebuf, sizeof(timebuf), "%d %b %H:%M:%S",
localtime((time_t *)&ut->ut_tv.tv_sec));
printf("%-10.10s %-9s %5d %-10.10s %-4.4s %-20.20s %s\n",
ut->ut_user,
type_str(ut->ut_type),
(int)ut->ut_pid,
ut->ut_line,
ut->ut_id,
ut->ut_host,
timebuf);
}
endutxent();
return 0;
}
Sample output (utmp):
USER TYPE PID LINE ID HOST DATE/TIME
---- ---- --- ---- -- ---- ---------
BOOT_TIME 0 05 Jun 10:00:01
RUN_LVL 1 05 Jun 10:00:05
ravi USER_PR 1234 tty1 1 05 Jun 10:05:12
priya USER_PR 5678 pts/0 /0 192.168.1.10 05 Jun 11:30:44
Uses getutxline() to search the wtmp file for all login and logout events for a specific terminal, pairing them to compute session durations.
/* search_utmpx.c
* Search wtmp for all records on a given terminal
* Compile: gcc search_utmpx.c -o search_utmpx
* Usage: ./search_utmpx pts/0
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utmpx.h>
#include <time.h>
#include <paths.h>
int main(int argc, char *argv[])
{
struct utmpx search_ut, *found;
char timebuf[32];
int count = 0;
if (argc != 2) {
fprintf(stderr, "Usage: %s terminal-name\n", argv[0]);
fprintf(stderr, " Example: %s pts/0\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Search wtmp for historical data */
if (utmpxname(_PATH_WTMP) == -1) {
perror("utmpxname");
exit(EXIT_FAILURE);
}
memset(&search_ut, 0, sizeof(struct utmpx));
strncpy(search_ut.ut_line, argv[1], sizeof(search_ut.ut_line) - 1);
search_ut.ut_type = USER_PROCESS; /* getutxline matches USER_PROCESS or LOGIN_PROCESS */
setutxent();
printf("Records for terminal: %s\n\n", argv[1]);
printf("%-12s %-12s %-20s\n", "USER", "TYPE", "TIME");
printf("%-12s %-12s %-20s\n", "----", "----", "----");
while (1) {
/* Clear the static cache buffer before each call */
if (found != NULL)
memset(found, 0, sizeof(struct utmpx));
found = getutxline(&search_ut);
if (found == NULL)
break;
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
localtime((time_t *)&found->ut_tv.tv_sec));
printf("%-12.12s %-12s %s\n",
found->ut_user,
found->ut_type == USER_PROCESS ? "LOGIN" : "LOGIN_PROC",
timebuf);
count++;
}
endutxent();
printf("\nTotal records found: %d\n", count);
return 0;
}
Scans the utmp file and counts how many active login sessions each user has โ useful for multi-session monitoring.
/* count_sessions.c
* Count active login sessions per user from utmp
* Compile: gcc count_sessions.c -o count_sessions
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utmpx.h>
#define MAX_USERS 256
struct user_count {
char name[32];
int sessions;
};
int main(void)
{
struct utmpx *ut;
struct user_count users[MAX_USERS];
int nusers = 0;
int i, found;
memset(users, 0, sizeof(users));
setutxent();
while ((ut = getutxent()) != NULL) {
if (ut->ut_type != USER_PROCESS)
continue;
/* Find or add this user */
found = 0;
for (i = 0; i < nusers; i++) {
if (strncmp(users[i].name, ut->ut_user, 32) == 0) {
users[i].sessions++;
found = 1;
break;
}
}
if (!found && nusers < MAX_USERS) {
strncpy(users[nusers].name, ut->ut_user, 31);
users[nusers].sessions = 1;
nusers++;
}
}
endutxent();
printf("Active Sessions Summary\n");
printf("%-20s %s\n", "Username", "Sessions");
printf("%-20s %s\n", "--------", "--------");
for (i = 0; i < nusers; i++)
printf("%-20s %d\n", users[i].name, users[i].sessions);
if (nusers == 0)
printf("No active user sessions found.\n");
return 0;
}
Answer: getutxent() reads the next record sequentially. getutxid() searches forward for a record matching a specific ut_type and ut_id. getutxline() searches forward for a USER_PROCESS or LOGIN_PROCESS record with a matching ut_line.
Answer: The file has a shared current position pointer. If a third-party library or earlier code has already read some records, the pointer may not be at the beginning. Calling setutxent() rewinds it to the start, ensuring you scan all records.
Answer: On some implementations, these functions cache the last returned record in the static buffer. If the cached record matches the search criteria on the next call, they return the same record without reading from the file. This can cause infinite loops when searching in a loop. The fix is to call memset(result, 0, sizeof(struct utmpx)) on the returned pointer after each iteration.
Answer: They all return a pointer to a single shared static buffer. If two threads call these functions simultaneously, one thread may read the buffer while another is overwriting it, causing data corruption. In multi-threaded code, you must either use a mutex to protect these calls or copy the returned struct immediately.
Answer: Call utmpxname(_PATH_WTMP) before calling setutxent(). This tells the utmpx functions to use the wtmp file. Then use setutxent() + getutxent() (or search functions) as normal. Remember that utmpxname() does not validate the path โ errors appear only on the first read.
