summaryrefslogtreecommitdiff
path: root/lib/boot-time.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/boot-time.c')
-rw-r--r--lib/boot-time.c294
1 files changed, 294 insertions, 0 deletions
diff --git a/lib/boot-time.c b/lib/boot-time.c
new file mode 100644
index 00000000000..c1171e8024d
--- /dev/null
+++ b/lib/boot-time.c
@@ -0,0 +1,294 @@
+/* Determine the time when the machine last booted.
+ Copyright (C) 2023-2024 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 __GLIBC__ && defined __linux__) && !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
+# if !HAVE_DECL_ENDUTENT /* Android */
+void endutent (void);
+# endif
+#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 /* Adélie Linux, old FreeBSD, OpenBSD, native Windows */
+
+# if defined __linux__ && !defined __ANDROID__
+ /* Workaround for Adélie Linux: */
+ get_linux_boot_time_fallback (&found_boot_time);
+ if (found_boot_time.tv_sec == 0)
+ get_linux_boot_time_final_fallback (&found_boot_time);
+# endif
+
+# 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