+2023-08-11 Bruno Haible <bruno@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 <bruno@clisp.org>
readutmp: Refactor boot time determination code.
#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