The best approaches to get the boot time access some files. For the
case of programs that execute in Docker containers on Linux, we have
the fallback that relies on the kernel's uptime counter — although it
produces wrong values in a VM that has been put to sleep and then resumed.

Similarly, it is useful to have a fallback for BSD 'jails' on *BSD systems.
There is a sysctl named KERN_BOOTTIME, that coreutils/src/uptime.c already
uses. For consistency between 'who' and 'uptime', it is good to have this
fallback in the 'readutmp' module.

This sysctl exists on macOS, FreeBSD, NetBSD, OpenBSD, GNU/kFreeBSD, Minix.
But note:
  - The boot time that this sysctl reports changes after a VM has been
    put to sleep, then resumed, then its 'date' changed. I've verified
    this on FreeBSD, NetBSD, OpenBSD, GNU/kFreeBSD. It's probably the
    same thing on macOS (but I can't test it).
  - On Minix 3.3, the result of this system call is garbage. When I compile
    the attached program, it produces completely different results when
    invoked as
      $ ./a.out
    vs.
      $ TZ=UTC ./a.out


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

        readutmp, boot-time: Use the BSD sysctl as fallback.
        * m4/readutmp.m4 (gl_PREREQ_READUTMP_H): Test for <sys/param.h>,
        <sys/sysctl.h>, sysctl.
        * lib/boot-time-aux.h (get_bsd_boot_time_final_fallback): New function.
        * lib/readutmp.c: Include <sys/param.h> and <sys/sysctl.h>.
        (read_utmp_from_file): Invoke get_bsd_boot_time_final_fallback as a
        fallback.
        * lib/boot-time.c: Include <sys/param.h> and <sys/sysctl.h>.
        (get_boot_time_uncached): Invoke get_bsd_boot_time_final_fallback as a
        fallback.

diff --git a/lib/boot-time-aux.h b/lib/boot-time-aux.h
index fc84086f3f..8de834efd7 100644
--- a/lib/boot-time-aux.h
+++ b/lib/boot-time-aux.h
@@ -199,7 +199,35 @@ get_openbsd_boot_time (struct timespec *p_boot_time)
 
 #endif
 
-# if defined __CYGWIN__ || defined _WIN32
+#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 __CYGWIN__ || defined _WIN32
 
 static int
 get_windows_boot_time (struct timespec *p_boot_time)
@@ -225,4 +253,4 @@ get_windows_boot_time (struct timespec *p_boot_time)
   return -1;
 }
 
-# endif
+#endif
diff --git a/lib/boot-time.c b/lib/boot-time.c
index 932ee22d69..b27cb6c6b6 100644
--- a/lib/boot-time.c
+++ b/lib/boot-time.c
@@ -32,6 +32,13 @@
 # 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
+
 #include "idx.h"
 #include "readutmp.h"
 #include "stat-time.h"
@@ -66,14 +73,7 @@ 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
+# 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.  */
 
@@ -198,6 +198,22 @@ get_boot_time_uncached (struct timespec *p_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 __CYGWIN__ || defined _WIN32
diff --git a/lib/readutmp.c b/lib/readutmp.c
index d7baa69909..9a5b34054a 100644
--- a/lib/readutmp.c
+++ b/lib/readutmp.c
@@ -40,6 +40,13 @@
 # include <systemd/sd-login.h>
 #endif
 
+#if HAVE_SYS_SYSCTL_H && !defined __minix
+# if HAVE_SYS_PARAM_H
+#  include <sys/param.h>
+# endif
+# include <sys/sysctl.h>
+#endif
+
 #include "stat-time.h"
 #include "xalloc.h"
 
@@ -570,14 +577,39 @@ read_utmp_from_file (char const *file, idx_t *n_entries, 
STRUCT_UTMP **utmp_buf,
 
 #  endif
 
+#  if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
+      && defined CTL_KERN && defined KERN_BOOTTIME \
+      && !defined __minix
+  if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
+      && strcmp (file, UTMP_FILE) == 0)
+    {
+      bool have_boot_time = false;
+      for (idx_t i = 0; i < a.filled; i++)
+        {
+          struct gl_utmp *ut = &a.utmp[i];
+          if (UT_TYPE_BOOT_TIME (ut))
+            {
+              have_boot_time = true;
+              break;
+            }
+        }
+      if (!have_boot_time)
+        {
+          struct timespec boot_time;
+          if (get_bsd_boot_time_final_fallback (&boot_time) >= 0)
+            a = add_utmp (a, options,
+                          "reboot", strlen ("reboot"),
+                          "", 0,
+                          "", 0,
+                          "", 0,
+                          0, BOOT_TIME, boot_time, 0, 0, 0);
+        }
+    }
+#  endif
+
 # endif
 
 # if defined __CYGWIN__ || defined _WIN32
-  /* 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.  */
   if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
       && strcmp (file, UTMP_FILE) == 0
       && a.filled == 0)
diff --git a/m4/readutmp.m4 b/m4/readutmp.m4
index 07eebcd43d..357bb8b3c8 100644
--- a/m4/readutmp.m4
+++ b/m4/readutmp.m4
@@ -1,4 +1,4 @@
-# readutmp.m4 serial 24
+# readutmp.m4 serial 25
 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,
@@ -45,7 +45,7 @@ AC_DEFUN([gl_READUTMP]
   gl_PREREQ_READUTMP_H
 ])
 
-# Prerequisites of 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
@@ -102,4 +102,14 @@ AC_DEFUN_ONCE([gl_PREREQ_READUTMP_H]
     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])
 ])
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/sysctl.h>

static const char *
as_time_string (time_t tim)
{
  struct tm *gmt = gmtime (&tim);
  static char timbuf[100];
  if (gmt == NULL
      || strftime (timbuf, sizeof (timbuf), "%Y-%m-%d %H:%M:%S", gmt)
         == 0)
    strcpy (timbuf, "---");
  return timbuf;
}

int main ()
{
  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)
    {
      printf ("from sysctl: %d.%06d = %s.%06d\n",
              (int) result.tv_sec, (int) result.tv_usec,
              as_time_string (result.tv_sec), (int) result.tv_usec);
    }

  return 0;
}

Reply via email to