exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / boot-time-aux.h
blob8b966fe691fc76d39749479d476e33a11f525e15
1 /* Auxiliary functions for determining the time when the machine last booted.
2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published
6 by the Free Software Foundation, either version 3 of the License,
7 or (at your option) any later version.
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Bruno Haible <bruno@clisp.org>. */
19 #define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
21 #if defined __linux__ || defined __ANDROID__
23 /* Store the uptime counter, as managed by the Linux kernel, in *P_UPTIME.
24 Return 0 upon success, -1 upon failure. */
25 _GL_ATTRIBUTE_MAYBE_UNUSED
26 static int
27 get_linux_uptime (struct timespec *p_uptime)
29 /* The clock_gettime facility returns the uptime with a resolution of 1 µsec.
30 It is available with glibc >= 2.14, Android, or musl libc.
31 In glibc < 2.17 it required linking with librt. */
32 # if !defined __GLIBC__ || 2 < __GLIBC__ + (17 <= __GLIBC_MINOR__)
33 if (clock_gettime (CLOCK_BOOTTIME, p_uptime) >= 0)
34 return 0;
35 # endif
37 /* /proc/uptime contains the uptime with a resolution of 0.01 sec.
38 But it does not have read permissions on Android. */
39 # if !defined __ANDROID__
40 FILE *fp = fopen ("/proc/uptime", "re");
41 if (fp != NULL)
43 char buf[32 + 1];
44 size_t n = fread (buf, 1, sizeof (buf) - 1, fp);
45 fclose (fp);
46 if (n > 0)
48 buf[n] = '\0';
49 /* buf now contains two values: the uptime and the idle time. */
50 time_t s = 0;
51 char *p;
52 for (p = buf; '0' <= *p && *p <= '9'; p++)
53 s = 10 * s + (*p - '0');
54 if (buf < p)
56 long ns = 0;
57 if (*p++ == '.')
58 for (int i = 0; i < 9; i++)
59 ns = 10 * ns + ('0' <= *p && *p <= '9' ? *p++ - '0' : 0);
60 p_uptime->tv_sec = s;
61 p_uptime->tv_nsec = ns;
62 return 0;
66 # endif
68 # if HAVE_DECL_SYSINFO /* not available in Android API < 9 */
69 /* The sysinfo call returns the uptime with a resolution of 1 sec only. */
70 struct sysinfo info;
71 if (sysinfo (&info) >= 0)
73 p_uptime->tv_sec = info.uptime;
74 p_uptime->tv_nsec = 0;
75 return 0;
77 # endif
79 return -1;
82 #endif
84 #if defined __linux__ && !defined __ANDROID__
86 static int
87 get_linux_boot_time_fallback (struct timespec *p_boot_time)
89 /* On Devuan with the 'runit' init system and on Artix with the 's6' init
90 system, UTMP_FILE contains USER_PROCESS and other entries, but no
91 BOOT_TIME entry.
92 On Alpine Linux, UTMP_FILE is not filled. It is always empty.
93 So, in both cases, get the time stamp of a file that gets touched only
94 during the boot process. */
96 const char * const boot_touched_files[] =
98 "/var/lib/systemd/random-seed", /* seen on distros with systemd */
99 "/var/lib/urandom/random-seed", /* seen on Devuan with runit */
100 "/var/lib/random-seed", /* seen on Artix with s6 */
101 /* This must come last, since on several distros /var/run/utmp is
102 modified when a user logs in, i.e. long after boot. */
103 "/var/run/utmp" /* seen on Alpine Linux with OpenRC */
105 for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
107 const char *filename = boot_touched_files[i];
108 struct stat statbuf;
109 if (stat (filename, &statbuf) >= 0)
111 *p_boot_time = get_stat_mtime (&statbuf);
112 return 0;
115 return -1;
118 /* The following approach is only usable as a fallback, because it is of
119 the form
120 boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
121 and therefore produces wrong values after the date has been bumped in the
122 running system, which happens frequently if the system is running in a
123 virtual machine and this VM has been put into "saved" or "sleep" state
124 and then resumed. */
125 static int
126 get_linux_boot_time_final_fallback (struct timespec *p_boot_time)
128 struct timespec uptime;
129 if (get_linux_uptime (&uptime) >= 0)
131 struct timespec result;
132 # if !defined __GLIBC__ || 2 < __GLIBC__ + (16 <= __GLIBC_MINOR__)
133 /* Better than:
134 if (0 <= clock_gettime (CLOCK_REALTIME, &result))
135 because timespec_get does not need -lrt in glibc 2.16.
137 if (! timespec_get (&result, TIME_UTC))
138 return -1;
139 # else
140 /* Fall back on lower-res approach that does not need -lrt.
141 This is good enough; on these hosts UPTIME is even lower-res. */
142 struct timeval tv;
143 int r = gettimeofday (&tv, NULL);
144 if (r < 0)
145 return r;
146 result.tv_sec = tv.tv_sec;
147 result.tv_nsec = tv.tv_usec * 1000;
148 # endif
150 if (result.tv_nsec < uptime.tv_nsec)
152 result.tv_nsec += 1000000000;
153 result.tv_sec -= 1;
155 result.tv_sec -= uptime.tv_sec;
156 result.tv_nsec -= uptime.tv_nsec;
157 *p_boot_time = result;
158 return 0;
160 return -1;
163 #endif
165 #if defined __ANDROID__
167 static int
168 get_android_boot_time (struct timespec *p_boot_time)
170 /* On Android, there is no /var, and normal processes don't have access
171 to system files. Therefore use the kernel's uptime counter, although
172 it produces wrong values after the date has been bumped in the running
173 system. */
174 struct timespec uptime;
175 if (get_linux_uptime (&uptime) >= 0)
177 struct timespec result;
178 if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
180 if (result.tv_nsec < uptime.tv_nsec)
182 result.tv_nsec += 1000000000;
183 result.tv_sec -= 1;
185 result.tv_sec -= uptime.tv_sec;
186 result.tv_nsec -= uptime.tv_nsec;
187 *p_boot_time = result;
188 return 0;
191 return -1;
194 #endif
196 #if defined __OpenBSD__
198 static int
199 get_openbsd_boot_time (struct timespec *p_boot_time)
201 /* On OpenBSD, UTMP_FILE is not filled. It contains only dummy entries.
202 So, get the time stamp of a file that gets touched only during the
203 boot process. */
204 const char * const boot_touched_files[] =
206 "/var/db/host.random",
207 "/var/run/utmp"
209 for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
211 const char *filename = boot_touched_files[i];
212 struct stat statbuf;
213 if (stat (filename, &statbuf) >= 0)
215 *p_boot_time = get_stat_mtime (&statbuf);
216 return 0;
219 return -1;
222 #endif
224 #if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
225 && defined CTL_KERN && defined KERN_BOOTTIME \
226 && !defined __minix
227 /* macOS, FreeBSD, GNU/kFreeBSD, NetBSD, OpenBSD */
228 /* On Minix 3.3 this sysctl produces garbage results. Therefore avoid it. */
230 /* The following approach is only usable as a fallback, because it produces
231 wrong values after the date has been bumped in the running system, which
232 happens frequently if the system is running in a virtual machine and this
233 VM has been put into "saved" or "sleep" state and then resumed. */
234 static int
235 get_bsd_boot_time_final_fallback (struct timespec *p_boot_time)
237 static int request[2] = { CTL_KERN, KERN_BOOTTIME };
238 struct timeval result;
239 size_t result_len = sizeof result;
241 if (sysctl (request, 2, &result, &result_len, NULL, 0) >= 0)
243 p_boot_time->tv_sec = result.tv_sec;
244 p_boot_time->tv_nsec = result.tv_usec * 1000;
245 return 0;
247 return -1;
250 #endif
252 #if defined __HAIKU__
254 static int
255 get_haiku_boot_time (struct timespec *p_boot_time)
257 /* On Haiku, /etc/utmp does not exist. During boot,
258 1. the current time is restored, but possibly with a wrong time zone,
259 that is, with an offset of a few hours,
260 2. some symlinks and files get created,
261 3. the various devices are brought up, in particular the network device,
262 4. the correct date and time is set,
263 5. some more device nodes get created.
264 The boot time can be retrieved by looking at a directory created during
265 phase 5, such as /dev/input. */
266 const char * const boot_touched_file = "/dev/input";
267 struct stat statbuf;
268 if (stat (boot_touched_file, &statbuf) >= 0)
270 *p_boot_time = get_stat_mtime (&statbuf);
271 return 0;
273 return -1;
276 #endif
278 #if HAVE_OS_H /* BeOS, Haiku */
280 /* The following approach is only usable as a fallback, because it produces
281 wrong values after the date has been bumped in the running system, which
282 happens frequently if the system is running in a virtual machine and this
283 VM has been put into "saved" or "sleep" state and then resumed. */
284 static int
285 get_haiku_boot_time_final_fallback (struct timespec *p_boot_time)
287 system_info si;
289 get_system_info (&si);
290 p_boot_time->tv_sec = si.boot_time / 1000000;
291 p_boot_time->tv_nsec = (si.boot_time % 1000000) * 1000;
292 return 0;
295 #endif
297 #if defined __CYGWIN__ || defined _WIN32
299 static int
300 get_windows_boot_time (struct timespec *p_boot_time)
302 /* On Cygwin, /var/run/utmp is empty.
303 On native Windows, <utmpx.h> and <utmp.h> don't exist.
304 Instead, on Windows, the boot time can be retrieved by looking at the
305 time stamp of a file that (normally) gets touched only during the boot
306 process, namely C:\pagefile.sys. */
307 const char * const boot_touched_file =
308 #if defined __CYGWIN__ && !defined _WIN32
309 "/cygdrive/c/pagefile.sys"
310 #else
311 "C:\\pagefile.sys"
312 #endif
314 struct stat statbuf;
315 if (stat (boot_touched_file, &statbuf) >= 0)
317 *p_boot_time = get_stat_mtime (&statbuf);
318 return 0;
320 return -1;
323 #endif