From: Bruno Haible Date: Fri, 11 Aug 2023 21:20:44 +0000 (+0200) Subject: readutmp: Refactor boot time determination code. X-Git-Tag: v1.0~931 X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=1657e67fe3f4be3cd572430358991bc12c62b193;p=gnulib.git readutmp: Refactor boot time determination code. * lib/boot-time-aux.h: New file, extracted from lib/readutmp.c. * lib/readutmp.c: On Linux, include even if !READUTMP_USE_SYSTEMD. Include boot-time-aux.h. (SIZEOF): Remove macro, moved to boot-time-aux.h. (get_linux_uptime): Remove function, moved to boot-time-aux.h. (read_utmp_from_file): Invoke get_linux_boot_time_fallback, get_android_boot_time, get_openbsd_boot_time, get_windows_boot_time. Code moved to boot-time-aux.h. (get_boot_time_uncached: Invoke get_linux_boot_time_final_fallback. Code moved to boot-time-aux.h. * m4/readutmp.m4 (gl_PREREQ_READUTMP_H): New macro, extracted from gl_READUTMP. (gl_READUTMP): Invoke it. * modules/readutmp (Files): Add lib/boot-time-aux.h. --- diff --git a/ChangeLog b/ChangeLog index 6836ca6308..29d5c73907 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2023-08-11 Bruno Haible + + readutmp: Refactor boot time determination code. + * lib/boot-time-aux.h: New file, extracted from lib/readutmp.c. + * lib/readutmp.c: On Linux, include even if + !READUTMP_USE_SYSTEMD. + Include boot-time-aux.h. + (SIZEOF): Remove macro, moved to boot-time-aux.h. + (get_linux_uptime): Remove function, moved to boot-time-aux.h. + (read_utmp_from_file): Invoke get_linux_boot_time_fallback, + get_android_boot_time, get_openbsd_boot_time, get_windows_boot_time. + Code moved to boot-time-aux.h. + (get_boot_time_uncached: Invoke get_linux_boot_time_final_fallback. + Code moved to boot-time-aux.h. + * m4/readutmp.m4 (gl_PREREQ_READUTMP_H): New macro, extracted from + gl_READUTMP. + (gl_READUTMP): Invoke it. + * modules/readutmp (Files): Add lib/boot-time-aux.h. + 2023-08-11 Bruno Haible readutmp: Make 'struct utmpx32' usable by other code. diff --git a/lib/boot-time-aux.h b/lib/boot-time-aux.h new file mode 100644 index 0000000000..fc84086f3f --- /dev/null +++ b/lib/boot-time-aux.h @@ -0,0 +1,228 @@ +/* Auxiliary functions for determining the time when the machine last booted. + Copyright (C) 2023 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 . */ + +#define SIZEOF(a) (sizeof(a)/sizeof(a[0])) + +#if defined __linux__ || defined __ANDROID__ + +/* Store the uptime counter, as managed by the Linux kernel, in *P_UPTIME. + Return 0 upon success, -1 upon failure. */ +_GL_ATTRIBUTE_MAYBE_UNUSED +static int +get_linux_uptime (struct timespec *p_uptime) +{ + /* The clock_gettime facility returns the uptime with a resolution of 1 µsec. + It is available with glibc >= 2.14. In glibc < 2.17 it required linking + with librt. */ +# if (__GLIBC__ + (__GLIBC_MINOR__ >= 17) > 2) || defined __ANDROID__ + if (clock_gettime (CLOCK_BOOTTIME, p_uptime) >= 0) + return 0; +# endif + + /* /proc/uptime contains the uptime with a resolution of 0.01 sec. + But it does not have read permissions on Android. */ +# if !defined __ANDROID__ + FILE *fp = fopen ("/proc/uptime", "re"); + if (fp != NULL) + { + char buf[32 + 1]; + size_t n = fread (buf, 1, sizeof (buf) - 1, fp); + fclose (fp); + if (n > 0) + { + buf[n] = '\0'; + /* buf now contains two values: the uptime and the idle time. */ + char *endptr; + double uptime = strtod (buf, &endptr); + if (endptr > buf) + { + p_uptime->tv_sec = (time_t) uptime; + p_uptime->tv_nsec = (uptime - p_uptime->tv_sec) * 1e9 + 0.5; + return 0; + } + } + } +# endif + + /* The sysinfo call returns the uptime with a resolution of 1 sec only. */ + struct sysinfo info; + if (sysinfo (&info) >= 0) + { + p_uptime->tv_sec = info.uptime; + p_uptime->tv_nsec = 0; + return 0; + } + + return -1; +} + +#endif + +#if defined __linux__ && !defined __ANDROID__ + +static int +get_linux_boot_time_fallback (struct timespec *p_boot_time) +{ + /* On Alpine Linux, UTMP_FILE is not filled. It is always empty. + So, get the time stamp of a file that gets touched only during the + boot process. */ + + const char * const boot_touched_files[] = + { + "/var/lib/systemd/random-seed", /* seen on distros with systemd */ + "/var/run/utmp", /* seen on distros with OpenRC */ + "/var/lib/random-seed" /* seen on older distros */ + }; + for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++) + { + const char *filename = boot_touched_files[i]; + struct stat statbuf; + if (stat (filename, &statbuf) >= 0) + { + *p_boot_time = get_stat_mtime (&statbuf); + return 0; + } + } + return -1; +} + +# if NEED_BOOT_TIME_FINAL_FALLBACK + +/* The following approach is only usable as a fallback, because it is of + the form + boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ()) + and therefore 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_linux_boot_time_final_fallback (struct timespec *p_boot_time) +{ + struct timespec uptime; + if (get_linux_uptime (&uptime) >= 0) + { + struct timespec result; + /* equivalent to: + if (clock_gettime (CLOCK_REALTIME, &result) >= 0) + */ + if (timespec_get (&result, TIME_UTC) >= 0) + { + if (result.tv_nsec < uptime.tv_nsec) + { + result.tv_nsec += 1000000000; + result.tv_sec -= 1; + } + result.tv_sec -= uptime.tv_sec; + result.tv_nsec -= uptime.tv_nsec; + *p_boot_time = result; + return 0; + } + } + return -1; +} + +# endif + +#endif + +#if defined __ANDROID__ + +static int +get_android_boot_time (struct timespec *p_boot_time) +{ + /* On Android, there is no /var, and normal processes don't have access + to system files. Therefore use the kernel's uptime counter, although + it produces wrong values after the date has been bumped in the running + system. */ + struct timespec uptime; + if (get_linux_uptime (&uptime) >= 0) + { + struct timespec result; + if (clock_gettime (CLOCK_REALTIME, &result) >= 0) + { + if (result.tv_nsec < uptime.tv_nsec) + { + result.tv_nsec += 1000000000; + result.tv_sec -= 1; + } + result.tv_sec -= uptime.tv_sec; + result.tv_nsec -= uptime.tv_nsec; + *p_boot_time = result; + return 0; + } + } + return -1; +} + +#endif + +#if defined __OpenBSD__ + +static int +get_openbsd_boot_time (struct timespec *p_boot_time) +{ + /* On OpenBSD, UTMP_FILE is not filled. It contains only dummy entries. + So, get the time stamp of a file that gets touched only during the + boot process. */ + const char * const boot_touched_files[] = + { + "/var/db/host.random", + "/var/run/utmp" + }; + for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++) + { + const char *filename = boot_touched_files[i]; + struct stat statbuf; + if (stat (filename, &statbuf) >= 0) + { + *p_boot_time = get_stat_mtime (&statbuf); + return 0; + } + } + return -1; +} + +#endif + +# if defined __CYGWIN__ || defined _WIN32 + +static int +get_windows_boot_time (struct timespec *p_boot_time) +{ + /* On Cygwin, /var/run/utmp is empty. + On native Windows, and 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. */ + const char * const boot_touched_file = + #if defined __CYGWIN__ && !defined _WIN32 + "/cygdrive/c/pagefile.sys" + #else + "C:\\pagefile.sys" + #endif + ; + struct stat statbuf; + if (stat (boot_touched_file, &statbuf) >= 0) + { + *p_boot_time = get_stat_mtime (&statbuf); + return 0; + } + return -1; +} + +# endif diff --git a/lib/readutmp.c b/lib/readutmp.c index 00dbbcea48..f2e66899bb 100644 --- a/lib/readutmp.c +++ b/lib/readutmp.c @@ -31,7 +31,7 @@ #include #include -#if READUTMP_USE_SYSTEMD || defined __ANDROID__ +#if defined __linux__ || defined __ANDROID__ # include # include #endif @@ -46,6 +46,10 @@ /* 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 READUTMP_USE_SYSTEMD +#include "boot-time-aux.h" + /* The following macros describe the 'struct UTMP_STRUCT_NAME', *not* 'struct gl_utmp'. */ #undef UT_USER @@ -135,8 +139,6 @@ /* Size of the ut->ut_host member. */ #define UT_HOST_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_host) -#define SIZEOF(a) (sizeof(a)/sizeof(a[0])) - #if 8 <= __GNUC__ # pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess" #endif @@ -288,60 +290,6 @@ finish_utmp (struct utmp_alloc a) return a; } -# if READUTMP_USE_SYSTEMD || defined __ANDROID__ - -/* Store the uptime counter, as managed by the Linux kernel, in *P_UPTIME. - Return 0 upon success, -1 upon failure. */ -static int -get_linux_uptime (struct timespec *p_uptime) -{ - /* The clock_gettime facility returns the uptime with a resolution of 1 µsec. - It is available with glibc >= 2.14. In glibc < 2.17 it required linking - with librt. */ -# if (__GLIBC__ + (__GLIBC_MINOR__ >= 17) > 2) || defined __ANDROID__ - if (clock_gettime (CLOCK_BOOTTIME, p_uptime) >= 0) - return 0; -# endif - - /* /proc/uptime contains the uptime with a resolution of 0.01 sec. - But it does not have read permissions on Android. */ -# if !defined __ANDROID__ - FILE *fp = fopen ("/proc/uptime", "re"); - if (fp != NULL) - { - char buf[32 + 1]; - size_t n = fread (buf, 1, sizeof (buf) - 1, fp); - fclose (fp); - if (n > 0) - { - buf[n] = '\0'; - /* buf now contains two values: the uptime and the idle time. */ - char *endptr; - double uptime = strtod (buf, &endptr); - if (endptr > buf) - { - p_uptime->tv_sec = (time_t) uptime; - p_uptime->tv_nsec = (uptime - p_uptime->tv_sec) * 1e9 + 0.5; - return 0; - } - } - } -# endif - - /* The sysinfo call returns the uptime with a resolution of 1 sec only. */ - struct sysinfo info; - if (sysinfo (&info) >= 0) - { - p_uptime->tv_sec = info.uptime; - p_uptime->tv_nsec = 0; - return 0; - } - - return -1; -} - -# endif - # if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION && !HAVE_DECL_GETUTENT struct utmp *getutent (void); # endif @@ -472,28 +420,14 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, if (!have_boot_time) { /* Workaround for Alpine Linux: */ - const char * const boot_touched_files[] = - { - "/var/lib/systemd/random-seed", /* seen on distros with systemd */ - "/var/run/utmp", /* seen on distros with OpenRC */ - "/var/lib/random-seed" /* seen on older distros */ - }; - for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++) - { - const char *filename = boot_touched_files[i]; - struct stat statbuf; - if (stat (filename, &statbuf) >= 0) - { - struct timespec boot_time = get_stat_mtime (&statbuf); - a = add_utmp (a, options, - "reboot", strlen ("reboot"), - "", 0, - "~", strlen ("~"), - "", 0, - 0, BOOT_TIME, boot_time, 0, 0, 0); - break; - } - } + struct timespec boot_time; + if (get_linux_boot_time_fallback (&boot_time) >= 0) + a = add_utmp (a, options, + "reboot", strlen ("reboot"), + "", 0, + "~", strlen ("~"), + "", 0, + 0, BOOT_TIME, boot_time, 0, 0, 0); } } # endif @@ -518,28 +452,14 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, } if (!have_boot_time) { - struct timespec uptime; - if (get_linux_uptime (&uptime) >= 0) - { - struct timespec result; - if (clock_gettime (CLOCK_REALTIME, &result) >= 0) - { - if (result.tv_nsec < uptime.tv_nsec) - { - result.tv_nsec += 1000000000; - result.tv_sec -= 1; - } - result.tv_sec -= uptime.tv_sec; - result.tv_nsec -= uptime.tv_nsec; - struct timespec boot_time = result; - a = add_utmp (a, options, - "reboot", strlen ("reboot"), - "", 0, - "", 0, - "", 0, - 0, BOOT_TIME, boot_time, 0, 0, 0); - } - } + struct timespec boot_time; + if (get_android_boot_time (&boot_time) >= 0) + a = add_utmp (a, options, + "reboot", strlen ("reboot"), + "", 0, + "", 0, + "", 0, + 0, BOOT_TIME, boot_time, 0, 0, 0); } } # endif @@ -629,9 +549,6 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, } # if defined __OpenBSD__ - /* On OpenBSD, UTMP_FILE is not filled. It contains only dummy entries. - So, fake a BOOT_TIME entry, by getting the time stamp of a file that - gets touched only during the boot process. */ if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 && strcmp (file, UTMP_FILE) == 0) { @@ -639,26 +556,16 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, bool have_boot_time = false; if (!have_boot_time) { - const char * const boot_touched_files[] = - { - "/var/db/host.random", - "/var/run/utmp" - }; - for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++) + struct timespec boot_time; + if (get_openbsd_boot_time (&boot_time) >= 0) { - const char *filename = boot_touched_files[i]; - struct stat statbuf; - if (stat (filename, &statbuf) >= 0) - { - struct timespec boot_time = get_stat_mtime (&statbuf); - a = add_utmp (a, options, - "reboot", strlen ("reboot"), - "", 0, - "", 0, - "", 0, - 0, BOOT_TIME, boot_time, 0, 0, 0); - break; - } + a = add_utmp (a, options, + "reboot", strlen ("reboot"), + "", 0, + "", 0, + "", 0, + 0, BOOT_TIME, boot_time, 0, 0, 0); + break; } } } @@ -678,24 +585,14 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, && strcmp (file, UTMP_FILE) == 0 && a.filled == 0) { - const char * const boot_touched_file = - #if defined __CYGWIN__ && !defined _WIN32 - "/cygdrive/c/pagefile.sys" - #else - "C:\\pagefile.sys" - #endif - ; - struct stat statbuf; - if (stat (boot_touched_file, &statbuf) >= 0) - { - struct timespec boot_time = get_stat_mtime (&statbuf); - a = add_utmp (a, options, - "reboot", strlen ("reboot"), - "", 0, - "", 0, - "", 0, - 0, BOOT_TIME, boot_time, 0, 0, 0); - } + struct timespec boot_time; + if (get_windows_boot_time (&boot_time) >= 0) + a = add_utmp (a, options, + "reboot", strlen ("reboot"), + "", 0, + "", 0, + "", 0, + 0, BOOT_TIME, boot_time, 0, 0, 0); } # endif @@ -727,33 +624,10 @@ get_boot_time_uncached (void) free (utmp); } - /* The following approach is only usable as a fallback, because it is of - the form - boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ()) - and therefore 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. */ { - struct timespec uptime; - if (get_linux_uptime (&uptime) >= 0) - { - struct timespec result; - /* equivalent to: - if (clock_gettime (CLOCK_REALTIME, &result) >= 0) - */ - if (timespec_get (&result, TIME_UTC) >= 0) - { - if (result.tv_nsec < uptime.tv_nsec) - { - result.tv_nsec += 1000000000; - result.tv_sec -= 1; - } - result.tv_sec -= uptime.tv_sec; - result.tv_nsec -= uptime.tv_nsec; - return result; - } - } + struct timespec boot_time; + if (get_linux_boot_time_final_fallback (&boot_time) >= 0) + return boot_time; } /* We shouldn't get here. */ diff --git a/m4/readutmp.m4 b/m4/readutmp.m4 index 6ba5b2e225..07eebcd43d 100644 --- a/m4/readutmp.m4 +++ b/m4/readutmp.m4 @@ -1,4 +1,4 @@ -# readutmp.m4 serial 23 +# readutmp.m4 serial 24 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, @@ -6,9 +6,6 @@ dnl with or without modifications, as long as this notice is preserved. AC_DEFUN([gl_READUTMP], [ - dnl Persuade utmpx.h to declare utmpxname - AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) - AC_REQUIRE([gl_SYSTEMD_CHOICE]) dnl Set READUTMP_LIB to '-lsystemd' or '', depending on whether use of @@ -45,6 +42,15 @@ AC_DEFUN([gl_READUTMP], fi AC_SUBST([READUTMP_LIB]) + gl_PREREQ_READUTMP_H +]) + +# Prerequisites of readutmp.h. +AC_DEFUN_ONCE([gl_PREREQ_READUTMP_H], +[ + dnl Persuade utmpx.h to declare utmpxname + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + AC_CHECK_HEADERS_ONCE([utmp.h utmpx.h]) if test $ac_cv_header_utmp_h = yes || test $ac_cv_header_utmpx_h = yes; then dnl Prerequisites of lib/readutmp.h and lib/readutmp.c. diff --git a/modules/readutmp b/modules/readutmp index 21f6de5777..85bbf74954 100644 --- a/modules/readutmp +++ b/modules/readutmp @@ -4,6 +4,7 @@ Read entire utmp file into memory. Files: lib/readutmp.h lib/readutmp.c +lib/boot-time-aux.h m4/readutmp.m4 m4/systemd.m4