Thanks for doing all that work. I looked into it, and found a problem: a command like "who /var/log/wtmp" stops working because the systemd emulation of read_utmp only supports plain "who" (rougnly equivalent to "who /var/run/utmp" on Fedora). So I installed it into coreutils, but the default is systemd is disabled. This should give people time to experiment with it.

Thorsten, is there some way to get the equivalent of /var/log/wtmp with systemd?

Also, I simplified the use of the new readutmp interface a bit, by going back to the old way where you simply call 'free' once to free the storage. Although I toyed with the idea of simplifying readutmp.h further, by moving most of it into readutmp.c and having just struct gl_utmp public (this would simplify coreutils quite a bit), I ran out of time and patience and decided to ship what I had. So I nstalled the first ten attached patches into Gnulib, and the last patch into coreutils.

I haven't tested this with the latest systemd so there could well be typos in that part of the code.
From 39a4cb0afdf4f2a1e6c2f3176b84e5b4cfe8996d Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 15:31:48 -0700
Subject: [PATCH 1/9] readutmp: simplify extract_trimmed_name via ximemdup0

* lib/readutmp.c (extract_trimmed_name): Simplify.
* modules/readutmp (Depends-on):
Add strnlen, which was a missing dependency.

* lib/readutmp.c: Include xmemdup0.
(extract_trimmed_name): Simplify.
* modules/readutmp (Depends-on): Add xmemdup0.
Add strnlen, which was a missing dependency already.
---
 ChangeLog        |  7 +++++++
 lib/readutmp.c   | 28 +++++++++++-----------------
 modules/readutmp |  1 +
 3 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b84cece6ff..7fa4e7b64a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
+
+	readutmp: simplify extract_trimmed_name via ximemdup0
+	* lib/readutmp.c (extract_trimmed_name): Simplify.
+	* modules/readutmp (Depends-on):
+	Add strnlen, which was a missing dependency.
+
 2023-08-03  Bruno Haible  <br...@clisp.org>
 
 	alignasof, stdalign: Avoid some -Wundef warnings from config.h.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 11dd1655c5..9057a36494 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -47,29 +47,23 @@
 # pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
 #endif
 
+/* Work around <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109614>.  */
+#if 11 <= __GNUC__
+# pragma GCC diagnostic ignored "-Wstringop-overread"
+#endif
+
 /* Copy UT_USER (UT) 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 *p, *trimmed_name;
-
-#if READUTMP_USE_SYSTEMD
-  trimmed_name = xstrdup (UT_USER (ut));
-#else
-  trimmed_name = xmalloc (UT_USER_SIZE + 1);
-  strncpy (trimmed_name, UT_USER (ut), UT_USER_SIZE);
-  /* Append a trailing NUL.  Some systems pad names shorter than the
-     maximum with spaces, others pad with NULs.  */
-  trimmed_name[UT_USER_SIZE] = '\0';
-#endif
-  /* Remove any trailing spaces.  */
-  for (p = trimmed_name + strlen (trimmed_name);
-       trimmed_name < p && p[-1] == ' ';
-       *--p = '\0')
-    ;
-  return trimmed_name;
+  char const *name = UT_USER (ut);
+  idx_t len = strnlen (name, UT_USER_SIZE);
+  char const *p;
+  for (p = name + len; name < p && p[-1] == ' '; p--)
+    continue;
+  return ximemdup0 (name, p - name);
 }
 
 #if READ_UTMP_SUPPORTED
diff --git a/modules/readutmp b/modules/readutmp
index 04893a4487..484edd1842 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -12,6 +12,7 @@ extensions
 xalloc
 stdbool
 stdint
+strnlen
 sys_time
 fopen-gnu
 unlocked-io-internal
-- 
2.39.2

From 1ccde926759e8638d4b86de3dabbd948ad921edc Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 15:53:27 -0700
Subject: [PATCH 2/9] =?UTF-8?q?readutmp:=20go=20back=20to=20simple=20?=
 =?UTF-8?q?=E2=80=98free=E2=80=99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Omit the new free_utmp function.  Instead, allocate storage
in one block, so that using code can still just call ‘free’.
* lib/readutmp.c (struct utmp_alloc) [READUTMP_USE_SYSTEMD]: New type.
(add_utmp) [READUTMP_USE_SYSTEMD]: New function.
(read_utmp) [READUTMP_USE_SYSTEMD]: Use it.
Also, use malloc a bit less heavily.
(free_utmp): Remove.
* tests/test-readutmp.c (main): Call free, not free_utmp.
---
 ChangeLog             |  10 ++
 NEWS                  |   4 +-
 lib/readutmp.c        | 285 +++++++++++++++++++++++-------------------
 lib/readutmp.h        |   4 -
 tests/test-readutmp.c |   4 +-
 5 files changed, 167 insertions(+), 140 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7fa4e7b64a..56b27d129f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
 
+	readutmp: go back to simple ‘free’
+	Omit the new free_utmp function.  Instead, allocate storage
+	in one block, so that using code can still just call ‘free’.
+	* lib/readutmp.c (struct utmp_alloc) [READUTMP_USE_SYSTEMD]: New type.
+	(add_utmp) [READUTMP_USE_SYSTEMD]: New function.
+	(read_utmp) [READUTMP_USE_SYSTEMD]: Use it.
+	Also, use malloc a bit less heavily.
+	(free_utmp): Remove.
+	* tests/test-readutmp.c (main): Call free, not free_utmp.
+
 	readutmp: simplify extract_trimmed_name via ximemdup0
 	* lib/readutmp.c (extract_trimmed_name): Simplify.
 	* modules/readutmp (Depends-on):
diff --git a/NEWS b/NEWS
index 87d49ffbe2..24139d4660 100644
--- a/NEWS
+++ b/NEWS
@@ -74,8 +74,8 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
-2023-08-01  readutmp        After processing the read_utmp() results, call
-                            free_utmp() in order to avoid a memory leak.
+2023-08-01  readutmp        Some STRUCT_UTMP members can be char *,
+                            rather than fixed-length char arrays.
                             Link additionally with $(READUTMP_LIB).
 
 2023-07-10  dfa             The signature of the function
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 9057a36494..508978ba79 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -223,34 +223,92 @@ 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 timeval t, 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_pid = pid;
+  ut->ut_type = type;
+  ut->ut_tv = t;
+  ut->ut_host = memcpy (p - hostsize, line, hostsize);
+  ut->ut_session = session;
+  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, size_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
-  /* Fill entries, simulating what an utmp file would contain.  */
-  idx_t n_filled = 0;
-  idx_t n_alloc = 0;
-  STRUCT_UTMP *utmp = NULL;
+  /* Fill entries, simulating what a utmp file would contain.  */
+  struct utmp_alloc a = {0};
 
   /* Synthesize a BOOT_TIME entry.  */
   if (!(options & READ_UTMP_USER_PROCESS))
-    {
-      if (n_filled == n_alloc)
-        utmp = xpalloc (utmp, &n_alloc, 1, -1, sizeof (STRUCT_UTMP));
-      STRUCT_UTMP *ut = &utmp[n_filled];
-      ut->ut_user = xstrdup ("reboot");
-      ut->ut_id = xstrdup ("");
-      ut->ut_line = xstrdup ("~");
-      ut->ut_pid = 0;
-      ut->ut_type = BOOT_TIME;
-      ut->ut_tv = get_boot_time ();
-      ut->ut_host = xstrdup ("");
-      ut->ut_session = 0;
-      if (desirable_utmp_entry (ut, options))
-        n_filled++;
-      else
-        free_utmp (1, ut);
-    }
+    a = add_utmp (a, options, "reboot", "", "~", 0,
+                  BOOT_TIME, get_boot_time (), "", 0);
 
   /* Synthesize USER_PROCESS entries.  */
   char **sessions;
@@ -273,48 +331,46 @@ read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
           if (sd_session_get_seat (session, &seat) < 0)
             seat = NULL;
 
+          char missing_type[] = "";
+          char *type = NULL;
           char *tty;
           if (sd_session_get_tty (session, &tty) < 0)
             {
               tty = NULL;
               /* Try harder to get a sensible value for the tty.  */
-              char *type;
-              if (sd_session_get_type (session, &type) >= 0)
+              if (sd_session_get_type (session, &type) < 0)
+                type = missing_type;
+              if (strcmp (type, "tty") == 0)
                 {
-                  if (strcmp (type, "tty") == 0)
-                    {
-                      char *service;
-                      if (sd_session_get_service (session, &service) < 0)
-                        service = NULL;
+                  char *service;
+                  if (sd_session_get_service (session, &service) < 0)
+                    service = NULL;
 
-                      char *pty;
-                      uid_t uid;
-                      if (sd_session_get_uid (session, &uid) >= 0)
-                        {
-                          struct timespec start_ts =
-                            {
-                              .tv_sec = start_tv.tv_sec,
-                              .tv_nsec = start_tv.tv_usec * 1000
-                            };
-                          pty = guess_pty_name (uid, start_ts);
-                        }
-                      else
-                        pty = NULL;
-
-                      if (service != NULL && pty != NULL)
+                  char *pty;
+                  uid_t uid;
+                  if (sd_session_get_uid (session, &uid) >= 0)
+                    {
+                      struct timespec start_ts =
                         {
-                          tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
-                          stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
-                        }
-                      else if (service != NULL)
-                        tty = xstrdup (service);
-                      else if (pty != NULL)
-                        tty = xstrdup (pty);
+                          .tv_sec = start_tv.tv_sec,
+                          .tv_nsec = start_tv.tv_usec * 1000
+                        };
+                      pty = guess_pty_name (uid, start_ts);
+                    }
+                  else
+                    pty = NULL;
 
+                  if (service != NULL && pty != NULL)
+                    {
+                      tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
+                      stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
                       free (pty);
                       free (service);
                     }
-                  free (type);
+                  else if (service != NULL)
+                    tty = service;
+                  else if (pty != NULL)
+                    tty = pty;
                 }
             }
 
@@ -324,92 +380,60 @@ read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
             {
               char *user;
               if (sd_session_get_username (session, &user) < 0)
-                user = xstrdup ("");
+                user = NULL;
 
               pid_t leader_pid;
               if (sd_session_get_leader (session, &leader_pid) < 0)
                 leader_pid = 0;
 
+              char *host;
               char *remote_host;
               if (sd_session_get_remote_host (session, &remote_host) < 0)
-                remote_host = NULL;
-              char *remote_user;
-              if (sd_session_get_remote_user (session, &remote_user) < 0)
-                remote_user = NULL;
-              char *host;
-              if (remote_host != NULL)
                 {
-                  if (remote_user != NULL)
+                  host = NULL;
+                  /* For backward compatibility, put the X11 display into the
+                     host field.  */
+                  if (!type && sd_session_get_type (session, &type) < 0)
+                    type = missing_type;
+                  if (strcmp (type, "x11") == 0)
                     {
-                      host = xmalloc (strlen (remote_user) + 1 + strlen (remote_host) + 1);
-                      stpcpy (stpcpy (stpcpy (host, remote_user), "@"), remote_host);
+                      char *display;
+                      if (sd_session_get_display (session, &display) < 0)
+                        display = NULL;
+                      host = display;
                     }
-                  else
-                    host = xstrdup (remote_host);
                 }
               else
                 {
-                  host = NULL;
-                  /* For backward compatibility, put the X11 display into the
-                     host field.  */
-                  char *type;
-                  if (sd_session_get_type (session, &type) >= 0)
+                  char *remote_user;
+                  if (sd_session_get_remote_user (session, &remote_user) < 0)
+                    host = remote_host;
+                  else
                     {
-                      if (strcmp (type, "x11") == 0)
-                        {
-                          char *display;
-                          if (sd_session_get_display (session, &display) < 0)
-                            display = NULL;
-                          host = display;
-                        }
-                      free (type);
+                      host = xmalloc (strlen (remote_user) + 1
+                                      + strlen (remote_host) + 1);
+                      stpcpy (stpcpy (stpcpy (host, remote_user), "@"),
+                              remote_host);
+                      free (remote_user);
+                      free (remote_host);
                     }
-                  if (host == NULL)
-                    host = xstrdup ("");
                 }
 
-              size_t n_filled_after = n_filled + (seat != NULL) + (tty != NULL);
-              if (n_filled_after > n_alloc)
-                utmp = xpalloc (utmp, &n_alloc, n_filled_after - n_alloc, -1,
-                                sizeof (STRUCT_UTMP));
               if (seat != NULL)
-                {
-                  STRUCT_UTMP *ut = &utmp[n_filled];
-                  ut->ut_user = xstrdup (user);
-                  ut->ut_id = xstrdup (session);
-                  ut->ut_line = xstrdup (seat);
-                  ut->ut_pid = leader_pid; /* this is the best we have */
-                  ut->ut_type = USER_PROCESS;
-                  ut->ut_tv = start_tv;
-                  ut->ut_host = xstrdup (host);
-                  ut->ut_session = leader_pid;
-                  if (desirable_utmp_entry (ut, options))
-                    n_filled++;
-                  else
-                    free_utmp (1, ut);
-                }
+                a = add_utmp (a, options, user, session, seat,
+                              leader_pid /* the best we have */,
+                              USER_PROCESS, start_tv, host, leader_pid);
               if (tty != NULL)
-                {
-                  STRUCT_UTMP *ut = &utmp[n_filled];
-                  ut->ut_user = xstrdup (user);
-                  ut->ut_id = xstrdup (session);
-                  ut->ut_line = xstrdup (tty);
-                  ut->ut_pid = leader_pid; /* this is the best we have */
-                  ut->ut_type = USER_PROCESS;
-                  ut->ut_tv = start_tv;
-                  ut->ut_host = xstrdup (host);
-                  ut->ut_session = leader_pid;
-                  if (desirable_utmp_entry (ut, options))
-                    n_filled++;
-                  else
-                    free_utmp (1, ut);
-                }
+                a = add_utmp (a, options, user, session, tty,
+                              leader_pid /* the best we have */,
+                              USER_PROCESS, start_tv, host, leader_pid);
 
               free (host);
-              free (remote_user);
-              free (remote_host);
               free (user);
             }
+
+          if (type != missing_type)
+            free (type);
           free (tty);
           free (seat);
           free (session);
@@ -417,8 +441,21 @@ read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
       free (sessions);
     }
 
-  *n_entries = n_filled;
-  *utmp_buf = utmp;
+  /* 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;
+      }
+  }
+
+  *n_entries = a.filled;
+  *utmp_buf = a.utmp;
 
   return 0;
 }
@@ -557,19 +594,3 @@ read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
 }
 
 #endif
-
-void
-free_utmp (size_t n_entries, STRUCT_UTMP *entries)
-{
-#if READUTMP_USE_SYSTEMD
-  size_t i;
-  for (i = 0; i < n_entries; i++)
-    {
-      STRUCT_UTMP *ut = &entries[i];
-      free (ut->ut_user);
-      free (ut->ut_id);
-      free (ut->ut_line);
-      free (ut->ut_host);
-    }
-#endif
-}
diff --git a/lib/readutmp.h b/lib/readutmp.h
index d7db9f3a67..956a8cafd9 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -389,8 +389,4 @@ char *extract_trimmed_name (const STRUCT_UTMP *ut)
 int read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
                int options);
 
-/* Free the memory allocated by the N_ENTRIES utmp entries, starting
-   at ENTRIES.  */
-void free_utmp (size_t n_entries, STRUCT_UTMP *entries);
-
 #endif /* __READUTMP_H__ */
diff --git a/tests/test-readutmp.c b/tests/test-readutmp.c
index ae17087a81..b03c5f9e47 100644
--- a/tests/test-readutmp.c
+++ b/tests/test-readutmp.c
@@ -138,9 +138,9 @@ main (int argc, char *argv[])
       time_t now = time (NULL);
       ASSERT (first >= now - 157680000);
       ASSERT (last <= now + 604800);
-
-      free_utmp (num_entries, entries);
     }
 
+  free (entries);
+
   return 0;
 }
-- 
2.39.2

From db1645dd8b4874600d12a42324d2c153ea05d9ee Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 15:53:30 -0700
Subject: [PATCH 3/9] readutmp: fix idx_t FIXME in API

* lib/readutmp.c (read_utmp): 2nd arg is now idx_t *, not
size_t *.
* lib/readutmp.h: Include idx.h, for idx_t.
* modules/readutmp (Depends-on): Add idx.
---
 ChangeLog        | 6 ++++++
 NEWS             | 5 +++--
 lib/readutmp.c   | 8 ++++----
 lib/readutmp.h   | 5 +++--
 modules/readutmp | 1 +
 5 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 56b27d129f..a87e6537b1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
 
+	readutmp: fix idx_t FIXME in API
+	* lib/readutmp.c (read_utmp): 2nd arg is now idx_t *, not
+	size_t *.
+	* lib/readutmp.h: Include idx.h, for idx_t.
+	* modules/readutmp (Depends-on): Add idx.
+
 	readutmp: go back to simple ‘free’
 	Omit the new free_utmp function.  Instead, allocate storage
 	in one block, so that using code can still just call ‘free’.
diff --git a/NEWS b/NEWS
index 24139d4660..f1c9b72aa9 100644
--- a/NEWS
+++ b/NEWS
@@ -74,8 +74,9 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
-2023-08-01  readutmp        Some STRUCT_UTMP members can be char *,
-                            rather than fixed-length char arrays.
+2023-08-02  readutmp        Some STRUCT_UTMP members can be char *,
+2023-08-01                  rather than fixed-length char arrays.
+                            read_utmp's 2nd arg is now idx_t * not size_t *.
                             Link additionally with $(READUTMP_LIB).
 
 2023-07-10  dfa             The signature of the function
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 508978ba79..66314ec1db 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -299,7 +299,7 @@ add_utmp (struct utmp_alloc a, int options,
 }
 
 int
-read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
+read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
   /* Fill entries, simulating what a utmp file would contain.  */
@@ -508,7 +508,7 @@ copy_utmp_entry (STRUCT_UTMP *dst, STRUCT_UTMP *src)
 }
 
 int
-read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
+read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
   idx_t n_read = 0;
@@ -544,7 +544,7 @@ read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
 # else
 
 int
-read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
+read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
   idx_t n_read = 0;
@@ -586,7 +586,7 @@ read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
 #else /* dummy fallback */
 
 int
-read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
+read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
   errno = ENOSYS;
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 956a8cafd9..4897b102d2 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -27,6 +27,8 @@
 #  error "Please include config.h first."
 # endif
 
+# include "idx.h"
+
 # include <stdlib.h>
 # include <sys/types.h>
 
@@ -385,8 +387,7 @@ char *extract_trimmed_name (const STRUCT_UTMP *ut)
    process-IDs do not currently exist.
    If OPTIONS & READ_UTMP_USER_PROCESS is nonzero, omit entries which
    do not correspond to a user process.  */
-/* FIXME: This header should use idx_t, not size_t.  */
-int read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf,
+int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
                int options);
 
 #endif /* __READUTMP_H__ */
diff --git a/modules/readutmp b/modules/readutmp
index 484edd1842..534ce4fa19 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -9,6 +9,7 @@ m4/systemd.m4
 
 Depends-on:
 extensions
+idx
 xalloc
 stdbool
 stdint
-- 
2.39.2

From e16e74a11015259f02f5cfe26af6ea8e2ce1a270 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 15:54:12 -0700
Subject: [PATCH 4/9] readutmp: pacify -Wstrict-prototypes

* lib/readutmp.c (get_boot_time_uncached, get_boot_time):
Pacify gcc 13 -Wstrict-prototypes.
---
 ChangeLog      | 4 ++++
 lib/readutmp.c | 4 ++--
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index a87e6537b1..1a75ea0c33 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
 
+	readutmp: pacify -Wstrict-prototypes
+	* lib/readutmp.c (get_boot_time_uncached, get_boot_time):
+	Pacify gcc 13 -Wstrict-prototypes.
+
 	readutmp: fix idx_t FIXME in API
 	* lib/readutmp.c (read_utmp): 2nd arg is now idx_t *, not
 	size_t *.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 66314ec1db..97e92e69af 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -94,7 +94,7 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
 /* Use systemd and Linux /proc and kernel APIs.  */
 
 static struct timeval
-get_boot_time_uncached ()
+get_boot_time_uncached (void)
 {
   /* /proc/uptime contains the uptime with a resolution of 0.01 sec.  */
   FILE *fp = fopen ("/proc/uptime", "re");
@@ -147,7 +147,7 @@ get_boot_time_uncached ()
 }
 
 static struct timeval
-get_boot_time ()
+get_boot_time (void)
 {
   static int cached;
   static struct timeval boot_time;
-- 
2.39.2

From 2ab324f6789713e55e05ad7c50691995db2b0f85 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 15:54:14 -0700
Subject: [PATCH 5/9] readutmp: fix # indentation

* lib/readutmp.h: Change # indentation to standard Gnulib style.
---
 ChangeLog      |   3 +
 lib/readutmp.h | 380 ++++++++++++++++++++++++-------------------------
 2 files changed, 191 insertions(+), 192 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1a75ea0c33..44b80d8c61 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
 
+	readutmp: fix # indentation
+	* lib/readutmp.h: Change # indentation to standard Gnulib style.
+
 	readutmp: pacify -Wstrict-prototypes
 	* lib/readutmp.c (get_boot_time_uncached, get_boot_time):
 	Pacify gcc 13 -Wstrict-prototypes.
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 4897b102d2..da7225152c 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -18,31 +18,31 @@
 /* Written by jla; revised by djm */
 
 #ifndef __READUTMP_H__
-# define __READUTMP_H__
+#define __READUTMP_H__
 
 /* 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.  */
-# if !_GL_CONFIG_H_INCLUDED
-#  error "Please include config.h first."
-# endif
+#if !_GL_CONFIG_H_INCLUDED
+# error "Please include config.h first."
+#endif
 
-# include "idx.h"
+#include "idx.h"
 
-# include <stdlib.h>
-# include <sys/types.h>
+#include <stdlib.h>
+#include <sys/types.h>
 
 /* AIX 4.3.3 has both utmp.h and utmpx.h, but only struct utmp
    has the ut_exit member.  */
-# if (HAVE_UTMPX_H && HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_EXIT \
-      && ! HAVE_STRUCT_UTMPX_UT_EXIT)
-#  undef HAVE_UTMPX_H
-# endif
+#if (HAVE_UTMPX_H && HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_EXIT \
+     && ! HAVE_STRUCT_UTMPX_UT_EXIT)
+# undef HAVE_UTMPX_H
+#endif
 
-# if READUTMP_USE_SYSTEMD
+#if READUTMP_USE_SYSTEMD
 
 /* Get 'struct timeval'.  */
-#  include <sys/time.h>
+# include <sys/time.h>
 
 /* Type for the entries returned by read_utmp.  */
 struct gl_utmp
@@ -59,14 +59,14 @@ struct gl_utmp
 };
 
 /* Get values for ut_type: BOOT_TIME, USER_PROCESS.  */
-#  include <utmpx.h>
+# include <utmpx.h>
 
-#  define UTMP_STRUCT_NAME gl_utmp
-#  define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
-#  define UT_EXIT_E_TERMINATION(UT) 0
-#  define UT_EXIT_E_EXIT(UT) 0
+# define UTMP_STRUCT_NAME gl_utmp
+# define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
+# define UT_EXIT_E_TERMINATION(UT) 0
+# define UT_EXIT_E_EXIT(UT) 0
 
-# elif HAVE_UTMPX_H
+#elif HAVE_UTMPX_H
 
 /* <utmpx.h> defines 'struct utmpx' with the following fields:
 
@@ -90,48 +90,44 @@ struct gl_utmp
    ⎣ ut_ss        struct sockaddr_storage    NetBSD, Minix
  */
 
-#  if HAVE_UTMP_H
+# if HAVE_UTMP_H
     /* HPUX 10.20 needs utmp.h, for the definition of e.g., UTMP_FILE.  */
-#   include <utmp.h>
-#  endif
-#  if defined _THREAD_SAFE && defined UTMP_DATA_INIT
+#  include <utmp.h>
+# endif
+# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
     /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
        defined, work around the duplicate struct utmp_data declaration.  */
-#   define utmp_data gl_aix_4_3_workaround_utmp_data
-#  endif
-#  include <utmpx.h>
-#  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
-#   define UTMP_NAME_FUNCTION utmpxname
-#  elif defined UTXDB_ACTIVE
-#   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)
-#  else
-#   if 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
-#  endif
-
-#  if HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT
-#   define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit)
-#  else
-#   if 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
-#  endif
-
-# elif HAVE_UTMP_H
+#  define utmp_data gl_aix_4_3_workaround_utmp_data
+# endif
+# include <utmpx.h>
+# 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
+#  define UTMP_NAME_FUNCTION utmpxname
+# elif defined UTXDB_ACTIVE
+#  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:
 
@@ -154,37 +150,37 @@ struct gl_utmp
    ⎣ ut_addr_v6   [u]int[4]                  glibc, musl, Android
  */
 
-#  include <utmp.h>
-#  if !HAVE_DECL_GETUTENT
+# include <utmp.h>
+# 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
-#   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
+# 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
+#  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
+
+#else
 
 /* Provide a dummy fallback.  */
 
 /* Get 'struct timeval'.  */
-#  include <sys/time.h>
+# include <sys/time.h>
 
 struct gl_utmp
 {
@@ -192,178 +188,178 @@ struct gl_utmp
   char ut_line[1];
   struct timeval ut_tv;
 };
-#  define UTMP_STRUCT_NAME gl_utmp
-#  define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
-#  define UT_EXIT_E_TERMINATION(UT) 0
-#  define UT_EXIT_E_EXIT(UT) 0
+# define UTMP_STRUCT_NAME gl_utmp
+# define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
+# define UT_EXIT_E_TERMINATION(UT) 0
+# define UT_EXIT_E_EXIT(UT) 0
 
-# endif
+#endif
 
 /* Accessor macro for the member named ut_user or ut_name.  */
-# if READUTMP_USE_SYSTEMD
+#if READUTMP_USE_SYSTEMD
 
-#  define UT_USER(UT) ((UT)->ut_user)
+# define UT_USER(UT) ((UT)->ut_user)
 
-# elif HAVE_UTMPX_H
+#elif HAVE_UTMPX_H
 
-#  if HAVE_STRUCT_UTMPX_UT_USER
-#   define UT_USER(UT) ((UT)->ut_user)
-#  endif
-#  if HAVE_STRUCT_UTMPX_UT_NAME
-#   undef UT_USER
-#   define UT_USER(UT) ((UT)->ut_name)
-#  endif
+# if HAVE_STRUCT_UTMPX_UT_USER
+#  define UT_USER(UT) ((UT)->ut_user)
+# endif
+# if HAVE_STRUCT_UTMPX_UT_NAME
+#  undef UT_USER
+#  define UT_USER(UT) ((UT)->ut_name)
+# endif
 
-# elif HAVE_UTMP_H
+#elif HAVE_UTMP_H
 
-#  if HAVE_STRUCT_UTMP_UT_USER
-#   define UT_USER(UT) ((UT)->ut_user)
-#  endif
-#  if HAVE_STRUCT_UTMP_UT_NAME
-#   undef UT_USER
-#   define UT_USER(UT) ((UT)->ut_name)
-#  endif
+# if HAVE_STRUCT_UTMP_UT_USER
+#  define UT_USER(UT) ((UT)->ut_user)
+# endif
+# if HAVE_STRUCT_UTMP_UT_NAME
+#  undef UT_USER
+#  define UT_USER(UT) ((UT)->ut_name)
+# endif
 
-# else /* dummy fallback */
+#else /* dummy fallback */
 
-#  define UT_USER(UT) ((UT)->ut_user)
+# define UT_USER(UT) ((UT)->ut_user)
 
-# endif
+#endif
 
-# if READUTMP_USE_SYSTEMD
-#  define HAVE_STRUCT_XTMP_UT_EXIT 0
-# else
-#  define HAVE_STRUCT_XTMP_UT_EXIT \
+#if READUTMP_USE_SYSTEMD
+# define HAVE_STRUCT_XTMP_UT_EXIT 0
+#else
+# define HAVE_STRUCT_XTMP_UT_EXIT \
      (HAVE_STRUCT_UTMP_UT_EXIT \
       || HAVE_STRUCT_UTMPX_UT_EXIT)
-# endif
+#endif
 
-# if READUTMP_USE_SYSTEMD
-#  define HAVE_STRUCT_XTMP_UT_ID 1
-# else
-#  define HAVE_STRUCT_XTMP_UT_ID \
+#if READUTMP_USE_SYSTEMD
+# define HAVE_STRUCT_XTMP_UT_ID 1
+#else
+# define HAVE_STRUCT_XTMP_UT_ID \
      (HAVE_STRUCT_UTMP_UT_ID \
       || HAVE_STRUCT_UTMPX_UT_ID)
-# endif
+#endif
 
-# if READUTMP_USE_SYSTEMD
-#  define HAVE_STRUCT_XTMP_UT_PID 1
-# else
-#  define HAVE_STRUCT_XTMP_UT_PID \
+#if READUTMP_USE_SYSTEMD
+# define HAVE_STRUCT_XTMP_UT_PID 1
+#else
+# define HAVE_STRUCT_XTMP_UT_PID \
      (HAVE_STRUCT_UTMP_UT_PID \
       || HAVE_STRUCT_UTMPX_UT_PID)
-# endif
+#endif
 
-# if READUTMP_USE_SYSTEMD
-#  define HAVE_STRUCT_XTMP_UT_HOST 1
-# else
-#  define HAVE_STRUCT_XTMP_UT_HOST \
+#if READUTMP_USE_SYSTEMD
+# define HAVE_STRUCT_XTMP_UT_HOST 1
+#else
+# define HAVE_STRUCT_XTMP_UT_HOST \
      (HAVE_STRUCT_UTMP_UT_HOST \
       || HAVE_STRUCT_UTMPX_UT_HOST)
-# endif
+#endif
 
 /* 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 READUTMP_USE_SYSTEMD
+#if READUTMP_USE_SYSTEMD
 enum { UT_USER_SIZE = -1 };
-# else
+#else
 enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) };
-#  define UT_USER_SIZE UT_USER_SIZE
-# endif
+# define UT_USER_SIZE UT_USER_SIZE
+#endif
 
 /* Size of the ut->ut_id member, or -1 if unbounded.  */
-# if READUTMP_USE_SYSTEMD
+#if READUTMP_USE_SYSTEMD
 enum { UT_ID_SIZE = -1 };
-# else
+#else
 enum { UT_ID_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_id) };
-#  define UT_ID_SIZE UT_ID_SIZE
-# endif
+# define UT_ID_SIZE UT_ID_SIZE
+#endif
 
 /* Size of the ut->ut_line member, or -1 if unbounded.  */
-# if READUTMP_USE_SYSTEMD
+#if READUTMP_USE_SYSTEMD
 enum { UT_LINE_SIZE = -1 };
-# else
+#else
 enum { UT_LINE_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_line) };
-#  define UT_LINE_SIZE UT_LINE_SIZE
-# endif
+# define UT_LINE_SIZE UT_LINE_SIZE
+#endif
 
 /* Size of the ut->ut_host member, or -1 if unbounded.  */
-# if READUTMP_USE_SYSTEMD
+#if READUTMP_USE_SYSTEMD
 enum { UT_HOST_SIZE = -1 };
-# else
+#else
 enum { UT_HOST_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_host) };
-#  define UT_HOST_SIZE UT_HOST_SIZE
-# endif
+# define UT_HOST_SIZE UT_HOST_SIZE
+#endif
 
 /* Definition of UTMP_FILE and WTMP_FILE.  */
 
-# if !defined UTMP_FILE && defined _PATH_UTMP
-#  define UTMP_FILE _PATH_UTMP
-# endif
+#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
+#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
+#ifdef UTMPX_FILE /* Solaris, SysVr4 */
+# undef UTMP_FILE
+# define UTMP_FILE UTMPX_FILE
+#endif
 
-# ifdef WTMPX_FILE /* Solaris, SysVr4 */
-#  undef WTMP_FILE
-#  define WTMP_FILE WTMPX_FILE
-# 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 UTMP_FILE
+# define UTMP_FILE "/etc/utmp"
+#endif
 
-# ifndef WTMP_FILE
-#  define WTMP_FILE "/etc/wtmp"
-# 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
+#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 READUTMP_USE_SYSTEMD || 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
-
-# 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
-#  define UT_TYPE_USER_PROCESS(UT) 0
-# endif
+#if READUTMP_USE_SYSTEMD || 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
+
+#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
+# define UT_TYPE_USER_PROCESS(UT) 0
+#endif
 
 /* Determines whether an entry *UT corresponds to a user process.  */
-# define IS_USER_PROCESS(UT)                                    \
+#define IS_USER_PROCESS(UT)                                    \
    (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
-#  define READ_UTMP_SUPPORTED 1
-# endif
+#if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H
+# define READ_UTMP_SUPPORTED 1
+#endif
 
 /* Options for read_utmp.  */
 enum
-- 
2.39.2

From c0a858b24839d69858a5418f30971fd355b42fc4 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 16:01:50 -0700
Subject: [PATCH 6/9] readutmp: switch new struct to struct timespec

* lib/readutmp.c (get_boot_time_uncached, get_boot_time)
(add_utmp, read_utmp):
Use struct timespec, not struct timeval.
* lib/readutmp.h: Always include <time.h>, for struct timespec.
Simplify when utmp.h and utmpx.h are included.
(struct gl_utmp): Use the same struct for both the
systemd and the dummy version.  Reorder members, and
use proper pid_t type for ut_session.  Rename ut_tv to ut_ts
and make it a struct timespec.  All uses changed.
(HAVE_GL_UTMP): New macro.  Use it where appropriate, instead
of READUTMP_USE_SYSTEMD.
(UT_USER, HAVE_STRUCT_XTMP_UT_EXIT, HAVE_STRUCT_XTMP_UT_ID)
(HAVE_STRUCT_XTMP_UT_PID, HAVE_STRUCT_XTMP_UT_HOST):
Simplify.
* modules/readutmp (Depends-on): Add time-h, timespec_get.
Remove sys_type.  Sort.
---
 ChangeLog        |  18 ++++++
 lib/readutmp.c   |  69 +++++++++++------------
 lib/readutmp.h   | 141 ++++++++++++++---------------------------------
 modules/readutmp |   7 ++-
 4 files changed, 93 insertions(+), 142 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 44b80d8c61..133f49df97 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
 2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
 
+	readutmp: switch new struct to struct timespec
+	* lib/readutmp.c (get_boot_time_uncached, get_boot_time)
+	(add_utmp, read_utmp):
+	Use struct timespec, not struct timeval.
+	* lib/readutmp.h: Always include <time.h>, for struct timespec.
+	Simplify when utmp.h and utmpx.h are included.
+	(struct gl_utmp): Use the same struct for both the
+	systemd and the dummy version.  Reorder members, and
+	use proper pid_t type for ut_session.  Rename ut_tv to ut_ts
+	and make it a struct timespec.  All uses changed.
+	(HAVE_GL_UTMP): New macro.  Use it where appropriate, instead
+	of READUTMP_USE_SYSTEMD.
+	(UT_USER, HAVE_STRUCT_XTMP_UT_EXIT, HAVE_STRUCT_XTMP_UT_ID)
+	(HAVE_STRUCT_XTMP_UT_PID, HAVE_STRUCT_XTMP_UT_HOST):
+	Simplify.
+	* modules/readutmp (Depends-on): Add time-h, timespec_get.
+	Remove sys_type.  Sort.
+
 	readutmp: fix # indentation
 	* lib/readutmp.h: Change # indentation to standard Gnulib style.
 
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 97e92e69af..113382c636 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -93,7 +93,7 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
 # if READUTMP_USE_SYSTEMD
 /* Use systemd and Linux /proc and kernel APIs.  */
 
-static struct timeval
+static struct timespec
 get_boot_time_uncached (void)
 {
   /* /proc/uptime contains the uptime with a resolution of 0.01 sec.  */
@@ -111,19 +111,22 @@ get_boot_time_uncached (void)
           double uptime = strtod (buf, &endptr);
           if (endptr > buf)
             {
-              struct timeval result;
-              if (gettimeofday (&result, NULL) >= 0)
+              struct timespec result;
+              if (0 <= timespec_get (&result, TIME_UTC))
                 {
-                  long uptime_sec = (long) uptime;
-                  int uptime_usec =
-                    (int) ((uptime - (double) uptime_sec) * 1000000.0 + 0.5);
-                  if (result.tv_usec < uptime_usec)
+                  time_t uptime_sec = uptime;
+                  struct timespec up =
                     {
-                      result.tv_usec += 1000000;
+                      .tv_sec = uptime_sec,
+                      .tv_nsec = (uptime - uptime_sec) * 1e9 + 0.5
+                    };
+                  if (result.tv_nsec < up.tv_nsec)
+                    {
+                      result.tv_nsec += 1000000000;
                       result.tv_sec -= 1;
                     }
-                  result.tv_sec -= uptime_sec;
-                  result.tv_usec -= uptime_usec;
+                  result.tv_sec -= up.tv_sec;
+                  result.tv_nsec -= up.tv_nsec;
                   return result;
                 }
             }
@@ -134,8 +137,8 @@ get_boot_time_uncached (void)
   struct sysinfo info;
   if (sysinfo (&info) >= 0)
     {
-      struct timeval result;
-      if (gettimeofday (&result, NULL) >= 0)
+      struct timespec result;
+      if (0 <= timespec_get (&result, TIME_UTC))
         {
           result.tv_sec -= info.uptime;
           return result;
@@ -143,19 +146,19 @@ get_boot_time_uncached (void)
     }
 
   /* We shouldn't get here.  */
-  return (struct timeval) { .tv_sec = 0, .tv_usec = 0 };
+  return (struct timespec) {0};
 }
 
-static struct timeval
+static struct timespec
 get_boot_time (void)
 {
-  static int cached;
-  static struct timeval boot_time;
+  static bool cached;
+  static struct timespec boot_time;
 
   if (!cached)
     {
+      cached = true;
       boot_time = get_boot_time_uncached ();
-      cached = 1;
     }
   return boot_time;
 }
@@ -252,7 +255,7 @@ struct utmp_alloc
 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 timeval t, char const *host, long session)
+          short type, struct timespec ts, char const *host, long session)
 {
   if (!user) user = "";
   if (!host) host = "";
@@ -277,11 +280,11 @@ add_utmp (struct utmp_alloc a, int options,
   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_pid = pid;
-  ut->ut_type = type;
-  ut->ut_tv = t;
   ut->ut_host = memcpy (p - hostsize, line, 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
@@ -323,9 +326,9 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
           uint64_t start_usec;
           if (sd_session_get_start_time (session, &start_usec) < 0)
             start_usec = 0;
-          struct timeval start_tv;
-          start_tv.tv_sec = start_usec / 1000000;
-          start_tv.tv_usec = start_usec % 1000000;
+          struct timespec start_ts;
+          start_ts.tv_sec = start_usec / 1000000;
+          start_ts.tv_nsec = start_usec % 1000000 * 1000;
 
           char *seat;
           if (sd_session_get_seat (session, &seat) < 0)
@@ -346,19 +349,9 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
                   if (sd_session_get_service (session, &service) < 0)
                     service = NULL;
 
-                  char *pty;
                   uid_t uid;
-                  if (sd_session_get_uid (session, &uid) >= 0)
-                    {
-                      struct timespec start_ts =
-                        {
-                          .tv_sec = start_tv.tv_sec,
-                          .tv_nsec = start_tv.tv_usec * 1000
-                        };
-                      pty = guess_pty_name (uid, start_ts);
-                    }
-                  else
-                    pty = NULL;
+                  char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL
+                               : guess_pty_name (uid, start_ts));
 
                   if (service != NULL && pty != NULL)
                     {
@@ -422,11 +415,11 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
               if (seat != NULL)
                 a = add_utmp (a, options, user, session, seat,
                               leader_pid /* the best we have */,
-                              USER_PROCESS, start_tv, host, leader_pid);
+                              USER_PROCESS, start_ts, host, leader_pid);
               if (tty != NULL)
                 a = add_utmp (a, options, user, session, tty,
                               leader_pid /* the best we have */,
-                              USER_PROCESS, start_tv, host, leader_pid);
+                              USER_PROCESS, start_ts, host, leader_pid);
 
               free (host);
               free (user);
diff --git a/lib/readutmp.h b/lib/readutmp.h
index da7225152c..b3cfaeb67c 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -31,6 +31,7 @@
 
 #include <stdlib.h>
 #include <sys/types.h>
+#include <time.h>
 
 /* AIX 4.3.3 has both utmp.h and utmpx.h, but only struct utmp
    has the ut_exit member.  */
@@ -39,30 +40,39 @@
 # undef HAVE_UTMPX_H
 #endif
 
-#if READUTMP_USE_SYSTEMD
+/* HPUX 10.20 needs utmp.h, for the definition of e.g., UTMP_FILE.  */
+#if HAVE_UTMP_H
+# include <utmp.h>
+#endif
 
-/* Get 'struct timeval'.  */
-# include <sys/time.h>
+/* Needed for BOOT_TIME and USER_PROCESS.  */
+#if HAVE_UTMPX_H
+# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
+    /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
+       defined, work around the duplicate struct utmp_data declaration.  */
+#  define utmp_data gl_aix_4_3_workaround_utmp_data
+# endif
+# include <utmpx.h>
+#endif
+
+#if READUTMP_USE_SYSTEMD || ! (HAVE_UTMPX_H || HAVE_UTMP_H)
 
-/* Type for the entries returned by read_utmp.  */
 struct gl_utmp
 {
   /* All 'char *' here are of arbitrary length and malloc-allocated.  */
   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 */
+  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 timeval ut_tv;         /* time */
-  char *ut_host;                /* for remote sessions: user@host or host */
-  long ut_session;              /* process ID of session leader */
 };
 
-/* Get values for ut_type: BOOT_TIME, USER_PROCESS.  */
-# include <utmpx.h>
-
+# define HAVE_GL_UTMP 1
 # define UTMP_STRUCT_NAME gl_utmp
-# define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
+# define UT_TIME_MEMBER(UT) ((UT)->ut_ts.tv_sec)
 # define UT_EXIT_E_TERMINATION(UT) 0
 # define UT_EXIT_E_EXIT(UT) 0
 
@@ -90,16 +100,6 @@ struct gl_utmp
    ⎣ ut_ss        struct sockaddr_storage    NetBSD, Minix
  */
 
-# if HAVE_UTMP_H
-    /* HPUX 10.20 needs utmp.h, for the definition of e.g., UTMP_FILE.  */
-#  include <utmp.h>
-# endif
-# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
-    /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
-       defined, work around the duplicate struct utmp_data declaration.  */
-#  define utmp_data gl_aix_4_3_workaround_utmp_data
-# endif
-# include <utmpx.h>
 # define UTMP_STRUCT_NAME utmpx
 # define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
 # define SET_UTMP_ENT setutxent
@@ -150,12 +150,11 @@ struct gl_utmp
    ⎣ ut_addr_v6   [u]int[4]                  glibc, musl, Android
  */
 
-# include <utmp.h>
 # if !HAVE_DECL_GETUTENT
     struct utmp *getutent (void);
 # endif
 # define UTMP_STRUCT_NAME utmp
-# define UT_TIME_MEMBER(UT) ((UT)->ut_time)
+# define UT_TIME_MEMBER(UT) ((UT)->ut_ts.tv_sec)
 # define SET_UTMP_ENT setutent
 # define GET_UTMP_ENT getutent
 # define END_UTMP_ENT endutent
@@ -175,94 +174,34 @@ struct gl_utmp
 #  define UT_EXIT_E_EXIT(UT) 0
 # endif
 
-#else
-
-/* Provide a dummy fallback.  */
-
-/* Get 'struct timeval'.  */
-# include <sys/time.h>
-
-struct gl_utmp
-{
-  char ut_user[1];
-  char ut_line[1];
-  struct timeval ut_tv;
-};
-# define UTMP_STRUCT_NAME gl_utmp
-# define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
-# define UT_EXIT_E_TERMINATION(UT) 0
-# define UT_EXIT_E_EXIT(UT) 0
-
 #endif
 
 /* Accessor macro for the member named ut_user or ut_name.  */
-#if READUTMP_USE_SYSTEMD
-
-# define UT_USER(UT) ((UT)->ut_user)
-
-#elif HAVE_UTMPX_H
-
-# if HAVE_STRUCT_UTMPX_UT_USER
-#  define UT_USER(UT) ((UT)->ut_user)
-# endif
-# if HAVE_STRUCT_UTMPX_UT_NAME
-#  undef UT_USER
-#  define UT_USER(UT) ((UT)->ut_name)
-# endif
-
-#elif HAVE_UTMP_H
-
-# if HAVE_STRUCT_UTMP_UT_USER
-#  define UT_USER(UT) ((UT)->ut_user)
-# endif
-# if HAVE_STRUCT_UTMP_UT_NAME
-#  undef UT_USER
-#  define UT_USER(UT) ((UT)->ut_name)
-# endif
-
-#else /* dummy fallback */
-
+#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
 
-#if READUTMP_USE_SYSTEMD
-# define HAVE_STRUCT_XTMP_UT_EXIT 0
-#else
-# define HAVE_STRUCT_XTMP_UT_EXIT \
-     (HAVE_STRUCT_UTMP_UT_EXIT \
-      || HAVE_STRUCT_UTMPX_UT_EXIT)
-#endif
+#define HAVE_STRUCT_XTMP_UT_EXIT \
+  (!HAVE_GL_UTMP && (HAVE_STRUCT_UTMP_UT_EXIT || HAVE_STRUCT_UTMPX_UT_EXIT)
 
-#if READUTMP_USE_SYSTEMD
-# define HAVE_STRUCT_XTMP_UT_ID 1
-#else
-# define HAVE_STRUCT_XTMP_UT_ID \
-     (HAVE_STRUCT_UTMP_UT_ID \
-      || HAVE_STRUCT_UTMPX_UT_ID)
-#endif
+#define HAVE_STRUCT_XTMP_UT_ID \
+  (HAVE_GL_UTMP || HAVE_STRUCT_UTMP_UT_ID || HAVE_STRUCT_UTMPX_UT_ID)
 
-#if READUTMP_USE_SYSTEMD
-# define HAVE_STRUCT_XTMP_UT_PID 1
-#else
-# define HAVE_STRUCT_XTMP_UT_PID \
-     (HAVE_STRUCT_UTMP_UT_PID \
-      || HAVE_STRUCT_UTMPX_UT_PID)
-#endif
+#define HAVE_STRUCT_XTMP_UT_PID \
+  (HAVE_GL_UTMP || HAVE_STRUCT_UTMP_UT_PID || HAVE_STRUCT_UTMPX_UT_PID)
 
-#if READUTMP_USE_SYSTEMD
-# define HAVE_STRUCT_XTMP_UT_HOST 1
-#else
-# define HAVE_STRUCT_XTMP_UT_HOST \
-     (HAVE_STRUCT_UTMP_UT_HOST \
-      || HAVE_STRUCT_UTMPX_UT_HOST)
-#endif
+#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 READUTMP_USE_SYSTEMD
+#if HAVE_GL_UTMP
 enum { UT_USER_SIZE = -1 };
 #else
 enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) };
@@ -270,7 +209,7 @@ enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) };
 #endif
 
 /* Size of the ut->ut_id member, or -1 if unbounded.  */
-#if READUTMP_USE_SYSTEMD
+#if HAVE_GL_UTMP
 enum { UT_ID_SIZE = -1 };
 #else
 enum { UT_ID_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_id) };
@@ -278,7 +217,7 @@ enum { UT_ID_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_id) };
 #endif
 
 /* Size of the ut->ut_line member, or -1 if unbounded.  */
-#if READUTMP_USE_SYSTEMD
+#if HAVE_GL_UTMP
 enum { UT_LINE_SIZE = -1 };
 #else
 enum { UT_LINE_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_line) };
@@ -286,7 +225,7 @@ enum { UT_LINE_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_line) };
 #endif
 
 /* Size of the ut->ut_host member, or -1 if unbounded.  */
-#if READUTMP_USE_SYSTEMD
+#if HAVE_GL_UTMP
 enum { UT_HOST_SIZE = -1 };
 #else
 enum { UT_HOST_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_host) };
@@ -330,7 +269,7 @@ enum { UT_HOST_SIZE = sizeof (((STRUCT_UTMP *) 0)->ut_host) };
 
 /* Accessor macros for the member named ut_type.  */
 
-#if READUTMP_USE_SYSTEMD || HAVE_STRUCT_UTMP_UT_TYPE || HAVE_STRUCT_UTMPX_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
diff --git a/modules/readutmp b/modules/readutmp
index 534ce4fa19..15c63a3d5e 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -9,14 +9,15 @@ m4/systemd.m4
 
 Depends-on:
 extensions
+fopen-gnu
 idx
-xalloc
 stdbool
 stdint
 strnlen
-sys_time
-fopen-gnu
+time-h
+timespec_get
 unlocked-io-internal
+xalloc
 
 configure.ac:
 gl_READUTMP
-- 
2.39.2

From 9765d190a95f379c1b690ccbf6f3cb1a62f1c803 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 16:01:52 -0700
Subject: [PATCH 7/9] readutmp: systemd supports only UTMP_FILE

* lib/readutmp.c (read_utmp): Fail if not UTMP_FILE.
* m4/systemd.m4 (gl_SYSTEMD_CHOICE): Default to no for now,
since yes means "who /var/log/wtmp" stops working.
---
 ChangeLog      | 5 +++++
 lib/readutmp.c | 7 +++++++
 m4/systemd.m4  | 6 +++---
 3 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 133f49df97..8730fd9bc8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2023-08-03  Paul Eggert  <egg...@cs.ucla.edu>
 
+	readutmp: systemd supports only UTMP_FILE
+	* lib/readutmp.c (read_utmp): Fail if not UTMP_FILE.
+	* m4/systemd.m4 (gl_SYSTEMD_CHOICE): Default to no for now,
+	since yes means "who /var/log/wtmp" stops working.
+
 	readutmp: switch new struct to struct timespec
 	* lib/readutmp.c (get_boot_time_uncached, get_boot_time)
 	(add_utmp, read_utmp):
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 113382c636..c692b3854d 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -305,6 +305,13 @@ int
 read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
+  /* The current implementation can imitate only UTMP_FILE.  */
+  if (strcmp (file, UTMP_FILE) != 0)
+    {
+      errno = ENOTSUP;
+      return -1;
+    }
+
   /* Fill entries, simulating what a utmp file would contain.  */
   struct utmp_alloc a = {0};
 
diff --git a/m4/systemd.m4 b/m4/systemd.m4
index 0c919385b2..c54e2fb47f 100644
--- a/m4/systemd.m4
+++ b/m4/systemd.m4
@@ -1,4 +1,4 @@
-# systemd.m4 serial 1
+# systemd.m4 serial 2
 dnl Copyright (C) 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,
@@ -10,9 +10,9 @@ AC_DEFUN([gl_SYSTEMD_CHOICE],
 [
   AC_MSG_CHECKING([whether to use systemd APIs])
   AC_ARG_ENABLE([systemd],
-    [  --disable-systemd       do not use systemd APIs],
+    [  --enable-systemd       use systemd APIs],
     [SYSTEMD_CHOICE="$enableval"],
-    [SYSTEMD_CHOICE=yes])
+    [SYSTEMD_CHOICE=no])
   AC_MSG_RESULT([$SYSTEMD_CHOICE])
   AC_SUBST([SYSTEMD_CHOICE])
 ])
-- 
2.39.2

From 8edf2ae3fcf7a20c28142dfada145f4a89808a05 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 18:27:35 -0700
Subject: [PATCH 8/9] readutmp: fix comments

---
 NEWS           | 5 ++++-
 lib/readutmp.h | 3 ++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index f1c9b72aa9..265f9788bc 100644
--- a/NEWS
+++ b/NEWS
@@ -74,8 +74,11 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
-2023-08-02  readutmp        Some STRUCT_UTMP members can be char *,
+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
+                            struct timespec, not ut_tv of type struct timeval,
+                            and ut_session is pid_t not long.
                             read_utmp's 2nd arg is now idx_t * not size_t *.
                             Link additionally with $(READUTMP_LIB).
 
diff --git a/lib/readutmp.h b/lib/readutmp.h
index b3cfaeb67c..01964d2622 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -59,7 +59,8 @@
 
 struct gl_utmp
 {
-  /* All 'char *' here are of arbitrary length and malloc-allocated.  */
+  /* All 'char *' here are of arbitrary length and point to storage
+     with lifetime equal to that of this struct.  */
   char *ut_user;                /* User name */
   char *ut_id;                  /* Session ID */
   char *ut_line;                /* seat / device */
-- 
2.39.2

From f6aa86ea62cd60141e918142bf3ba9fda8ee2463 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 18:41:52 -0700
Subject: [PATCH 9/9] * m4/systemd.m4: Fix --help lineup.

---
 m4/systemd.m4 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/m4/systemd.m4 b/m4/systemd.m4
index c54e2fb47f..4a0931f0f2 100644
--- a/m4/systemd.m4
+++ b/m4/systemd.m4
@@ -1,4 +1,4 @@
-# systemd.m4 serial 2
+# systemd.m4 serial 3
 dnl Copyright (C) 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,
@@ -10,7 +10,7 @@ AC_DEFUN([gl_SYSTEMD_CHOICE],
 [
   AC_MSG_CHECKING([whether to use systemd APIs])
   AC_ARG_ENABLE([systemd],
-    [  --enable-systemd       use systemd APIs],
+    [  --enable-systemd        use systemd APIs],
     [SYSTEMD_CHOICE="$enableval"],
     [SYSTEMD_CHOICE=no])
   AC_MSG_RESULT([$SYSTEMD_CHOICE])
-- 
2.39.2

From 0e58c95000114f246db1e44507f0b1dd17d22a1a Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 3 Aug 2023 18:35:29 -0700
Subject: [PATCH] maint: Update after gnulib module 'readutmp' changed
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

(This patch is coauthored with Bruno Haible,
with original version at <https://bugs.gnu.org/64937#>.)
This updates the gnulib submodule to latest.
For year-2038 safety on Linux/{x86,arm},
this adds an --enable-systemd option to ‘configure’.
The idea is that this sort of thing will become the default
after it has been tested more.
* configure.ac: Don't test whether struct utmp and struct utmpx
have the ut_host field; this is now done in gnulib's readutmp module.
* src/local.mk: Link the programs 'pinky', 'uptime', 'users',
'who' with $(READUTMP_LIB).
* src/pinky.c, src/who.c:
Test HAVE_STRUCT_XTMP_UT_HOST instead of HAVE_UT_HOST.
* src/pinky.c (print_entry):
* src/who.c (print_user, print_deadprocs, print_login)
(print_initspawn, scan_entries):
Support the situation where ut_line is a 'char *' rather than a
'char[]' of fixed size.  Likewise for ut_user and ut_host.
(make_id_equals_comment): Likewise for ut_id.
* src/pinky.c (print_entry):
* src/who.c (print_user):
Open /dev to simplify looking up its entries.
Don’t use printf if the output might in theory be longer than INT_MAX.
* src/pinky.c (scan_entries, short_pinky):
* src/uptime.c (print_uptime, uptime):
* src/users.c (list_entries_users, users):
* src/who.c (who):
Use idx_t where new read_utmp needs it.
* src/system.h (STREQ_LEN): Add comment that last arg can be -1.
---
 NEWS         |   4 ++
 configure.ac |  30 ------------
 gnulib       |   2 +-
 src/local.mk |   6 +++
 src/pinky.c  |  95 +++++++++++++++++++++++++-------------
 src/system.h |   2 +-
 src/uptime.c |   4 +-
 src/users.c  |  10 ++--
 src/who.c    | 127 ++++++++++++++++++++++++++++++---------------------
 9 files changed, 159 insertions(+), 121 deletions(-)

diff --git a/NEWS b/NEWS
index 2b8f984ba..53a342a5e 100644
--- a/NEWS
+++ b/NEWS
@@ -41,6 +41,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
   pinky, uptime, users, and who no longer misbehave on 32-bit GNU/Linux
   platforms like x86 and ARM where time_t was historically 32 bits.
+  A new configure-time option --enable-systemd enables experimental
+  support for using systemd, to let these programs continue to work on
+  these old platforms even after the year 2038, so long as systemd
+  is also installed.
   [bug introduced in coreutils-9.0]
 
   'pr --length=1 --double-space' no longer enters an infinite loop.
diff --git a/configure.ac b/configure.ac
index 33441a82f..afc1098f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -406,36 +406,6 @@ AC_DEFUN([coreutils_DUMMY_1],
 ])
 coreutils_DUMMY_1
 
-AC_MSG_CHECKING([ut_host in struct utmp])
-AC_CACHE_VAL([su_cv_func_ut_host_in_utmp],
-[AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
-                                   #include <utmp.h>
-                                   struct utmp ut;
-                                   int s = sizeof ut.ut_host;]])],
-  [su_cv_func_ut_host_in_utmp=yes],
-  [su_cv_func_ut_host_in_utmp=no])])
-AC_MSG_RESULT([$su_cv_func_ut_host_in_utmp])
-if test $su_cv_func_ut_host_in_utmp = yes; then
-  have_ut_host=1
-  AC_DEFINE([HAVE_UT_HOST], [1], [FIXME])
-fi
-
-if test -z "$have_ut_host"; then
-  AC_MSG_CHECKING([ut_host in struct utmpx])
-  AC_CACHE_VAL([su_cv_func_ut_host_in_utmpx],
-  [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
-                                     #include <utmpx.h>
-                                     struct utmpx ut;
-                                     int s = sizeof ut.ut_host;]])],
-    [su_cv_func_ut_host_in_utmpx=yes],
-    [su_cv_func_ut_host_in_utmpx=no])])
-  AC_MSG_RESULT([$su_cv_func_ut_host_in_utmpx])
-  if test $su_cv_func_ut_host_in_utmpx = yes; then
-    AC_DEFINE([HAVE_UTMPX_H], [1], [FIXME])
-    AC_DEFINE([HAVE_UT_HOST], [1], [FIXME])
-  fi
-fi
-
 GNULIB_BOOT_TIME([gl_ADD_PROG([optional_bin_progs], [uptime])])
 
 AC_SYS_POSIX_TERMIOS()
diff --git a/gnulib b/gnulib
index 0e7af0cff..f6aa86ea6 160000
--- a/gnulib
+++ b/gnulib
@@ -1 +1 @@
-Subproject commit 0e7af0cff0011f25f5f842af5216325923017b34
+Subproject commit f6aa86ea62cd60141e918142bf3ba9fda8ee2463
diff --git a/src/local.mk b/src/local.mk
index cb9b39274..4d7df2789 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -317,6 +317,12 @@ src_who_LDADD += $(GETADDRINFO_LIB)
 src_hostname_LDADD += $(GETHOSTNAME_LIB)
 src_uname_LDADD += $(GETHOSTNAME_LIB)
 
+# for read_utmp
+src_pinky_LDADD += $(READUTMP_LIB)
+src_uptime_LDADD += $(READUTMP_LIB)
+src_users_LDADD += $(READUTMP_LIB)
+src_who_LDADD += $(READUTMP_LIB)
+
 # for strsignal
 src_kill_LDADD += $(LIBTHREAD)
 
diff --git a/src/pinky.c b/src/pinky.c
index b6d411c85..38ceccbea 100644
--- a/src/pinky.c
+++ b/src/pinky.c
@@ -62,7 +62,7 @@ static bool include_home_and_shell = true;
 static bool do_short_format = true;
 
 /* if true, display the ut_host field. */
-#ifdef HAVE_UT_HOST
+#if HAVE_STRUCT_XTMP_UT_HOST
 static bool include_where = true;
 #endif
 
@@ -203,20 +203,32 @@ print_entry (const STRUCT_UTMP *utmp_ent)
   time_t last_change;
   char mesg;
 
-#define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
-#define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
-
-  char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
-  char *p = line;
+#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
 
-  /* Copy ut_line into LINE, prepending '/dev/' if ut_line is not
-     already an absolute file name.  Some system may put the full,
-     absolute file name in ut_line.  */
-  if ( ! IS_ABSOLUTE_FILE_NAME (utmp_ent->ut_line))
-    p = stpcpy (p, DEV_DIR_WITH_TRAILING_SLASH);
-  stzncpy (p, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
+  int dirfd;
+  if (IS_ABSOLUTE_FILE_NAME (line))
+    dirfd = AT_FDCWD;
+  else
+    {
+      static int dev_dirfd;
+      if (!dev_dirfd)
+        {
+          dev_dirfd = open ("/dev", O_PATHSEARCH | O_DIRECTORY);
+          if (dev_dirfd < 0)
+            dev_dirfd = AT_FDCWD - 1;
+        }
+      dirfd = dev_dirfd;
+    }
 
-  if (stat (line, &stats) == 0)
+  if (AT_FDCWD <= dirfd && fstatat (dirfd, line, &stats, 0) == 0)
     {
       mesg = (stats.st_mode & S_IWGRP) ? ' ' : '*';
       last_change = stats.st_atime;
@@ -227,15 +239,20 @@ print_entry (const STRUCT_UTMP *utmp_ent)
       last_change = 0;
     }
 
-  printf ("%-8.*s", UT_USER_SIZE, UT_USER (utmp_ent));
+  if (0 <= UT_USER_SIZE || strnlen (UT_USER (utmp_ent), 8) < 8)
+    printf ("%-8.*s", UT_USER_SIZE, UT_USER (utmp_ent));
+  else
+    fputs (UT_USER (utmp_ent), stdout);
 
   if (include_fullname)
     {
-      struct passwd *pw;
+#ifdef UT_USER_SIZE
       char name[UT_USER_SIZE + 1];
-
       stzncpy (name, UT_USER (utmp_ent), UT_USER_SIZE);
-      pw = getpwnam (name);
+#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. */
         printf (" %19s", _("        ???"));
@@ -253,8 +270,12 @@ print_entry (const STRUCT_UTMP *utmp_ent)
         }
     }
 
-  printf (" %c%-8.*s",
-          mesg, (int) sizeof (utmp_ent->ut_line), utmp_ent->ut_line);
+  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);
+  else
+    fputs (utmp_ent->ut_line, stdout);
 
   if (include_idle)
     {
@@ -267,15 +288,18 @@ print_entry (const STRUCT_UTMP *utmp_ent)
 
   printf (" %s", time_string (utmp_ent));
 
-#ifdef HAVE_UT_HOST
+#ifdef HAVE_STRUCT_XTMP_UT_HOST
   if (include_where && utmp_ent->ut_host[0])
     {
-      char ut_host[sizeof (utmp_ent->ut_host) + 1];
       char *host = nullptr;
       char *display = nullptr;
 
-      /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
-      stzncpy (ut_host, utmp_ent->ut_host, sizeof (utmp_ent->ut_host));
+# 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, ':');
@@ -288,10 +312,13 @@ print_entry (const STRUCT_UTMP *utmp_ent)
       if ( ! host)
         host = ut_host;
 
+      fputc (' ', stdout);
+      fputs (host, stdout);
       if (display)
-        printf (" %s:%s", host, display);
-      else
-        printf (" %s", host);
+        {
+          fputc (':', stdout);
+          fputs (display, stdout);
+        }
 
       if (host != ut_host)
         free (host);
@@ -408,17 +435,23 @@ print_heading (void)
   if (include_idle)
     printf (" %-6s", _("Idle"));
   printf (" %-*s", time_format_width, _("When"));
-#ifdef HAVE_UT_HOST
+#ifdef HAVE_STRUCT_XTMP_UT_HOST
   if (include_where)
     printf (" %s", _("Where"));
 #endif
   putchar ('\n');
 }
 
+/* Work around <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109614>,
+   triggered by STREQ_LEN with a negative length.  */
+#if 11 <= __GNUC__
+# pragma GCC diagnostic ignored "-Wstringop-overread"
+#endif
+
 /* Display UTMP_BUF, which should have N entries. */
 
 static void
-scan_entries (size_t n, const STRUCT_UTMP *utmp_buf,
+scan_entries (idx_t n, const STRUCT_UTMP *utmp_buf,
               const int argc_names, char *const argv_names[])
 {
   if (hard_locale (LC_TIME))
@@ -461,7 +494,7 @@ static void
 short_pinky (char const *filename,
              const int argc_names, char *const argv_names[])
 {
-  size_t n_users;
+  idx_t n_users;
   STRUCT_UTMP *utmp_buf = nullptr;
 
   if (read_utmp (filename, &n_users, &utmp_buf, 0) != 0)
@@ -550,14 +583,14 @@ main (int argc, char **argv)
 
         case 'i':
           include_fullname = false;
-#ifdef HAVE_UT_HOST
+#ifdef HAVE_STRUCT_XTMP_UT_HOST
           include_where = false;
 #endif
           break;
 
         case 'q':
           include_fullname = false;
-#ifdef HAVE_UT_HOST
+#ifdef HAVE_STRUCT_XTMP_UT_HOST
           include_where = false;
 #endif
           include_idle = false;
diff --git a/src/system.h b/src/system.h
index b5ec074e7..7b736604b 100644
--- a/src/system.h
+++ b/src/system.h
@@ -192,7 +192,7 @@ select_plural (uintmax_t n)
 }
 
 #define STREQ(a, b) (strcmp (a, b) == 0)
-#define STREQ_LEN(a, b, n) (strncmp (a, b, n) == 0)
+#define STREQ_LEN(a, b, n) (strncmp (a, b, n) == 0) /* n==-1 means unbounded */
 #define STRPREFIX(a, b) (strncmp (a, b, strlen (b)) == 0)
 
 /* Just like strncmp, but the second argument must be a literal string
diff --git a/src/uptime.c b/src/uptime.c
index bdbf1451a..ad1bbb9a1 100644
--- a/src/uptime.c
+++ b/src/uptime.c
@@ -45,7 +45,7 @@
   proper_name ("Kaveh Ghazi")
 
 static void
-print_uptime (size_t n, const STRUCT_UTMP *this)
+print_uptime (idx_t n, const STRUCT_UTMP *this)
 {
   idx_t entries = 0;
   time_t boot_time = 0;
@@ -169,7 +169,7 @@ print_uptime (size_t n, const STRUCT_UTMP *this)
 static _Noreturn void
 uptime (char const *filename, int options)
 {
-  size_t n_users;
+  idx_t n_users;
   STRUCT_UTMP *utmp_buf = nullptr;
 
   if (read_utmp (filename, &n_users, &utmp_buf, options) != 0)
diff --git a/src/users.c b/src/users.c
index e14f3fc3e..353c1765d 100644
--- a/src/users.c
+++ b/src/users.c
@@ -42,11 +42,11 @@ userid_compare (const void *v_a, const void *v_b)
 }
 
 static void
-list_entries_users (size_t n, const STRUCT_UTMP *this)
+list_entries_users (idx_t n, const STRUCT_UTMP *this)
 {
-  char **u = xnmalloc (n, sizeof *u);
-  size_t i;
-  size_t n_entries = 0;
+  char **u = xinmalloc (n, sizeof *u);
+  idx_t i;
+  idx_t n_entries = 0;
 
   while (n--)
     {
@@ -82,7 +82,7 @@ list_entries_users (size_t n, const STRUCT_UTMP *this)
 static void
 users (char const *filename, int options)
 {
-  size_t n_users;
+  idx_t n_users;
   STRUCT_UTMP *utmp_buf;
 
   if (read_utmp (filename, &n_users, &utmp_buf, options) != 0)
diff --git a/src/who.c b/src/who.c
index ec0dff792..0e94d3c83 100644
--- a/src/who.c
+++ b/src/who.c
@@ -333,26 +333,38 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
   time_t last_change;
   char mesg;
   char idlestr[IDLESTR_LEN + 1];
+  PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
   static char *hoststr;
-#if HAVE_UT_HOST
-  static size_t hostlen;
+#if HAVE_STRUCT_XTMP_UT_HOST
+  static idx_t hostlen;
 #endif
 
-#define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
-#define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
-
-  char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
-  char *p = line;
-  PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
+#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
 
-  /* Copy ut_line into LINE, prepending '/dev/' if ut_line is not
-     already an absolute file name.  Some systems may put the full,
-     absolute file name in ut_line.  */
-  if ( ! IS_ABSOLUTE_FILE_NAME (utmp_ent->ut_line))
-    p = stpcpy (p, DEV_DIR_WITH_TRAILING_SLASH);
-  stzncpy (p, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
+  int dirfd;
+  if (IS_ABSOLUTE_FILE_NAME (line))
+    dirfd = AT_FDCWD;
+  else
+    {
+      static int dev_dirfd;
+      if (!dev_dirfd)
+        {
+          dev_dirfd = open ("/dev", O_PATHSEARCH | O_DIRECTORY);
+          if (dev_dirfd < 0)
+            dev_dirfd = AT_FDCWD - 1;
+        }
+      dirfd = dev_dirfd;
+    }
 
-  if (stat (line, &stats) == 0)
+  if (AT_FDCWD <= dirfd && fstatat (dirfd, line, &stats, 0) == 0)
     {
       mesg = is_tty_writable (&stats) ? '+' : '-';
       last_change = stats.st_atime;
@@ -368,15 +380,18 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
   else
     sprintf (idlestr, "  ?");
 
-#if HAVE_UT_HOST
+#if HAVE_STRUCT_XTMP_UT_HOST
   if (utmp_ent->ut_host[0])
     {
-      char ut_host[sizeof (utmp_ent->ut_host) + 1];
       char *host = nullptr;
       char *display = nullptr;
 
-      /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
-      stzncpy (ut_host, utmp_ent->ut_host, sizeof (utmp_ent->ut_host));
+# 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, ':');
@@ -394,23 +409,29 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
 
       if (display)
         {
-          if (hostlen < strlen (host) + strlen (display) + 4)
+          idx_t needed = strlen (host) + strlen (display) + 4;
+          if (hostlen < needed)
             {
-              hostlen = strlen (host) + strlen (display) + 4;
               free (hoststr);
-              hoststr = xmalloc (hostlen);
+              hoststr = xpalloc (nullptr, &hostlen, needed - hostlen, -1, 1);
             }
-          sprintf (hoststr, "(%s:%s)", host, display);
+          char *p = hoststr;
+          *p++ = '(';
+          p = stpcpy (p, host);
+          *p++ = ':';
+          strcpy (stpcpy (p, display), ")");
         }
       else
         {
-          if (hostlen < strlen (host) + 3)
+          idx_t needed = strlen (host) + 3;
+          if (hostlen < needed)
             {
-              hostlen = strlen (host) + 3;
               free (hoststr);
-              hoststr = xmalloc (hostlen);
+              hoststr = xpalloc (nullptr, &hostlen, needed - hostlen, -1, 1);
             }
-          sprintf (hoststr, "(%s)", host);
+          char *p = hoststr;
+          *p++ = '(';
+          strcpy (stpcpy (p, host), ")");
         }
 
       if (host != ut_host)
@@ -419,17 +440,13 @@ print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
   else
     {
       if (hostlen < 1)
-        {
-          hostlen = 1;
-          free (hoststr);
-          hoststr = xmalloc (hostlen);
-        }
+        hoststr = xpalloc (hoststr, &hostlen, 1, -1, 1);
       *hoststr = '\0';
     }
 #endif
 
-  print_line (sizeof UT_USER (utmp_ent), UT_USER (utmp_ent), mesg,
-              sizeof utmp_ent->ut_line, utmp_ent->ut_line,
+  print_line (UT_USER_SIZE, UT_USER (utmp_ent), mesg,
+              UT_LINE_SIZE, utmp_ent->ut_line,
               time_string (utmp_ent), idlestr, pidstr,
               hoststr ? hoststr : "", "");
 }
@@ -444,11 +461,14 @@ print_boottime (const STRUCT_UTMP *utmp_ent)
 static char *
 make_id_equals_comment (STRUCT_UTMP const *utmp_ent)
 {
-  size_t utmpsize = sizeof UT_ID (utmp_ent);
-  char *comment = xmalloc (strlen (_("id=")) + utmpsize + 1);
-
-  char *p = stpcpy (comment, _("id="));
-  stzncpy (p, UT_ID (utmp_ent), utmpsize);
+  char const *id = UT_ID (utmp_ent);
+  idx_t idlen = strnlen (id, UT_ID_SIZE);
+  char const *prefix = _("id=");
+  idx_t prefixlen = strlen (prefix);
+  char *comment = xmalloc (prefixlen + idlen + 1);
+  char *p = mempcpy (comment, prefix, prefixlen);
+  p = mempcpy (p, id, idlen);
+  *p = '\0';
   return comment;
 }
 
@@ -470,7 +490,7 @@ print_deadprocs (const STRUCT_UTMP *utmp_ent)
 
   /* FIXME: add idle time? */
 
-  print_line (-1, "", ' ', sizeof utmp_ent->ut_line, utmp_ent->ut_line,
+  print_line (-1, "", ' ', UT_LINE_SIZE, utmp_ent->ut_line,
               time_string (utmp_ent), "", pidstr, comment, exitstr);
   free (comment);
 }
@@ -483,7 +503,7 @@ print_login (const STRUCT_UTMP *utmp_ent)
 
   /* FIXME: add idle time? */
 
-  print_line (-1, _("LOGIN"), ' ', sizeof utmp_ent->ut_line, utmp_ent->ut_line,
+  print_line (-1, _("LOGIN"), ' ', UT_LINE_SIZE, utmp_ent->ut_line,
               time_string (utmp_ent), "", pidstr, comment, "");
   free (comment);
 }
@@ -494,7 +514,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, "", ' ', sizeof utmp_ent->ut_line, utmp_ent->ut_line,
+  print_line (-1, "", ' ', UT_LINE_SIZE, utmp_ent->ut_line,
               time_string (utmp_ent), "", pidstr, comment, "");
   free (comment);
 }
@@ -531,9 +551,9 @@ print_runlevel (const STRUCT_UTMP *utmp_ent)
 /* Print the username of each valid entry and the number of valid entries
    in UTMP_BUF, which should have N elements. */
 static void
-list_entries_who (size_t n, const STRUCT_UTMP *utmp_buf)
+list_entries_who (idx_t n, const STRUCT_UTMP *utmp_buf)
 {
-  unsigned long int entries = 0;
+  idx_t entries = 0;
   char const *separator = "";
 
   while (n--)
@@ -551,7 +571,7 @@ list_entries_who (size_t n, const STRUCT_UTMP *utmp_buf)
         }
       utmp_buf++;
     }
-  printf (_("\n# users=%lu\n"), entries);
+  printf (_("\n# users=%td\n"), entries);
 }
 
 static void
@@ -561,9 +581,15 @@ print_heading (void)
               _("PID"), _("COMMENT"), _("EXIT"));
 }
 
+/* Work around <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109614>,
+   triggered by STREQ_LEN with a negative length.  */
+#if 11 <= __GNUC__
+# pragma GCC diagnostic ignored "-Wstringop-overread"
+#endif
+
 /* Display UTMP_BUF, which should have N entries. */
 static void
-scan_entries (size_t n, const STRUCT_UTMP *utmp_buf)
+scan_entries (idx_t n, const STRUCT_UTMP *utmp_buf)
 {
   char *ttyname_b IF_LINT ( = nullptr);
   time_t boottime = TYPE_MINIMUM (time_t);
@@ -576,15 +602,14 @@ scan_entries (size_t n, const STRUCT_UTMP *utmp_buf)
       ttyname_b = ttyname (STDIN_FILENO);
       if (!ttyname_b)
         return;
-      if (STRNCMP_LIT (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH) == 0)
-        ttyname_b += DEV_DIR_LEN;	/* Discard /dev/ prefix.  */
+      if (STRNCMP_LIT (ttyname_b, "/dev/") == 0)
+        ttyname_b += sizeof "/dev/" - 1;	/* Discard /dev/ prefix.  */
     }
 
   while (n--)
     {
       if (!my_line_only
-          || STREQ_LEN (ttyname_b, utmp_buf->ut_line,
-                        sizeof (utmp_buf->ut_line)))
+          || STREQ_LEN (ttyname_b, utmp_buf->ut_line, UT_LINE_SIZE))
         {
           if (need_users && IS_USER_PROCESS (utmp_buf))
             print_user (utmp_buf, boottime);
@@ -617,7 +642,7 @@ scan_entries (size_t n, const STRUCT_UTMP *utmp_buf)
 static void
 who (char const *filename, int options)
 {
-  size_t n_users;
+  idx_t n_users;
   STRUCT_UTMP *utmp_buf;
 
   if (read_utmp (filename, &n_users, &utmp_buf, options) != 0)
-- 
2.39.2

Reply via email to