From 622d2c0763777eb909a4fda5048238f524cc36f9 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Tue, 8 Aug 2023 17:36:10 +0200 Subject: [PATCH] readutmp: Return entries with unbounded strings on all platforms. Suggested by Paul Eggert in . * m4/readutmp.m4 (gl_READUTMP): Test also whether struct utmp has an ut_tv member, and whether struct utmp and struct utmpx have an ut_session member. * lib/readutmp.h (struct gl_utmp): Define always. Add ut_exit field. (HAVE_GL_UTMP): Remove macro. (UT_USER, UT_TIME_MEMBER, UT_PID, UT_TYPE_EQ, UT_TYPE_NOT_DEFINED, UT_EXIT_E_TERMINATION, UT_EXIT_E_EXIT, STRUCT_UTMP): Define w.r.t. struct gl_utmp. (UT_USER_SIZE, UT_ID_SIZE, UT_LINE_SIZE, UT_HOST_SIZE): Define to -1 always. (getutent): Remove declaration. (HAVE_STRUCT_XTMP_UT_EXIT): Remove unused macro. (HAVE_STRUCT_XTMP_UT_ID, HAVE_STRUCT_XTMP_UT_PID, HAVE_STRUCT_XTMP_UT_HOST): Change to match the way coreutils uses these macros. * lib/readutmp.c (UT_USER, UT_TIME_MEMBER, UT_PID, UT_TYPE_EQ, UT_TYPE_NOT_DEFINED, IS_USER_PROCESS, UT_EXIT_E_TERMINATION, UT_EXIT_E_EXIT, UT_USER_SIZE, UT_ID_SIZE, UT_LINE_SIZE, UT_HOST_SIZE): Define w.r.t. struct utmpx or struct utmp. (extract_trimmed_name): Don't use UT_USER or UT_USER_SIZE here. (desirable_utmp_entry): Don't use UT_TIME_MEMBER or UT_USER here. (struct utmp_alloc): Define always. (add_utmp): Likewise. Add user_len, id_len, line_len, host_len, termination, exit arguments. Don't require that user, id, line, host are NUL-terminated. Assume user and host are non-NULL. (finish_utmp): New function, extracted from read_utmp. (read_utmp) [READUTMP_USE_SYSTEMD]: Update add_utmp invocations. Pass a non-NULL user and a non-NULL host. Call finish_utmp. (getutent): Move declaration from readutmp.h to here. (copy_utmp_entry): Remove function. (read_utmp) [UTMP_NAME_FUNCTION]: Replace variables n_read, n_alloc, utmp with a 'struct utmp_alloc'. Use 'struct utmpx32' from copy_utmp_entry here. Invoke add_utmp and finish_utmp. (read_utmp) [!UTMP_NAME_FUNCTION]: Replace variables n_read, n_alloc, utmp with a 'struct utmp_alloc'. Invoke add_utmp and finish_utmp. * NEWS: Mention the API change. --- ChangeLog | 42 ++++ NEWS | 4 + lib/readutmp.c | 540 ++++++++++++++++++++++++++++++++----------------- lib/readutmp.h | 195 ++++++------------ m4/readutmp.m4 | 5 +- 5 files changed, 467 insertions(+), 319 deletions(-) diff --git a/ChangeLog b/ChangeLog index b655ce185d..dd730a435f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,45 @@ +2023-08-08 Bruno Haible + + readutmp: Return entries with unbounded strings on all platforms. + Suggested by Paul Eggert in + . + * m4/readutmp.m4 (gl_READUTMP): Test also whether struct utmp has an + ut_tv member, and whether struct utmp and struct utmpx have an + ut_session member. + * lib/readutmp.h (struct gl_utmp): Define always. Add ut_exit field. + (HAVE_GL_UTMP): Remove macro. + (UT_USER, UT_TIME_MEMBER, UT_PID, UT_TYPE_EQ, UT_TYPE_NOT_DEFINED, + UT_EXIT_E_TERMINATION, UT_EXIT_E_EXIT, STRUCT_UTMP): Define w.r.t. + struct gl_utmp. + (UT_USER_SIZE, UT_ID_SIZE, UT_LINE_SIZE, UT_HOST_SIZE): Define to -1 + always. + (getutent): Remove declaration. + (HAVE_STRUCT_XTMP_UT_EXIT): Remove unused macro. + (HAVE_STRUCT_XTMP_UT_ID, HAVE_STRUCT_XTMP_UT_PID, + HAVE_STRUCT_XTMP_UT_HOST): Change to match the way coreutils uses these + macros. + * lib/readutmp.c (UT_USER, UT_TIME_MEMBER, UT_PID, UT_TYPE_EQ, + UT_TYPE_NOT_DEFINED, IS_USER_PROCESS, UT_EXIT_E_TERMINATION, + UT_EXIT_E_EXIT, UT_USER_SIZE, UT_ID_SIZE, UT_LINE_SIZE, UT_HOST_SIZE): + Define w.r.t. struct utmpx or struct utmp. + (extract_trimmed_name): Don't use UT_USER or UT_USER_SIZE here. + (desirable_utmp_entry): Don't use UT_TIME_MEMBER or UT_USER here. + (struct utmp_alloc): Define always. + (add_utmp): Likewise. Add user_len, id_len, line_len, host_len, + termination, exit arguments. Don't require that user, id, line, host are + NUL-terminated. Assume user and host are non-NULL. + (finish_utmp): New function, extracted from read_utmp. + (read_utmp) [READUTMP_USE_SYSTEMD]: Update add_utmp invocations. Pass a + non-NULL user and a non-NULL host. Call finish_utmp. + (getutent): Move declaration from readutmp.h to here. + (copy_utmp_entry): Remove function. + (read_utmp) [UTMP_NAME_FUNCTION]: Replace variables n_read, n_alloc, + utmp with a 'struct utmp_alloc'. Use 'struct utmpx32' from + copy_utmp_entry here. Invoke add_utmp and finish_utmp. + (read_utmp) [!UTMP_NAME_FUNCTION]: Replace variables n_read, n_alloc, + utmp with a 'struct utmp_alloc'. Invoke add_utmp and finish_utmp. + * NEWS: Mention the API change. + 2023-08-08 Bruno Haible readutmp: Fix compilation error on OpenBSD and AIX (regr. 2023-08-03). diff --git a/NEWS b/NEWS index 265f9788bc..081a5f6035 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,10 @@ User visible incompatible changes Date Modules Changes +2023-08-08 readutmp The result element type of the function read_utmp, + STRUCT_UTMP, is no longer the same as the result + value type of the function getutxent, struct utmpx. + 2023-08-03 readutmp Some STRUCT_UTMP members can be char *, 2023-08-01 rather than fixed-length char arrays. On some platforms, the timestamp is ut_ts of type diff --git a/lib/readutmp.c b/lib/readutmp.c index 4e1d7ec26b..7ef5bfe84c 100644 --- a/lib/readutmp.c +++ b/lib/readutmp.c @@ -43,6 +43,95 @@ /* Each of the FILE streams in this file is only used in a single thread. */ #include "unlocked-io.h" +/* The following macros describe the 'struct UTMP_STRUCT_NAME', + *not* 'struct gl_utmp'. */ +#undef UT_USER +#undef UT_TIME_MEMBER +#undef UT_PID +#undef UT_TYPE_EQ +#undef UT_TYPE_NOT_DEFINED +#undef IS_USER_PROCESS +#undef UT_EXIT_E_TERMINATION +#undef UT_EXIT_E_EXIT + +/* Accessor macro for the member named ut_user or ut_name. */ +#if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \ + : HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME) +# define UT_USER(UT) ((UT)->ut_name) +#else +# define UT_USER(UT) ((UT)->ut_user) +#endif + +/* Accessor macro for the member of type time_t (or 'unsigned int'). */ +#if HAVE_UTMPX_H || (HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_TV) +# define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec) +#else +# define UT_TIME_MEMBER(UT) ((UT)->ut_time) +#endif + +/* Accessor macro for the member named ut_pid. */ +#if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID) +# define UT_PID(UT) ((UT)->ut_pid) +#else +# define UT_PID(UT) 0 +#endif + +/* Accessor macros for the member named ut_type. */ +#if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMP_UT_TYPE : HAVE_STRUCT_UTMPX_UT_TYPE) +# define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V)) +# define UT_TYPE_NOT_DEFINED 0 +#else +# define UT_TYPE_EQ(UT, V) 0 +# define UT_TYPE_NOT_DEFINED 1 +#endif + +/* Determines whether an entry *UT corresponds to a user process. */ +#define IS_USER_PROCESS(UT) \ + ((UT)->ut_user[0] \ + && (UT_TYPE_USER_PROCESS (UT) \ + || (UT_TYPE_NOT_DEFINED && (UT)->ut_ts.tv_sec != 0))) + +#if HAVE_UTMPX_H +# if HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION +# define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) +# elif HAVE_STRUCT_UTMPX_UT_EXIT_UT_TERMINATION /* OSF/1 */ +# define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.ut_termination) +# else +# define UT_EXIT_E_TERMINATION(UT) 0 +# endif +#elif HAVE_UTMP_H +# if HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION +# define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) +# else +# define UT_EXIT_E_TERMINATION(UT) 0 +# endif +#endif + +#if HAVE_UTMPX_H +# if HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT +# define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) +# elif HAVE_STRUCT_UTMPX_UT_EXIT_UT_EXIT /* OSF/1 */ +# define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.ut_exit) +# else +# define UT_EXIT_E_EXIT(UT) 0 +# endif +#elif HAVE_UTMP_H +# if HAVE_STRUCT_UTMP_UT_EXIT_E_EXIT +# define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) +# else +# define UT_EXIT_E_EXIT(UT) 0 +# endif +#endif + +/* Size of the UT_USER (ut) member. */ +#define UT_USER_SIZE sizeof UT_USER ((struct UTMP_STRUCT_NAME *) 0) +/* Size of the ut->ut_id member. */ +#define UT_ID_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_id) +/* Size of the ut->ut_line member. */ +#define UT_LINE_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_line) +/* Size of the ut->ut_host member. */ +#define UT_HOST_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_host) + #if 8 <= __GNUC__ # pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess" #endif @@ -52,14 +141,14 @@ # pragma GCC diagnostic ignored "-Wstringop-overread" #endif -/* Copy UT_USER (UT) into storage obtained from malloc. Then remove any +/* Copy UT->ut_user into storage obtained from malloc. Then remove any trailing spaces from the copy, NUL terminate it, and return the copy. */ char * extract_trimmed_name (const STRUCT_UTMP *ut) { - char const *name = UT_USER (ut); - idx_t len = strnlen (name, UT_USER_SIZE); + char const *name = ut->ut_user; + idx_t len = strlen (name); char const *p; for (p = name + len; name < p && p[-1] == ' '; p--) continue; @@ -73,6 +162,12 @@ extract_trimmed_name (const STRUCT_UTMP *ut) static bool desirable_utmp_entry (STRUCT_UTMP const *ut, int options) { +# if defined __OpenBSD__ && !HAVE_UTMPX_H + /* Eliminate entirely empty entries. */ + if (ut->ut_ts.tv_sec == 0 && ut->ut_user[0] == '\0' + && ut->ut_line[0] == '\0' && ut->ut_host[0] == '\0') + return false; +# endif bool user_proc = IS_USER_PROCESS (ut); if ((options & READ_UTMP_USER_PROCESS) && !user_proc) return false; @@ -81,15 +176,108 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options) && 0 < UT_PID (ut) && (kill (UT_PID (ut), 0) < 0 && errno == ESRCH)) return false; -# if defined __OpenBSD__ && !HAVE_UTMPX_H - /* Eliminate entirely empty entries. */ - if (UT_TIME_MEMBER (ut) == 0 && UT_USER (ut)[0] == '\0' - && ut->ut_line[0] == '\0' && ut->ut_host[0] == '\0') - return false; -# endif return true; } +/* A memory allocation for an in-progress read_utmp. */ + +struct utmp_alloc +{ + /* A pointer to a possibly-empty array of utmp entries, + followed by a possibly-empty sequence of unused bytes, + followed by a possibly-empty sequence of string bytes. + UTMP is either null or allocated by malloc. */ + struct gl_utmp *utmp; + + /* The number of utmp entries. */ + idx_t filled; + + /* The string byte sequence length. Strings are null-terminated. */ + idx_t string_bytes; + + /* The total number of bytes allocated. This equals + FILLED * sizeof *UTMP + [size of free area] + STRING_BYTES. */ + idx_t alloc_bytes; +}; + +/* Use the memory allocation A, and if the read_utmp options OPTIONS + permit it, add a new entry with the given USER, etc. Grow A as + needed, reporting an error and exit on memory allocation failure. + Return the resulting memory allocation. */ + +static struct utmp_alloc +add_utmp (struct utmp_alloc a, int options, + char const *user, idx_t user_len, + char const *id, idx_t id_len, + char const *line, idx_t line_len, + char const *host, idx_t host_len, + pid_t pid, short type, struct timespec ts, long session, + int termination, int exit) +{ + int entry_bytes = sizeof (struct gl_utmp); + idx_t avail = a.alloc_bytes - (entry_bytes * a.filled + a.string_bytes); + idx_t needed_string_bytes = + (user_len + 1) + (id_len + 1) + (line_len + 1) + (host_len + 1); + idx_t needed = entry_bytes + needed_string_bytes; + if (avail < needed) + { + idx_t old_string_offset = a.alloc_bytes - a.string_bytes; + void *new = xpalloc (a.utmp, &a.alloc_bytes, needed - avail, -1, 1); + idx_t new_string_offset = a.alloc_bytes - a.string_bytes; + a.utmp = new; + char *q = new; + memmove (q + new_string_offset, q + old_string_offset, a.string_bytes); + } + struct gl_utmp *ut = &a.utmp[a.filled]; + char *stringlim = (char *) a.utmp + a.alloc_bytes; + char *p = stringlim - a.string_bytes; + *--p = '\0'; /* NUL-terminate ut->ut_user */ + ut->ut_user = p = memcpy (p - user_len, user, user_len); + *--p = '\0'; /* NUL-terminate ut->ut_id */ + ut->ut_id = p = memcpy (p - id_len, id, id_len); + *--p = '\0'; /* NUL-terminate ut->ut_line */ + ut->ut_line = p = memcpy (p - line_len, line, line_len); + *--p = '\0'; /* NUL-terminate ut->ut_host */ + ut->ut_host = memcpy (p - host_len, host, host_len); + ut->ut_ts = ts; + ut->ut_pid = pid; + ut->ut_session = session; + ut->ut_type = type; + ut->ut_exit.e_termination = termination; + ut->ut_exit.e_exit = exit; + if (desirable_utmp_entry (ut, options)) + { + /* Now that UT has been checked, relocate its string slots to be + relative to the end of the allocated storage, so that these + slots survive realloc. The slots will be relocated back just + before read_utmp returns. */ + ut->ut_user = (char *) (intptr_t) (ut->ut_user - stringlim); + ut->ut_id = (char *) (intptr_t) (ut->ut_id - stringlim); + ut->ut_line = (char *) (intptr_t) (ut->ut_line - stringlim); + ut->ut_host = (char *) (intptr_t) (ut->ut_host - stringlim); + a.filled++; + a.string_bytes += needed_string_bytes; + } + return a; +} + +/* Relocate the string pointers in A back to their natural position. */ +static struct utmp_alloc +finish_utmp (struct utmp_alloc a) +{ + char *stringlim = (char *) a.utmp + a.alloc_bytes; + + for (idx_t i = 0; i < a.filled; i++) + { + a.utmp[i].ut_user = (intptr_t) a.utmp[i].ut_user + stringlim; + a.utmp[i].ut_id = (intptr_t) a.utmp[i].ut_id + stringlim; + a.utmp[i].ut_line = (intptr_t) a.utmp[i].ut_line + stringlim; + a.utmp[i].ut_host = (intptr_t) a.utmp[i].ut_host + stringlim; + } + + return a; +} + # if READUTMP_USE_SYSTEMD /* Use systemd and Linux /proc and kernel APIs. */ @@ -226,81 +414,6 @@ guess_pty_name (uid_t uid, const struct timespec at) return NULL; } -/* A memory allocation for an in-progress read_utmp. */ - -struct utmp_alloc -{ - /* A pointer to a possibly-empty array of utmp entries, - followed by a possibly-empty sequence of unused bytes, - followed by a possibly-empty sequence of string bytes. - UTMP is either null or allocated by malloc. */ - STRUCT_UTMP *utmp; - - /* The number of utmp entries. */ - idx_t filled; - - /* The string byte sequence length. Strings are null-terminated. */ - idx_t string_bytes; - - /* The total number of bytes allocated. This equals - FILLED * sizeof *UTMP + [size of free area] + STRING_BYTES. */ - idx_t alloc_bytes; -}; - -/* Use the memory allocation A, and if the read_utmp options OPTIONS - permit it, add a new entry with the given USER, etc. Grow A as - needed, reporting an error and exit on memory allocation failure. - Return the resulting memory allocation. */ - -static struct utmp_alloc -add_utmp (struct utmp_alloc a, int options, - char const *user, char const *id, char const *line, pid_t pid, - short type, struct timespec ts, char const *host, long session) -{ - if (!user) user = ""; - if (!host) host = ""; - int entry_bytes = sizeof (STRUCT_UTMP); - idx_t usersize = strlen (user) + 1, idsize = strlen (id) + 1, - linesize = strlen (line) + 1, hostsize = strlen (host) + 1; - idx_t avail = a.alloc_bytes - (entry_bytes * a.filled + a.string_bytes); - idx_t needed_string_bytes = usersize + idsize + linesize + hostsize; - idx_t needed = entry_bytes + needed_string_bytes; - if (avail < needed) - { - idx_t old_string_offset = a.alloc_bytes - a.string_bytes; - void *new = xpalloc (a.utmp, &a.alloc_bytes, needed - avail, -1, 1); - idx_t new_string_offset = a.alloc_bytes - a.string_bytes; - a.utmp = new; - char *q = new; - memmove (q + new_string_offset, q + old_string_offset, a.string_bytes); - } - STRUCT_UTMP *ut = &a.utmp[a.filled]; - char *stringlim = (char *) a.utmp + a.alloc_bytes; - char *p = stringlim - a.string_bytes; - ut->ut_user = p = memcpy (p - usersize, user, usersize); - ut->ut_id = p = memcpy (p - idsize, id, idsize); - ut->ut_line = p = memcpy (p - linesize, line, linesize); - ut->ut_host = memcpy (p - hostsize, host, hostsize); - ut->ut_ts = ts; - ut->ut_pid = pid; - ut->ut_session = session; - ut->ut_type = type; - if (desirable_utmp_entry (ut, options)) - { - /* Now that UT has been checked, relocate its string slots to be - relative to the end of the allocated storage, so that these - slots survive realloc. The slots will be relocated back just - before read_utmp returns. */ - ut->ut_user = (char *) (intptr_t) (ut->ut_user - stringlim); - ut->ut_id = (char *) (intptr_t) (ut->ut_id - stringlim); - ut->ut_line = (char *) (intptr_t) (ut->ut_line - stringlim); - ut->ut_host = (char *) (intptr_t) (ut->ut_host - stringlim); - a.filled++; - a.string_bytes += needed_string_bytes; - } - return a; -} - int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) @@ -317,8 +430,12 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, /* Synthesize a BOOT_TIME entry. */ if (!(options & READ_UTMP_USER_PROCESS)) - a = add_utmp (a, options, "reboot", "", "~", 0, - BOOT_TIME, get_boot_time (), "", 0); + a = add_utmp (a, options, + "reboot", strlen ("reboot"), + "", 0, + "~", strlen ("~"), + "", 0, + 0, BOOT_TIME, get_boot_time (), 0, 0, 0); /* Synthesize USER_PROCESS entries. */ char **sessions; @@ -341,7 +458,8 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, if (sd_session_get_seat (session, &seat) < 0) seat = NULL; - char missing_type[] = ""; + char missing[] = ""; + char *type = NULL; char *tty; if (sd_session_get_tty (session, &tty) < 0) @@ -349,7 +467,7 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, tty = NULL; /* Try harder to get a sensible value for the tty. */ if (sd_session_get_type (session, &type) < 0) - type = missing_type; + type = missing; if (strcmp (type, "tty") == 0) { char *service; @@ -380,7 +498,7 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, { char *user; if (sd_session_get_username (session, &user) < 0) - user = NULL; + user = missing; pid_t leader_pid; if (sd_session_get_leader (session, &leader_pid) < 0) @@ -390,11 +508,11 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, char *remote_host; if (sd_session_get_remote_host (session, &remote_host) < 0) { - host = NULL; + host = missing; /* For backward compatibility, put the X11 display into the host field. */ if (!type && sd_session_get_type (session, &type) < 0) - type = missing_type; + type = missing; if (strcmp (type, "x11") == 0) { char *display; @@ -420,19 +538,29 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, } if (seat != NULL) - a = add_utmp (a, options, user, session, seat, + a = add_utmp (a, options, + user, strlen (user), + session, strlen (session), + seat, strlen (seat), + host, strlen (host), leader_pid /* the best we have */, - USER_PROCESS, start_ts, host, leader_pid); + USER_PROCESS, start_ts, leader_pid, 0, 0); if (tty != NULL) - a = add_utmp (a, options, user, session, tty, + a = add_utmp (a, options, + user, strlen (user), + session, strlen (session), + tty, strlen (tty), + host, strlen (host), leader_pid /* the best we have */, - USER_PROCESS, start_ts, host, leader_pid); + USER_PROCESS, start_ts, leader_pid, 0, 0); - free (host); - free (user); + if (host != missing) + free (host); + if (user != missing) + free (user); } - if (type != missing_type) + if (type != missing) free (type); free (tty); free (seat); @@ -441,18 +569,7 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, free (sessions); } - /* Relocate the string pointers back to their natural position. */ - { - char *stringlim = (char *) a.utmp + a.alloc_bytes; - - for (idx_t i = 0; i < a.filled; i++) - { - a.utmp[i].ut_user = (intptr_t) a.utmp[i].ut_user + stringlim; - a.utmp[i].ut_id = (intptr_t) a.utmp[i].ut_id + stringlim; - a.utmp[i].ut_line = (intptr_t) a.utmp[i].ut_line + stringlim; - a.utmp[i].ut_host = (intptr_t) a.utmp[i].ut_host + stringlim; - } - } + a = finish_utmp (a); *n_entries = a.filled; *utmp_buf = a.utmp; @@ -460,67 +577,16 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, return 0; } -# elif defined UTMP_NAME_FUNCTION +# elif defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */ -static void -copy_utmp_entry (STRUCT_UTMP *dst, STRUCT_UTMP *src) -{ -# if __GLIBC__ && _TIME_BITS == 64 - /* Convert from external form in SRC to internal form in DST. - It is OK to convert now, rather than earlier, before - desirable_utmp_entry was invoked, because desirable_utmp_entry - inspects only the leading prefix of the entry, which is the - same in both external and internal forms. */ - - /* This is a near-copy of glibc's struct utmpx, which stops working - after the year 2038. Unlike the glibc version, struct utmpx32 - describes the file format even if time_t is 64 bits. */ - struct utmpx32 - { - short int ut_type; /* Type of login. */ - pid_t ut_pid; /* Process ID of login process. */ - char ut_line[sizeof src->ut_line]; /* Devicename. */ - char ut_id[sizeof src->ut_id]; /* Inittab ID. */ - char ut_user[sizeof src->ut_user]; /* Username. */ - char ut_host[sizeof src->ut_host]; /* Hostname for remote login. */ - struct __exit_status ut_exit; /* Exit status of a process marked - as DEAD_PROCESS. */ - /* The fields ut_session and ut_tv must be the same size when compiled - 32- and 64-bit. This allows files and shared memory to be shared - between 32- and 64-bit applications. */ - int ut_session; /* Session ID, used for windowing. */ - struct - { - /* Seconds. Unsigned not signed, as glibc did not exist before 1970, - and if the format is still in use after 2038 its timestamps - will surely have the sign bit on. This hack stops working - at 2106-02-07 06:28:16 UTC. */ - unsigned int tv_sec; - - int tv_usec; /* Microseconds. */ - } ut_tv; /* Time entry was made. */ - int ut_addr_v6[4]; /* Internet address of remote host. */ - char ut_reserved[20]; /* Reserved for future use. */ - } *s = (struct utmpx32 *) src; - memcpy (dst, s, offsetof (struct utmpx32, ut_session)); - dst->ut_session = s->ut_session; - dst->ut_tv.tv_sec = s->ut_tv.tv_sec; - dst->ut_tv.tv_usec = s->ut_tv.tv_usec; - memcpy (&dst->ut_addr_v6, s->ut_addr_v6, sizeof dst->ut_addr_v6); -# else - *dst = *src; +# if !HAVE_UTMPX_H && HAVE_UTMP_H && !HAVE_DECL_GETUTENT +struct utmp *getutent (void); # endif -} int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) { - idx_t n_read = 0; - idx_t n_alloc = 0; - STRUCT_UTMP *utmp = NULL; - STRUCT_UTMP *ut; - /* Ignore the return value for now. Solaris' utmpname returns 1 upon success -- which is contrary to what the GNU libc version does. In addition, older GNU libc @@ -529,59 +595,163 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, SET_UTMP_ENT (); - while ((ut = GET_UTMP_ENT ()) != NULL) - if (desirable_utmp_entry (ut, options)) + struct utmp_alloc a = {0}; + void const *entry; + + while ((entry = GET_UTMP_ENT ()) != NULL) + { +# if __GLIBC__ && _TIME_BITS == 64 + /* This is a near-copy of glibc's struct utmpx, which stops working + after the year 2038. Unlike the glibc version, struct utmpx32 + describes the file format even if time_t is 64 bits. */ + struct utmpx32 { - if (n_read == n_alloc) - utmp = xpalloc (utmp, &n_alloc, 1, -1, sizeof *utmp); + short int ut_type; /* Type of login. */ + pid_t ut_pid; /* Process ID of login process. */ + char ut_line[UT_LINE_SIZE]; /* Devicename. */ + char ut_id[UT_ID_SIZE]; /* Inittab ID. */ + char ut_user[UT_USER_SIZE]; /* Username. */ + char ut_host[UT_HOST_SIZE]; /* Hostname for remote login. */ + struct __exit_status ut_exit; /* Exit status of a process marked + as DEAD_PROCESS. */ + /* The fields ut_session and ut_tv must be the same size when compiled + 32- and 64-bit. This allows files and shared memory to be shared + between 32- and 64-bit applications. */ + int ut_session; /* Session ID, used for windowing. */ + struct + { + /* Seconds. Unsigned not signed, as glibc did not exist before 1970, + and if the format is still in use after 2038 its timestamps + will surely have the sign bit on. This hack stops working + at 2106-02-07 06:28:16 UTC. */ + unsigned int tv_sec; + int tv_usec; /* Microseconds. */ + } ut_tv; /* Time entry was made. */ + int ut_addr_v6[4]; /* Internet address of remote host. */ + char ut_reserved[20]; /* Reserved for future use. */ + }; + struct utmpx32 const *ut = (struct utmpx32 const *) entry; +# else + struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry; +# endif - copy_utmp_entry (&utmp[n_read++], ut); - } + a = add_utmp (a, options, + UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE), + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID) + ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE), + #else + "", 0, + #endif + ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE), + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST) + ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE), + #else + "", 0, + #endif + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID) + ut->ut_pid, + #else + 0, + #endif + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE) + ut->ut_type, + #else + 0, + #endif + #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV) + (struct timespec) { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 }, + #else + (struct timespec) { .tv_sec = ut->ut_time, .tv_nsec = 0 }, + #endif + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION) + ut->ut_session, + #else + 0, + #endif + UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut) + ); + } END_UTMP_ENT (); - *n_entries = n_read; - *utmp_buf = utmp; + a = finish_utmp (a); + + *n_entries = a.filled; + *utmp_buf = a.utmp; return 0; } -# else +# else /* old FreeBSD, OpenBSD, HP-UX */ int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) { - idx_t n_read = 0; - idx_t n_alloc = 0; - STRUCT_UTMP *utmp = NULL; - int saved_errno; FILE *f = fopen (file, "re"); if (! f) return -1; + struct utmp_alloc a = {0}; + for (;;) { - if (n_read == n_alloc) - utmp = xpalloc (utmp, &n_alloc, 1, -1, sizeof *utmp); - if (fread (&utmp[n_read], sizeof utmp[n_read], 1, f) == 0) + struct UTMP_STRUCT_NAME ut; + + if (fread (&ut, sizeof ut, 1, f) == 0) break; - n_read += desirable_utmp_entry (&utmp[n_read], options); + a = add_utmp (a, options, + UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE), + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID) + ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE), + #else + "", 0, + #endif + ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE), + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST) + ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE), + #else + "", 0, + #endif + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID) + ut.ut_pid, + #else + 0, + #endif + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE) + ut.ut_type, + #else + 0, + #endif + #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV) + (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 }, + #else + (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 }, + #endif + #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION) + ut.ut_session, + #else + 0, + #endif + UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut) + ); } - saved_errno = ferror (f) ? errno : 0; + int saved_errno = ferror (f) ? errno : 0; if (fclose (f) != 0) saved_errno = errno; if (saved_errno != 0) { - free (utmp); + free (a.utmp); errno = saved_errno; return -1; } - *n_entries = n_read; - *utmp_buf = utmp; + a = finish_utmp (a); + + *n_entries = a.filled; + *utmp_buf = a.utmp; return 0; } diff --git a/lib/readutmp.h b/lib/readutmp.h index 043ae6df16..1ddb617b28 100644 --- a/lib/readutmp.h +++ b/lib/readutmp.h @@ -22,7 +22,7 @@ /* This file uses _GL_ATTRIBUTE_MALLOC, _GL_ATTRIBUTE_RETURNS_NONNULL, HAVE_UTMP_H, HAVE_UTMPX_H, HAVE_STRUCT_UTMP_*, HAVE_STRUCT_UTMPX_*, - HAVE_UTMPNAME, HAVE_UTMPXNAME, HAVE_DECL_GETUTENT. */ + HAVE_UTMPNAME, HAVE_UTMPXNAME. */ #if !_GL_CONFIG_H_INCLUDED # error "Please include config.h first." #endif @@ -55,8 +55,8 @@ # include #endif -#if READUTMP_USE_SYSTEMD || ! (HAVE_UTMPX_H || HAVE_UTMP_H) +/* Type of entries returned by read_utmp on all platforms. */ struct gl_utmp { /* All 'char *' here are of arbitrary length and point to storage @@ -64,20 +64,46 @@ struct gl_utmp char *ut_user; /* User name */ char *ut_id; /* Session ID */ char *ut_line; /* seat / device */ - char *ut_host; /* for remote sessions: user@host or host */ + char *ut_host; /* for remote sessions: user@host or host, + for local sessions: the X11 display :N */ struct timespec ut_ts; /* time */ pid_t ut_pid; /* process ID of ? */ pid_t ut_session; /* process ID of session leader */ short ut_type; /* BOOT_TIME or USER_PROCESS */ + struct { int e_termination; int e_exit; } ut_exit; }; -# define HAVE_GL_UTMP 1 -# define UTMP_STRUCT_NAME gl_utmp -# define UT_TIME_MEMBER(UT) ((UT)->ut_ts.tv_sec) -# define UT_EXIT_E_TERMINATION(UT) 0 -# define UT_EXIT_E_EXIT(UT) 0 +/* The following types, macros, and constants describe the 'struct gl_utmp'. */ +#define UT_USER(UT) ((UT)->ut_user) +#define UT_TIME_MEMBER(UT) ((UT)->ut_ts.tv_sec) +#define UT_PID(UT) ((UT)->ut_pid) +#define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V)) +#define UT_TYPE_NOT_DEFINED 0 +#define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) +#define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) -#elif HAVE_UTMPX_H +/* Type of entry returned by read_utmp(). */ +typedef struct gl_utmp STRUCT_UTMP; + +/* Size of the UT_USER (ut) member, or -1 if unbounded. */ +enum { UT_USER_SIZE = -1 }; + +/* Size of the ut->ut_id member, or -1 if unbounded. */ +enum { UT_ID_SIZE = -1 }; + +/* Size of the ut->ut_line member, or -1 if unbounded. */ +enum { UT_LINE_SIZE = -1 }; + +/* Size of the ut->ut_host member, or -1 if unbounded. */ +enum { UT_HOST_SIZE = -1 }; + + +/* When read_utmp accesses a file (as opposed to fetching the information + from systemd), it uses the following low-level types and macros. + Keep them here, rather than moving them into readutmp.c, for backward + compatibility. */ + +#if HAVE_UTMPX_H /* defines 'struct utmpx' with the following fields: @@ -102,32 +128,15 @@ struct gl_utmp */ # define UTMP_STRUCT_NAME utmpx -# define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec) # define SET_UTMP_ENT setutxent # define GET_UTMP_ENT getutxent # define END_UTMP_ENT endutxent -# ifdef HAVE_UTMPXNAME +# ifdef HAVE_UTMPXNAME /* glibc, musl, macOS, NetBSD, Minix, IRIX, Solaris, Cygwin */ # define UTMP_NAME_FUNCTION utmpxname -# elif defined UTXDB_ACTIVE +# elif defined UTXDB_ACTIVE /* FreeBSD */ # define UTMP_NAME_FUNCTION(x) setutxdb (UTXDB_ACTIVE, x) # endif -# if HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION -# define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) -# elif HAVE_STRUCT_UTMPX_UT_EXIT_UT_TERMINATION /* OSF/1 */ -# define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.ut_termination) -# else -# define UT_EXIT_E_TERMINATION(UT) 0 -# endif - -# if HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT -# define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) -# elif HAVE_STRUCT_UTMPX_UT_EXIT_UT_EXIT /* OSF/1 */ -# define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.ut_exit) -# else -# define UT_EXIT_E_EXIT(UT) 0 -# endif - #elif HAVE_UTMP_H /* defines 'struct utmp' with the following fields: @@ -151,143 +160,63 @@ struct gl_utmp ⎣ ut_addr_v6 [u]int[4] glibc, musl, Android */ -# if !HAVE_DECL_GETUTENT - struct utmp *getutent (void); -# endif # define UTMP_STRUCT_NAME utmp -# define UT_TIME_MEMBER(UT) ((UT)->ut_time) # define SET_UTMP_ENT setutent # define GET_UTMP_ENT getutent # define END_UTMP_ENT endutent -# ifdef HAVE_UTMPNAME +# ifdef HAVE_UTMPNAME /* glibc, musl, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin, Android */ # define UTMP_NAME_FUNCTION utmpname # endif -# if HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION -# define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) -# else -# define UT_EXIT_E_TERMINATION(UT) 0 -# endif - -# if HAVE_STRUCT_UTMP_UT_EXIT_E_EXIT -# define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) -# else -# define UT_EXIT_E_EXIT(UT) 0 -# endif - #endif -/* Accessor macro for the member named ut_user or ut_name. */ -#if (!HAVE_GL_UTMP \ - && (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \ - : HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME)) -# define UT_USER(UT) ((UT)->ut_name) -#else -# define UT_USER(UT) ((UT)->ut_user) -#endif - -#define HAVE_STRUCT_XTMP_UT_EXIT \ - (!HAVE_GL_UTMP && (HAVE_STRUCT_UTMP_UT_EXIT || HAVE_STRUCT_UTMPX_UT_EXIT) - +/* Evaluates to 1 if gl_utmp's ut_id field may ever have a non-zero value. */ #define HAVE_STRUCT_XTMP_UT_ID \ - (HAVE_GL_UTMP || HAVE_STRUCT_UTMP_UT_ID || HAVE_STRUCT_UTMPX_UT_ID) + (READUTMP_USE_SYSTEMD \ + || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)) +/* Evaluates to 1 if gl_utmp's ut_pid field may ever have a non-zero value. */ #define HAVE_STRUCT_XTMP_UT_PID \ - (HAVE_GL_UTMP || HAVE_STRUCT_UTMP_UT_PID || HAVE_STRUCT_UTMPX_UT_PID) + (READUTMP_USE_SYSTEMD \ + || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)) +/* Evaluates to 1 if gl_utmp's ut_host field may ever be non-empty. */ #define HAVE_STRUCT_XTMP_UT_HOST \ - (HAVE_GL_UTMP || HAVE_STRUCT_UTMP_UT_HOST || HAVE_STRUCT_UTMPX_UT_HOST) - -/* Type of entry returned by read_utmp(). */ -typedef struct UTMP_STRUCT_NAME STRUCT_UTMP; - -/* Size of the UT_USER (ut) member, or -1 if unbounded. */ -#if HAVE_GL_UTMP -enum { UT_USER_SIZE = -1 }; -#else -enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) }; -# define UT_USER_SIZE UT_USER_SIZE -#endif - -/* Size of the ut->ut_id member, or -1 if unbounded. */ -#if HAVE_GL_UTMP -enum { UT_ID_SIZE = -1 }; -#else -# if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID) -enum { UT_ID_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_id) }; -# else -enum { UT_ID_SIZE = 1 }; -# endif -# define UT_ID_SIZE UT_ID_SIZE -#endif - -/* Size of the ut->ut_line member, or -1 if unbounded. */ -#if HAVE_GL_UTMP -enum { UT_LINE_SIZE = -1 }; -#else -enum { UT_LINE_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_line) }; -# define UT_LINE_SIZE UT_LINE_SIZE -#endif - -/* Size of the ut->ut_host member, or -1 if unbounded. */ -#if HAVE_GL_UTMP -enum { UT_HOST_SIZE = -1 }; -#else -enum { UT_HOST_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_host) }; -# define UT_HOST_SIZE UT_HOST_SIZE -#endif - -/* Definition of UTMP_FILE and WTMP_FILE. */ + (READUTMP_USE_SYSTEMD \ + || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)) +/* Definition of UTMP_FILE. + On glibc systems, UTMP_FILE is "/var/run/utmp". */ #if !defined UTMP_FILE && defined _PATH_UTMP # define UTMP_FILE _PATH_UTMP #endif - -#if !defined WTMP_FILE && defined _PATH_WTMP -# define WTMP_FILE _PATH_WTMP -#endif - #ifdef UTMPX_FILE /* Solaris, SysVr4 */ # undef UTMP_FILE # define UTMP_FILE UTMPX_FILE #endif +#ifndef UTMP_FILE +# define UTMP_FILE "/etc/utmp" +#endif +/* Definition of WTMP_FILE. + On glibc systems, UTMP_FILE is "/var/log/wtmp". */ +#if !defined WTMP_FILE && defined _PATH_WTMP +# define WTMP_FILE _PATH_WTMP +#endif #ifdef WTMPX_FILE /* Solaris, SysVr4 */ # undef WTMP_FILE # define WTMP_FILE WTMPX_FILE #endif - -#ifndef UTMP_FILE -# define UTMP_FILE "/etc/utmp" -#endif - #ifndef WTMP_FILE # define WTMP_FILE "/etc/wtmp" #endif -/* Accessor macro for the member named ut_pid. */ -#if HAVE_STRUCT_XTMP_UT_PID -# define UT_PID(UT) ((UT)->ut_pid) -#else -# define UT_PID(UT) 0 -#endif - -/* Accessor macros for the member named ut_type. */ - -#if HAVE_GL_UTMP || HAVE_STRUCT_UTMP_UT_TYPE || HAVE_STRUCT_UTMPX_UT_TYPE -# define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V)) -# define UT_TYPE_NOT_DEFINED 0 -#else -# define UT_TYPE_EQ(UT, V) 0 -# define UT_TYPE_NOT_DEFINED 1 -#endif - +/* Macros that test (UT)->ut_type. */ #ifdef BOOT_TIME # define UT_TYPE_BOOT_TIME(UT) UT_TYPE_EQ (UT, BOOT_TIME) #else # define UT_TYPE_BOOT_TIME(UT) 0 #endif - #ifdef USER_PROCESS # define UT_TYPE_USER_PROCESS(UT) UT_TYPE_EQ (UT, USER_PROCESS) #else @@ -296,9 +225,9 @@ enum { UT_HOST_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_host) }; /* Determines whether an entry *UT corresponds to a user process. */ #define IS_USER_PROCESS(UT) \ - (UT_USER (UT)[0] \ - && (UT_TYPE_USER_PROCESS (UT) \ - || (UT_TYPE_NOT_DEFINED && UT_TIME_MEMBER (UT) != 0))) + (UT_USER (UT)[0] \ + && (UT_TYPE_USER_PROCESS (UT) \ + || (UT_TYPE_NOT_DEFINED && UT_TIME_MEMBER (UT) != 0))) /* Define if read_utmp is not just a dummy. */ #if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H @@ -312,7 +241,7 @@ enum READ_UTMP_USER_PROCESS = 2 }; -/* Return a copy of UT_USER (UT), without trailing spaces, +/* Return a copy of (UT)->ut_user, without trailing spaces, as a freshly allocated string. */ char *extract_trimmed_name (const STRUCT_UTMP *ut) _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE diff --git a/m4/readutmp.m4 b/m4/readutmp.m4 index a4b1cb4642..6ba5b2e225 100644 --- a/m4/readutmp.m4 +++ b/m4/readutmp.m4 @@ -1,4 +1,4 @@ -# readutmp.m4 serial 22 +# readutmp.m4 serial 23 dnl Copyright (C) 2002-2023 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -78,10 +78,13 @@ AC_DEFUN([gl_READUTMP] AC_CHECK_MEMBERS([struct utmp.ut_type],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmpx.ut_pid],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmp.ut_pid],,,[$utmp_includes]) + AC_CHECK_MEMBERS([struct utmp.ut_tv],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmpx.ut_host],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmp.ut_host],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmpx.ut_id],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmp.ut_id],,,[$utmp_includes]) + AC_CHECK_MEMBERS([struct utmpx.ut_session],,,[$utmp_includes]) + AC_CHECK_MEMBERS([struct utmp.ut_session],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmpx.ut_exit],,,[$utmp_includes]) AC_CHECK_MEMBERS([struct utmp.ut_exit],,,[$utmp_includes]) -- 2.34.1