/* 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 . */ /* Written by Bruno Haible . */ #include /* Specification. */ #include "boot-time.h" #include #include #include #include #include #if defined __linux__ || defined __ANDROID__ # include # include #endif #if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) && !defined __minix # if HAVE_SYS_PARAM_H # include # endif # include #endif #if HAVE_OS_H # include #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