all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Paul Eggert <eggert@cs.ucla.edu>
To: Bruno Haible <bruno@clisp.org>
Cc: bug-gnulib@gnu.org, Emacs Development <Emacs-devel@gnu.org>
Subject: Re: boot-time: straighten code
Date: Sat, 12 Aug 2023 19:49:04 -0700	[thread overview]
Message-ID: <5a77c0b9-8692-83bf-1aca-2f1d27122877@cs.ucla.edu> (raw)
In-Reply-To: <4536176.VaOIPsP7d9@nimes>

[-- Attachment #1: Type: text/plain, Size: 927 bytes --]

On 2023-08-11 14:49, Bruno Haible wrote:
> 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.)

Thanks for doing all that. I installed the attached patch into Emacs 
master, which you should be able to test via:

	git clone https://git.savannah.gnu.org/git/emacs.git
	cd emacs
	./autogen.sh
	./configure
	make
	src/emacs

Please give it a try, especially on any MS-Windows platform you happen 
to have. I have tested only on Ubuntu 23.04 so far.

A simple way to test is to use Emacs to start editing a file (without 
saving) and then inspect the symbolic link .#* that Emacs uses as a lock 
file. The trailing digits of that link's contents should be the boot 
time. These symlinks are Emacs's only use of boot time.

[-- Attachment #2: 0001-Improve-boot-time-gathering.patch --]
[-- Type: text/x-patch, Size: 51156 bytes --]

From 5e736ca6ccfa131736ab0b3a298de2cb319e7dfb Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat, 12 Aug 2023 19:39:11 -0700
Subject: [PATCH] Improve boot-time gathering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Simplify Emacs proper by using Gnulib’s boot-time module
instead of doing it all by hand.  This should port Emacs
better to obscurish hosts, as Bruno Haible has merged the
best of Emacs’s and Gnulib’s boot-time gathering.
* lib/boot-time-aux.h, lib/boot-time.c, lib/boot-time.h:
* lib/readutmp.h, m4/readutmp.m4: New files, copied from Gnulib.
* admin/merge-gnulib (GNULIB_MODULES): Add boot-time.
* configure.ac: Do not check for utmp.h;
the boot-time module now does this.
(BOOT_TIME_FILE): Remove; no longer used.
* lib/gnulib.mk.in, m4/gnulib-comp.m4: Regenerate.
* src/filelock.c [__FreeBSD__]: Do not include <sys/sysctl.h>.
[HAVE_UTMP_H]: Do not include utmp.h.
Include boot-time instead: boot-time does the work now.
(BOOT_TIME) [HAVE_ANDROID && !ANDROID_STUBIFY]: Don’t undef.
(WTMP_FILE): Don’t define.
(boot_time, boot_time_initialized, get_boot_time_1, get_boot_time):
Remove.
(get_boot_sec): New function that simply calls Gnulib get_boot_time.
(lock_file_1, current_lock_owner): Use get_boot_sec instead
of get_boot_time.
---
 admin/merge-gnulib  |   2 +-
 configure.ac        |  47 +------
 lib/boot-time-aux.h | 315 ++++++++++++++++++++++++++++++++++++++++++
 lib/boot-time.c     | 285 ++++++++++++++++++++++++++++++++++++++
 lib/boot-time.h     |  44 ++++++
 lib/gnulib.mk.in    |  11 ++
 lib/readutmp.h      | 325 ++++++++++++++++++++++++++++++++++++++++++++
 m4/gnulib-comp.m4   |   7 +
 m4/readutmp.m4      | 117 ++++++++++++++++
 src/filelock.c      | 172 ++---------------------
 10 files changed, 1116 insertions(+), 209 deletions(-)
 create mode 100644 lib/boot-time-aux.h
 create mode 100644 lib/boot-time.c
 create mode 100644 lib/boot-time.h
 create mode 100644 lib/readutmp.h
 create mode 100644 m4/readutmp.m4

diff --git a/admin/merge-gnulib b/admin/merge-gnulib
index 2a713beb01a..fe88d1106ae 100755
--- a/admin/merge-gnulib
+++ b/admin/merge-gnulib
@@ -26,7 +26,7 @@
 GNULIB_URL=https://git.savannah.gnu.org/git/gnulib.git
 
 GNULIB_MODULES='
-  alignasof alloca-opt binary-io byteswap c-ctype c-strcase
+  alignasof alloca-opt binary-io boot-time byteswap c-ctype c-strcase
   canonicalize-lgpl
   careadlinkat close-stream copy-file-range
   count-leading-zeros count-one-bits count-trailing-zeros
diff --git a/configure.ac b/configure.ac
index 35dfed247d0..0234a82b92f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2539,7 +2539,7 @@ AC_DEFUN
   sys/sysinfo.h
   coff.h pty.h
   sys/resource.h
-  sys/utsname.h pwd.h utmp.h util.h
+  sys/utsname.h pwd.h util.h
   sanitizer/lsan_interface.h
   sanitizer/asan_interface.h
   sanitizer/common_interface_defs.h])
@@ -2635,51 +2635,6 @@ AC_DEFUN
 fi
 AC_SUBST([AUTO_DEPEND])
 
-BOOT_TIME_FILE=
-AC_CACHE_CHECK([for old but post-boot file],
-  [emacs_cv_boot_time_file],
-  [AS_CASE([$opsys],
-     [gnu-linux],
-     [emacs_cv_boot_time_file=unknown
-      AS_IF([test $cross_compiling = no],
-	[# systemd puts it in /var/lib/systemd.
-	 # initscripts puts it in /var/lib/urandom (previously /var/lib).
-	 # Linux drivers/char/random.c before 2022-02-21 suggests /var/run.
-	 for file in \
-	     /var/lib/systemd/random-seed \
-	     /var/lib/urandom/random-seed \
-	     /var/lib/random-seed \
-	     /var/run/random-seed
-	 do
-	   test -f $file && { emacs_cv_boot_time_file=$file; break; }
-	 done])],
-     # This isn't perfect, as some systems might have the page file in
-     # another place.  Also, I suspect that the time stamp of that
-     # file might also change when Windows enlarges the file due to
-     # insufficient VM.  Still, this seems to be the most reliable
-     # way; the alternative (of using GetSystemTimes) won't work on
-     # laptops that hibernate, because the system clock is stopped
-     # then.  Other possibility would be to run "net statistics
-     # workstation" and parse the output, but that's gross.  So this
-     # should do; if the file is not there, the boot time will be
-     # returned as zero, and filelock.c already handles that.
-     [mingw32], [emacs_cv_boot_time_file=C:/pagefile.sys],
-     [*], [emacs_cv_boot_time_file=not-needed])])
-
-AS_CASE([$emacs_cv_boot_time_file],
-  [/*|*:*], [BOOT_TIME_FILE=\"$emacs_cv_boot_time_file\"],
-  [not-needed], [BOOT_TIME_FILE=],
-  [# Guess systemd if unknown.
-   # If guess is wrong, Emacs falls back on something else.
-   BOOT_TIME_FILE=\"/var/lib/systemd/random-seed\"])
-
-AS_IF([test -n "$BOOT_TIME_FILE"],
-  [AC_DEFINE_UNQUOTED([BOOT_TIME_FILE], [$BOOT_TIME_FILE],
-    [Name of file that, if it exists, postdates boot and predates
-     the first Emacs invocation; or a null pointer if no such file is known.
-     This file is used only on GNU/Linux and other systems
-     that lack the FreeBSD-style sysctl with KERN_BOOTTIME.])])
-
 #### Choose a window system.
 
 ## We leave window_system equal to none if
diff --git a/lib/boot-time-aux.h b/lib/boot-time-aux.h
new file mode 100644
index 00000000000..348611fc85c
--- /dev/null
+++ b/lib/boot-time-aux.h
@@ -0,0 +1,315 @@
+/* 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 <bruno@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, Android, or musl libc.
+     In glibc < 2.17 it required linking with librt.  */
+# if !defined __GLIBC__ || 2 < __GLIBC__ + (17 <= __GLIBC_MINOR__)
+  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.  */
+          time_t s = 0;
+          char *p;
+          for (p = buf; '0' <= *p && *p <= '9'; p++)
+            s = 10 * s + (*p - '0');
+          if (buf < p)
+            {
+              long ns = 0;
+              if (*p++ == '.')
+                for (int i = 0; i < 9; i++)
+                  ns = 10 * ns + ('0' <= *p && *p <= '9' ? *p++ - '0' : 0);
+              p_uptime->tv_sec = s;
+              p_uptime->tv_nsec = ns;
+              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;
+}
+
+/* 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;
+# if !defined __GLIBC__ || 2 < __GLIBC__ + (16 <= __GLIBC_MINOR__)
+      /* Better than:
+      if (0 <= clock_gettime (CLOCK_REALTIME, &result))
+         because timespec_get does not need -lrt in glibc 2.16.
+      */
+      if (! timespec_get (&result, TIME_UTC))
+        return -1;
+#  else
+      /* Fall back on lower-res approach that does not need -lrt.
+         This is good enough; on these hosts UPTIME is even lower-res.  */
+      struct timeval tv;
+      int r = gettimeofday (&tv, NULL);
+      if (r < 0)
+        return r;
+      result.tv_sec = tv.tv_sec;
+      result.tv_nsec = tv.tv_usec * 1000;
+#  endif
+
+      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 __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 HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
+    && defined CTL_KERN && defined KERN_BOOTTIME \
+    && !defined __minix
+/* macOS, FreeBSD, GNU/kFreeBSD, NetBSD, OpenBSD */
+/* On Minix 3.3 this sysctl produces garbage results.  Therefore avoid it.  */
+
+/* The following approach is only usable as a fallback, because it 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_bsd_boot_time_final_fallback (struct timespec *p_boot_time)
+{
+  static int request[2] = { CTL_KERN, KERN_BOOTTIME };
+  struct timeval result;
+  size_t result_len = sizeof result;
+
+  if (sysctl (request, 2, &result, &result_len, NULL, 0) >= 0)
+    {
+      p_boot_time->tv_sec = result.tv_sec;
+      p_boot_time->tv_nsec = result.tv_usec * 1000;
+      return 0;
+    }
+  return -1;
+}
+
+#endif
+
+#if defined __HAIKU__
+
+static int
+get_haiku_boot_time (struct timespec *p_boot_time)
+{
+  /* On Haiku, /etc/utmp does not exist.  During boot,
+       1. the current time is restored, but possibly with a wrong time zone,
+          that is, with an offset of a few hours,
+       2. some symlinks and files get created,
+       3. the various devices are brought up, in particular the network device,
+       4. the correct date and time is set,
+       5. some more device nodes get created.
+     The boot time can be retrieved by looking at a directory created during
+     phase 5, such as /dev/input.  */
+  const char * const boot_touched_file = "/dev/input";
+  struct stat statbuf;
+  if (stat (boot_touched_file, &statbuf) >= 0)
+    {
+      *p_boot_time = get_stat_mtime (&statbuf);
+      return 0;
+    }
+  return -1;
+}
+
+#endif
+
+#if HAVE_OS_H /* BeOS, Haiku */
+
+/* The following approach is only usable as a fallback, because it 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_haiku_boot_time_final_fallback (struct timespec *p_boot_time)
+{
+  system_info si;
+
+  get_system_info (&si);
+  p_boot_time->tv_sec = si.boot_time / 1000000;
+  p_boot_time->tv_nsec = (si.boot_time % 1000000) * 1000;
+  return 0;
+}
+
+#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/boot-time.c b/lib/boot-time.c
new file mode 100644
index 00000000000..d813bfa5825
--- /dev/null
+++ b/lib/boot-time.c
@@ -0,0 +1,285 @@
+/* Determine 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 <bruno@clisp.org>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#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
+
+#if HAVE_SYS_SYSCTL_H && !defined __minix
+# if HAVE_SYS_PARAM_H
+#  include <sys/param.h>
+# endif
+# include <sys/sysctl.h>
+#endif
+
+#if HAVE_OS_H
+# include <OS.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.  */
+#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)
+
+  /* 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, Haiku */
+
+  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
+
+# else /* old FreeBSD, OpenBSD, native Windows */
+
+#  if defined __OpenBSD__
+  /* Workaround for OpenBSD:  */
+  get_openbsd_boot_time (&found_boot_time);
+#  endif
+
+# endif
+
+# if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
+     && defined CTL_KERN && defined KERN_BOOTTIME \
+     && !defined __minix
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_bsd_boot_time_final_fallback (&found_boot_time);
+    }
+# endif
+
+# if defined __HAIKU__
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_haiku_boot_time (&found_boot_time);
+    }
+# endif
+
+# if HAVE_OS_H
+  if (found_boot_time.tv_sec == 0)
+    {
+      get_haiku_boot_time_final_fallback (&found_boot_time);
+    }
+# 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)
+{
+  /* 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 = cached_boot_time;
+      return 0;
+    }
+  else
+    return -1;
+}
+
+#else
+
+int
+get_boot_time (struct timespec *p_boot_time)
+{
+  return -1;
+}
+
+#endif
diff --git a/lib/boot-time.h b/lib/boot-time.h
new file mode 100644
index 00000000000..401e854adbb
--- /dev/null
+++ b/lib/boot-time.h
@@ -0,0 +1,44 @@
+/* Determine 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 <bruno@clisp.org>.  */
+
+#ifndef _BOOT_TIME_H
+#define _BOOT_TIME_H
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Store the approximate time when the machine last booted in *P_BOOT_TIME,
+   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);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BOOT_TIME_H */
diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in
index 785bdc70c5c..3b33f39f73b 100644
--- a/lib/gnulib.mk.in
+++ b/lib/gnulib.mk.in
@@ -76,6 +76,7 @@
 #  alignasof \
 #  alloca-opt \
 #  binary-io \
+#  boot-time \
 #  byteswap \
 #  c-ctype \
 #  c-strcase \
@@ -1601,6 +1602,16 @@ libgnu_a_SOURCES += binary-io.h binary-io.c
 endif
 ## end   gnulib module binary-io
 
+## begin gnulib module boot-time
+ifeq (,$(OMIT_GNULIB_MODULE_boot-time))
+
+libgnu_a_SOURCES += boot-time.c
+
+EXTRA_DIST += boot-time-aux.h boot-time.h readutmp.h
+
+endif
+## end   gnulib module boot-time
+
 ## begin gnulib module byteswap
 ifeq (,$(OMIT_GNULIB_MODULE_byteswap))
 
diff --git a/lib/readutmp.h b/lib/readutmp.h
new file mode 100644
index 00000000000..1cf588d265b
--- /dev/null
+++ b/lib/readutmp.h
@@ -0,0 +1,325 @@
+/* Declarations for GNU's read utmp module.
+
+   Copyright (C) 1992-2007, 2009-2023 Free Software Foundation, Inc.
+
+   This program 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 program 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 jla; revised by djm */
+
+#ifndef __READUTMP_H__
+#define __READUTMP_H__
+
+/* This file uses _GL_ATTRIBUTE_MALLOC, _GL_ATTRIBUTE_RETURNS_NONNULL,
+   HAVE_UTMP_H, HAVE_UTMPX_H, HAVE_STRUCT_UTMP_*, HAVE_STRUCT_UTMPX_*,
+   HAVE_UTMPNAME, HAVE_UTMPXNAME.  */
+#if !_GL_CONFIG_H_INCLUDED
+# error "Please include config.h first."
+#endif
+
+#include "idx.h"
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+
+/* AIX 4.3.3 has both utmp.h and utmpx.h, but only struct utmp
+   has the ut_exit member.  */
+#if (HAVE_UTMPX_H && HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_EXIT \
+     && ! HAVE_STRUCT_UTMPX_UT_EXIT)
+# undef HAVE_UTMPX_H
+#endif
+
+/* HPUX 10.20 needs utmp.h, for the definition of e.g., UTMP_FILE.  */
+#if HAVE_UTMP_H
+# include <utmp.h>
+#endif
+
+/* Needed for BOOT_TIME and USER_PROCESS.  */
+#if HAVE_UTMPX_H
+# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
+    /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
+       defined, work around the duplicate struct utmp_data declaration.  */
+#  define utmp_data gl_aix_4_3_workaround_utmp_data
+# endif
+# include <utmpx.h>
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Type of entries returned by read_utmp on all platforms.  */
+struct gl_utmp
+{
+  /* All 'char *' here are of arbitrary length and point to storage
+     with lifetime equal to that of this struct.  */
+  char *ut_user;                /* User name */
+  char *ut_id;                  /* Session ID */
+  char *ut_line;                /* seat / device */
+  char *ut_host;                /* for remote sessions: user@host or host,
+                                   for local sessions: the X11 display :N */
+  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, USER_PROCESS, or other */
+  struct { int e_termination; int e_exit; } ut_exit;
+};
+
+/* The following types, macros, and constants describe the 'struct gl_utmp'.  */
+#define UT_USER(UT) ((UT)->ut_user)
+#define UT_TIME_MEMBER(UT) ((UT)->ut_ts.tv_sec)
+#define UT_PID(UT) ((UT)->ut_pid)
+#define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V))
+#define UT_TYPE_NOT_DEFINED 0
+#define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination)
+#define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit)
+
+/* Type of entry returned by read_utmp().  */
+typedef struct gl_utmp STRUCT_UTMP;
+
+/* Size of the UT_USER (ut) member, or -1 if unbounded.  */
+enum { UT_USER_SIZE = -1 };
+
+/* Size of the ut->ut_id member, or -1 if unbounded.  */
+enum { UT_ID_SIZE = -1 };
+
+/* Size of the ut->ut_line member, or -1 if unbounded.  */
+enum { UT_LINE_SIZE = -1 };
+
+/* Size of the ut->ut_host member, or -1 if unbounded.  */
+enum { UT_HOST_SIZE = -1 };
+
+
+/* When read_utmp accesses a file (as opposed to fetching the information
+   from systemd), it uses the following low-level types and macros.
+   Keep them here, rather than moving them into readutmp.c, for backward
+   compatibility.  */
+
+#if HAVE_UTMPX_H
+
+/* <utmpx.h> defines 'struct utmpx' with the following fields:
+
+     Field        Type                       Platforms
+     ----------   ------                     ---------
+   ⎡ ut_user      char[]                     glibc, musl, macOS, FreeBSD, AIX, HP-UX, IRIX, Solaris, Cygwin
+   ⎣ ut_name      char[]                     NetBSD, Minix
+     ut_id        char[]                     glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_line      char[]                     glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_pid       pid_t                      glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_type      short                      glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+   ⎡ ut_tv        struct                     glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+   ⎢              { tv_sec; tv_usec; }
+   ⎣ ut_time      time_t                     Cygwin
+     ut_host      char[]                     glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_exit      struct                     glibc, musl, NetBSD, Minix, HP-UX, IRIX, Solaris
+                  { e_termination; e_exit; }
+     ut_session   [long] int                 glibc, musl, NetBSD, Minix, IRIX, Solaris
+   ⎡ ut_addr      [long] int                 HP-UX, Cygwin
+   ⎢ ut_addr_v6   [u]int[4]                  glibc, musl
+   ⎣ ut_ss        struct sockaddr_storage    NetBSD, Minix
+ */
+
+# 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
+# ifdef HAVE_UTMPXNAME /* glibc, musl, macOS, NetBSD, Minix, IRIX, Solaris, Cygwin */
+#  define UTMP_NAME_FUNCTION utmpxname
+# elif defined UTXDB_ACTIVE /* FreeBSD */
+#  define UTMP_NAME_FUNCTION(x) setutxdb (UTXDB_ACTIVE, x)
+# endif
+
+#elif HAVE_UTMP_H
+
+/* <utmp.h> defines 'struct utmp' with the following fields:
+
+     Field        Type                       Platforms
+     ----------   ------                     ---------
+   ⎡ ut_user      char[]                     glibc, musl, AIX, HP-UX, IRIX, Solaris, Cygwin, Android
+   ⎣ ut_name      char[]                     macOS, old FreeBSD, NetBSD, OpenBSD, Minix
+     ut_id        char[]                     glibc, musl, AIX, HP-UX, IRIX, Solaris, Cygwin, Android
+     ut_line      char[]                     glibc, musl, macOS, old FreeBSD, NetBSD, OpenBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin, Android
+     ut_pid       pid_t                      glibc, musl, AIX, HP-UX, IRIX, Solaris, Cygwin, Android
+     ut_type      short                      glibc, musl, AIX, HP-UX, IRIX, Solaris, Cygwin, Android
+   ⎡ ut_tv        struct                     glibc, musl, Android
+   ⎢              { tv_sec; tv_usec; }
+   ⎣ ut_time      time_t                     macOS, old FreeBSD, NetBSD, OpenBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin
+     ut_host      char[]                     glibc, musl, macOS, old FreeBSD, NetBSD, OpenBSD, Minix, AIX, HP-UX, Cygwin, Android
+     ut_exit      struct                     glibc, musl, AIX, HP-UX, IRIX, Solaris, Android
+                  { e_termination; e_exit; }
+     ut_session   [long] int                 glibc, musl, Android
+   ⎡ ut_addr      [long] int                 HP-UX, Cygwin
+   ⎣ ut_addr_v6   [u]int[4]                  glibc, musl, Android
+ */
+
+# define UTMP_STRUCT_NAME utmp
+# define SET_UTMP_ENT setutent
+# define GET_UTMP_ENT getutent
+# define END_UTMP_ENT endutent
+# ifdef HAVE_UTMPNAME /* glibc, musl, NetBSD, Minix, AIX, HP-UX, IRIX, Solaris, Cygwin, Android */
+#  define UTMP_NAME_FUNCTION utmpname
+# endif
+
+#endif
+
+/* Evaluates to 1 if gl_utmp's ut_id field may ever have a non-zero value.  */
+#define HAVE_STRUCT_XTMP_UT_ID \
+  (READUTMP_USE_SYSTEMD \
+   || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID))
+
+/* Evaluates to 1 if gl_utmp's ut_pid field may ever have a non-zero value.  */
+#define HAVE_STRUCT_XTMP_UT_PID \
+  (READUTMP_USE_SYSTEMD \
+   || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID))
+
+/* Evaluates to 1 if gl_utmp's ut_host field may ever be non-empty.  */
+#define HAVE_STRUCT_XTMP_UT_HOST \
+  (READUTMP_USE_SYSTEMD \
+   || (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST))
+
+/* Definition of UTMP_FILE.
+   On glibc systems, UTMP_FILE is "/var/run/utmp".  */
+#if !defined UTMP_FILE && defined _PATH_UTMP
+# define UTMP_FILE _PATH_UTMP
+#endif
+#ifdef UTMPX_FILE /* Solaris, SysVr4 */
+# undef UTMP_FILE
+# define UTMP_FILE UTMPX_FILE
+#endif
+#ifndef UTMP_FILE
+# define UTMP_FILE "/etc/utmp"
+#endif
+
+/* Definition of WTMP_FILE.
+   On glibc systems, UTMP_FILE is "/var/log/wtmp".  */
+#if !defined WTMP_FILE && defined _PATH_WTMP
+# define WTMP_FILE _PATH_WTMP
+#endif
+#ifdef WTMPX_FILE /* Solaris, SysVr4 */
+# undef WTMP_FILE
+# define WTMP_FILE WTMPX_FILE
+#endif
+#ifndef WTMP_FILE
+# 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)
+#else
+# define UT_TYPE_BOOT_TIME(UT) 0
+#endif
+#ifdef USER_PROCESS
+# define UT_TYPE_USER_PROCESS(UT) UT_TYPE_EQ (UT, USER_PROCESS)
+#else
+# define UT_TYPE_USER_PROCESS(UT) 0
+#endif
+
+/* Determines whether an entry *UT corresponds to a user process.  */
+#define IS_USER_PROCESS(UT)                                    \
+  (UT_USER (UT)[0]                                             \
+   && (UT_TYPE_USER_PROCESS (UT)                               \
+       || (UT_TYPE_NOT_DEFINED && UT_TIME_MEMBER (UT) != 0)))
+
+/* Define if read_utmp is not just a dummy.  */
+#if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H || defined __CYGWIN__ || defined _WIN32
+# define READ_UTMP_SUPPORTED 1
+#endif
+
+/* Options for read_utmp.  */
+enum
+  {
+    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,
+   as a freshly allocated string.  */
+char *extract_trimmed_name (const STRUCT_UTMP *ut)
+  _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE
+  _GL_ATTRIBUTE_RETURNS_NONNULL;
+
+/* Read the utmp entries corresponding to file FILE into freshly-
+   malloc'd storage, set *UTMP_BUF to that pointer, set *N_ENTRIES to
+   the number of entries, and return zero.  If there is any error,
+   return -1, setting errno, and don't modify the parameters.
+   A good candidate for FILE is UTMP_FILE.
+   If OPTIONS & READ_UTMP_CHECK_PIDS is nonzero, omit entries whose
+   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
+   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.  */
+int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
+               int options);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __READUTMP_H__ */
diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4
index 3382e9bc241..14ff92040a4 100644
--- a/m4/gnulib-comp.m4
+++ b/m4/gnulib-comp.m4
@@ -51,6 +51,7 @@ AC_DEFUN
   # Code from module at-internal:
   # Code from module attribute:
   # Code from module binary-io:
+  # Code from module boot-time:
   # Code from module builtin-expect:
   # Code from module byteswap:
   # Code from module c-ctype:
@@ -243,6 +244,7 @@ AC_DEFUN
   gl_ASSERT_H
   gl_CONDITIONAL_HEADER([assert.h])
   AC_PROG_MKDIR_P
+  gl_PREREQ_READUTMP_H
   gl___BUILTIN_EXPECT
   gl_BYTESWAP
   gl_CONDITIONAL_HEADER([byteswap.h])
@@ -1252,6 +1254,9 @@ AC_DEFUN
   lib/attribute.h
   lib/binary-io.c
   lib/binary-io.h
+  lib/boot-time-aux.h
+  lib/boot-time.c
+  lib/boot-time.h
   lib/byteswap.in.h
   lib/c++defs.h
   lib/c-ctype.c
@@ -1383,6 +1388,7 @@ AC_DEFUN
   lib/rawmemchr.valgrind
   lib/readlink.c
   lib/readlinkat.c
+  lib/readutmp.h
   lib/realloc.c
   lib/regcomp.c
   lib/regex.c
@@ -1542,6 +1548,7 @@ AC_DEFUN
   m4/rawmemchr.m4
   m4/readlink.m4
   m4/readlinkat.m4
+  m4/readutmp.m4
   m4/realloc.m4
   m4/regex.m4
   m4/sha1.m4
diff --git a/m4/readutmp.m4 b/m4/readutmp.m4
new file mode 100644
index 00000000000..fff8d4eb7bf
--- /dev/null
+++ b/m4/readutmp.m4
@@ -0,0 +1,117 @@
+# readutmp.m4 serial 28
+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,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_READUTMP],
+[
+  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])
+
+  gl_PREREQ_READUTMP_H
+])
+
+# Prerequisites of readutmp.h and boot-time-aux.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.
+    AC_CHECK_FUNCS_ONCE([utmpname utmpxname])
+    AC_CHECK_DECLS([getutent],,,[[
+/* <sys/types.h> is a prerequisite of <utmp.h> on FreeBSD 8.0, OpenBSD 4.6.  */
+#include <sys/types.h>
+#ifdef HAVE_UTMP_H
+# include <utmp.h>
+#endif
+]])
+    utmp_includes="\
+AC_INCLUDES_DEFAULT
+#ifdef HAVE_UTMPX_H
+# include <utmpx.h>
+#endif
+#ifdef HAVE_UTMP_H
+# if defined _THREAD_SAFE && defined UTMP_DATA_INIT
+   /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE
+      defined, work around the duplicate struct utmp_data declaration.  */
+#  define utmp_data gl_aix_4_3_workaround_utmp_data
+# endif
+# include <utmp.h>
+#endif
+"
+    AC_CHECK_MEMBERS([struct utmpx.ut_user],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_user],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_name],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_name],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_type],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_type],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_pid],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_pid],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_tv],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_host],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_host],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_id],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_id],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_session],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_session],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_exit],,,[$utmp_includes])
+
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.ut_exit],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.e_exit],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_exit.e_exit],,,[$utmp_includes])
+
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.ut_termination],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmpx.ut_exit.e_termination],,,[$utmp_includes])
+    AC_CHECK_MEMBERS([struct utmp.ut_exit.e_termination],,,[$utmp_includes])
+  fi
+
+  AC_CHECK_HEADERS_ONCE([sys/param.h])
+  dnl <sys/sysctl.h> requires <sys/param.h> on OpenBSD 4.0.
+  AC_CHECK_HEADERS([sys/sysctl.h],,,
+    [AC_INCLUDES_DEFAULT
+     #if HAVE_SYS_PARAM_H
+     # include <sys/param.h>
+     #endif
+    ])
+  AC_CHECK_FUNCS([sysctl])
+
+  AC_CHECK_HEADERS_ONCE([OS.h])
+])
diff --git a/src/filelock.c b/src/filelock.c
index 3b1ff8ad566..d2161f1e58a 100644
--- a/src/filelock.c
+++ b/src/filelock.c
@@ -36,13 +36,9 @@ Copyright (C) 1985-1987, 1993-1994, 1996, 1998-2023 Free Software
 #include <sys/file.h>
 #include <fcntl.h>
 #include <unistd.h>
-
-#ifdef __FreeBSD__
-#include <sys/sysctl.h>
-#endif /* __FreeBSD__ */
-
 #include <errno.h>
 
+#include <boot-time.h>
 #include <c-ctype.h>
 
 #include "lisp.h"
@@ -55,20 +51,6 @@ Copyright (C) 1985-1987, 1993-1994, 1996, 1998-2023 Free Software
 
 #ifndef MSDOS
 
-#ifdef HAVE_UTMP_H
-#include <utmp.h>
-#endif
-
-/* Boot time is not available on Android.  */
-
-#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
-#undef BOOT_TIME
-#endif
-
-#if !defined WTMP_FILE && !defined WINDOWSNT && defined BOOT_TIME
-#define WTMP_FILE "/var/log/wtmp"
-#endif
-
 #ifdef HAVE_ANDROID
 #include "android.h" /* For `android_is_special_directory'.  */
 #endif /* HAVE_ANDROID */
@@ -127,153 +109,19 @@ #define WTMP_FILE "/var/log/wtmp"
 \f
 /* Return the time of the last system boot.  */
 
-static time_t boot_time;
-static bool boot_time_initialized;
-
-#ifdef BOOT_TIME
-static void get_boot_time_1 (const char *, bool);
-#endif
-
 static time_t
-get_boot_time (void)
+get_boot_sec (void)
 {
-  if (boot_time_initialized)
-    return boot_time;
-  boot_time_initialized = 1;
-
-#if defined (CTL_KERN) && defined (KERN_BOOTTIME)
-  {
-    int mib[2];
-    size_t size;
-    struct timeval boottime_val;
-
-    mib[0] = CTL_KERN;
-    mib[1] = KERN_BOOTTIME;
-    size = sizeof (boottime_val);
-
-    if (sysctl (mib, 2, &boottime_val, &size, NULL, 0) >= 0 && size != 0)
-      {
-	boot_time = boottime_val.tv_sec;
-	return boot_time;
-      }
-  }
-#endif /* defined (CTL_KERN) && defined (KERN_BOOTTIME) */
-
-#ifdef BOOT_TIME_FILE
-    {
-      struct stat st;
-      if (stat (BOOT_TIME_FILE, &st) == 0)
-	{
-	  boot_time = st.st_mtime;
-	  return boot_time;
-	}
-    }
-#endif /* BOOT_TIME_FILE */
-
-#if defined (BOOT_TIME)
-  /* The utmp routines maintain static state.  Don't touch that state
+  /* get_boot_time maintains static state.  Don't touch that state
      if we are going to dump, since it might not survive dumping.  */
   if (will_dump_p ())
-    return boot_time;
-
-  /* Try to get boot time from utmp before wtmp,
-     since utmp is typically much smaller than wtmp.
-     Passing a null pointer causes get_boot_time_1
-     to inspect the default file, namely utmp.  */
-  get_boot_time_1 (0, 0);
-  if (boot_time)
-    return boot_time;
-
-  /* Try to get boot time from the current wtmp file.  */
-  get_boot_time_1 (WTMP_FILE, 1);
-
-  /* If we did not find a boot time in wtmp, look at wtmp.1,
-     wtmp.1.gz, wtmp.2, wtmp.2.gz, and so on.  */
-  for (int counter = 0; counter < 20 && ! boot_time; counter++)
-    {
-      Lisp_Object filename = Qnil;
-      bool delete_flag = false;
-      char cmd_string[sizeof WTMP_FILE ".19.gz"];
-      AUTO_STRING_WITH_LEN (tempname, cmd_string,
-			    sprintf (cmd_string, "%s.%d", WTMP_FILE, counter));
-      if (! NILP (Ffile_exists_p (tempname)))
-	filename = tempname;
-      else
-	{
-	  tempname = make_formatted_string (cmd_string, "%s.%d.gz",
-					    WTMP_FILE, counter);
-	  if (! NILP (Ffile_exists_p (tempname)))
-	    {
-	      /* The utmp functions on older systems accept only file
-		 names up to 8 bytes long.  Choose a 2 byte prefix, so
-		 the 6-byte suffix does not make the name too long.  */
-	      filename = Fmake_temp_file_internal (build_string ("wt"), Qnil,
-						   empty_unibyte_string, Qnil);
-	      CALLN (Fcall_process, build_string ("gzip"), Qnil,
-		     list2 (QCfile, filename), Qnil,
-		     build_string ("-cd"), tempname);
-	      delete_flag = true;
-	    }
-	}
-
-      if (! NILP (filename))
-	{
-	  get_boot_time_1 (SSDATA (filename), 1);
-	  if (delete_flag)
-	    emacs_unlink (SSDATA (filename));
-	}
-    }
-
-  return boot_time;
-#else
-  return 0;
-#endif
-}
+    return 0;
 
-#ifdef BOOT_TIME
-/* Try to get the boot time from wtmp file FILENAME.
-   This succeeds if that file contains a reboot record.
-
-   If FILENAME is zero, use the same file as before;
-   if no FILENAME has ever been specified, this is the utmp file.
-   Use the newest reboot record if NEWEST,
-   the first reboot record otherwise.
-   Ignore all reboot records on or before BOOT_TIME.
-   Success is indicated by setting BOOT_TIME to a larger value.  */
-
-void
-get_boot_time_1 (const char *filename, bool newest)
-{
-  struct utmp ut, *utp;
-
-  if (filename)
-    utmpname (filename);
-
-  setutent ();
-
-  while (1)
-    {
-      /* Find the next reboot record.  */
-      ut.ut_type = BOOT_TIME;
-      utp = getutid (&ut);
-      if (! utp)
-	break;
-      /* Compare reboot times and use the newest one.  */
-      if (utp->ut_time > boot_time)
-	{
-	  boot_time = utp->ut_time;
-	  if (! newest)
-	    break;
-	}
-      /* Advance on element in the file
-	 so that getutid won't repeat the same one.  */
-      utp = getutent ();
-      if (! utp)
-	break;
-    }
-  endutent ();
+  struct timespec boot_time;
+  boot_time.tv_sec = 0;
+  get_boot_time (&boot_time);
+  return boot_time.tv_sec;
 }
-#endif /* BOOT_TIME */
 \f
 /* An arbitrary limit on lock contents length.  8 K should be plenty
    big enough in practice.  */
@@ -418,7 +266,7 @@ create_lock_file (char *lfname, char *lock_info_str, bool force)
 static int
 lock_file_1 (Lisp_Object lfname, bool force)
 {
-  intmax_t boot = get_boot_time ();
+  intmax_t boot = get_boot_sec ();
   Lisp_Object luser_name = Fuser_login_name (Qnil);
   Lisp_Object lhost_name = Fsystem_name ();
 
@@ -604,7 +452,7 @@ current_lock_owner (lock_info_type *owner, Lisp_Object lfname)
                && (kill (pid, 0) >= 0 || errno == EPERM)
 	       && (boot_time == 0
 		   || (boot_time <= TYPE_MAXIMUM (time_t)
-		       && within_one_second (boot_time, get_boot_time ()))))
+		       && within_one_second (boot_time, get_boot_sec ()))))
         return ANOTHER_OWNS_IT;
       /* The owner process is dead or has a strange pid, so try to
          zap the lockfile.  */
-- 
2.39.2


       reply	other threads:[~2023-08-13  2:49 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <4536176.VaOIPsP7d9@nimes>
2023-08-13  2:49 ` Paul Eggert [this message]
2023-08-13  3:26   ` boot-time: straighten code Po Lu
2023-08-13  6:35     ` Paul Eggert
2023-08-13 13:45     ` Bruno Haible
2023-08-13 14:16     ` Bruno Haible
2023-08-13 14:36     ` Bruno Haible
2023-08-13 23:44       ` Po Lu
2023-08-13 23:59         ` Bruno Haible
2023-08-14  1:07           ` Po Lu
2023-08-14  2:14             ` Corwin Brust
2023-08-15 19:57               ` Windows port binaries Bruno Haible
2023-08-16  6:45                 ` Po Lu
2023-08-16 11:36                 ` Eli Zaretskii
2023-08-17 14:01                   ` Bruno Haible
2023-08-17 14:14                     ` Eli Zaretskii
2023-08-14  8:02   ` boot-time: straighten code Andreas Schwab
2023-08-14  9:15     ` Bruno Haible
2023-08-14  9:20       ` Andreas Schwab
2023-08-14 10:19         ` Bruno Haible
2023-08-14 10:33           ` Andreas Schwab
2023-08-14 13:51             ` Bruno Haible
2023-08-15 23:03               ` Paul Eggert
2023-08-15 21:12   ` Bruno Haible
2023-08-16 10:13     ` Bruno Haible

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5a77c0b9-8692-83bf-1aca-2f1d27122877@cs.ucla.edu \
    --to=eggert@cs.ucla.edu \
    --cc=Emacs-devel@gnu.org \
    --cc=bruno@clisp.org \
    --cc=bug-gnulib@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.