I wrote:
> >      (1) The API of the readutmp module should provide unlimited-length 
> > ut_user,
> >          ut_host etc. fields always. No more #ifdef UT_USER_SIZE.
> >      (2) The readutmp module should use a runtime 'if' rather than a 
> > compile-time
> >          #if, in order to dispatch between the systemd backend and the 
> > file-based
> >          backend.
> > 
> >      I'll work on (1) today.

(1) done through the following patch.

It does not break coreutils. But coreutils can now be simplified through the
attached 0001-maint-Simplify-after-gnulib-changed.patch .


2023-08-08  Bruno Haible  <br...@clisp.org>

        readutmp: Return entries with unbounded strings on all platforms.
        Suggested  by Paul Eggert in
        <https://lists.gnu.org/archive/html/bug-gnulib/2023-07/msg00165.html>.
        * 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.

From 622d2c0763777eb909a4fda5048238f524cc36f9 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
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
<https://lists.gnu.org/archive/html/bug-gnulib/2023-07/msg00165.html>.

* 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  <br...@clisp.org>
+
+	readutmp: Return entries with unbounded strings on all platforms.
+	Suggested  by Paul Eggert in
+	<https://lists.gnu.org/archive/html/bug-gnulib/2023-07/msg00165.html>.
+	* 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  <br...@clisp.org>
 
 	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 <utmpx.h>
 #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
 
 /* <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
 
 /* <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

>From 9949868ca1fdc6acd45dde3dae946084775f0f5f Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 8 Aug 2023 18:56:39 +0200
Subject: [PATCH] maint: Simplify after gnulib changed

All of UT_USER_SIZE, UT_ID_SIZE, UT_LINE_SIZE, UT_HOST_SIZE are now -1.

* src/pinky.c (print_entry): Remove code for bounded-length ut_line,
ut_user, ut_host.
(scan_entries): Remove code for bounded-length ut_user.
* src/who.c (print_line): Remove userlen, linelen arguments.
(print_user): Remove code for bounded-length ut_line, ut_user, ut_host.
(make_id_equals_comment): Remove code for bounded-length ut_id.
(print_boottime, print_deadprocs, print_login, print_initspawn,
print_clockchange, print_runlevel, print_heading): Update print_line
invocations.
(scan_entries): Remove code for bounded-length ut_line.
---
 src/pinky.c | 26 +++++---------------------
 src/who.c   | 45 +++++++++++++++++----------------------------
 2 files changed, 22 insertions(+), 49 deletions(-)

diff --git a/src/pinky.c b/src/pinky.c
index 38ceccbea..1429dd073 100644
--- a/src/pinky.c
+++ b/src/pinky.c
@@ -203,15 +203,10 @@ print_entry (const STRUCT_UTMP *utmp_ent)
   time_t last_change;
   char mesg;
 
-#ifdef UT_LINE_SIZE
-  char line[UT_LINE_SIZE + 1];
-  stzncpy (line, utmp_ent->ut_line, UT_LINE_SIZE);
-#else
   /* If ut_line contains a space, the device name starts after the space.  */
   char *line = utmp_ent->ut_line;
   char *space = strchr (line, ' ');
   line = space ? space + 1 : line;
-#endif
 
   int dirfd;
   if (IS_ABSOLUTE_FILE_NAME (line))
@@ -239,19 +234,14 @@ print_entry (const STRUCT_UTMP *utmp_ent)
       last_change = 0;
     }
 
-  if (0 <= UT_USER_SIZE || strnlen (UT_USER (utmp_ent), 8) < 8)
-    printf ("%-8.*s", UT_USER_SIZE, UT_USER (utmp_ent));
+  if (strnlen (UT_USER (utmp_ent), 8) < 8)
+    printf ("%-8s", UT_USER (utmp_ent));
   else
     fputs (UT_USER (utmp_ent), stdout);
 
   if (include_fullname)
     {
-#ifdef UT_USER_SIZE
-      char name[UT_USER_SIZE + 1];
-      stzncpy (name, UT_USER (utmp_ent), UT_USER_SIZE);
-#else
       char *name = UT_USER (utmp_ent);
-#endif
       struct passwd *pw = getpwnam (name);
       if (pw == nullptr)
         /* TRANSLATORS: Real name is unknown; at most 19 characters. */
@@ -272,8 +262,8 @@ print_entry (const STRUCT_UTMP *utmp_ent)
 
   fputc (' ', stdout);
   fputc (mesg, stdout);
-  if (0 <= UT_LINE_SIZE || strnlen (utmp_ent->ut_line, 8) < 8)
-    printf ("%-8.*s", UT_LINE_SIZE, utmp_ent->ut_line);
+  if (strnlen (utmp_ent->ut_line, 8) < 8)
+    printf ("%-8s", utmp_ent->ut_line);
   else
     fputs (utmp_ent->ut_line, stdout);
 
@@ -293,13 +283,7 @@ print_entry (const STRUCT_UTMP *utmp_ent)
     {
       char *host = nullptr;
       char *display = nullptr;
-
-# ifdef UT_HOST_SIZE
-      char ut_host[UT_HOST_SIZE + 1];
-      stzncpy (ut_host, utmp_ent->ut_host, UT_HOST_SIZE);
-# else
       char *ut_host = utmp_ent->ut_host;
-# endif
 
       /* Look for an X display.  */
       display = strchr (ut_host, ':');
@@ -475,7 +459,7 @@ scan_entries (idx_t n, const STRUCT_UTMP *utmp_buf,
           if (argc_names)
             {
               for (int i = 0; i < argc_names; i++)
-                if (STREQ_LEN (UT_USER (utmp_buf), argv_names[i], UT_USER_SIZE))
+                if (STREQ (UT_USER (utmp_buf), argv_names[i]))
                   {
                     print_entry (utmp_buf);
                     break;
diff --git a/src/who.c b/src/who.c
index 0e94d3c83..27a7904e1 100644
--- a/src/who.c
+++ b/src/who.c
@@ -238,8 +238,8 @@ time_string (const STRUCT_UTMP *utmp_ent)
    will need tweaking if any of the localization stuff is done, or for 64 bit
    pids, etc. */
 static void
-print_line (int userlen, char const *user, const char state,
-            int linelen, char const *line,
+print_line (char const *user, const char state,
+            char const *line,
             char const *time_str, char const *idle, char const *pid,
             char const *comment, char const *exitstr)
 {
@@ -269,18 +269,18 @@ print_line (int userlen, char const *user, const char state,
     *x_exitstr = '\0';
 
   err = asprintf (&buf,
-                  "%-8.*s"
+                  "%-8s"
                   "%s"
-                  " %-12.*s"
+                  " %-12s"
                   " %-*s"
                   "%s"
                   "%s"
                   " %-8s"
                   "%s"
                   ,
-                  userlen, user ? user : "   .",
+                  user ? user : "   .",
                   include_mesg ? mesg : "",
-                  linelen, line,
+                  line,
                   time_format_width,
                   time_str,
                   x_idle,
@@ -339,15 +339,10 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
   static idx_t hostlen;
 #endif
 
-#ifdef UT_LINE_SIZE
-  char line[UT_LINE_SIZE + 1];
-  stzncpy (line, utmp_ent->ut_line, UT_LINE_SIZE);
-#else
   /* If ut_line contains a space, the device name starts after the space.  */
   char *line = utmp_ent->ut_line;
   char *space = strchr (line, ' ');
   line = space ? space + 1 : line;
-#endif
 
   int dirfd;
   if (IS_ABSOLUTE_FILE_NAME (line))
@@ -385,13 +380,7 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
     {
       char *host = nullptr;
       char *display = nullptr;
-
-# ifdef UT_HOST_SIZE
-      char ut_host[UT_HOST_SIZE + 1];
-      stzncpy (ut_host, utmp_ent->ut_host, UT_HOST_SIZE);
-# else
       char *ut_host = utmp_ent->ut_host;
-# endif
 
       /* Look for an X display.  */
       display = strchr (ut_host, ':');
@@ -445,8 +434,8 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
     }
 #endif
 
-  print_line (UT_USER_SIZE, UT_USER (utmp_ent), mesg,
-              UT_LINE_SIZE, utmp_ent->ut_line,
+  print_line (UT_USER (utmp_ent), mesg,
+              utmp_ent->ut_line,
               time_string (utmp_ent), idlestr, pidstr,
               hoststr ? hoststr : "", "");
 }
@@ -454,7 +443,7 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
 static void
 print_boottime (const STRUCT_UTMP *utmp_ent)
 {
-  print_line (-1, "", ' ', -1, _("system boot"),
+  print_line ("", ' ', _("system boot"),
               time_string (utmp_ent), "", "", "", "");
 }
 
@@ -462,7 +451,7 @@ static char *
 make_id_equals_comment (STRUCT_UTMP const *utmp_ent)
 {
   char const *id = UT_ID (utmp_ent);
-  idx_t idlen = strnlen (id, UT_ID_SIZE);
+  idx_t idlen = strlen (id);
   char const *prefix = _("id=");
   idx_t prefixlen = strlen (prefix);
   char *comment = xmalloc (prefixlen + idlen + 1);
@@ -490,7 +479,7 @@ print_deadprocs (const STRUCT_UTMP *utmp_ent)
 
   /* FIXME: add idle time? */
 
-  print_line (-1, "", ' ', UT_LINE_SIZE, utmp_ent->ut_line,
+  print_line ("", ' ', utmp_ent->ut_line,
               time_string (utmp_ent), "", pidstr, comment, exitstr);
   free (comment);
 }
@@ -503,7 +492,7 @@ print_login (const STRUCT_UTMP *utmp_ent)
 
   /* FIXME: add idle time? */
 
-  print_line (-1, _("LOGIN"), ' ', UT_LINE_SIZE, utmp_ent->ut_line,
+  print_line (_("LOGIN"), ' ', utmp_ent->ut_line,
               time_string (utmp_ent), "", pidstr, comment, "");
   free (comment);
 }
@@ -514,7 +503,7 @@ print_initspawn (const STRUCT_UTMP *utmp_ent)
   char *comment = make_id_equals_comment (utmp_ent);
   PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
 
-  print_line (-1, "", ' ', UT_LINE_SIZE, utmp_ent->ut_line,
+  print_line ("", ' ', utmp_ent->ut_line,
               time_string (utmp_ent), "", pidstr, comment, "");
   free (comment);
 }
@@ -523,7 +512,7 @@ static void
 print_clockchange (const STRUCT_UTMP *utmp_ent)
 {
   /* FIXME: handle NEW_TIME & OLD_TIME both */
-  print_line (-1, "", ' ', -1, _("clock change"),
+  print_line ("", ' ', _("clock change"),
               time_string (utmp_ent), "", "", "", "");
 }
 
@@ -542,7 +531,7 @@ print_runlevel (const STRUCT_UTMP *utmp_ent)
     comment = xmalloc (strlen (_("last=")) + 2);
   sprintf (comment, "%s%c", _("last="), (last == 'N') ? 'S' : last);
 
-  print_line (-1, "", ' ', -1, runlevline, time_string (utmp_ent),
+  print_line ("", ' ', runlevline, time_string (utmp_ent),
               "", "", c_isprint (last) ? comment : "", "");
 
   return;
@@ -577,7 +566,7 @@ list_entries_who (idx_t n, const STRUCT_UTMP *utmp_buf)
 static void
 print_heading (void)
 {
-  print_line (-1, _("NAME"), ' ', -1, _("LINE"), _("TIME"), _("IDLE"),
+  print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"),
               _("PID"), _("COMMENT"), _("EXIT"));
 }
 
@@ -609,7 +598,7 @@ scan_entries (idx_t n, const STRUCT_UTMP *utmp_buf)
   while (n--)
     {
       if (!my_line_only
-          || STREQ_LEN (ttyname_b, utmp_buf->ut_line, UT_LINE_SIZE))
+          || STREQ (ttyname_b, utmp_buf->ut_line))
         {
           if (need_users && IS_USER_PROCESS (utmp_buf))
             print_user (utmp_buf, boottime);
-- 
2.34.1

Reply via email to