This set of patches straightens the code of the 'boot-time' module:
Determining the boot time does require iterating through the /var/run/utmpx
file, but it does not require
  - memory allocation,
  - copying of strings,
  - evaluating an options parameter,
  - linking with libsystemd.

So, let me replace the code of this module with a simplified copy of
lib/readutmp.c, while at the same time avoiding large amounts of code
duplication.

Paul: With this simplification, you may consider using the 'boot-time' module
in Emacs. I bet that it produces a better result than Emacs' src/filelock.c
on many platforms. (I haven't tested it, but I could test it if you give me
a manual testing recipe.)


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

        boot-time: Add comment about multithread-safety.
        * lib/boot-time.h (get_boot_time): Add comment, same as in readutmp.h.

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

        boot-time: Simplify execution.
        * lib/boot-time.c: Include <stdio.h>, <string.h>, <sys/types.h>,
        <sys/stat.h>, <sys/sysinfo.h>, <time.h>, stat-time.h, unlocked-io.h,
        boot-time-aux.h.
        (UT_USER): New macro, from lib/readutmp.c.
        (getutent): New declaration.
        (get_boot_time_uncached): New function, containing a simplified code
        from lib/readutmp.c.
        (get_boot_time): Don't invoke read_utmp. Instead, invoke
        get_boot_time_uncached and cache the result.
        * modules/boot-time (Files): Add lib/boot-time-aux.h, lib/readutmp.h,
        m4/readutmp.m4.
        (Depends-on): Remove readutmp. Add extensions, fopen-gnu, stat-time,
        stdbool, time-h, timespec_get, unlocked-io-internal.
        (configure.ac): Invoke gl_PREREQ_READUTMP_H.
        (Link): Remove $(READUTMP_LIB). Add $(CLOCK_TIME_LIB).
        * modules/boot-time-tests (Makefile.am): Link test-boot-time with
        $(CLOCK_TIME_LIB), not with $(READUTMP_LIB).

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

        readutmp: Refactor boot time determination code.
        * lib/boot-time-aux.h: New file, extracted from lib/readutmp.c.
        * lib/readutmp.c: On Linux, include <sys/sysinfo.h> even if
        !READUTMP_USE_SYSTEMD.
        Include boot-time-aux.h.
        (SIZEOF): Remove macro, moved to boot-time-aux.h.
        (get_linux_uptime): Remove function, moved to boot-time-aux.h.
        (read_utmp_from_file): Invoke get_linux_boot_time_fallback,
        get_android_boot_time, get_openbsd_boot_time, get_windows_boot_time.
        Code moved to boot-time-aux.h.
        (get_boot_time_uncached: Invoke get_linux_boot_time_final_fallback.
        Code moved to boot-time-aux.h.
        * m4/readutmp.m4 (gl_PREREQ_READUTMP_H): New macro, extracted from
        gl_READUTMP.
        (gl_READUTMP): Invoke it.
        * modules/readutmp (Files): Add lib/boot-time-aux.h.

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

        readutmp: Make 'struct utmpx32' usable by other code.
        * lib/readutmp.h (struct utmpx32): Moved to here from lib/readutmp.c.
        (UTMP_STRUCT_NAME): Define as utmpx32 if needed.
        * lib/readutmp.c (read_utmp_from_file): Simply use UTMP_STRUCT_NAME.

From cdbe33c428a82d42ff6fdcb48815edeb9d9d7fd8 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 11 Aug 2023 16:09:22 +0200
Subject: [PATCH 1/4] readutmp: Make 'struct utmpx32' usable by other code.

* lib/readutmp.h (struct utmpx32): Moved to here from lib/readutmp.c.
(UTMP_STRUCT_NAME): Define as utmpx32 if needed.
* lib/readutmp.c (read_utmp_from_file): Simply use UTMP_STRUCT_NAME.
---
 ChangeLog      |  7 +++++++
 lib/readutmp.c | 33 ---------------------------------
 lib/readutmp.h | 35 ++++++++++++++++++++++++++++++++++-
 3 files changed, 41 insertions(+), 34 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 526e5cb4be..6836ca6308 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2023-08-11  Bruno Haible  <br...@clisp.org>
+
+	readutmp: Make 'struct utmpx32' usable by other code.
+	* lib/readutmp.h (struct utmpx32): Moved to here from lib/readutmp.c.
+	(UTMP_STRUCT_NAME): Define as utmpx32 if needed.
+	* lib/readutmp.c (read_utmp_from_file): Simply use UTMP_STRUCT_NAME.
+
 2023-08-11  Bruno Haible  <br...@clisp.org>
 
 	readutmp tests: Fix link error.
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 40cee0e58c..00dbbcea48 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -383,40 +383,7 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
 
   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
 
       struct timespec ts =
         #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
diff --git a/lib/readutmp.h b/lib/readutmp.h
index 70cd3801db..1cf588d265 100644
--- a/lib/readutmp.h
+++ b/lib/readutmp.h
@@ -132,7 +132,40 @@ enum { UT_HOST_SIZE = -1 };
    ⎣ ut_ss        struct sockaddr_storage    NetBSD, Minix
  */
 
-# define UTMP_STRUCT_NAME utmpx
+# 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_LINESIZE];     /* Devicename.  */
+  char ut_id[4];                   /* Inittab ID.  */
+  char ut_user[__UT_USERSIZE];     /* Username.  */
+  char ut_host[__UT_HOSTSIZE];     /* 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.  */
+};
+#  define UTMP_STRUCT_NAME utmpx32
+# else
+#  define UTMP_STRUCT_NAME utmpx
+# endif
 # define SET_UTMP_ENT setutxent
 # define GET_UTMP_ENT getutxent
 # define END_UTMP_ENT endutxent
-- 
2.34.1

From 1657e67fe3f4be3cd572430358991bc12c62b193 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 11 Aug 2023 23:20:44 +0200
Subject: [PATCH 2/4] readutmp: Refactor boot time determination code.

* lib/boot-time-aux.h: New file, extracted from lib/readutmp.c.
* lib/readutmp.c: On Linux, include <sys/sysinfo.h> even if
!READUTMP_USE_SYSTEMD.
Include boot-time-aux.h.
(SIZEOF): Remove macro, moved to boot-time-aux.h.
(get_linux_uptime): Remove function, moved to boot-time-aux.h.
(read_utmp_from_file): Invoke get_linux_boot_time_fallback,
get_android_boot_time, get_openbsd_boot_time, get_windows_boot_time.
Code moved to boot-time-aux.h.
(get_boot_time_uncached: Invoke get_linux_boot_time_final_fallback.
Code moved to boot-time-aux.h.
* m4/readutmp.m4 (gl_PREREQ_READUTMP_H): New macro, extracted from
gl_READUTMP.
(gl_READUTMP): Invoke it.
* modules/readutmp (Files): Add lib/boot-time-aux.h.
---
 ChangeLog           |  19 ++++
 lib/boot-time-aux.h | 228 ++++++++++++++++++++++++++++++++++++++++++++
 lib/readutmp.c      | 208 ++++++++--------------------------------
 m4/readutmp.m4      |  14 ++-
 modules/readutmp    |   1 +
 5 files changed, 299 insertions(+), 171 deletions(-)
 create mode 100644 lib/boot-time-aux.h

diff --git a/ChangeLog b/ChangeLog
index 6836ca6308..29d5c73907 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2023-08-11  Bruno Haible  <br...@clisp.org>
+
+	readutmp: Refactor boot time determination code.
+	* lib/boot-time-aux.h: New file, extracted from lib/readutmp.c.
+	* lib/readutmp.c: On Linux, include <sys/sysinfo.h> even if
+	!READUTMP_USE_SYSTEMD.
+	Include boot-time-aux.h.
+	(SIZEOF): Remove macro, moved to boot-time-aux.h.
+	(get_linux_uptime): Remove function, moved to boot-time-aux.h.
+	(read_utmp_from_file): Invoke get_linux_boot_time_fallback,
+	get_android_boot_time, get_openbsd_boot_time, get_windows_boot_time.
+	Code moved to boot-time-aux.h.
+	(get_boot_time_uncached: Invoke get_linux_boot_time_final_fallback.
+	Code moved to boot-time-aux.h.
+	* m4/readutmp.m4 (gl_PREREQ_READUTMP_H): New macro, extracted from
+	gl_READUTMP.
+	(gl_READUTMP): Invoke it.
+	* modules/readutmp (Files): Add lib/boot-time-aux.h.
+
 2023-08-11  Bruno Haible  <br...@clisp.org>
 
 	readutmp: Make 'struct utmpx32' usable by other code.
diff --git a/lib/boot-time-aux.h b/lib/boot-time-aux.h
new file mode 100644
index 0000000000..fc84086f3f
--- /dev/null
+++ b/lib/boot-time-aux.h
@@ -0,0 +1,228 @@
+/* Auxiliary functions for determining the time when the machine last booted.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published
+   by the Free Software Foundation, either version 3 of the License,
+   or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>.  */
+
+#define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
+
+#if defined __linux__ || defined __ANDROID__
+
+/* Store the uptime counter, as managed by the Linux kernel, in *P_UPTIME.
+   Return 0 upon success, -1 upon failure.  */
+_GL_ATTRIBUTE_MAYBE_UNUSED
+static int
+get_linux_uptime (struct timespec *p_uptime)
+{
+  /* 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.  */
+# if (__GLIBC__ + (__GLIBC_MINOR__ >= 17) > 2) || defined __ANDROID__
+  if (clock_gettime (CLOCK_BOOTTIME, p_uptime) >= 0)
+    return 0;
+# endif
+
+  /* /proc/uptime contains the uptime with a resolution of 0.01 sec.
+     But it does not have read permissions on Android.  */
+# if !defined __ANDROID__
+  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)
+            {
+              p_uptime->tv_sec = (time_t) uptime;
+              p_uptime->tv_nsec = (uptime - p_uptime->tv_sec) * 1e9 + 0.5;
+              return 0;
+            }
+        }
+    }
+# endif
+
+  /* The sysinfo call returns the uptime with a resolution of 1 sec only.  */
+  struct sysinfo info;
+  if (sysinfo (&info) >= 0)
+    {
+      p_uptime->tv_sec = info.uptime;
+      p_uptime->tv_nsec = 0;
+      return 0;
+    }
+
+  return -1;
+}
+
+#endif
+
+#if defined __linux__ && !defined __ANDROID__
+
+static int
+get_linux_boot_time_fallback (struct timespec *p_boot_time)
+{
+  /* On Alpine Linux, UTMP_FILE is not filled.  It is always empty.
+     So, get the time stamp of a file that gets touched only during the
+     boot process.  */
+
+  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)
+        {
+          *p_boot_time = get_stat_mtime (&statbuf);
+          return 0;
+        }
+    }
+  return -1;
+}
+
+# if NEED_BOOT_TIME_FINAL_FALLBACK
+
+/* The following approach is only usable as a fallback, because it is of
+   the form
+     boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
+   and therefore produces 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.  */
+static int
+get_linux_boot_time_final_fallback (struct timespec *p_boot_time)
+{
+  struct timespec uptime;
+  if (get_linux_uptime (&uptime) >= 0)
+    {
+      struct timespec result;
+      /* equivalent to:
+      if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
+      */
+      if (timespec_get (&result, TIME_UTC) >= 0)
+        {
+          if (result.tv_nsec < uptime.tv_nsec)
+            {
+              result.tv_nsec += 1000000000;
+              result.tv_sec -= 1;
+            }
+          result.tv_sec -= uptime.tv_sec;
+          result.tv_nsec -= uptime.tv_nsec;
+          *p_boot_time = result;
+          return 0;
+        }
+    }
+  return -1;
+}
+
+# endif
+
+#endif
+
+#if defined __ANDROID__
+
+static int
+get_android_boot_time (struct timespec *p_boot_time)
+{
+  /* On Android, there is no /var, and normal processes don't have access
+     to system files.  Therefore use the kernel's uptime counter, although
+     it produces wrong values after the date has been bumped in the running
+     system.  */
+  struct timespec uptime;
+  if (get_linux_uptime (&uptime) >= 0)
+    {
+      struct timespec result;
+      if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
+        {
+          if (result.tv_nsec < uptime.tv_nsec)
+            {
+              result.tv_nsec += 1000000000;
+              result.tv_sec -= 1;
+            }
+          result.tv_sec -= uptime.tv_sec;
+          result.tv_nsec -= uptime.tv_nsec;
+          *p_boot_time = result;
+          return 0;
+        }
+    }
+  return -1;
+}
+
+#endif
+
+#if defined __OpenBSD__
+
+static int
+get_openbsd_boot_time (struct timespec *p_boot_time)
+{
+  /* On OpenBSD, UTMP_FILE is not filled.  It contains only dummy entries.
+     So, get the time stamp of a file that gets touched only during the
+     boot process.  */
+  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)
+        {
+          *p_boot_time = get_stat_mtime (&statbuf);
+          return 0;
+        }
+    }
+  return -1;
+}
+
+#endif
+
+# if defined __CYGWIN__ || defined _WIN32
+
+static int
+get_windows_boot_time (struct timespec *p_boot_time)
+{
+  /* On Cygwin, /var/run/utmp is empty.
+     On native Windows, <utmpx.h> and <utmp.h> don't exist.
+     Instead, on Windows, the boot time can be retrieved by looking at the
+     time stamp of a file that (normally) gets touched only during the boot
+     process, namely C:\pagefile.sys.  */
+  const char * const boot_touched_file =
+    #if defined __CYGWIN__ && !defined _WIN32
+    "/cygdrive/c/pagefile.sys"
+    #else
+    "C:\\pagefile.sys"
+    #endif
+    ;
+  struct stat statbuf;
+  if (stat (boot_touched_file, &statbuf) >= 0)
+    {
+      *p_boot_time = get_stat_mtime (&statbuf);
+      return 0;
+    }
+  return -1;
+}
+
+# endif
diff --git a/lib/readutmp.c b/lib/readutmp.c
index 00dbbcea48..f2e66899bb 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -31,7 +31,7 @@
 #include <stdlib.h>
 #include <stdint.h>
 
-#if READUTMP_USE_SYSTEMD || defined __ANDROID__
+#if defined __linux__ || defined __ANDROID__
 # include <sys/sysinfo.h>
 # include <time.h>
 #endif
@@ -46,6 +46,10 @@
 /* Each of the FILE streams in this file is only used in a single thread.  */
 #include "unlocked-io.h"
 
+/* Some helper functions.  */
+#define NEED_BOOT_TIME_FINAL_FALLBACK READUTMP_USE_SYSTEMD
+#include "boot-time-aux.h"
+
 /* The following macros describe the 'struct UTMP_STRUCT_NAME',
    *not* 'struct gl_utmp'.  */
 #undef UT_USER
@@ -135,8 +139,6 @@
 /* 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
@@ -288,60 +290,6 @@ finish_utmp (struct utmp_alloc a)
   return a;
 }
 
-# if READUTMP_USE_SYSTEMD || defined __ANDROID__
-
-/* Store the uptime counter, as managed by the Linux kernel, in *P_UPTIME.
-   Return 0 upon success, -1 upon failure.  */
-static int
-get_linux_uptime (struct timespec *p_uptime)
-{
-  /* 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.  */
-#  if (__GLIBC__ + (__GLIBC_MINOR__ >= 17) > 2) || defined __ANDROID__
-  if (clock_gettime (CLOCK_BOOTTIME, p_uptime) >= 0)
-    return 0;
-#  endif
-
-  /* /proc/uptime contains the uptime with a resolution of 0.01 sec.
-     But it does not have read permissions on Android.  */
-#  if !defined __ANDROID__
-  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)
-            {
-              p_uptime->tv_sec = (time_t) uptime;
-              p_uptime->tv_nsec = (uptime - p_uptime->tv_sec) * 1e9 + 0.5;
-              return 0;
-            }
-        }
-    }
-#  endif
-
-  /* The sysinfo call returns the uptime with a resolution of 1 sec only.  */
-  struct sysinfo info;
-  if (sysinfo (&info) >= 0)
-    {
-      p_uptime->tv_sec = info.uptime;
-      p_uptime->tv_nsec = 0;
-      return 0;
-    }
-
-  return -1;
-}
-
-# endif
-
 # if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION && !HAVE_DECL_GETUTENT
 struct utmp *getutent (void);
 # endif
@@ -472,28 +420,14 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
       if (!have_boot_time)
         {
           /* Workaround for Alpine Linux:  */
-          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;
-                }
-            }
+          struct timespec boot_time;
+          if (get_linux_boot_time_fallback (&boot_time) >= 0)
+            a = add_utmp (a, options,
+                          "reboot", strlen ("reboot"),
+                          "", 0,
+                          "~", strlen ("~"),
+                          "", 0,
+                          0, BOOT_TIME, boot_time, 0, 0, 0);
         }
     }
 #   endif
@@ -518,28 +452,14 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
         }
       if (!have_boot_time)
         {
-          struct timespec uptime;
-          if (get_linux_uptime (&uptime) >= 0)
-            {
-              struct timespec result;
-              if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
-                {
-                  if (result.tv_nsec < uptime.tv_nsec)
-                    {
-                      result.tv_nsec += 1000000000;
-                      result.tv_sec -= 1;
-                    }
-                  result.tv_sec -= uptime.tv_sec;
-                  result.tv_nsec -= uptime.tv_nsec;
-                  struct timespec boot_time = result;
-                  a = add_utmp (a, options,
-                                "reboot", strlen ("reboot"),
-                                "", 0,
-                                "", 0,
-                                "", 0,
-                                0, BOOT_TIME, boot_time, 0, 0, 0);
-                }
-            }
+          struct timespec boot_time;
+          if (get_android_boot_time (&boot_time) >= 0)
+            a = add_utmp (a, options,
+                          "reboot", strlen ("reboot"),
+                          "", 0,
+                          "", 0,
+                          "", 0,
+                          0, BOOT_TIME, boot_time, 0, 0, 0);
         }
     }
 #   endif
@@ -629,9 +549,6 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
     }
 
 #   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)
     {
@@ -639,26 +556,16 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
       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++)
+          struct timespec boot_time;
+          if (get_openbsd_boot_time (&boot_time) >= 0)
             {
-              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,
-                                "", 0,
-                                "", 0,
-                                0, BOOT_TIME, boot_time, 0, 0, 0);
-                  break;
-                }
+              a = add_utmp (a, options,
+                            "reboot", strlen ("reboot"),
+                            "", 0,
+                            "", 0,
+                            "", 0,
+                            0, BOOT_TIME, boot_time, 0, 0, 0);
+              break;
             }
         }
     }
@@ -678,24 +585,14 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
       && strcmp (file, UTMP_FILE) == 0
       && a.filled == 0)
     {
-      const char * const boot_touched_file =
-        #if defined __CYGWIN__ && !defined _WIN32
-        "/cygdrive/c/pagefile.sys"
-        #else
-        "C:\\pagefile.sys"
-        #endif
-        ;
-      struct stat statbuf;
-      if (stat (boot_touched_file, &statbuf) >= 0)
-        {
-          struct timespec boot_time = get_stat_mtime (&statbuf);
-          a = add_utmp (a, options,
-                        "reboot", strlen ("reboot"),
-                        "", 0,
-                        "", 0,
-                        "", 0,
-                        0, BOOT_TIME, boot_time, 0, 0, 0);
-        }
+      struct timespec boot_time;
+      if (get_windows_boot_time (&boot_time) >= 0)
+        a = add_utmp (a, options,
+                      "reboot", strlen ("reboot"),
+                      "", 0,
+                      "", 0,
+                      "", 0,
+                      0, BOOT_TIME, boot_time, 0, 0, 0);
     }
 # endif
 
@@ -727,33 +624,10 @@ get_boot_time_uncached (void)
     free (utmp);
   }
 
-  /* The following approach is only usable as a fallback, because it is of
-     the form
-       boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
-     and therefore produces 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.  */
   {
-    struct timespec uptime;
-    if (get_linux_uptime (&uptime) >= 0)
-      {
-        struct timespec result;
-        /* equivalent to:
-        if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
-        */
-        if (timespec_get (&result, TIME_UTC) >= 0)
-          {
-            if (result.tv_nsec < uptime.tv_nsec)
-              {
-                result.tv_nsec += 1000000000;
-                result.tv_sec -= 1;
-              }
-            result.tv_sec -= uptime.tv_sec;
-            result.tv_nsec -= uptime.tv_nsec;
-            return result;
-          }
-      }
+    struct timespec boot_time;
+    if (get_linux_boot_time_final_fallback (&boot_time) >= 0)
+      return boot_time;
   }
 
   /* We shouldn't get here.  */
diff --git a/m4/readutmp.m4 b/m4/readutmp.m4
index 6ba5b2e225..07eebcd43d 100644
--- a/m4/readutmp.m4
+++ b/m4/readutmp.m4
@@ -1,4 +1,4 @@
-# readutmp.m4 serial 23
+# readutmp.m4 serial 24
 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,
@@ -6,9 +6,6 @@
 
 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
@@ -45,6 +42,15 @@ AC_DEFUN([gl_READUTMP]
   fi
   AC_SUBST([READUTMP_LIB])
 
+  gl_PREREQ_READUTMP_H
+])
+
+# Prerequisites of readutmp.h.
+AC_DEFUN_ONCE([gl_PREREQ_READUTMP_H],
+[
+  dnl Persuade utmpx.h to declare utmpxname
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
   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/modules/readutmp b/modules/readutmp
index 21f6de5777..85bbf74954 100644
--- a/modules/readutmp
+++ b/modules/readutmp
@@ -4,6 +4,7 @@ Read entire utmp file into memory.
 Files:
 lib/readutmp.h
 lib/readutmp.c
+lib/boot-time-aux.h
 m4/readutmp.m4
 m4/systemd.m4
 
-- 
2.34.1

>From bea15dd14f8388a4b78b63ecfa0decad0414ec88 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 11 Aug 2023 23:30:01 +0200
Subject: [PATCH 3/4] boot-time: Simplify execution.

* lib/boot-time.c: Include <stdio.h>, <string.h>, <sys/types.h>,
<sys/stat.h>, <sys/sysinfo.h>, <time.h>, stat-time.h, unlocked-io.h,
boot-time-aux.h.
(UT_USER): New macro, from lib/readutmp.c.
(getutent): New declaration.
(get_boot_time_uncached): New function, containing a simplified code
from lib/readutmp.c.
(get_boot_time): Don't invoke read_utmp. Instead, invoke
get_boot_time_uncached and cache the result.
* modules/boot-time (Files): Add lib/boot-time-aux.h, lib/readutmp.h,
m4/readutmp.m4.
(Depends-on): Remove readutmp. Add extensions, fopen-gnu, stat-time,
stdbool, time-h, timespec_get, unlocked-io-internal.
(configure.ac): Invoke gl_PREREQ_READUTMP_H.
(Link): Remove $(READUTMP_LIB). Add $(CLOCK_TIME_LIB).
* modules/boot-time-tests (Makefile.am): Link test-boot-time with
$(CLOCK_TIME_LIB), not with $(READUTMP_LIB).
---
 ChangeLog               |  21 ++++
 lib/boot-time.c         | 223 ++++++++++++++++++++++++++++++++++++++--
 modules/boot-time       |  14 ++-
 modules/boot-time-tests |   2 +-
 4 files changed, 250 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 29d5c73907..bfc2954a80 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2023-08-11  Bruno Haible  <br...@clisp.org>
+
+	boot-time: Simplify execution.
+	* lib/boot-time.c: Include <stdio.h>, <string.h>, <sys/types.h>,
+	<sys/stat.h>, <sys/sysinfo.h>, <time.h>, stat-time.h, unlocked-io.h,
+	boot-time-aux.h.
+	(UT_USER): New macro, from lib/readutmp.c.
+	(getutent): New declaration.
+	(get_boot_time_uncached): New function, containing a simplified code
+	from lib/readutmp.c.
+	(get_boot_time): Don't invoke read_utmp. Instead, invoke
+	get_boot_time_uncached and cache the result.
+	* modules/boot-time (Files): Add lib/boot-time-aux.h, lib/readutmp.h,
+	m4/readutmp.m4.
+	(Depends-on): Remove readutmp. Add extensions, fopen-gnu, stat-time,
+	stdbool, time-h, timespec_get, unlocked-io-internal.
+	(configure.ac): Invoke gl_PREREQ_READUTMP_H.
+	(Link): Remove $(READUTMP_LIB). Add $(CLOCK_TIME_LIB).
+	* modules/boot-time-tests (Makefile.am): Link test-boot-time with
+	$(CLOCK_TIME_LIB), not with $(READUTMP_LIB).
+
 2023-08-11  Bruno Haible  <br...@clisp.org>
 
 	readutmp: Refactor boot time determination code.
diff --git a/lib/boot-time.c b/lib/boot-time.c
index fe1e13eccb..932ee22d69 100644
--- a/lib/boot-time.c
+++ b/lib/boot-time.c
@@ -22,22 +22,231 @@
 #include "boot-time.h"
 
 #include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined __linux__ || defined __ANDROID__
+# include <sys/sysinfo.h>
+# include <time.h>
+#endif
 
 #include "idx.h"
 #include "readutmp.h"
+#include "stat-time.h"
+
+/* Each of the FILE streams in this file is only used in a single thread.  */
+#include "unlocked-io.h"
+
+/* Some helper functions.  */
+#define NEED_BOOT_TIME_FINAL_FALLBACK 1
+#include "boot-time-aux.h"
+
+/* The following macros describe the 'struct UTMP_STRUCT_NAME',
+   *not* 'struct gl_utmp'.  */
+#undef UT_USER
+
+/* Accessor macro for the member named ut_user or ut_name.  */
+#if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \
+     : HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME)
+# define UT_USER(UT) ((UT)->ut_name)
+#else
+# define UT_USER(UT) ((UT)->ut_user)
+#endif
+
+#if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION && !HAVE_DECL_GETUTENT
+struct utmp *getutent (void);
+#endif
+
+#if defined __linux__ || HAVE_UTMPX_H || HAVE_UTMP_H || defined __CYGWIN__ || defined _WIN32
+
+static int
+get_boot_time_uncached (struct timespec *p_boot_time)
+{
+  struct timespec found_boot_time = {0};
+
+# if !(HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE) /* old FreeBSD, OpenBSD, native Windows */
+
+#  if defined __OpenBSD__
+  /* Workaround for OpenBSD:  */
+  get_openbsd_boot_time (&found_boot_time);
+#  endif
+
+# else
+
+  /* Try to find the boot time in the /var/run/utmp file.  */
+
+#  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 *) UTMP_FILE);
+
+  SET_UTMP_ENT ();
+
+#   if (defined __linux__ && !defined __ANDROID__) || defined __minix
+  /* Timestamp of the "runlevel" entry, if any.  */
+  struct timespec runlevel_ts = {0};
+#   endif
+
+  void const *entry;
+
+  while ((entry = GET_UTMP_ENT ()) != NULL)
+    {
+      struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
+
+      struct timespec ts =
+        #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+        { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 };
+        #else
+        { .tv_sec = ut->ut_time, .tv_nsec = 0 };
+        #endif
+
+      if (ut->ut_type == BOOT_TIME)
+        found_boot_time = ts;
+
+#   if defined __linux__ && !defined __ANDROID__
+      if (memcmp (UT_USER (ut), "runlevel", strlen ("runlevel") + 1) == 0
+          && memcmp (ut->ut_line, "~", strlen ("~") + 1) == 0)
+        runlevel_ts = ts;
+#   endif
+#   if defined __minix
+      if (UT_USER (ut)[0] == '\0'
+          && memcmp (ut->ut_line, "run-level ", strlen ("run-level ")) == 0)
+        runlevel_ts = ts;
+#   endif
+    }
+
+  END_UTMP_ENT ();
+
+#   if defined __linux__ && !defined __ANDROID__
+  /* On Raspbian, which runs on hardware without a real-time clock, during boot,
+       1. the clock gets set to 1970-01-01 00:00:00,
+       2. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
+          ut_user = "reboot", ut_line = "~", time = 1970-01-01 00:00:05 or so,
+       3. the clock gets set to a correct value through NTP,
+       4. an entry gets written into /var/run/utmp, with
+          ut_user = "runlevel", ut_line = "~", time = correct value.
+     In this case, get the time from the "runlevel" entry.  */
+
+  /* Workaround for Raspbian:  */
+  if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
+    found_boot_time = runlevel_ts;
+  if (found_boot_time.tv_sec == 0)
+    {
+      /* Workaround for Alpine Linux:  */
+      get_linux_boot_time_fallback (&found_boot_time);
+    }
+#   endif
+
+#   if defined __ANDROID__
+  if (found_boot_time.tv_sec == 0)
+    {
+      /* Workaround for Android:  */
+      get_android_boot_time (&found_boot_time);
+    }
+#   endif
+
+#   if defined __minix
+  /* On Minix, during boot,
+       1. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
+          ut_user = "", ut_line = "system boot", time = 1970-01-01 00:00:00,
+       2. an entry gets written into /var/run/utmp, with
+          ut_user = "", ut_line = "run-level m", time = correct value.
+     In this case, copy the time from the "run-level m" entry to the
+     "system boot" entry.  */
+  if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
+    found_boot_time = runlevel_ts;
+#   endif
+
+#  else /* HP-UX */
+
+  FILE *f = fopen (UTMP_FILE, "re");
+
+  if (f != NULL)
+    {
+      for (;;)
+        {
+          struct UTMP_STRUCT_NAME ut;
+
+          if (fread (&ut, sizeof ut, 1, f) == 0)
+            break;
+
+          struct timespec ts =
+            #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
+            { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 };
+            #else
+            { .tv_sec = ut.ut_time, .tv_nsec = 0 };
+            #endif
+
+          if (ut.ut_type == BOOT_TIME)
+            found_boot_time = ts;
+        }
+
+      fclose (f);
+    }
+
+#  endif
+
+#  if defined __linux__ && !defined __ANDROID__
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_linux_boot_time_final_fallback (&found_boot_time);
+    }
+#  endif
+
+# endif
+
+# if defined __CYGWIN__ || defined _WIN32
+  if (found_boot_time.tv_sec == 0)
+    {
+      /* Workaround for Windows:  */
+      get_windows_boot_time (&found_boot_time);
+    }
+# endif
+
+  if (found_boot_time.tv_sec != 0)
+    {
+      *p_boot_time = found_boot_time;
+      return 0;
+    }
+  else
+    return -1;
+}
 
 int
 get_boot_time (struct timespec *p_boot_time)
 {
-  idx_t n_entries = 0;
-  STRUCT_UTMP *utmp = NULL;
-  read_utmp (UTMP_FILE, &n_entries, &utmp, READ_UTMP_BOOT_TIME);
-  if (n_entries > 0)
+  /* Cache the result from get_boot_time_uncached.  */
+  static int volatile cached_result = -1;
+  static struct timespec volatile cached_boot_time;
+
+  if (cached_result < 0)
+    {
+      struct timespec boot_time;
+      int result = get_boot_time_uncached (&boot_time);
+      cached_boot_time = boot_time;
+      cached_result = result;
+    }
+
+  if (cached_result == 0)
     {
-      *p_boot_time = utmp[0].ut_ts;
-      free (utmp);
+      *p_boot_time = cached_boot_time;
       return 0;
     }
-  free (utmp);
+  else
+    return -1;
+}
+
+#else
+
+int
+get_boot_time (struct timespec *p_boot_time)
+{
   return -1;
 }
+
+#endif
diff --git a/modules/boot-time b/modules/boot-time
index 99ae5c9fe7..ba8a2b4e6f 100644
--- a/modules/boot-time
+++ b/modules/boot-time
@@ -4,12 +4,22 @@ Determine the time when the machine last booted.
 Files:
 lib/boot-time.h
 lib/boot-time.c
+lib/boot-time-aux.h
+lib/readutmp.h
+m4/readutmp.m4
 
 Depends-on:
+extensions
+fopen-gnu
 idx
-readutmp
+stat-time
+stdbool
+time-h
+timespec_get
+unlocked-io-internal
 
 configure.ac:
+gl_PREREQ_READUTMP_H
 
 Makefile.am:
 lib_SOURCES += boot-time.c
@@ -18,7 +28,7 @@ Include:
 "boot-time.h"
 
 Link:
-$(READUTMP_LIB)
+$(CLOCK_TIME_LIB)
 
 License:
 GPL
diff --git a/modules/boot-time-tests b/modules/boot-time-tests
index 0ba9d467ca..bb56ee36e5 100644
--- a/modules/boot-time-tests
+++ b/modules/boot-time-tests
@@ -8,4 +8,4 @@ configure.ac:
 Makefile.am:
 TESTS += test-boot-time
 check_PROGRAMS += test-boot-time
-test_boot_time_LDADD = $(LDADD) $(READUTMP_LIB)
+test_boot_time_LDADD = $(LDADD) $(CLOCK_TIME_LIB)
-- 
2.34.1

>From 5cd9573bc638b433eae64331dc61188eae6e434e Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Fri, 11 Aug 2023 23:31:37 +0200
Subject: [PATCH 4/4] boot-time: Add comment about multithread-safety.

* lib/boot-time.h (get_boot_time): Add comment, same as in readutmp.h.
---
 ChangeLog       | 5 +++++
 lib/boot-time.h | 8 +++++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index bfc2954a80..472349e5e4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2023-08-11  Bruno Haible  <br...@clisp.org>
+
+	boot-time: Add comment about multithread-safety.
+	* lib/boot-time.h (get_boot_time): Add comment, same as in readutmp.h.
+
 2023-08-11  Bruno Haible  <br...@clisp.org>
 
 	boot-time: Simplify execution.
diff --git a/lib/boot-time.h b/lib/boot-time.h
index 159a0056f9..401e854adb 100644
--- a/lib/boot-time.h
+++ b/lib/boot-time.h
@@ -27,7 +27,13 @@ extern "C" {
 
 
 /* Store the approximate time when the machine last booted in *P_BOOT_TIME,
-   and return 0.  If it cannot be determined, return -1.  */
+   and return 0.  If it cannot be determined, return -1.
+
+   This function is not multithread-safe, since on many platforms it
+   invokes the functions setutxent, getutxent, endutxent.  These
+   functions are needed because they may lock FILE (so that we don't
+   read garbage when a concurrent process writes to FILE), but their
+   drawback is that they have a common global state.  */
 extern int get_boot_time (struct timespec *p_boot_time);
 
 
-- 
2.34.1

Reply via email to