getutent(3)

๐Ÿ’– getutent(3) โ€” the utmp girlies' guide ๐Ÿ’–

okay bestie, gather round, because we're talking about the utmp file access functions from the Standard C library (libc, -lc). it's giving โœจlogin trackingโœจ and honestly? it's a whole vibe. let's go.


๐ŸŒธ NAME ๐ŸŒธ

getutent, getutid, getutline, pututline, setutent, endutent, utmpname โ€” the whole squad that accesses utmp file entries ๐Ÿฆ‹


๐Ÿ“š LIBRARY ๐Ÿ“š

Standard C library (libc, -lc) ๐Ÿ’…


๐ŸŽ€ SYNOPSIS ๐ŸŽ€

#include <utmp.h>

struct utmp *getutent(void);
struct utmp *getutid(const struct utmp *ut);
struct utmp *getutline(const struct utmp *ut);

struct utmp *pututline(const struct utmp *ut);

void setutent(void);
void endutent(void);

int utmpname(const char *path);

no cap, that's the whole roster ๐Ÿ’–


๐Ÿซถ DESCRIPTION ๐Ÿซถ

okay first thing, and this is important: new applications should use the POSIX.1-specified "utmpx" versions of these functions โ€” peep the STANDARDS section below for the tea. ๐Ÿ‘‘

utmpname() sets the pathname of the utmp-format file that the other utmp functions go and access. and listen โ€” if you don't use utmpname() to set that pathname before you use the other functions, they're just gonna assume _PATH_UTMP, as defined in <paths.h>. default behaviour, main character energy. ๐Ÿ’…

setutent() rewinds the file pointer back to the beginning of the utmp file. generally a good idea to call it before any of the other functions, fr fr โ€” it's like a little reset button for your cursor. ๐Ÿ”„

endutent() closes the utmp file. call it when your user code is done accessing the file with the other functions. clean up after yourself, bestie. ๐Ÿงน

getutent() reads a line from the current file position in the utmp file. it returns a pointer to a structure containing the fields of the line. the definition of this structure lives in utmp(5) if you wanna go look. ๐Ÿ‘€

getutid() searches forward from the current file position in the utmp file, based on ut. here's the switcheroo:

  • if ut->ut_type is one of RUN_LVL, BOOT_TIME, NEW_TIME, or OLD_TIME, then getutid() finds the first entry whose ut_type field matches ut->ut_type.
  • if ut->ut_type is one of INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, or DEAD_PROCESS, then getutid() finds the first entry whose ut_id field matches ut->ut_id.

getutline() searches forward from the current file position in the utmp file. it scans entries whose ut_type is USER_PROCESS or LOGIN_PROCESS, and returns the first one whose ut_line field matches ut->ut_line. ๐ŸŽฏ

pututline() writes the utmp structure ut into the utmp file. it uses getutid() to search for the proper place in the file to insert the new entry. and if it can't find an appropriate slot for ut, pututline() will just append the new entry to the end of the file โ€” no drama, it finds a way. ๐Ÿ’–


โญ RETURN VALUE โญ

getutent(), getutid(), and getutline() return a pointer to a struct utmp on success, and NULL on failure (and yes, that includes the "record not found" case, sadly ๐Ÿ˜ญ). heads up though: this struct utmp is allocated in static storage, and it may be overwritten by subsequent calls. so don't go holding onto that pointer like it's your emotional support bestie โ€” it is not. ๐Ÿ’€

on success pututline() returns ut; on failure, it returns NULL.

utmpname() returns 0 if the new name was successfully stored, or -1 on failure.

on failure, these functions set errno to indicate the error. โœจ


๐Ÿ˜ญ ERRORS ๐Ÿ˜ญ

  • ENOMEM โ€” Out of memory. the vibes are not immaculate, we're all out of brain space.
  • ESRCH โ€” Record not found. it's not there bestie, we looked. ๐Ÿ•ต๏ธ

also: setutent(), pututline(), and the getut*() functions can also fail for the reasons described in open(2). so go read that one too if you're getting weird errors, iykyk.


๐Ÿ“‚ FILES ๐Ÿ“‚

  • /var/run/utmp โ€” database of currently logged-in users ๐Ÿ‘‘ (the "who's online rn" file)
  • /var/log/wtmp โ€” database of past user logins ๐Ÿ•ฐ๏ธ (the "who has ever been online" file)

๐ŸŒˆ ATTRIBUTES ๐ŸŒˆ

for an explanation of the terms used in this section, see attributes(7). the table from the original page, but make it a list because we're cute like that:

  • getutent() โ€” Thread safety: MT-Unsafe init race:utent race:utentbuf sig:ALRM timer
  • getutid(), getutline() โ€” Thread safety: MT-Unsafe init race:utent sig:ALRM timer
  • pututline() โ€” Thread safety: MT-Unsafe race:utent sig:ALRM timer
  • setutent(), endutent(), utmpname() โ€” Thread safety: MT-Unsafe race:utent

and in the above table, utent in race:utent means this: if any of the functions setutent(), getutent(), getutid(), getutline(), pututline(), utmpname(), or endutent() are used in parallel in different threads of a program, then data races could occur. bestie. please. do not. ๐Ÿ˜ญ


๐Ÿ“œ STANDARDS ๐Ÿ“œ

None. that's it. that's the whole section. iconic behaviour honestly. ๐Ÿ’…โœจ


๐Ÿ•ฐ๏ธ HISTORY ๐Ÿ•ฐ๏ธ

XPG2, SVr4. the ancestors, the originators, the blueprint. ๐Ÿ‘‘

in XPG2 and SVID 2 the function pututline() is documented to return void, and that is genuinely what it does on many systems (AIX, HP-UX). HP-UX introduces a new function _pututline() with the prototype given above for pututline(). so like, heads up if you're porting. ๐Ÿšจ

all these functions are obsolete now on non-Linux systems. POSIX.1-2001 and POSIX.1-2008, following SUSv1, do not have any of these functions โ€” instead they use:

#include <utmpx.h>

struct utmpx *getutxent(void);
struct utmpx *getutxid(const struct utmpx *);
struct utmpx *getutxline(const struct utmpx *);
struct utmpx *pututxline(const struct utmpx *);
void setutxent(void);
void endutxent(void);

these functions are provided by glibc, and they perform the exact same task as their equivalents without the "x" โ€” except they use struct utmpx, which on Linux is defined to be the same as struct utmp. for completeness, glibc also provides utmpxname(), even though this function is not specified by POSIX.1. glibc really said "and one more for the girlies" ๐Ÿ’–

on some other systems, the utmpx structure is a superset of the utmp structure, with additional fields, and larger versions of the existing fields, and parallel files are maintained โ€” often /var/*/utmpx and /var/*/wtmpx. fancy. ๐ŸŒธ

Linux glibc on the other hand does not use a parallel utmpx file, since its utmp structure is already large enough. the "x" functions listed above are just aliases for their counterparts without the "x" โ€” for example, getutxent() is an alias for getutent(). ๐Ÿ”ฅ


๐Ÿ“ NOTES ๐Ÿ“

๐ŸŒŸ glibc notes ๐ŸŒŸ

the above functions are not thread-safe. glibc adds reentrant versions:

#include <utmp.h>

int getutent_r(struct utmp *ubuf, struct utmp **ubufp);
int getutid_r(struct utmp *ut,
              struct utmp *ubuf, struct utmp **ubufp);
int getutline_r(struct utmp *ut,
                struct utmp *ubuf, struct utmp **ubufp);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

getutent_r(), getutid_r(), getutline_r():
    _GNU_SOURCE
        || /* Since glibc 2.19: */ _DEFAULT_SOURCE
        || /* glibc <= 2.19: */    _SVID_SOURCE || _BSD_SOURCE

these functions are GNU extensions โ€” the analogs of the functions of the same name without the _r suffix. the ubuf argument gives these functions a place to store their result. on success, they return 0, and a pointer to the result is written in *ubufp. on error, these functions return -1. and there are no utmpx equivalents of the above functions (POSIX.1 does not specify such functions). sad but true. ๐Ÿ˜ญ


๐Ÿงช EXAMPLES ๐Ÿงช

the following example adds and removes a utmp record, assuming it is run from within a pseudo terminal. for usage in a real application, you should check the return values of getpwuid(3) and ttyname(3). don't be a chaos gremlin, check your returns. ๐Ÿ™ˆ

#include <err.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <utmp.h>

int
main(void)
{
    struct utmp entry;

    if (system("echo before adding entry:;who") == -1)
        err(EXIT_FAILURE, "system");

    entry.ut_type = USER_PROCESS;
    entry.ut_pid = getpid();
    strcpy(entry.ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"));
    /* only correct for ptys named /dev/tty[pqr][0-9a-z] */
    strcpy(entry.ut_id, ttyname(STDIN_FILENO) + strlen("/dev/tty"));
    entry.ut_time = time(NULL);
    strcpy(entry.ut_user, getpwuid(getuid())->pw_name);
    memset(entry.ut_host, 0, UT_HOSTSIZE);
    entry.ut_addr = 0;
    setutent();
    if (pututline(&entry) == NULL)
        err(EXIT_FAILURE, "pututline");

    if (system("echo after adding entry:;who") == -1)
        err(EXIT_FAILURE, "system");

    entry.ut_type = DEAD_PROCESS;
    memset(entry.ut_line, 0, UT_LINESIZE);
    entry.ut_time = 0;
    memset(entry.ut_user, 0, UT_NAMESIZE);
    setutent();
    if (pututline(&entry) == NULL)
        err(EXIT_FAILURE, "pututline");

    if (system("echo after removing entry:;who") == -1)
        err(EXIT_FAILURE, "system");

    endutent();
    exit(EXIT_SUCCESS);
}

ate. no notes. ๐Ÿ’…โœจ


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

getutmp(3), utmp(5) โ€” go read these next, bestie, they're the sequel ๐Ÿฆ‹


and that's the whole page!! every flag, every field, every caveat, all of it, kept safe and sound ๐Ÿซถ ngl this one's a classic.

and before you go โ€” this little love letter came to you from Linux man-pages 6.20, dated 2026-02-08, and it's the getutent(3) page, periodt. ๐Ÿ’–๐ŸŽ€๐Ÿ’œ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: GPL-2.0-or-later
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms