This set of patches straightens the code of the 'boot-time' module: Determining the boot time does require iterating through the /var/run/utmpx file, but it does not require - memory allocation, - copying of strings, - evaluating an options parameter, - linking with libsystemd.
So, let me replace the code of this module with a simplified copy of lib/readutmp.c, while at the same time avoiding large amounts of code duplication. Paul: With this simplification, you may consider using the 'boot-time' module in Emacs. I bet that it produces a better result than Emacs' src/filelock.c on many platforms. (I haven't tested it, but I could test it if you give me a manual testing recipe.) 2023-08-11 Bruno Haible <br...@clisp.org> boot-time: Add comment about multithread-safety. * lib/boot-time.h (get_boot_time): Add comment, same as in readutmp.h. 2023-08-11 Bruno Haible <br...@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 <br...@clisp.org> readutmp: Refactor boot time determination code. * lib/boot-time-aux.h: New file, extracted from lib/readutmp.c. * lib/readutmp.c: On Linux, include <sys/sysinfo.h> 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 <br...@clisp.org> readutmp: Make 'struct utmpx32' usable by other code. * lib/readutmp.h (struct utmpx32): Moved to here from lib/readutmp.c. (UTMP_STRUCT_NAME): Define as utmpx32 if needed. * lib/readutmp.c (read_utmp_from_file): Simply use UTMP_STRUCT_NAME.
From cdbe33c428a82d42ff6fdcb48815edeb9d9d7fd8 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 11 Aug 2023 16:09:22 +0200 Subject: [PATCH 1/4] readutmp: Make 'struct utmpx32' usable by other code. * lib/readutmp.h (struct utmpx32): Moved to here from lib/readutmp.c. (UTMP_STRUCT_NAME): Define as utmpx32 if needed. * lib/readutmp.c (read_utmp_from_file): Simply use UTMP_STRUCT_NAME. --- ChangeLog | 7 +++++++ lib/readutmp.c | 33 --------------------------------- lib/readutmp.h | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/ChangeLog b/ChangeLog index 526e5cb4be..6836ca6308 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2023-08-11 Bruno Haible <br...@clisp.org> + + readutmp: Make 'struct utmpx32' usable by other code. + * lib/readutmp.h (struct utmpx32): Moved to here from lib/readutmp.c. + (UTMP_STRUCT_NAME): Define as utmpx32 if needed. + * lib/readutmp.c (read_utmp_from_file): Simply use UTMP_STRUCT_NAME. + 2023-08-11 Bruno Haible <br...@clisp.org> readutmp tests: Fix link error. diff --git a/lib/readutmp.c b/lib/readutmp.c index 40cee0e58c..00dbbcea48 100644 --- a/lib/readutmp.c +++ b/lib/readutmp.c @@ -383,40 +383,7 @@ read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, while ((entry = GET_UTMP_ENT ()) != NULL) { -# if __GLIBC__ && _TIME_BITS == 64 - /* This is a near-copy of glibc's struct utmpx, which stops working - after the year 2038. Unlike the glibc version, struct utmpx32 - describes the file format even if time_t is 64 bits. */ - struct utmpx32 - { - short int ut_type; /* Type of login. */ - pid_t ut_pid; /* Process ID of login process. */ - char ut_line[UT_LINE_SIZE]; /* Devicename. */ - char ut_id[UT_ID_SIZE]; /* Inittab ID. */ - char ut_user[UT_USER_SIZE]; /* Username. */ - char ut_host[UT_HOST_SIZE]; /* Hostname for remote login. */ - struct __exit_status ut_exit; /* Exit status of a process marked - as DEAD_PROCESS. */ - /* The fields ut_session and ut_tv must be the same size when compiled - 32- and 64-bit. This allows files and shared memory to be shared - between 32- and 64-bit applications. */ - int ut_session; /* Session ID, used for windowing. */ - struct - { - /* Seconds. Unsigned not signed, as glibc did not exist before 1970, - and if the format is still in use after 2038 its timestamps - will surely have the sign bit on. This hack stops working - at 2106-02-07 06:28:16 UTC. */ - unsigned int tv_sec; - int tv_usec; /* Microseconds. */ - } ut_tv; /* Time entry was made. */ - int ut_addr_v6[4]; /* Internet address of remote host. */ - char ut_reserved[20]; /* Reserved for future use. */ - }; - struct utmpx32 const *ut = (struct utmpx32 const *) entry; -# else struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry; -# endif struct timespec ts = #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV) diff --git a/lib/readutmp.h b/lib/readutmp.h index 70cd3801db..1cf588d265 100644 --- a/lib/readutmp.h +++ b/lib/readutmp.h @@ -132,7 +132,40 @@ enum { UT_HOST_SIZE = -1 }; ⎣ ut_ss struct sockaddr_storage NetBSD, Minix */ -# define UTMP_STRUCT_NAME utmpx +# if __GLIBC__ && _TIME_BITS == 64 +/* This is a near-copy of glibc's struct utmpx, which stops working + after the year 2038. Unlike the glibc version, struct utmpx32 + describes the file format even if time_t is 64 bits. */ +struct utmpx32 +{ + short int ut_type; /* Type of login. */ + pid_t ut_pid; /* Process ID of login process. */ + char ut_line[__UT_LINESIZE]; /* Devicename. */ + char ut_id[4]; /* Inittab ID. */ + char ut_user[__UT_USERSIZE]; /* Username. */ + char ut_host[__UT_HOSTSIZE]; /* Hostname for remote login. */ + struct __exit_status ut_exit; /* Exit status of a process marked + as DEAD_PROCESS. */ + /* The fields ut_session and ut_tv must be the same size when compiled + 32- and 64-bit. This allows files and shared memory to be shared + between 32- and 64-bit applications. */ + int ut_session; /* Session ID, used for windowing. */ + struct + { + /* Seconds. Unsigned not signed, as glibc did not exist before 1970, + and if the format is still in use after 2038 its timestamps + will surely have the sign bit on. This hack stops working + at 2106-02-07 06:28:16 UTC. */ + unsigned int tv_sec; + int tv_usec; /* Microseconds. */ + } ut_tv; /* Time entry was made. */ + int ut_addr_v6[4]; /* Internet address of remote host. */ + char ut_reserved[20]; /* Reserved for future use. */ +}; +# define UTMP_STRUCT_NAME utmpx32 +# else +# define UTMP_STRUCT_NAME utmpx +# endif # define SET_UTMP_ENT setutxent # define GET_UTMP_ENT getutxent # define END_UTMP_ENT endutxent -- 2.34.1
From 1657e67fe3f4be3cd572430358991bc12c62b193 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 11 Aug 2023 23:20:44 +0200 Subject: [PATCH 2/4] readutmp: Refactor boot time determination code. * lib/boot-time-aux.h: New file, extracted from lib/readutmp.c. * lib/readutmp.c: On Linux, include <sys/sysinfo.h> 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. --- ChangeLog | 19 ++++ lib/boot-time-aux.h | 228 ++++++++++++++++++++++++++++++++++++++++++++ lib/readutmp.c | 208 ++++++++-------------------------------- m4/readutmp.m4 | 14 ++- modules/readutmp | 1 + 5 files changed, 299 insertions(+), 171 deletions(-) create mode 100644 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 <br...@clisp.org> + + readutmp: Refactor boot time determination code. + * lib/boot-time-aux.h: New file, extracted from lib/readutmp.c. + * lib/readutmp.c: On Linux, include <sys/sysinfo.h> 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 <br...@clisp.org> 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 <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>. */ + +#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, <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. */ + 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 <stdlib.h> #include <stdint.h> -#if READUTMP_USE_SYSTEMD || defined __ANDROID__ +#if defined __linux__ || defined __ANDROID__ # include <sys/sysinfo.h> # include <time.h> #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 @@ 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 -- 2.34.1
>From bea15dd14f8388a4b78b63ecfa0decad0414ec88 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 11 Aug 2023 23:30:01 +0200 Subject: [PATCH 3/4] 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). --- ChangeLog | 21 ++++ lib/boot-time.c | 223 ++++++++++++++++++++++++++++++++++++++-- modules/boot-time | 14 ++- modules/boot-time-tests | 2 +- 4 files changed, 250 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index 29d5c73907..bfc2954a80 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2023-08-11 Bruno Haible <br...@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 <br...@clisp.org> readutmp: Refactor boot time determination code. diff --git a/lib/boot-time.c b/lib/boot-time.c index fe1e13eccb..932ee22d69 100644 --- a/lib/boot-time.c +++ b/lib/boot-time.c @@ -22,22 +22,231 @@ #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 diff --git a/modules/boot-time b/modules/boot-time index 99ae5c9fe7..ba8a2b4e6f 100644 --- a/modules/boot-time +++ b/modules/boot-time @@ -4,12 +4,22 @@ Determine the time when the machine last booted. Files: lib/boot-time.h lib/boot-time.c +lib/boot-time-aux.h +lib/readutmp.h +m4/readutmp.m4 Depends-on: +extensions +fopen-gnu idx -readutmp +stat-time +stdbool +time-h +timespec_get +unlocked-io-internal configure.ac: +gl_PREREQ_READUTMP_H Makefile.am: lib_SOURCES += boot-time.c @@ -18,7 +28,7 @@ Include: "boot-time.h" Link: -$(READUTMP_LIB) +$(CLOCK_TIME_LIB) License: GPL diff --git a/modules/boot-time-tests b/modules/boot-time-tests index 0ba9d467ca..bb56ee36e5 100644 --- a/modules/boot-time-tests +++ b/modules/boot-time-tests @@ -8,4 +8,4 @@ configure.ac: Makefile.am: TESTS += test-boot-time check_PROGRAMS += test-boot-time -test_boot_time_LDADD = $(LDADD) $(READUTMP_LIB) +test_boot_time_LDADD = $(LDADD) $(CLOCK_TIME_LIB) -- 2.34.1
>From 5cd9573bc638b433eae64331dc61188eae6e434e Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 11 Aug 2023 23:31:37 +0200 Subject: [PATCH 4/4] boot-time: Add comment about multithread-safety. * lib/boot-time.h (get_boot_time): Add comment, same as in readutmp.h. --- ChangeLog | 5 +++++ lib/boot-time.h | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index bfc2954a80..472349e5e4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-08-11 Bruno Haible <br...@clisp.org> + + boot-time: Add comment about multithread-safety. + * lib/boot-time.h (get_boot_time): Add comment, same as in readutmp.h. + 2023-08-11 Bruno Haible <br...@clisp.org> boot-time: Simplify execution. diff --git a/lib/boot-time.h b/lib/boot-time.h index 159a0056f9..401e854adb 100644 --- a/lib/boot-time.h +++ b/lib/boot-time.h @@ -27,7 +27,13 @@ extern "C" { /* Store the approximate time when the machine last booted in *P_BOOT_TIME, - and return 0. If it cannot be determined, return -1. */ + and return 0. If it cannot be determined, return -1. + + This function is not multithread-safe, since on many platforms it + invokes the functions setutxent, getutxent, endutxent. These + functions are needed because they may lock FILE (so that we don't + read garbage when a concurrent process writes to FILE), but their + drawback is that they have a common global state. */ extern int get_boot_time (struct timespec *p_boot_time); -- 2.34.1