Every time a user logs into a Linux system, the kernel and system libraries record that event. This process is called login accounting. It answers two important questions:
- Who is currently logged in? (answered by the
utmpfile) - Who logged in and out in the past? (answered by the
wtmpfile)
Programs like who, w, last, and uptime all read these files to show you this information.
Linux maintains two special binary files for login accounting. Both live in fixed locations on disk and are updated automatically by login programs like login, sshd, and getty.
| File | Default Path | Purpose | Used By |
|---|---|---|---|
| utmp | /var/run/utmp | Tracks currently logged-in users. Records are written on login and erased on logout. | who(1), w(1) |
| wtmp | /var/log/wtmp | Audit trail of all logins and logouts. Records are only appended, never erased. | last(1), lastb(1) |
Think of utmp as a whiteboard. When you log in, your name is written on it. When you log out, your name is erased. Programs like who just read this whiteboard to show who is currently logged in.
to utmp
who readsthis record
record erased
The key field in each utmp record is ut_user which holds the login name of the user.
Think of wtmp as a logbook. Every event is permanently recorded. Login adds an entry with your username. Logout adds another entry with the username field zeroed out (blank). The last command reads this logbook and pairs login+logout entries to show your session history.
| # | Event | ut_user | ut_type | Time |
|---|---|---|---|---|
| 1 | ravi logs in on tty1 | ravi | USER_PROCESS | 10:00 AM |
| 2 | ravi logs out | (empty) | DEAD_PROCESS | 10:45 AM |
| 3 | priya logs in via ssh | priya | USER_PROCESS | 11:00 AM |
Notice how row 2 has an empty ut_user โ this is how wtmp marks a logout. The last command matches row 1 and row 2 to tell you “ravi logged in at 10:00, logged out at 10:45, session was 45 minutes”.
The actual file paths are compiled into the C library (glibc). Your programs should use the predefined constants from <paths.h> instead of hardcoding the path strings:
#include <paths.h>
/* Use these constants, not hardcoded strings */
_PATH_UTMP โ "/var/run/utmp"
_PATH_WTMP โ "/var/log/wtmp"
/* Older style (also available in <utmp.h> on Linux) */
UTMP_FILE โ same as _PATH_UTMP
WTMP_FILE โ same as _PATH_WTMP
โ ๏ธ Note: SUSv3 does NOT standardize the symbolic names for these paths. _PATH_UTMP and _PATH_WTMP are used on Linux and BSDs. Other UNIX systems may use UTMP_FILE and WTMP_FILE only.
This simple program checks whether the utmp and wtmp files are accessible on your system using the standard path constants.
/* check_login_files.c
* Verifies presence of utmp and wtmp login accounting files
* Compile: gcc check_login_files.c -o check_login_files
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <paths.h> /* _PATH_UTMP, _PATH_WTMP */
int main(void)
{
printf("=== Login Accounting File Check ===\n\n");
/* Check utmp file */
printf("utmp path: %s\n", _PATH_UTMP);
if (access(_PATH_UTMP, R_OK) == 0)
printf(" Status : Readable [OK]\n");
else
printf(" Status : NOT readable (may need sudo)\n");
/* Check wtmp file */
printf("\nwtmp path: %s\n", _PATH_WTMP);
if (access(_PATH_WTMP, R_OK) == 0)
printf(" Status : Readable [OK]\n");
else
printf(" Status : NOT readable (may need sudo)\n");
printf("\nNote: Writing to these files requires root/privilege.\n");
return 0;
}
Expected output:
=== Login Accounting File Check ===
utmp path: /var/run/utmp
Status : Readable [OK]
wtmp path: /var/log/wtmp
Status : Readable [OK]
Note: Writing to these files requires root/privilege.
Since each record in these files is a fixed-size struct utmpx, you can calculate how many records exist just from the file size.
/* count_utmp_records.c
* Counts number of records in utmp/wtmp using file size
* Compile: gcc count_utmp_records.c -o count_utmp_records
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <utmpx.h>
#include <paths.h>
void count_records(const char *path)
{
struct stat st;
if (stat(path, &st) == -1) {
perror(path);
return;
}
long num_records = st.st_size / sizeof(struct utmpx);
printf("File : %s\n", path);
printf("Size : %ld bytes\n", (long)st.st_size);
printf("Record : sizeof(struct utmpx) = %zu bytes\n",
sizeof(struct utmpx));
printf("Count : %ld records\n\n", num_records);
}
int main(void)
{
printf("=== utmpx Record Count ===\n\n");
count_records(_PATH_UTMP);
count_records(_PATH_WTMP);
return 0;
}
๐ก Insight: The wtmp file grows over time because records are only appended. Large wtmp files are normal on busy systems. Admins use logrotate to rotate/compress wtmp periodically.
Answer: utmp tracks currently logged-in users โ records are written on login and overwritten/erased on logout. wtmp is a permanent audit trail โ records are only appended and never deleted, capturing the full history of all logins and logouts.
Answer: The login program overwrites the existing utmp record for that terminal. The new record has ut_type set to DEAD_PROCESS and the ut_user field zeroed out. The who command ignores DEAD_PROCESS records.
Answer: The actual file path may vary across Linux distributions or future versions. Using _PATH_UTMP from <paths.h> makes the code portable โ if the path changes, only glibc needs to be updated, not your program.
Answer: who(1) and w(1) read from utmp to show currently logged-in users. last(1) reads from wtmp to show login history. lastlog(1) reads from the separate lastlog file.
Answer: Records are only appended to wtmp, never deleted, so it grows indefinitely. On busy servers, this can become large. System administrators use logrotate to periodically rotate and compress the wtmp file. Tools like last can still read compressed/archived wtmp files.
