I wrote:
> So, all approaches that compute the boot time through a subtraction are
> going to be wrong in these scenarios.
> 
> The better approach is really to read the boot time from a time stamp —
> inside a file such as /var/run/utmp or /var/log/wtmp, or attached to
> a file such as
>   /var/lib/systemd/random-seed    (first file touched during boot)
>   /var/log/boot.log               (one of the last files touched during boot).
> 
> And on Alpine Linux, while /var/run/utmp is empty, its time stamp is
> essentially the boot time.

With the attached patches, the boot time returned by read_utmp() is stable
across 'sudo date MMDDhhmm' invocations.

On Linux distros, I found these files to be touched after boot:

/var/lib/systemd/random-seed   (seen on distros with systemd, e.g. CentOS 7,
                                Fedora 27, Debian 9, Ubuntu 17.10, Arch 19.11,
                                Manjaro 17)
/var/run/utmp                  (seen on distros with OpenRC, e.g. Alpine Linux)
/var/lib/random-seed           (seen on OpenSUSE 12.1, CentOS 5)

And, while at it, also on OpenBSD. It touches /var/db/host.random and
/var/run/utmp.

Bruno


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

        readutmp: Return a boot time also on OpenBSD.
        * lib/readutmp.h (BOOT_TIME, USER_PROCESS): Provide fallback
        definitions.
        * lib/readutmp.c (read_utmp_from_file) [__OpenBSD__]: Fake a BOOT_TIME
        entry by looking at the time stamp of a specific file.

        readutmp: Return a boot time also on Alpine Linux.
        * lib/readutmp.c: Include stat-time.h.
        (SIZEOF): New macro.
        (read_utmp_from_file) [__linux__]: Fake a BOOT_TIME entry by looking
        at the time stamp of a specific file.
        * modules/readutmp (Depends-on): Add stat-time.

        readutmp: Fix boot time in VMs after sleep state and date update.
        * lib/readutmp.c (read_utmp_from_file): New function, extracted from
        read_utmp.
        (get_boot_time_uncached): Before all other approaches, try to find the
        boot time in the /var/run/utmp file.
        (read_utmp): Invoke read_utmp_from_file.

        readutmp: Make it easier to filter for/against the boot-time entry.
        * lib/readutmp.h (READ_UTMP_BOOT_TIME, READ_UTMP_NO_BOOT_TIME): New
        enum items.
        * lib/readutmp.c (desirable_utmp_entry, read_utmp_from_systemd):
        Implement them.
        (read_utmp): If no entries can match the given options, return
        immediately.

From 1e4bc08eb8ce3c00f7b8e72dc69e07ccbc63ed20 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Aug 2023 18:49:22 +0200
Subject: [PATCH 1/4] readutmp: Make it easier to filter for/against the
 boot-time entry.

* lib/readutmp.h (READ_UTMP_BOOT_TIME, READ_UTMP_NO_BOOT_TIME): New
enum items.
* lib/readutmp.c (desirable_utmp_entry, read_utmp_from_systemd):
Implement them.
(read_utmp): If no entries can match the given options, return
immediately.
---
 ChangeLog      |  12 ++-
 lib/readutmp.c | 245 +++++++++++++++++++++++++++----------------------
 lib/readutmp.h |  12 ++-
 3 files changed, 157 insertions(+), 112 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 40274c0a08..c2c9613643 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,17 @@
+2023-08-09  Bruno Haible  <br...@clisp.org>
+
+	readutmp: Make it easier to filter for/against the boot-time entry.
+	* lib/readutmp.h (READ_UTMP_BOOT_TIME, READ_UTMP_NO_BOOT_TIME): New
+	enum items.
+	* lib/readutmp.c (desirable_utmp_entry, read_utmp_from_systemd):
+	Implement them.
+	(read_utmp): If no entries can match the given options, return
+	immediately.
+
 2023-08-08  Paul Eggert  <egg...@cs.ucla.edu>
 
 	readutmp: omit pragma
-	* lib/readutmp.c: Omit -Sstringop-overread pragma.
+	* lib/readutmp.c: Omit -Wstringop-overread pragma.
 	It’s no longer needed now that extract_trimmed_name
 	no longer calls strnlen.
 
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 31db4023a1..a7773c4f36 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -163,6 +163,13 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
       && ut->ut_line[0] == '\0' && ut->ut_host[0] == '\0')
     return false;
 # endif
+
+  bool boot_time = UT_TYPE_BOOT_TIME (ut);
+  if ((options & READ_UTMP_BOOT_TIME) && !boot_time)
+    return false;
+  if ((options & READ_UTMP_NO_BOOT_TIME) && boot_time)
+    return false;
+
   bool user_proc = IS_USER_PROCESS (ut);
   if ((options & READ_UTMP_USER_PROCESS) && !user_proc)
     return false;
@@ -171,6 +178,7 @@ desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
       && 0 < UT_PID (ut)
       && (kill (UT_PID (ut), 0) < 0 && errno == ESRCH))
     return false;
+
   return true;
 }
 
@@ -441,7 +449,7 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
   struct utmp_alloc a = {0};
 
   /* Synthesize a BOOT_TIME entry.  */
-  if (!(options & READ_UTMP_USER_PROCESS))
+  if (!(options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)))
     a = add_utmp (a, options,
                   "reboot", strlen ("reboot"),
                   "", 0,
@@ -450,135 +458,138 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
                   0, BOOT_TIME, get_boot_time (), 0, 0, 0);
 
   /* Synthesize USER_PROCESS entries.  */
-  char **sessions;
-  int num_sessions = sd_get_sessions (&sessions);
-  if (num_sessions >= 0)
+  if (!(options & READ_UTMP_BOOT_TIME))
     {
-      char **session_ptr;
-      for (session_ptr = sessions; *session_ptr != NULL; session_ptr++)
+      char **sessions;
+      int num_sessions = sd_get_sessions (&sessions);
+      if (num_sessions >= 0)
         {
-          char *session = *session_ptr;
+          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 timespec start_ts;
-          start_ts.tv_sec = start_usec / 1000000;
-          start_ts.tv_nsec = start_usec % 1000000 * 1000;
+              uint64_t start_usec;
+              if (sd_session_get_start_time (session, &start_usec) < 0)
+                start_usec = 0;
+              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)
-            seat = NULL;
+              char *seat;
+              if (sd_session_get_seat (session, &seat) < 0)
+                seat = NULL;
 
-          char missing[] = "";
+              char missing[] = "";
 
-          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.  */
-              if (sd_session_get_type (session, &type) < 0)
-                type = missing;
-              if (strcmp (type, "tty") == 0)
+              char *type = NULL;
+              char *tty;
+              if (sd_session_get_tty (session, &tty) < 0)
                 {
-                  char *service;
-                  if (sd_session_get_service (session, &service) < 0)
-                    service = NULL;
-
-                  uid_t uid;
-                  char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL
-                               : guess_pty_name (uid, start_ts));
-
-                  if (service != NULL && pty != NULL)
+                  tty = NULL;
+                  /* Try harder to get a sensible value for the tty.  */
+                  if (sd_session_get_type (session, &type) < 0)
+                    type = missing;
+                  if (strcmp (type, "tty") == 0)
                     {
-                      tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
-                      stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
-                      free (pty);
-                      free (service);
+                      char *service;
+                      if (sd_session_get_service (session, &service) < 0)
+                        service = NULL;
+
+                      uid_t uid;
+                      char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL
+                                   : guess_pty_name (uid, start_ts));
+
+                      if (service != NULL && pty != NULL)
+                        {
+                          tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
+                          stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
+                          free (pty);
+                          free (service);
+                        }
+                      else if (service != NULL)
+                        tty = service;
+                      else if (pty != NULL)
+                        tty = pty;
                     }
-                  else if (service != NULL)
-                    tty = service;
-                  else if (pty != NULL)
-                    tty = pty;
                 }
-            }
 
-          /* 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 = missing;
+              /* 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 = missing;
 
-              pid_t leader_pid;
-              if (sd_session_get_leader (session, &leader_pid) < 0)
-                leader_pid = 0;
+                  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)
-                {
-                  host = missing;
-                  /* For backward compatibility, put the X11 display into the
-                     host field.  */
-                  if (!type && sd_session_get_type (session, &type) < 0)
-                    type = missing;
-                  if (strcmp (type, "x11") == 0)
+                  char *host;
+                  char *remote_host;
+                  if (sd_session_get_remote_host (session, &remote_host) < 0)
                     {
-                      char *display;
-                      if (sd_session_get_display (session, &display) < 0)
-                        display = NULL;
-                      host = display;
+                      host = missing;
+                      /* For backward compatibility, put the X11 display into the
+                         host field.  */
+                      if (!type && sd_session_get_type (session, &type) < 0)
+                        type = missing;
+                      if (strcmp (type, "x11") == 0)
+                        {
+                          char *display;
+                          if (sd_session_get_display (session, &display) < 0)
+                            display = NULL;
+                          host = display;
+                        }
                     }
-                }
-              else
-                {
-                  char *remote_user;
-                  if (sd_session_get_remote_user (session, &remote_user) < 0)
-                    host = remote_host;
                   else
                     {
-                      host = xmalloc (strlen (remote_user) + 1
-                                      + strlen (remote_host) + 1);
-                      stpcpy (stpcpy (stpcpy (host, remote_user), "@"),
-                              remote_host);
-                      free (remote_user);
-                      free (remote_host);
+                      char *remote_user;
+                      if (sd_session_get_remote_user (session, &remote_user) < 0)
+                        host = remote_host;
+                      else
+                        {
+                          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 (seat != NULL)
+                    a = add_utmp (a, options,
+                                  user, strlen (user),
+                                  session, strlen (session),
+                                  seat, strlen (seat),
+                                  host, strlen (host),
+                                  leader_pid /* the best we have */,
+                                  USER_PROCESS, start_ts, leader_pid, 0, 0);
+                  if (tty != NULL)
+                    a = add_utmp (a, options,
+                                  user, strlen (user),
+                                  session, strlen (session),
+                                  tty, strlen (tty),
+                                  host, strlen (host),
+                                  leader_pid /* the best we have */,
+                                  USER_PROCESS, start_ts, leader_pid, 0, 0);
+
+                  if (host != missing)
+                    free (host);
+                  if (user != missing)
+                    free (user);
                 }
 
-              if (seat != NULL)
-                a = add_utmp (a, options,
-                              user, strlen (user),
-                              session, strlen (session),
-                              seat, strlen (seat),
-                              host, strlen (host),
-                              leader_pid /* the best we have */,
-                              USER_PROCESS, start_ts, leader_pid, 0, 0);
-              if (tty != NULL)
-                a = add_utmp (a, options,
-                              user, strlen (user),
-                              session, strlen (session),
-                              tty, strlen (tty),
-                              host, strlen (host),
-                              leader_pid /* the best we have */,
-                              USER_PROCESS, start_ts, leader_pid, 0, 0);
-
-              if (host != missing)
-                free (host);
-              if (user != missing)
-                free (user);
+              if (type != missing)
+                free (type);
+              free (tty);
+              free (seat);
+              free (session);
             }
-
-          if (type != missing)
-            free (type);
-          free (tty);
-          free (seat);
-          free (session);
+          free (sessions);
         }
-      free (sessions);
     }
 
   a = finish_utmp (a);
@@ -607,6 +618,15 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
     return read_utmp_from_systemd (n_entries, utmp_buf, options);
 #  endif
 
+  if ((options & READ_UTMP_BOOT_TIME) != 0
+      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
+    {
+      /* No entries can match the given options.  */
+      *n_entries = 0;
+      *utmp_buf = NULL;
+      return 0;
+    }
+
   /* Ignore the return value for now.
      Solaris' utmpname returns 1 upon success -- which is contrary
      to what the GNU libc version does.  In addition, older GNU libc
@@ -708,6 +728,15 @@ int
 read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
+  if ((options & READ_UTMP_BOOT_TIME) != 0
+      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
+    {
+      /* No entries can match the given options.  */
+      *n_entries = 0;
+      *utmp_buf = NULL;
+      return 0;
+    }
+
   FILE *f = fopen (file, "re");
 
   if (! f)
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 9377a1b57f..88c0d94303 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -69,7 +69,7 @@ struct gl_utmp
   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 */
+  short ut_type;                /* BOOT_TIME, USER_PROCESS, or other */
   struct { int e_termination; int e_exit; } ut_exit;
 };
 
@@ -237,8 +237,10 @@ enum { UT_HOST_SIZE = -1 };
 /* Options for read_utmp.  */
 enum
   {
-    READ_UTMP_CHECK_PIDS = 1,
-    READ_UTMP_USER_PROCESS = 2
+    READ_UTMP_CHECK_PIDS   = 1,
+    READ_UTMP_USER_PROCESS = 2,
+    READ_UTMP_BOOT_TIME    = 4,
+    READ_UTMP_NO_BOOT_TIME = 8
   };
 
 /* Return a copy of (UT)->ut_user, without trailing spaces,
@@ -256,6 +258,10 @@ 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.
+   If OPTIONS & READ_UTMP_BOOT_TIME is nonzero, omit all entries except
+   the one that contains the boot time.
+   If OPTIONS & READ_UTMP_NO_BOOT_TIME is nonzero, omit the boot time
+   entries.
 
    This function is not multithread-safe, since on many platforms it
    invokes the functions setutxent, getutxent, endutxent.  These
-- 
2.34.1

From 321daa595e02717c7dca111184fc091fda3862f8 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Aug 2023 21:12:40 +0200
Subject: [PATCH 2/4] readutmp: Fix boot time in VMs after sleep state and date
 update.

* lib/readutmp.c (read_utmp_from_file): New function, extracted from
read_utmp.
(get_boot_time_uncached): Before all other approaches, try to find the
boot time in the /var/run/utmp file.
(read_utmp): Invoke read_utmp_from_file.
---
 ChangeLog      |   7 +
 lib/readutmp.c | 397 +++++++++++++++++++++++++------------------------
 2 files changed, 209 insertions(+), 195 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index c2c9613643..3725c3bf9e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2023-08-09  Bruno Haible  <br...@clisp.org>
 
+	readutmp: Fix boot time in VMs after sleep state and date update.
+	* lib/readutmp.c (read_utmp_from_file): New function, extracted from
+	read_utmp.
+	(get_boot_time_uncached): Before all other approaches, try to find the
+	boot time in the /var/run/utmp file.
+	(read_utmp): Invoke read_utmp_from_file.
+
 	readutmp: Make it easier to filter for/against the boot-time entry.
 	* lib/readutmp.h (READ_UTMP_BOOT_TIME, READ_UTMP_NO_BOOT_TIME): New
 	enum items.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index a7773c4f36..f7a839a1eb 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -281,12 +281,211 @@ finish_utmp (struct utmp_alloc a)
   return a;
 }
 
+# if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION && !HAVE_DECL_GETUTENT
+struct utmp *getutent (void);
+# endif
+
+static int
+read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
+                     int options)
+{
+  if ((options & READ_UTMP_BOOT_TIME) != 0
+      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
+    {
+      /* No entries can match the given options.  */
+      *n_entries = 0;
+      *utmp_buf = NULL;
+      return 0;
+    }
+
+  struct utmp_alloc a = {0};
+
+# if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */
+
+  /* Ignore the return value for now.
+     Solaris' utmpname returns 1 upon success -- which is contrary
+     to what the GNU libc version does.  In addition, older GNU libc
+     versions are actually void.   */
+  UTMP_NAME_FUNCTION ((char *) file);
+
+  SET_UTMP_ENT ();
+
+  void const *entry;
+
+  while ((entry = GET_UTMP_ENT ()) != NULL)
+    {
+#  if __GLIBC__ && _TIME_BITS == 64
+      /* This is a near-copy of glibc's struct utmpx, which stops working
+         after the year 2038.  Unlike the glibc version, struct utmpx32
+         describes the file format even if time_t is 64 bits.  */
+      struct utmpx32
+      {
+        short int ut_type;               /* Type of login.  */
+        pid_t ut_pid;                    /* Process ID of login process.  */
+        char ut_line[UT_LINE_SIZE];      /* Devicename.  */
+        char ut_id[UT_ID_SIZE];          /* Inittab ID.  */
+        char ut_user[UT_USER_SIZE];      /* Username.  */
+        char ut_host[UT_HOST_SIZE];      /* Hostname for remote login. */
+        struct __exit_status ut_exit;    /* Exit status of a process marked
+                                            as DEAD_PROCESS.  */
+        /* The fields ut_session and ut_tv must be the same size when compiled
+           32- and 64-bit.  This allows files and shared memory to be shared
+           between 32- and 64-bit applications.  */
+        int ut_session;                  /* Session ID, used for windowing.  */
+        struct
+        {
+          /* Seconds.  Unsigned not signed, as glibc did not exist before 1970,
+             and if the format is still in use after 2038 its timestamps
+             will surely have the sign bit on.  This hack stops working
+             at 2106-02-07 06:28:16 UTC.  */
+          unsigned int tv_sec;
+          int tv_usec;                   /* Microseconds.  */
+        } ut_tv;                         /* Time entry was made.  */
+        int ut_addr_v6[4];               /* Internet address of remote host.  */
+        char ut_reserved[20];            /* Reserved for future use.  */
+      };
+      struct utmpx32 const *ut = (struct utmpx32 const *) entry;
+#  else
+      struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
+#  endif
+
+      a = add_utmp (a, options,
+                    UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
+                    ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
+                    ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
+                    ut->ut_pid,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+                    ut->ut_type,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+                    (struct timespec) { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 },
+                    #else
+                    (struct timespec) { .tv_sec = ut->ut_time, .tv_nsec = 0 },
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
+                    ut->ut_session,
+                    #else
+                    0,
+                    #endif
+                    UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut)
+                   );
+    }
+
+  END_UTMP_ENT ();
+
+# else /* old FreeBSD, OpenBSD, HP-UX */
+
+  FILE *f = fopen (file, "re");
+
+  if (! f)
+    return -1;
+
+  for (;;)
+    {
+      struct UTMP_STRUCT_NAME ut;
+
+      if (fread (&ut, sizeof ut, 1, f) == 0)
+        break;
+      a = add_utmp (a, options,
+                    UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
+                    ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE),
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
+                    ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE),
+                    #else
+                    "", 0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
+                    ut.ut_pid,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+                    ut.ut_type,
+                    #else
+                    0,
+                    #endif
+                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+                    (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 },
+                    #else
+                    (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 },
+                    #endif
+                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
+                    ut.ut_session,
+                    #else
+                    0,
+                    #endif
+                    UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut)
+                   );
+    }
+
+  int saved_errno = ferror (f) ? errno : 0;
+  if (fclose (f) != 0)
+    saved_errno = errno;
+  if (saved_errno != 0)
+    {
+      free (a.utmp);
+      errno = saved_errno;
+      return -1;
+    }
+
+# endif
+
+  a = finish_utmp (a);
+
+  *n_entries = a.filled;
+  *utmp_buf = a.utmp;
+
+  return 0;
+}
+
 # if READUTMP_USE_SYSTEMD
 /* Use systemd and Linux /proc and kernel APIs.  */
 
 static struct timespec
 get_boot_time_uncached (void)
 {
+  /* Try to find the boot time in the /var/run/utmp file.  */
+  {
+    idx_t n_entries = 0;
+    STRUCT_UTMP *utmp = NULL;
+    read_utmp_from_file (UTMP_FILE, &n_entries, &utmp, READ_UTMP_BOOT_TIME);
+    if (n_entries > 0)
+      {
+        struct timespec result = utmp[0].ut_ts;
+        free (utmp);
+        return result;
+      }
+    free (utmp);
+  }
+
+  /* The following approaches are only usable as fallbacks, because they are
+     all of the form
+       boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
+     and therefore produce wrong values after the date has been bumped in the
+     running system, which happens frequently if the system is running in a
+     virtual machine and this VM has been put into "saved" or "sleep" state
+     and then resumed.  */
+
   /* The clock_gettime facility returns the uptime with a resolution of 1 µsec.
      It is available with glibc >= 2.14.  In glibc < 2.17 it required linking
      with librt.  */
@@ -602,211 +801,19 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
 
 # endif
 
-# if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */
-
-#  if !HAVE_UTMPX_H && HAVE_UTMP_H && !HAVE_DECL_GETUTENT
-struct utmp *getutent (void);
-#  endif
-
 int
 read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
            int options)
 {
-#  if READUTMP_USE_SYSTEMD
+# if READUTMP_USE_SYSTEMD
   if (strcmp (file, UTMP_FILE) == 0)
     /* Imitate reading UTMP_FILE, using systemd and Linux APIs.  */
     return read_utmp_from_systemd (n_entries, utmp_buf, options);
-#  endif
-
-  if ((options & READ_UTMP_BOOT_TIME) != 0
-      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
-    {
-      /* No entries can match the given options.  */
-      *n_entries = 0;
-      *utmp_buf = NULL;
-      return 0;
-    }
-
-  /* Ignore the return value for now.
-     Solaris' utmpname returns 1 upon success -- which is contrary
-     to what the GNU libc version does.  In addition, older GNU libc
-     versions are actually void.   */
-  UTMP_NAME_FUNCTION ((char *) file);
-
-  SET_UTMP_ENT ();
-
-  struct utmp_alloc a = {0};
-  void const *entry;
-
-  while ((entry = GET_UTMP_ENT ()) != NULL)
-    {
-#  if __GLIBC__ && _TIME_BITS == 64
-      /* This is a near-copy of glibc's struct utmpx, which stops working
-         after the year 2038.  Unlike the glibc version, struct utmpx32
-         describes the file format even if time_t is 64 bits.  */
-      struct utmpx32
-      {
-        short int ut_type;               /* Type of login.  */
-        pid_t ut_pid;                    /* Process ID of login process.  */
-        char ut_line[UT_LINE_SIZE];      /* Devicename.  */
-        char ut_id[UT_ID_SIZE];          /* Inittab ID.  */
-        char ut_user[UT_USER_SIZE];      /* Username.  */
-        char ut_host[UT_HOST_SIZE];      /* Hostname for remote login. */
-        struct __exit_status ut_exit;    /* Exit status of a process marked
-                                            as DEAD_PROCESS.  */
-        /* The fields ut_session and ut_tv must be the same size when compiled
-           32- and 64-bit.  This allows files and shared memory to be shared
-           between 32- and 64-bit applications.  */
-        int ut_session;                  /* Session ID, used for windowing.  */
-        struct
-        {
-          /* Seconds.  Unsigned not signed, as glibc did not exist before 1970,
-             and if the format is still in use after 2038 its timestamps
-             will surely have the sign bit on.  This hack stops working
-             at 2106-02-07 06:28:16 UTC.  */
-          unsigned int tv_sec;
-          int tv_usec;                   /* Microseconds.  */
-        } ut_tv;                         /* Time entry was made.  */
-        int ut_addr_v6[4];               /* Internet address of remote host.  */
-        char ut_reserved[20];            /* Reserved for future use.  */
-      };
-      struct utmpx32 const *ut = (struct utmpx32 const *) entry;
-#  else
-      struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
-#  endif
-
-      a = add_utmp (a, options,
-                    UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
-                    ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
-                    ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
-                    ut->ut_pid,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
-                    ut->ut_type,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
-                    (struct timespec) { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 },
-                    #else
-                    (struct timespec) { .tv_sec = ut->ut_time, .tv_nsec = 0 },
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
-                    ut->ut_session,
-                    #else
-                    0,
-                    #endif
-                    UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut)
-                   );
-    }
-
-  END_UTMP_ENT ();
-
-  a = finish_utmp (a);
-
-  *n_entries = a.filled;
-  *utmp_buf = a.utmp;
-
-  return 0;
-}
-
-# else /* old FreeBSD, OpenBSD, HP-UX */
-
-int
-read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
-           int options)
-{
-  if ((options & READ_UTMP_BOOT_TIME) != 0
-      && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
-    {
-      /* No entries can match the given options.  */
-      *n_entries = 0;
-      *utmp_buf = NULL;
-      return 0;
-    }
-
-  FILE *f = fopen (file, "re");
-
-  if (! f)
-    return -1;
-
-  struct utmp_alloc a = {0};
-
-  for (;;)
-    {
-      struct UTMP_STRUCT_NAME ut;
-
-      if (fread (&ut, sizeof ut, 1, f) == 0)
-        break;
-      a = add_utmp (a, options,
-                    UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
-                    ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE),
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
-                    ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE),
-                    #else
-                    "", 0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
-                    ut.ut_pid,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
-                    ut.ut_type,
-                    #else
-                    0,
-                    #endif
-                    #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
-                    (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 },
-                    #else
-                    (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 },
-                    #endif
-                    #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
-                    ut.ut_session,
-                    #else
-                    0,
-                    #endif
-                    UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut)
-                   );
-    }
-
-  int saved_errno = ferror (f) ? errno : 0;
-  if (fclose (f) != 0)
-    saved_errno = errno;
-  if (saved_errno != 0)
-    {
-      free (a.utmp);
-      errno = saved_errno;
-      return -1;
-    }
-
-  a = finish_utmp (a);
-
-  *n_entries = a.filled;
-  *utmp_buf = a.utmp;
+# endif
 
-  return 0;
+  return read_utmp_from_file (file, n_entries, utmp_buf, options);
 }
 
-# endif
-
 #else /* dummy fallback */
 
 int
-- 
2.34.1

>From 51f5c00413f3b2fc42520d4f520a8b9181afe531 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Aug 2023 22:27:16 +0200
Subject: [PATCH 3/4] readutmp: Return a boot time also on Alpine Linux.

* lib/readutmp.c: Include stat-time.h.
(SIZEOF): New macro.
(read_utmp_from_file) [__linux__]: Fake a BOOT_TIME entry by looking
at the time stamp of a specific file.
* modules/readutmp (Depends-on): Add stat-time.
---
 ChangeLog        |  7 +++++++
 lib/readutmp.c   | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 modules/readutmp |  1 +
 3 files changed, 56 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index 3725c3bf9e..c6e7001859 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2023-08-09  Bruno Haible  <br...@clisp.org>
 
+	readutmp: Return a boot time also on Alpine Linux.
+	* lib/readutmp.c: Include stat-time.h.
+	(SIZEOF): New macro.
+	(read_utmp_from_file) [__linux__]: Fake a BOOT_TIME entry by looking
+	at the time stamp of a specific file.
+	* modules/readutmp (Depends-on): Add stat-time.
+
 	readutmp: Fix boot time in VMs after sleep state and date update.
 	* lib/readutmp.c (read_utmp_from_file): New function, extracted from
 	read_utmp.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index f7a839a1eb..2eb3395e0c 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -38,6 +38,7 @@
 # include <time.h>
 #endif
 
+#include "stat-time.h"
 #include "xalloc.h"
 
 /* Each of the FILE streams in this file is only used in a single thread.  */
@@ -132,6 +133,8 @@
 /* Size of the ut->ut_host member.  */
 #define UT_HOST_SIZE  sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_host)
 
+#define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
+
 #if 8 <= __GNUC__
 # pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
 #endif
@@ -388,6 +391,51 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
 
   END_UTMP_ENT ();
 
+#  if defined __linux__
+  /* On Alpine Linux, UTMP_FILE is not filled.  It is always empty.
+     So, fake a BOOT_TIME entry, by getting the time stamp of a file that
+     gets touched only during the boot process.  */
+  if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
+      && strcmp (file, UTMP_FILE) == 0)
+    {
+      bool have_boot_time = false;
+      for (idx_t i = 0; i < a.filled; i++)
+        {
+          struct gl_utmp *ut = &a.utmp[i];
+          if (UT_TYPE_BOOT_TIME (ut))
+            {
+              have_boot_time = true;
+              break;
+            }
+        }
+      if (!have_boot_time)
+        {
+          const char * const boot_touched_files[] =
+            {
+              "/var/lib/systemd/random-seed", /* seen on distros with systemd */
+              "/var/run/utmp",                /* seen on distros with OpenRC */
+              "/var/lib/random-seed"          /* seen on older distros */
+            };
+          for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
+            {
+              const char *filename = boot_touched_files[i];
+              struct stat statbuf;
+              if (stat (filename, &statbuf) >= 0)
+                {
+                  struct timespec boot_time = get_stat_mtime (&statbuf);
+                  a = add_utmp (a, options,
+                                "reboot", strlen ("reboot"),
+                                "", 0,
+                                "~", strlen ("~"),
+                                "", 0,
+                                0, BOOT_TIME, boot_time, 0, 0, 0);
+                  break;
+                }
+            }
+        }
+    }
+#  endif
+
 # else /* old FreeBSD, OpenBSD, HP-UX */
 
   FILE *f = fopen (file, "re");
diff --git a/modules/readutmp b/modules/readutmp
index 15c63a3d5e..21f6de5777 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -11,6 +11,7 @@ Depends-on:
 extensions
 fopen-gnu
 idx
+stat-time
 stdbool
 stdint
 strnlen
-- 
2.34.1

>From e069091227c58e0bb4f8bfb091f102f55f181f23 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Aug 2023 22:52:22 +0200
Subject: [PATCH 4/4] readutmp: Return a boot time also on OpenBSD.

* lib/readutmp.h (BOOT_TIME, USER_PROCESS): Provide fallback
definitions.
* lib/readutmp.c (read_utmp_from_file) [__OpenBSD__]: Fake a BOOT_TIME
entry by looking at the time stamp of a specific file.
---
 ChangeLog      |  6 ++++++
 lib/readutmp.c | 36 ++++++++++++++++++++++++++++++++++++
 lib/readutmp.h |  8 ++++++++
 3 files changed, 50 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index c6e7001859..6fde91a679 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2023-08-09  Bruno Haible  <br...@clisp.org>
 
+	readutmp: Return a boot time also on OpenBSD.
+	* lib/readutmp.h (BOOT_TIME, USER_PROCESS): Provide fallback
+	definitions.
+	* lib/readutmp.c (read_utmp_from_file) [__OpenBSD__]: Fake a BOOT_TIME
+	entry by looking at the time stamp of a specific file.
+
 	readutmp: Return a boot time also on Alpine Linux.
 	* lib/readutmp.c: Include stat-time.h.
 	(SIZEOF): New macro.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 2eb3395e0c..537846c144 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -496,6 +496,42 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
       return -1;
     }
 
+#  if defined __OpenBSD__
+  /* On OpenBSD, UTMP_FILE is not filled.  It contains only dummy entries.
+     So, fake a BOOT_TIME entry, by getting the time stamp of a file that
+     gets touched only during the boot process.  */
+  if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
+      && strcmp (file, UTMP_FILE) == 0)
+    {
+      /* OpenBSD's 'struct utmp' does not have an ut_type field.  */
+      bool have_boot_time = false;
+      if (!have_boot_time)
+        {
+          const char * const boot_touched_files[] =
+            {
+              "/var/db/host.random",
+              "/var/run/utmp"
+            };
+          for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
+            {
+              const char *filename = boot_touched_files[i];
+              struct stat statbuf;
+              if (stat (filename, &statbuf) >= 0)
+                {
+                  struct timespec boot_time = get_stat_mtime (&statbuf);
+                  a = add_utmp (a, options,
+                                "reboot", strlen ("reboot"),
+                                "", 0,
+                                "", strlen (""),
+                                "", 0,
+                                0, BOOT_TIME, boot_time, 0, 0, 0);
+                  break;
+                }
+            }
+        }
+    }
+#  endif
+
 # endif
 
   a = finish_utmp (a);
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 88c0d94303..b74d37cde3 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -211,6 +211,14 @@ enum { UT_HOST_SIZE = -1 };
 # define WTMP_FILE "/etc/wtmp"
 #endif
 
+/* Some platforms, such as OpenBSD, don't have an ut_type field and don't have
+   the BOOT_TIME and USER_PROCESS macros.  But we want to support them in
+   'struct gl_utmp'.  */
+#if !(HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
+# define BOOT_TIME 2
+# define USER_PROCESS 0
+#endif
+
 /* Macros that test (UT)->ut_type.  */
 #ifdef BOOT_TIME
 # define UT_TYPE_BOOT_TIME(UT) UT_TYPE_EQ (UT, BOOT_TIME)
-- 
2.34.1

Reply via email to