Thorsten Kukuk wrote:
> If you haven't seen yet, I made some time ago a mapping
> between utmp struct entries and libsystemd functions: 
> https://github.com/thkukuk/utmpx/blob/main/utmp-to-logind.md

Thanks. This is helpful.

The only problem with this mapping is for the ut_line row, which
used to contain, for inbound ssh, the pty device name. It is not easy
to find the pty name in this situation; at least, none of the systemd
APIs and /run/systemd/** files that I looked at helped.

> But be aware:
> 1. you need to extend ut_tv from 32bit time_t to 64bit time_t, or your
> patch will not survive 2038.

This is already done by the 'year2038' module of Gnulib, which
coreutils and many other packages use.

> 2. the string entries are size limited in struct utmp, but are not with
> libsystemd. So could be that you need to truncate device names, user
> names and host names.

Thanks for the hint. Truncating would mean to introduce arbitrary limits,
which is against the GNU Coding Standards
<https://www.gnu.org/prep/standards/html_node/Semantics.html>. Hence
we'll happily use arbitrary-length strings in our code.

Paul Eggert wrote:
> This suggests that Gnulib needs to redo the readutmp API so that it's 
> not tied to struct utmpx's fixed string lengths. That's a bit of a 
> hassle but should be doable (change char array to char *). An advantage 
> is that Gnulib is a source-code library and so we can make binary- (and 
> even source-) incompatible changes to the API without too much trouble.

For the moment, I've extended the 'readutmp' module to use arbitrary-
length strings only when compiling with libsystemd.

Here are the changes I committed in gnulib.


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

        readutmp: Small changes to reduce the code size on the coreutils side.
        * m4/readutmp.m4 (gl_READUTMP): Test also for the ut_host field in
        'struct utmpx' and 'struct utmp'.
        * lib/readutmp.h (HAVE_STRUCT_XTMP_UT_HOST): New macro.
        (UT_USER_SIZE): Define also as a macro. Set to -1 if
        READUTMP_USE_SYSTEMD.
        (UT_LINE_SIZE, UT_HOST_SIZE): New constants and macros.

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

        readutmp: For year-2038 safety on Linux/{x86,arm}, use systemd APIs.
        Suggested by Thorsten Kukuk <ku...@suse.com> in
        <https://www.thkukuk.de/blog/Y2038_glibc_utmp_64bit/> and
        <https://github.com/thkukuk/utmpx/blob/main/utmp-to-logind.md>.
        * m4/systemd.m4: New file.
        * m4/readutmp.m4 (gl_READUTMP): Require gl_SYSTEMD_CHOICE. Set
        READUTMP_LIB. Conditionally define READUTMP_USE_SYSTEMD.
        * lib/readutmp.h: For READUTMP_USE_SYSTEMD, include <sys/time.h> and
        <utmpx.h>.
        (struct gl_utmp): New type.
        (UTMP_STRUCT_NAME, UT_TIME_MEMBER, UT_EXIT_E_TERMINATION,
        UT_EXIT_E_EXIT, UT_USER, HAVE_STRUCT_XTMP_UT_EXIT,
        HAVE_STRUCT_XTMP_UT_ID, HAVE_STRUCT_XTMP_UT_PID): Define differently for
        READUTMP_USE_SYSTEMD.
        (UT_USER_SIZE): Don't define for READUTMP_USE_SYSTEMD.
        (UT_TYPE_EQ, UT_TYPE_NOT_DEFINED, READ_UTMP_SUPPORTED): Define also for
        READUTMP_USE_SYSTEMD.
        (free_utmp): New declaration.
        * lib/readutmp.c: Add new includes for READUTMP_USE_SYSTEMD.
        (extract_trimmed_name): Adapt to READUTMP_USE_SYSTEMD.
        (get_boot_time_uncached, get_boot_time, guess_pty_name): New functions.
        (read_utmp): New implementation for READUTMP_USE_SYSTEMD.
        (free_utmp): New function.
        * tests/test-readutmp.c (main): At the end, invoke free_utmp.
        * modules/readutmp (Files): Add m4/systemd.m4.
        (Link): New section.
        * modules/readutmp-tests (Makefile.am): Link test-readutmp with
        READUTMP_LIB.
        * NEWS: Mention the free_utmp function and the READUTMP_LIB link
        requirement.


When the output of the 'test-readutmp' program without libsystemd is
 ==============================================================================
Here are the read_utmp results.
Flags: B = Boot, U = User Process

                                                              Termi‐      Flags
    Time (GMT)             User          Device        PID    nation Exit  B U
------------------- ------------------ ----------- ---------- ------ ----  - -
2023-07-30 09:25:15 reboot             ~                    0    0     0   X  
2023-07-30 09:25:31 runlevel           ~                   53    0     0      
2023-07-30 09:25:48 bruno              seat0             1663    0     0     X
2023-07-30 09:25:48 bruno              tty2              1663    0     0     X
2023-07-30 09:31:17                    pts/1             3210    0     0      
2023-07-31 23:36:52 bruno              pts/3            30619    0     0     X
2023-08-01 15:19:26 other              pts/4            40513    0     0     X
2023-08-01 19:02:20                    pts/5            43376    0     1      
 ==============================================================================

with libsystemd it is:

 ==============================================================================
Here are the read_utmp results.
Flags: B = Boot, U = User Process

                                                              Termi‐      Flags
    Time (GMT)             User          Device        PID    nation Exit  B U
------------------- ------------------ ----------- ---------- ------ ----  - -
2023-07-30 09:25:14 reboot             ~                    0    0     0   X  
2023-07-30 09:25:47 bruno              seat0             1593    0     0     X
2023-07-30 09:25:47 bruno              tty2              1593    0     0     X
2023-08-01 15:19:25 other              sshd pts/4       40513    0     0     X
 ==============================================================================

The major changes are:
* Entries without BOOT_TIME or USER_PROCESS flag are gone. This includes
  - runlevel and possibly "old time" / "new time" entries, which I don't know
    how to get from systemd,
  - entries for pseudo-terminals (xterm etc.) that are not login shells;
    I don't know how to get them from systemd either, and (IMO) they were
    uninteresting anyway,
  - login shells that have exited.
* The reboot time is a little more precise, since the systemd code uses
  the /proc/uptime virtual file.
* The pids now refer to the session leader, which is a parent (or ancestor)
  of the process which has allocated the pty.
* In the ut_line field ("Device" column) I added extra info about the
  service name, in this case "sshd". I imagine that is at least as useful as
  knowing the pty name ("pts/4").
>From 0b71ff37a7cb0ce546975796f9366eee25be5fb7 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 1 Aug 2023 21:44:07 +0200
Subject: [PATCH 1/2] readutmp: For year-2038 safety on Linux/{x86,arm}, use
 systemd APIs.

Suggested by Thorsten Kukuk <ku...@suse.com> in
<https://www.thkukuk.de/blog/Y2038_glibc_utmp_64bit/> and
<https://github.com/thkukuk/utmpx/blob/main/utmp-to-logind.md>.

* m4/systemd.m4: New file.
* m4/readutmp.m4 (gl_READUTMP): Require gl_SYSTEMD_CHOICE. Set
READUTMP_LIB. Conditionally define READUTMP_USE_SYSTEMD.
* lib/readutmp.h: For READUTMP_USE_SYSTEMD, include <sys/time.h> and
<utmpx.h>.
(struct gl_utmp): New type.
(UTMP_STRUCT_NAME, UT_TIME_MEMBER, UT_EXIT_E_TERMINATION,
UT_EXIT_E_EXIT, UT_USER, HAVE_STRUCT_XTMP_UT_EXIT,
HAVE_STRUCT_XTMP_UT_ID, HAVE_STRUCT_XTMP_UT_PID): Define differently for
READUTMP_USE_SYSTEMD.
(UT_USER_SIZE): Don't define for READUTMP_USE_SYSTEMD.
(UT_TYPE_EQ, UT_TYPE_NOT_DEFINED, READ_UTMP_SUPPORTED): Define also for
READUTMP_USE_SYSTEMD.
(free_utmp): New declaration.
* lib/readutmp.c: Add new includes for READUTMP_USE_SYSTEMD.
(extract_trimmed_name): Adapt to READUTMP_USE_SYSTEMD.
(get_boot_time_uncached, get_boot_time, guess_pty_name): New functions.
(read_utmp): New implementation for READUTMP_USE_SYSTEMD.
(free_utmp): New function.
* tests/test-readutmp.c (main): At the end, invoke free_utmp.
* modules/readutmp (Files): Add m4/systemd.m4.
(Link): New section.
* modules/readutmp-tests (Makefile.am): Link test-readutmp with
READUTMP_LIB.
* NEWS: Mention the free_utmp function and the READUTMP_LIB link
requirement.
---
 ChangeLog              |  33 ++++
 NEWS                   |   4 +
 lib/readutmp.c         | 350 ++++++++++++++++++++++++++++++++++++++++-
 lib/readutmp.h         |  75 +++++++--
 m4/readutmp.m4         |  36 +++++
 m4/systemd.m4          |  23 +++
 modules/readutmp       |   4 +
 modules/readutmp-tests |   1 +
 tests/test-readutmp.c  |   2 +
 9 files changed, 511 insertions(+), 17 deletions(-)
 create mode 100644 m4/systemd.m4

diff --git a/ChangeLog b/ChangeLog
index a96252f2a0..16bf75a66d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,36 @@
+2023-08-01  Bruno Haible  <br...@clisp.org>
+
+	readutmp: For year-2038 safety on Linux/{x86,arm}, use systemd APIs.
+	Suggested by Thorsten Kukuk <ku...@suse.com> in
+	<https://www.thkukuk.de/blog/Y2038_glibc_utmp_64bit/> and
+	<https://github.com/thkukuk/utmpx/blob/main/utmp-to-logind.md>.
+	* m4/systemd.m4: New file.
+	* m4/readutmp.m4 (gl_READUTMP): Require gl_SYSTEMD_CHOICE. Set
+	READUTMP_LIB. Conditionally define READUTMP_USE_SYSTEMD.
+	* lib/readutmp.h: For READUTMP_USE_SYSTEMD, include <sys/time.h> and
+	<utmpx.h>.
+	(struct gl_utmp): New type.
+	(UTMP_STRUCT_NAME, UT_TIME_MEMBER, UT_EXIT_E_TERMINATION,
+	UT_EXIT_E_EXIT, UT_USER, HAVE_STRUCT_XTMP_UT_EXIT,
+	HAVE_STRUCT_XTMP_UT_ID, HAVE_STRUCT_XTMP_UT_PID): Define differently for
+	READUTMP_USE_SYSTEMD.
+	(UT_USER_SIZE): Don't define for READUTMP_USE_SYSTEMD.
+	(UT_TYPE_EQ, UT_TYPE_NOT_DEFINED, READ_UTMP_SUPPORTED): Define also for
+	READUTMP_USE_SYSTEMD.
+	(free_utmp): New declaration.
+	* lib/readutmp.c: Add new includes for READUTMP_USE_SYSTEMD.
+	(extract_trimmed_name): Adapt to READUTMP_USE_SYSTEMD.
+	(get_boot_time_uncached, get_boot_time, guess_pty_name): New functions.
+	(read_utmp): New implementation for READUTMP_USE_SYSTEMD.
+	(free_utmp): New function.
+	* tests/test-readutmp.c (main): At the end, invoke free_utmp.
+	* modules/readutmp (Files): Add m4/systemd.m4.
+	(Link): New section.
+	* modules/readutmp-tests (Makefile.am): Link test-readutmp with
+	READUTMP_LIB.
+	* NEWS: Mention the free_utmp function and the READUTMP_LIB link
+	requirement.
+
 2023-08-01  Bruno Haible  <br...@clisp.org>
 
 	readutmp: Trivial simplification.
diff --git a/NEWS b/NEWS
index 9aa243182c..87d49ffbe2 100644
--- a/NEWS
+++ b/NEWS
@@ -74,6 +74,10 @@ 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.
+                            Link additionally with $(READUTMP_LIB).
+
 2023-07-10  dfa             The signature of the function
                             case_folded_counterparts, declared in localeinfo.h,
                             has changed.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 34b96d999e..ba4b0eadb1 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -31,6 +31,13 @@
 #include <stdlib.h>
 #include <stdint.h>
 
+#if READUTMP_USE_SYSTEMD
+# include <dirent.h>
+# include <sys/sysinfo.h>
+# include <systemd/sd-login.h>
+# include <time.h>
+#endif
+
 #include "xalloc.h"
 
 /* Each of the FILE streams in this file is only used in a single thread.  */
@@ -48,16 +55,20 @@ 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.  Remove any trailing
-     spaces.  */
+     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')
-    continue;
+    ;
   return trimmed_name;
 }
 
@@ -85,7 +96,322 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
   return true;
 }
 
-# if defined UTMP_NAME_FUNCTION
+# if READUTMP_USE_SYSTEMD
+/* Use systemd and Linux /proc and kernel APIs.  */
+
+static struct timeval
+get_boot_time_uncached ()
+{
+  /* /proc/uptime contains the uptime with a resolution of 0.01 sec.  */
+  FILE *fp = fopen ("/proc/uptime", "re");
+  if (fp != NULL)
+    {
+      char buf[32 + 1];
+      size_t n = fread (buf, 1, sizeof (buf) - 1, fp);
+      fclose (fp);
+      if (n > 0)
+        {
+          buf[n] = '\0';
+          /* buf now contains two values: the uptime and the idle time.  */
+          char *endptr;
+          double uptime = strtod (buf, &endptr);
+          if (endptr > buf)
+            {
+              struct timeval result;
+              if (gettimeofday (&result, NULL) >= 0)
+                {
+                  long uptime_sec = (long) uptime;
+                  int uptime_usec =
+                    (int) ((uptime - (double) uptime_sec) * 1000000.0 + 0.5);
+                  if (result.tv_usec < uptime_usec)
+                    {
+                      result.tv_usec += 1000000;
+                      result.tv_sec -= 1;
+                    }
+                  result.tv_sec -= uptime_sec;
+                  result.tv_usec -= uptime_usec;
+                  return result;
+                }
+            }
+        }
+    }
+
+  /* The sysinfo call returns the uptime with a resolution of 1 sec only.  */
+  struct sysinfo info;
+  if (sysinfo (&info) >= 0)
+    {
+      struct timeval result;
+      if (gettimeofday (&result, NULL) >= 0)
+        {
+          result.tv_sec -= info.uptime;
+          return result;
+        }
+    }
+
+  /* We shouldn't get here.  */
+  return (struct timeval) { .tv_sec = 0, .tv_usec = 0 };
+}
+
+static struct timeval
+get_boot_time ()
+{
+  static int cached;
+  static struct timeval boot_time;
+
+  if (!cached)
+    {
+      boot_time = get_boot_time_uncached ();
+      cached = 1;
+    }
+  return boot_time;
+}
+
+/* Guess the pty name that was opened for the given user right after
+   the given time AT.  */
+static char *
+guess_pty_name (uid_t uid, const struct timespec at)
+{
+  /* Traverse the entries of the /dev/pts/ directory, looking for devices
+     which are owned by UID and whose ctime is shortly after AT.  */
+  DIR *dirp = opendir ("/dev/pts");
+  if (dirp != NULL)
+    {
+      /* Buffer containing /dev/pts/N.  */
+      char name_buf[9 + 10 + 1];
+      memcpy (name_buf, "/dev/pts/", 9);
+
+      char best_name[9 + 10 + 1];
+      struct timespec best_time = { .tv_sec = 0, .tv_nsec = 0 };
+
+      for (;;)
+        {
+          struct dirent *dp = readdir (dirp);
+          if (dp == NULL)
+            break;
+          if (dp->d_name[0] != '.' && strlen (dp->d_name) <= 10)
+            {
+              /* Compose the absolute file name /dev/pts/N.  */
+              strcpy (name_buf + 9, dp->d_name);
+
+              /* Find its owner and ctime.  */
+              struct stat st;
+              if (stat (name_buf, &st) >= 0
+                  && st.st_uid == uid
+                  && (st.st_ctim.tv_sec > at.tv_sec
+                      || (st.st_ctim.tv_sec == at.tv_sec
+                          && st.st_ctim.tv_nsec >= at.tv_nsec)))
+                {
+                  /* This entry has the owner UID and a ctime >= AT.  */
+                  /* Is this entry the best one so far?  */
+                  if ((best_time.tv_sec == 0 && best_time.tv_nsec == 0)
+                      || (st.st_ctim.tv_sec < best_time.tv_sec
+                          || (st.st_ctim.tv_sec == best_time.tv_sec
+                              && st.st_ctim.tv_nsec < best_time.tv_nsec)))
+                    {
+                      strcpy (best_name, name_buf);
+                      best_time = st.st_ctim;
+                    }
+                }
+            }
+        }
+
+      closedir (dirp);
+
+      /* Did we find an entry owned by ID, and is it at most 5 seconds
+         after AT?  */
+      if (!(best_time.tv_sec == 0 && best_time.tv_nsec == 0)
+          && (best_time.tv_sec < at.tv_sec + 5
+              || (best_time.tv_sec == at.tv_sec + 5
+                  && best_time.tv_nsec <= at.tv_nsec)))
+        return xstrdup (best_name + 5);
+    }
+
+  return NULL;
+}
+
+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;
+
+  /* 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);
+    }
+
+  /* Synthesize USER_PROCESS entries.  */
+  char **sessions;
+  int num_sessions = sd_get_sessions (&sessions);
+  if (num_sessions >= 0)
+    {
+      char **session_ptr;
+      for (session_ptr = sessions; *session_ptr != NULL; session_ptr++)
+        {
+          char *session = *session_ptr;
+
+          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;
+
+          char *seat;
+          if (sd_session_get_seat (session, &seat) < 0)
+            seat = 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 (strcmp (type, "tty") == 0)
+                    {
+                      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)
+                        {
+                          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);
+
+                      free (pty);
+                      free (service);
+                    }
+                  free (type);
+                }
+            }
+
+          /* Create up to two USER_PROCESS entries: one for the seat,
+             one for the tty.  */
+          if (seat != NULL || tty != NULL)
+            {
+              char *user;
+              if (sd_session_get_username (session, &user) < 0)
+                user = xstrdup ("");
+
+              pid_t leader_pid;
+              if (sd_session_get_leader (session, &leader_pid) < 0)
+                leader_pid = 0;
+
+              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 = xmalloc (strlen (remote_user) + 1 + strlen (remote_host) + 1);
+                      stpcpy (stpcpy (stpcpy (host, remote_user), "@"), remote_host);
+                    }
+                  else
+                    host = xstrdup (remote_host);
+                }
+              else
+                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);
+                }
+              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);
+                }
+
+              free (host);
+              free (remote_user);
+              free (remote_host);
+              free (user);
+            }
+          free (tty);
+          free (seat);
+          free (session);
+        }
+      free (sessions);
+    }
+
+  *n_entries = n_filled;
+  *utmp_buf = utmp;
+
+  return 0;
+}
+
+# elif defined UTMP_NAME_FUNCTION
 
 static void
 copy_utmp_entry (STRUCT_UTMP *dst, STRUCT_UTMP *src)
@@ -219,3 +545,19 @@ 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 271dd4921a..e03e0b0a32 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -37,7 +37,34 @@
 #  undef HAVE_UTMPX_H
 # endif
 
-# if HAVE_UTMPX_H
+# if READUTMP_USE_SYSTEMD
+
+/* Get 'struct timeval'.  */
+#  include <sys/time.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 */
+  pid_t ut_pid;                 /* process ID of ? */
+  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 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
 
 /* <utmpx.h> defines 'struct utmpx' with the following fields:
 
@@ -171,7 +198,11 @@ struct gl_utmp
 # endif
 
 /* Accessor macro for the member named ut_user or ut_name.  */
-# if HAVE_UTMPX_H
+# 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)
@@ -197,23 +228,37 @@ struct gl_utmp
 
 # endif
 
-# define HAVE_STRUCT_XTMP_UT_EXIT \
-    (HAVE_STRUCT_UTMP_UT_EXIT \
-     || HAVE_STRUCT_UTMPX_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
 
-# define HAVE_STRUCT_XTMP_UT_ID \
-    (HAVE_STRUCT_UTMP_UT_ID \
-     || HAVE_STRUCT_UTMPX_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
 
-# define HAVE_STRUCT_XTMP_UT_PID \
-    (HAVE_STRUCT_UTMP_UT_PID \
-     || HAVE_STRUCT_UTMPX_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
 
 /* Type of entry returned by read_utmp().  */
 typedef struct UTMP_STRUCT_NAME STRUCT_UTMP;
 
 /* Size of the UT_USER (ut) member.  */
+# if !READUTMP_USE_SYSTEMD
 enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) };
+# endif
 
 /* Definition of UTMP_FILE and WTMP_FILE.  */
 
@@ -252,7 +297,7 @@ enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) };
 
 /* Accessor macros for the member named ut_type.  */
 
-# if HAVE_STRUCT_UTMP_UT_TYPE || HAVE_STRUCT_UTMPX_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
@@ -279,7 +324,7 @@ enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) };
         || (UT_TYPE_NOT_DEFINED && UT_TIME_MEMBER (UT) != 0)))
 
 /* Define if read_utmp is not just a dummy.  */
-# if HAVE_UTMPX_H || HAVE_UTMP_H
+# if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H
 #  define READ_UTMP_SUPPORTED 1
 # endif
 
@@ -309,4 +354,8 @@ 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/m4/readutmp.m4 b/m4/readutmp.m4
index 44fcc6151b..2dfd3fe8d0 100644
--- a/m4/readutmp.m4
+++ b/m4/readutmp.m4
@@ -9,6 +9,42 @@ AC_DEFUN([gl_READUTMP]
   dnl Persuade utmpx.h to declare utmpxname
   AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
 
+  AC_REQUIRE([gl_SYSTEMD_CHOICE])
+
+  dnl Set READUTMP_LIB to '-lsystemd' or '', depending on whether use of
+  dnl systemd APIs is possible and desired (only the systemd login API, here).
+  dnl AC_LIB_LINKFLAGS_BODY would be overkill here, since few people install
+  dnl libsystemd in non-system directories.
+  READUTMP_LIB=
+  if test "$SYSTEMD_CHOICE" = yes; then
+    AC_CHECK_HEADER([systemd/sd-login.h])
+    if test $ac_cv_header_systemd_sd_login_h = yes; then
+      AC_CACHE_CHECK([for libsystemd version >= 254],
+        [gl_cv_lib_readutmp_systemd],
+        [gl_save_LIBS="$LIBS"
+         LIBS="$LIBS -lsystemd"
+         AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM([[
+              #include <stdint.h>
+              #include <systemd/sd-login.h>
+              ]], [[
+              uint64_t st;
+              sd_session_get_start_time ("1", &st);
+              ]])
+           ],
+           [gl_cv_lib_readutmp_systemd=yes],
+           [gl_cv_lib_readutmp_systemd=no])
+         LIBS="$gl_save_LIBS"
+        ])
+      if test $gl_cv_lib_readutmp_systemd = yes; then
+        AC_DEFINE([READUTMP_USE_SYSTEMD], [1],
+          [Define if the readutmp module should use the systemd login API.])
+        READUTMP_LIB='-lsystemd'
+      fi
+    fi
+  fi
+  AC_SUBST([READUTMP_LIB])
+
   AC_CHECK_HEADERS_ONCE([utmp.h utmpx.h])
   if test $ac_cv_header_utmp_h = yes || test $ac_cv_header_utmpx_h = yes; then
     dnl Prerequisites of lib/readutmp.h and lib/readutmp.c.
diff --git a/m4/systemd.m4 b/m4/systemd.m4
new file mode 100644
index 0000000000..0c919385b2
--- /dev/null
+++ b/m4/systemd.m4
@@ -0,0 +1,23 @@
+# systemd.m4 serial 1
+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,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Sets SYSTEMD_CHOICE to 'yes' or 'no', depending on the preferred use of
+# systemd APIs.
+AC_DEFUN([gl_SYSTEMD_CHOICE],
+[
+  AC_MSG_CHECKING([whether to use systemd APIs])
+  AC_ARG_ENABLE([systemd],
+    [  --disable-systemd       do not use systemd APIs],
+    [SYSTEMD_CHOICE="$enableval"],
+    [SYSTEMD_CHOICE=yes])
+  AC_MSG_RESULT([$SYSTEMD_CHOICE])
+  AC_SUBST([SYSTEMD_CHOICE])
+])
+
+# Pre-built package name for the libsystemd library:
+# - On Debian and Debian-based systems: libsystemd-dev
+# - On Red Hat distributions: systemd-devel
+# - Other: https://repology.org/project/systemd/versions
diff --git a/modules/readutmp b/modules/readutmp
index 4b2de331dc..04893a4487 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -5,6 +5,7 @@ Files:
 lib/readutmp.h
 lib/readutmp.c
 m4/readutmp.m4
+m4/systemd.m4
 
 Depends-on:
 extensions
@@ -24,6 +25,9 @@ lib_SOURCES += readutmp.c
 Include:
 "readutmp.h"
 
+Link:
+$(READUTMP_LIB)
+
 License:
 GPL
 
diff --git a/modules/readutmp-tests b/modules/readutmp-tests
index 792a80e792..06f06a7c3c 100644
--- a/modules/readutmp-tests
+++ b/modules/readutmp-tests
@@ -11,3 +11,4 @@ configure.ac:
 Makefile.am:
 TESTS += test-readutmp
 check_PROGRAMS += test-readutmp
+test_readutmp_LDADD = $(LDADD) @READUTMP_LIB@
diff --git a/tests/test-readutmp.c b/tests/test-readutmp.c
index b806b47116..ae17087a81 100644
--- a/tests/test-readutmp.c
+++ b/tests/test-readutmp.c
@@ -138,6 +138,8 @@ main (int argc, char *argv[])
       time_t now = time (NULL);
       ASSERT (first >= now - 157680000);
       ASSERT (last <= now + 604800);
+
+      free_utmp (num_entries, entries);
     }
 
   return 0;
-- 
2.34.1

>From a6d192b9608516430d95c0255594aa4b003d5ebb Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 2 Aug 2023 00:56:25 +0200
Subject: [PATCH 2/2] readutmp: Small changes to reduce the code size on the
 coreutils side.

* m4/readutmp.m4 (gl_READUTMP): Test also for the ut_host field in
'struct utmpx' and 'struct utmp'.
* lib/readutmp.h (HAVE_STRUCT_XTMP_UT_HOST): New macro.
(UT_USER_SIZE): Define also as a macro. Set to -1 if
READUTMP_USE_SYSTEMD.
(UT_LINE_SIZE, UT_HOST_SIZE): New constants and macros.
---
 ChangeLog      | 10 ++++++++++
 lib/readutmp.h | 31 +++++++++++++++++++++++++++++--
 m4/readutmp.m4 |  4 +++-
 3 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 16bf75a66d..e7fd56e609 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2023-08-01  Bruno Haible  <br...@clisp.org>
+
+	readutmp: Small changes to reduce the code size on the coreutils side.
+	* m4/readutmp.m4 (gl_READUTMP): Test also for the ut_host field in
+	'struct utmpx' and 'struct utmp'.
+	* lib/readutmp.h (HAVE_STRUCT_XTMP_UT_HOST): New macro.
+	(UT_USER_SIZE): Define also as a macro. Set to -1 if
+	READUTMP_USE_SYSTEMD.
+	(UT_LINE_SIZE, UT_HOST_SIZE): New constants and macros.
+
 2023-08-01  Bruno Haible  <br...@clisp.org>
 
 	readutmp: For year-2038 safety on Linux/{x86,arm}, use systemd APIs.
diff --git a/lib/readutmp.h b/lib/readutmp.h
index e03e0b0a32..06d2a69daf 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -252,12 +252,39 @@ struct gl_utmp
       || HAVE_STRUCT_UTMPX_UT_PID)
 # endif
 
+# 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
+
 /* Type of entry returned by read_utmp().  */
 typedef struct UTMP_STRUCT_NAME STRUCT_UTMP;
 
-/* Size of the UT_USER (ut) member.  */
-# if !READUTMP_USE_SYSTEMD
+/* Size of the UT_USER (ut) member, or -1 if unbounded.  */
+# if READUTMP_USE_SYSTEMD
+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_line member, or -1 if unbounded.  */
+# if READUTMP_USE_SYSTEMD
+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 READUTMP_USE_SYSTEMD
+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.  */
diff --git a/m4/readutmp.m4 b/m4/readutmp.m4
index 2dfd3fe8d0..a4b1cb4642 100644
--- a/m4/readutmp.m4
+++ b/m4/readutmp.m4
@@ -1,4 +1,4 @@
-# readutmp.m4 serial 21
+# readutmp.m4 serial 22
 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,6 +78,8 @@ 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 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_exit],,,[$utmp_includes])
-- 
2.34.1

Reply via email to