errno-h: document Haiku errors can’t be -1
[gnulib.git] / lib / boot-time.c
blob9104bae16ed45cde8d6d7e35d7d211b8c039930b
1 /* Determine the time when the machine last booted.
2 Copyright (C) 2023-2025 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 #include <config.h>
21 /* Specification. */
22 #include "boot-time.h"
24 #include <stddef.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
30 #if defined __linux__ || defined __ANDROID__
31 # include <sys/sysinfo.h>
32 # include <time.h>
33 #endif
35 #if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) && !defined __minix
36 # if HAVE_SYS_PARAM_H
37 # include <sys/param.h>
38 # endif
39 # include <sys/sysctl.h>
40 #endif
42 #if HAVE_OS_H
43 # include <OS.h>
44 #endif
46 #if defined _WIN32 && ! defined __CYGWIN__
47 # define WIN32_LEAN_AND_MEAN
48 # include <windows.h>
49 # include <sys/time.h>
50 #endif
52 #include "idx.h"
53 #include "readutmp.h"
54 #include "stat-time.h"
56 /* Each of the FILE streams in this file is only used in a single thread. */
57 #include "unlocked-io.h"
59 /* Some helper functions. */
60 #include "boot-time-aux.h"
62 /* The following macros describe the 'struct UTMP_STRUCT_NAME',
63 *not* 'struct gl_utmp'. */
64 #undef UT_USER
66 /* Accessor macro for the member named ut_user or ut_name. */
67 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \
68 : HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME)
69 # define UT_USER(UT) ((UT)->ut_name)
70 #else
71 # define UT_USER(UT) ((UT)->ut_user)
72 #endif
74 #if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION
75 # if !HAVE_DECL_ENDUTENT /* Android */
76 void endutent (void);
77 # endif
78 #endif
80 #if defined __linux__ || HAVE_UTMPX_H || HAVE_UTMP_H || defined __CYGWIN__ || defined _WIN32
82 static int
83 get_boot_time_uncached (struct timespec *p_boot_time)
85 struct timespec found_boot_time = {0};
87 # if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
89 /* Try to find the boot time in the /var/run/utmp file. */
91 # if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */
93 /* Ignore the return value for now.
94 Solaris' utmpname returns 1 upon success -- which is contrary
95 to what the GNU libc version does. In addition, older GNU libc
96 versions are actually void. */
97 UTMP_NAME_FUNCTION ((char *) UTMP_FILE);
99 SET_UTMP_ENT ();
101 # if (defined __linux__ && !defined __ANDROID__) || defined __minix
102 /* Timestamp of the "runlevel" entry, if any. */
103 struct timespec runlevel_ts = {0};
104 # endif
106 void const *entry;
108 while ((entry = GET_UTMP_ENT ()) != NULL)
110 struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
112 struct timespec ts =
113 #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
114 { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 };
115 #else
116 { .tv_sec = ut->ut_time, .tv_nsec = 0 };
117 #endif
119 if (ut->ut_type == BOOT_TIME)
120 found_boot_time = ts;
122 # if defined __linux__ && !defined __ANDROID__
123 if (memcmp (UT_USER (ut), "runlevel", strlen ("runlevel") + 1) == 0
124 && memcmp (ut->ut_line, "~", strlen ("~") + 1) == 0)
125 runlevel_ts = ts;
126 # endif
127 # if defined __minix
128 if (UT_USER (ut)[0] == '\0'
129 && memcmp (ut->ut_line, "run-level ", strlen ("run-level ")) == 0)
130 runlevel_ts = ts;
131 # endif
134 END_UTMP_ENT ();
136 # if defined __linux__ && !defined __ANDROID__
137 /* On Raspbian, which runs on hardware without a real-time clock, during boot,
138 1. the clock gets set to 1970-01-01 00:00:00,
139 2. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
140 ut_user = "reboot", ut_line = "~", time = 1970-01-01 00:00:05 or so,
141 3. the clock gets set to a correct value through NTP,
142 4. an entry gets written into /var/run/utmp, with
143 ut_user = "runlevel", ut_line = "~", time = correct value.
144 In this case, get the time from the "runlevel" entry. */
146 /* Workaround for Raspbian: */
147 if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
148 found_boot_time = runlevel_ts;
149 if (found_boot_time.tv_sec == 0)
151 /* Workaround for Alpine Linux: */
152 get_linux_boot_time_fallback (&found_boot_time);
154 # endif
156 # if defined __ANDROID__
157 if (found_boot_time.tv_sec == 0)
159 /* Workaround for Android: */
160 get_android_boot_time (&found_boot_time);
162 # endif
164 # if defined __minix
165 /* On Minix, during boot,
166 1. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
167 ut_user = "", ut_line = "system boot", time = 1970-01-01 00:00:00,
168 2. an entry gets written into /var/run/utmp, with
169 ut_user = "", ut_line = "run-level m", time = correct value.
170 In this case, copy the time from the "run-level m" entry to the
171 "system boot" entry. */
172 if (found_boot_time.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
173 found_boot_time = runlevel_ts;
174 # endif
176 # else /* HP-UX, Haiku */
178 FILE *f = fopen (UTMP_FILE, "re");
180 if (f != NULL)
182 for (;;)
184 struct UTMP_STRUCT_NAME ut;
186 if (fread (&ut, sizeof ut, 1, f) == 0)
187 break;
189 struct timespec ts =
190 #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
191 { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 };
192 #else
193 { .tv_sec = ut.ut_time, .tv_nsec = 0 };
194 #endif
196 if (ut.ut_type == BOOT_TIME)
197 found_boot_time = ts;
200 fclose (f);
203 # endif
205 # if defined __linux__ && !defined __ANDROID__
206 if (found_boot_time.tv_sec == 0)
208 get_linux_boot_time_final_fallback (&found_boot_time);
210 # endif
212 # else /* Adélie Linux, old FreeBSD, OpenBSD, native Windows */
214 # if defined __linux__ && !defined __ANDROID__
215 /* Workaround for Adélie Linux: */
216 get_linux_boot_time_fallback (&found_boot_time);
217 if (found_boot_time.tv_sec == 0)
218 get_linux_boot_time_final_fallback (&found_boot_time);
219 # endif
221 # if defined __OpenBSD__
222 /* Workaround for OpenBSD: */
223 get_openbsd_boot_time (&found_boot_time);
224 # endif
226 # endif
228 # if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
229 && defined CTL_KERN && defined KERN_BOOTTIME \
230 && !defined __minix
231 if (found_boot_time.tv_sec == 0)
233 get_bsd_boot_time_final_fallback (&found_boot_time);
235 # endif
237 # if defined __HAIKU__
238 if (found_boot_time.tv_sec == 0)
240 get_haiku_boot_time (&found_boot_time);
242 # endif
244 # if HAVE_OS_H
245 if (found_boot_time.tv_sec == 0)
247 get_haiku_boot_time_final_fallback (&found_boot_time);
249 # endif
251 # if defined __CYGWIN__ || defined _WIN32
252 if (found_boot_time.tv_sec == 0)
254 /* Workaround for Windows: */
255 get_windows_boot_time (&found_boot_time);
256 # ifndef __CYGWIN__
257 if (found_boot_time.tv_sec == 0)
258 get_windows_boot_time_fallback (&found_boot_time);
259 # endif
261 # endif
263 if (found_boot_time.tv_sec != 0)
265 *p_boot_time = found_boot_time;
266 return 0;
268 else
269 return -1;
273 get_boot_time (struct timespec *p_boot_time)
275 /* Cache the result from get_boot_time_uncached. */
276 static int volatile cached_result = -1;
277 static struct timespec volatile cached_boot_time;
279 if (cached_result < 0)
281 struct timespec boot_time;
282 int result = get_boot_time_uncached (&boot_time);
283 cached_boot_time = boot_time;
284 cached_result = result;
287 if (cached_result == 0)
289 *p_boot_time = cached_boot_time;
290 return 0;
292 else
293 return -1;
296 #else
299 get_boot_time (struct timespec *p_boot_time)
301 return -1;
304 #endif