openat: don’t close (-1)
[gnulib.git] / lib / boot-time-aux.h
blob8b98c4e57344213caac48d6a5414fc65e4cc9ed4
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 struct timespec boot_time = get_stat_mtime (&statbuf);
112 /* On Alpine 3.20.0_rc2 /var/run/utmp was observed with bogus
113 timestamps of ~10 s. Reject timestamps before
114 2005-07-25 23:34:15 UTC (1122334455), as neither Alpine
115 nor Devuan existed then. */
116 if (boot_time.tv_sec >= 1122334455)
118 *p_boot_time = boot_time;
119 return 0;
123 return -1;
126 /* The following approach is only usable as a fallback, because it is of
127 the form
128 boot_time = (time now) - (kernel's ktime_get_boottime[_ts64] ())
129 and therefore produces wrong values after the date has been bumped in the
130 running system, which happens frequently if the system is running in a
131 virtual machine and this VM has been put into "saved" or "sleep" state
132 and then resumed. */
133 static int
134 get_linux_boot_time_final_fallback (struct timespec *p_boot_time)
136 struct timespec uptime;
137 if (get_linux_uptime (&uptime) >= 0)
139 struct timespec result;
140 # if !defined __GLIBC__ || 2 < __GLIBC__ + (16 <= __GLIBC_MINOR__)
141 /* Better than:
142 if (0 <= clock_gettime (CLOCK_REALTIME, &result))
143 because timespec_get does not need -lrt in glibc 2.16.
145 if (! timespec_get (&result, TIME_UTC))
146 return -1;
147 # else
148 /* Fall back on lower-res approach that does not need -lrt.
149 This is good enough; on these hosts UPTIME is even lower-res. */
150 struct timeval tv;
151 int r = gettimeofday (&tv, NULL);
152 if (r < 0)
153 return r;
154 result.tv_sec = tv.tv_sec;
155 result.tv_nsec = tv.tv_usec * 1000;
156 # endif
158 if (result.tv_nsec < uptime.tv_nsec)
160 result.tv_nsec += 1000000000;
161 result.tv_sec -= 1;
163 result.tv_sec -= uptime.tv_sec;
164 result.tv_nsec -= uptime.tv_nsec;
165 *p_boot_time = result;
166 return 0;
168 return -1;
171 #endif
173 #if defined __ANDROID__
175 static int
176 get_android_boot_time (struct timespec *p_boot_time)
178 /* On Android, there is no /var, and normal processes don't have access
179 to system files. Therefore use the kernel's uptime counter, although
180 it produces wrong values after the date has been bumped in the running
181 system. */
182 struct timespec uptime;
183 if (get_linux_uptime (&uptime) >= 0)
185 struct timespec result;
186 if (clock_gettime (CLOCK_REALTIME, &result) >= 0)
188 if (result.tv_nsec < uptime.tv_nsec)
190 result.tv_nsec += 1000000000;
191 result.tv_sec -= 1;
193 result.tv_sec -= uptime.tv_sec;
194 result.tv_nsec -= uptime.tv_nsec;
195 *p_boot_time = result;
196 return 0;
199 return -1;
202 #endif
204 #if defined __OpenBSD__
206 static int
207 get_openbsd_boot_time (struct timespec *p_boot_time)
209 /* On OpenBSD, UTMP_FILE is not filled. It contains only dummy entries.
210 So, get the time stamp of a file that gets touched only during the
211 boot process. */
212 const char * const boot_touched_files[] =
214 "/var/db/host.random",
215 "/var/run/utmp"
217 for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
219 const char *filename = boot_touched_files[i];
220 struct stat statbuf;
221 if (stat (filename, &statbuf) >= 0)
223 *p_boot_time = get_stat_mtime (&statbuf);
224 return 0;
227 return -1;
230 #endif
232 #if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
233 && defined CTL_KERN && defined KERN_BOOTTIME \
234 && !defined __minix
235 /* macOS, FreeBSD, GNU/kFreeBSD, NetBSD, OpenBSD */
236 /* On Minix 3.3 this sysctl produces garbage results. Therefore avoid it. */
238 /* The following approach is only usable as a fallback, because it produces
239 wrong values after the date has been bumped in the running system, which
240 happens frequently if the system is running in a virtual machine and this
241 VM has been put into "saved" or "sleep" state and then resumed. */
242 static int
243 get_bsd_boot_time_final_fallback (struct timespec *p_boot_time)
245 static int request[2] = { CTL_KERN, KERN_BOOTTIME };
246 struct timeval result;
247 size_t result_len = sizeof result;
249 if (sysctl (request, 2, &result, &result_len, NULL, 0) >= 0)
251 p_boot_time->tv_sec = result.tv_sec;
252 p_boot_time->tv_nsec = result.tv_usec * 1000;
253 return 0;
255 return -1;
258 #endif
260 #if defined __HAIKU__
262 static int
263 get_haiku_boot_time (struct timespec *p_boot_time)
265 /* On Haiku, /etc/utmp does not exist. During boot,
266 1. the current time is restored, but possibly with a wrong time zone,
267 that is, with an offset of a few hours,
268 2. some symlinks and files get created,
269 3. the various devices are brought up, in particular the network device,
270 4. the correct date and time is set,
271 5. some more device nodes get created.
272 The boot time can be retrieved by looking at a directory created during
273 phase 5, such as /dev/input. */
274 const char * const boot_touched_file = "/dev/input";
275 struct stat statbuf;
276 if (stat (boot_touched_file, &statbuf) >= 0)
278 *p_boot_time = get_stat_mtime (&statbuf);
279 return 0;
281 return -1;
284 #endif
286 #if HAVE_OS_H /* BeOS, Haiku */
288 /* The following approach is only usable as a fallback, because it produces
289 wrong values after the date has been bumped in the running system, which
290 happens frequently if the system is running in a virtual machine and this
291 VM has been put into "saved" or "sleep" state and then resumed. */
292 static int
293 get_haiku_boot_time_final_fallback (struct timespec *p_boot_time)
295 system_info si;
297 get_system_info (&si);
298 p_boot_time->tv_sec = si.boot_time / 1000000;
299 p_boot_time->tv_nsec = (si.boot_time % 1000000) * 1000;
300 return 0;
303 #endif
305 #if defined __CYGWIN__ || defined _WIN32
307 static int
308 get_windows_boot_time (struct timespec *p_boot_time)
310 /* On Cygwin, /var/run/utmp is empty.
311 On native Windows, <utmpx.h> and <utmp.h> don't exist.
312 Instead, on Windows, the boot time can be retrieved by looking at the
313 time stamp of a file that (normally) gets touched only during the boot
314 process, namely C:\pagefile.sys. */
315 const char * const boot_touched_files[] =
317 #if defined __CYGWIN__ && !defined _WIN32
318 /* It is more portable to use /proc/cygdrive/c than /cygdrive/c. */
319 "/proc/cygdrive/c/pagefile.sys",
320 /* A fallback, working around a Cygwin 3.5.3 bug. It has a modification
321 time about 1.5 minutes after the last boot; but that's better than
322 nothing. */
323 "/proc/cygdrive/c/ProgramData/Microsoft/Windows/DeviceMetadataCache/dmrc.idx"
324 #else
325 "C:\\pagefile.sys"
326 #endif
328 for (idx_t i = 0; i < SIZEOF (boot_touched_files); i++)
330 const char *filename = boot_touched_files[i];
331 struct stat statbuf;
332 if (stat (filename, &statbuf) >= 0)
334 # if defined __CYGWIN__ && !defined _WIN32
335 /* Work around a Cygwin 3.5.3 bug.
336 <https://cygwin.com/pipermail/cygwin/2024-May/255931.html> */
337 if (!S_ISDIR (statbuf.st_mode))
338 # endif
340 *p_boot_time = get_stat_mtime (&statbuf);
341 return 0;
345 return -1;
348 # ifndef __CYGWIN__
349 # if !(_WIN32_WINNT >= _WIN32_WINNT_VISTA)
351 /* Don't assume that UNICODE is not defined. */
352 # undef LoadLibrary
353 # define LoadLibrary LoadLibraryA
355 /* Avoid warnings from gcc -Wcast-function-type. */
356 # define GetProcAddress \
357 (void *) GetProcAddress
359 /* GetTickCount64 is only available on Windows Vista and later. */
360 typedef ULONGLONG (WINAPI * GetTickCount64FuncType) (void);
362 static GetTickCount64FuncType GetTickCount64Func = NULL;
363 static BOOL initialized = FALSE;
365 static void
366 initialize (void)
368 HMODULE kernel32 = LoadLibrary ("kernel32.dll");
369 if (kernel32 != NULL)
371 GetTickCount64Func =
372 (GetTickCount64FuncType) GetProcAddress (kernel32, "GetTickCount64");
374 initialized = TRUE;
377 # else
379 # define GetTickCount64Func GetTickCount64
381 # endif
383 /* Fallback for Windows in the form:
384 boot time = current time - uptime
385 This uses the GetTickCount64 function which is only available on Windows
386 Vista and later. See:
387 <https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64>. */
388 static int
389 get_windows_boot_time_fallback (struct timespec *p_boot_time)
391 # if !(_WIN32_WINNT >= _WIN32_WINNT_VISTA)
392 if (! initialized)
393 initialize ();
394 # endif
395 if (GetTickCount64Func != NULL)
397 ULONGLONG uptime_ms = GetTickCount64Func ();
398 struct timespec uptime;
399 struct timespec result;
400 struct timeval tv;
401 if (gettimeofday (&tv, NULL) >= 0)
403 uptime.tv_sec = uptime_ms / 1000;
404 uptime.tv_nsec = (uptime_ms % 1000) * 1000000;
405 result.tv_sec = tv.tv_sec;
406 result.tv_nsec = tv.tv_usec * 1000;
407 if (result.tv_nsec < uptime.tv_nsec)
409 result.tv_nsec += 1000000000;
410 result.tv_sec -= 1;
412 result.tv_sec -= uptime.tv_sec;
413 result.tv_nsec -= uptime.tv_nsec;
414 *p_boot_time = result;
415 return 0;
418 return -1;
421 # endif
422 #endif