]> Savannah Git Hosting - gnulib.git/commitdiff
readutmp: For year-2038 safety on Linux/{x86,arm}, use systemd APIs.
authorBruno Haible <bruno@clisp.org>
Tue, 1 Aug 2023 19:44:07 +0000 (21:44 +0200)
committerBruno Haible <bruno@clisp.org>
Tue, 1 Aug 2023 19:44:07 +0000 (21:44 +0200)
Suggested by Thorsten Kukuk <kukuk@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
NEWS
lib/readutmp.c
lib/readutmp.h
m4/readutmp.m4
m4/systemd.m4 [new file with mode: 0644]
modules/readutmp
modules/readutmp-tests
tests/test-readutmp.c

index a96252f2a0b1c14eef05cc179d89cc5e6958d62a..16bf75a66df2896bb12961fc559f29a257a98ff6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,36 @@
+2023-08-01  Bruno Haible  <bruno@clisp.org>
+
+       readutmp: For year-2038 safety on Linux/{x86,arm}, use systemd APIs.
+       Suggested by Thorsten Kukuk <kukuk@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  <bruno@clisp.org>
 
        readutmp: Trivial simplification.
diff --git a/NEWS b/NEWS
index 9aa243182cddbb8d600c785e3eceb8fbcee48cc8..87d49ffbe2ea70eb18c725b6d146c890c3e6b8ec 100644 (file)
--- 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.
index 34b96d999e7f4b9f79da3c4043b8902f5c88212b..ba4b0eadb1a041a5f9116e670ec8ba2f1e8df887 100644 (file)
 #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
+}
index 271dd4921a2b11bf39dfa32fc54e96d829566b35..e03e0b0a323c056ce6ecbd6bfe1c8b3b89c162cd 100644 (file)
 #  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__ */
index 44fcc6151b318efb9b8744562d954f789cc3f654..2dfd3fe8d055149ab4b88fb0675c69fc242d251b 100644 (file)
@@ -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 (file)
index 0000000..0c91938
--- /dev/null
@@ -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
index 4b2de331dcf5b42a593bd4cad905c24bb60c16f1..04893a4487b2838190f16e67a2ae2ae52ff5a231 100644 (file)
@@ -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
 
index 792a80e79211b4ca7d132c209d019929f0837d67..06f06a7c3c42d5cf7d09c4c57767d758a7c8b3fc 100644 (file)
@@ -11,3 +11,4 @@ configure.ac:
 Makefile.am:
 TESTS += test-readutmp
 check_PROGRAMS += test-readutmp
+test_readutmp_LDADD = $(LDADD) @READUTMP_LIB@
index b806b47116f817b0cae4f2510d6addb38051cd71..ae17087a81c672ced7cfc44809a03b1d5ac9042e 100644 (file)
@@ -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;