In the early days of UNIX, every vendor added their own extensions to the login accounting system. BSD had one style, System V had another. This caused a mess of incompatible interfaces.
System V Release 4 (SVR4) created a new, extended API with an “x” suffix โ utmpx, wtmpx, and related functions โ to distinguish the new extended versions from the old ones. SUSv3 then standardized the “x” API.
On Linux, both APIs (utmp and utmpx) work on the same underlying files and return identical information. You should prefer the utmpx API for portability.
| Early UNIX | Simple utmp/wtmp with minimal fields. BSD and System V diverge and add incompatible extensions. |
| SVR4 (1988) | System V Release 4 introduces extended utmpx structure and API with “x” suffix. Adds new fields like ut_addr_v6, ut_session. |
| SUSv3 | Standardizes the utmpx API. Mandates setutxent(), endutxent(), getutxent(), getutxid(), getutxline(), pututxline(). |
| Linux (glibc) | Implements a hybrid: single utmp/wtmp file pair, but provides both old utmp API and new utmpx API. Both APIs access the same files and return same data. |
Unlike strict System V, Linux does NOT create separate utmpx and wtmpx files. Instead:
getutent()
getutid()
getutline()
pututline()
getutxent()
getutxid()
getutxline()
pututxline()
Both APIs access the same file and return identical data on Linux. Use the utmpx API for portability to other UNIX systems.
โ ๏ธ One key difference: The old utmp API provides reentrant versions (getutent_r(), getutid_r(), getutline_r()). The utmpx API does NOT have reentrant versions โ neither in glibc nor in SUSv3.
Always include <utmpx.h> when using the utmpx API. On Linux, you may also want <paths.h> for the path constants.
#include <utmpx.h> /* struct utmpx, all utmpx functions */
#include <paths.h> /* _PATH_UTMP, _PATH_WTMP */
/* Optional: only needed for Linux-specific extensions */
#define _GNU_SOURCE
The _GNU_SOURCE feature test macro is needed to expose some Linux-specific constants like RUN_LVL and the ut_session field without a double-underscore prefix.
This example shows how the old and new APIs are equivalent on Linux. It opens the utmp file using both APIs (demonstrating that they’re the same thing underneath).
/* api_compare.c
* Shows the old utmp API and new utmpx API are equivalent on Linux
* Compile: gcc api_compare.c -o api_compare
* Run as root or with readable utmp permissions
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <utmpx.h> /* New utmpx API (preferred) */
#include <utmp.h> /* Old utmp API (for comparison) */
#include <paths.h>
int main(void)
{
struct utmpx *utx;
struct utmp *ut;
int utmpx_count = 0, utmp_count = 0;
/* --- Count records using NEW utmpx API --- */
setutxent();
while ((utx = getutxent()) != NULL) {
if (utx->ut_type == USER_PROCESS)
utmpx_count++;
}
endutxent();
/* --- Count records using OLD utmp API --- */
setutent();
while ((ut = getutent()) != NULL) {
if (ut->ut_type == USER_PROCESS)
utmp_count++;
}
endutent();
printf("Users found via utmpx API : %d\n", utmpx_count);
printf("Users found via utmp API : %d\n", utmp_count);
printf("\nBoth counts should be identical on Linux.\n");
printf("Use utmpx API for portability to other UNIX systems.\n");
return 0;
}
By default the utmpx functions read from /var/run/utmp. The utmpxname() function lets you switch to any other file โ commonly used to read wtmp.
/* switch_utmpfile.c
* Demonstrates utmpxname() to switch between utmp and wtmp files
* Compile: gcc switch_utmpfile.c -o switch_utmpfile
* Usage: ./switch_utmpfile [utmp|wtmp]
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utmpx.h>
#include <paths.h>
/* Returns a human-readable string for ut_type */
const char *type_name(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_PROC";
case LOGIN_PROCESS: return "LOGIN_PROC";
case USER_PROCESS: return "USER_PROC";
case DEAD_PROCESS: return "DEAD_PROC";
default: return "UNKNOWN";
}
}
int main(int argc, char *argv[])
{
const char *filepath;
struct utmpx *ut;
int count = 0;
/* Decide which file to read based on argument */
if (argc > 1 && strcmp(argv[1], "wtmp") == 0)
filepath = _PATH_WTMP;
else
filepath = _PATH_UTMP;
printf("Reading from: %s\n\n", filepath);
/* Switch the file the utmpx functions will use */
if (utmpxname(filepath) == -1) {
perror("utmpxname");
exit(EXIT_FAILURE);
}
/* IMPORTANT: utmpxname() does NOT open the file.
* Call setutxent() to actually open/rewind it. */
setutxent();
printf("%-12s %-12s %s\n", "USER", "TYPE", "LINE");
printf("%-12s %-12s %s\n", "----", "----", "----");
while ((ut = getutxent()) != NULL) {
printf("%-12.12s %-12s %s\n",
ut->ut_user,
type_name(ut->ut_type),
ut->ut_line);
count++;
}
endutxent();
printf("\nTotal records: %d\n", count);
return 0;
}
๐ก Important: utmpxname() only records the new pathname. It closes any previously open file but does NOT open the new one. The file is opened lazily when you call setutxent() or any getutx*() function. So if you pass a bad path, you won’t see the error until the next read call.
A simple reimplementation of the who command using the utmpx API โ shows only USER_PROCESS records.
/* my_who.c
* Simple reimplementation of who(1) using utmpx API
* Compile: gcc my_who.c -o my_who
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <utmpx.h>
#include <time.h>
int main(void)
{
struct utmpx *ut;
char timebuf[64];
struct tm *tm_info;
/* Always rewind to start before scanning */
setutxent();
while ((ut = getutxent()) != NULL) {
/* who only shows USER_PROCESS records */
if (ut->ut_type != USER_PROCESS)
continue;
/* Format the login time */
tm_info = localtime((time_t *)&ut->ut_tv.tv_sec);
strftime(timebuf, sizeof(timebuf), "%b %d %H:%M", tm_info);
/* Print: username terminal time (hostname if remote) */
printf("%-12s %-10s %s", ut->ut_user, ut->ut_line, timebuf);
/* Show remote host if this is a remote login (e.g., ssh) */
if (ut->ut_host[0] != '\0')
printf(" (%s)", ut->ut_host);
printf("\n");
}
endutxent();
return 0;
}
Sample output:
ravi tty1 Jun 05 10:30
priya pts/0 Jun 05 11:15 (192.168.1.10)
admin pts/1 Jun 05 09:00 (laptop.local)
Answer: The “x” stands for “extended.” System V Release 4 (SVR4) introduced an extended version of the login accounting API with additional fields like IPv6 address (ut_addr_v6) and session ID. SUSv3 later standardized this extended API.
Answer: No. Unlike strict System V which creates separate utmpx/wtmpx files, Linux uses a hybrid approach where both the old utmp API and the new utmpx API access the same /var/run/utmp and /var/log/wtmp files and return identical data.
Answer: utmpxname() records the pathname for the file to be used by subsequent getutx*() calls. It closes any currently open file but does NOT open the new file and does NOT validate that the path exists. An invalid path will only cause an error (NULL return) when you later call getutxent() or similar functions.
Answer: No. The utmpx API does not provide reentrant versions. The old utmp API provides getutent_r(), getutid_r(), and getutline_r(). Reentrant versions matter in multi-threaded programs where multiple threads might call these functions simultaneously โ non-reentrant functions use a shared static buffer which can cause race conditions.
Answer: The utmpx API is preferred because it is standardized in SUSv3, making it portable across different UNIX implementations (Linux, BSD, AIX, Solaris, etc.). The old utmp API is Linux/BSD specific and not standardized.
