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; }