exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / readutmp.c
blobae2e3ae8c676f8400b9069b09b6eb83607a85d07
1 /* GNU's read utmp module.
3 Copyright (C) 1992-2001, 2003-2006, 2009-2024 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Written by jla; revised by djm */
20 #include <config.h>
22 #include "readutmp.h"
24 #include <errno.h>
25 #include <stdio.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <signal.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <stdint.h>
34 #if defined __linux__ || defined __ANDROID__
35 # include <sys/sysinfo.h>
36 # include <time.h>
37 #endif
38 #if READUTMP_USE_SYSTEMD
39 # include <dirent.h>
40 # include <systemd/sd-login.h>
41 #endif
43 #if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) && !defined __minix
44 # if HAVE_SYS_PARAM_H
45 # include <sys/param.h>
46 # endif
47 # include <sys/sysctl.h>
48 #endif
50 #if HAVE_OS_H
51 # include <OS.h>
52 #endif
54 #include "stat-time.h"
55 #include "xalloc.h"
57 /* Each of the FILE streams in this file is only used in a single thread. */
58 #include "unlocked-io.h"
60 /* Some helper functions. */
61 #include "boot-time-aux.h"
63 /* The following macros describe the 'struct UTMP_STRUCT_NAME',
64 *not* 'struct gl_utmp'. */
65 #undef UT_USER
66 #undef UT_TIME_MEMBER
67 #undef UT_PID
68 #undef UT_TYPE_EQ
69 #undef UT_TYPE_NOT_DEFINED
70 #undef UT_EXIT_E_TERMINATION
71 #undef UT_EXIT_E_EXIT
73 /* Accessor macro for the member named ut_user or ut_name. */
74 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_NAME \
75 : HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_NAME)
76 # define UT_USER(UT) ((UT)->ut_name)
77 #else
78 # define UT_USER(UT) ((UT)->ut_user)
79 #endif
81 /* Accessor macro for the member of type time_t (or 'unsigned int'). */
82 #if HAVE_UTMPX_H || (HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_TV)
83 # define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec)
84 #else
85 # define UT_TIME_MEMBER(UT) ((UT)->ut_time)
86 #endif
88 /* Accessor macro for the member named ut_pid. */
89 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
90 # define UT_PID(UT) ((UT)->ut_pid)
91 #else
92 # define UT_PID(UT) 0
93 #endif
95 /* Accessor macros for the member named ut_type. */
96 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
97 # define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V))
98 # define UT_TYPE_NOT_DEFINED 0
99 #else
100 # define UT_TYPE_EQ(UT, V) 0
101 # define UT_TYPE_NOT_DEFINED 1
102 #endif
104 #if HAVE_UTMPX_H
105 # if HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION
106 # define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination)
107 # elif HAVE_STRUCT_UTMPX_UT_EXIT_UT_TERMINATION /* OSF/1 */
108 # define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.ut_termination)
109 # else
110 # define UT_EXIT_E_TERMINATION(UT) 0
111 # endif
112 #elif HAVE_UTMP_H
113 # if HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION
114 # define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination)
115 # else
116 # define UT_EXIT_E_TERMINATION(UT) 0
117 # endif
118 #endif
120 #if HAVE_UTMPX_H
121 # if HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT
122 # define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit)
123 # elif HAVE_STRUCT_UTMPX_UT_EXIT_UT_EXIT /* OSF/1 */
124 # define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.ut_exit)
125 # else
126 # define UT_EXIT_E_EXIT(UT) 0
127 # endif
128 #elif HAVE_UTMP_H
129 # if HAVE_STRUCT_UTMP_UT_EXIT_E_EXIT
130 # define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit)
131 # else
132 # define UT_EXIT_E_EXIT(UT) 0
133 # endif
134 #endif
136 /* Size of the UT_USER (ut) member. */
137 #define UT_USER_SIZE sizeof UT_USER ((struct UTMP_STRUCT_NAME *) 0)
138 /* Size of the ut->ut_id member. */
139 #define UT_ID_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_id)
140 /* Size of the ut->ut_line member. */
141 #define UT_LINE_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_line)
142 /* Size of the ut->ut_host member. */
143 #define UT_HOST_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_host)
145 #if 8 <= __GNUC__
146 # pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
147 #endif
149 /* Copy UT->ut_user into storage obtained from malloc. Then remove any
150 trailing spaces from the copy, NUL terminate it, and return the copy. */
152 char *
153 extract_trimmed_name (const STRUCT_UTMP *ut)
155 char const *name = ut->ut_user;
156 idx_t len = strlen (name);
157 char const *p;
158 for (p = name + len; name < p && p[-1] == ' '; p--)
159 continue;
160 return ximemdup0 (name, p - name);
163 #if READ_UTMP_SUPPORTED
165 /* Is the utmp entry UT desired by the user who asked for OPTIONS? */
167 static bool
168 desirable_utmp_entry (STRUCT_UTMP const *ut, int options)
170 # if defined __OpenBSD__ && !HAVE_UTMPX_H
171 /* Eliminate entirely empty entries. */
172 if (ut->ut_ts.tv_sec == 0 && ut->ut_user[0] == '\0'
173 && ut->ut_line[0] == '\0' && ut->ut_host[0] == '\0')
174 return false;
175 # endif
177 bool boot_time = UT_TYPE_BOOT_TIME (ut);
178 if ((options & READ_UTMP_BOOT_TIME) && !boot_time)
179 return false;
180 if ((options & READ_UTMP_NO_BOOT_TIME) && boot_time)
181 return false;
183 bool user_proc = IS_USER_PROCESS (ut);
184 if ((options & READ_UTMP_USER_PROCESS) && !user_proc)
185 return false;
186 # if !(defined __CYGWIN__ || defined _WIN32)
187 if ((options & READ_UTMP_CHECK_PIDS)
188 && user_proc
189 && 0 < UT_PID (ut)
190 && (kill (UT_PID (ut), 0) < 0 && errno == ESRCH))
191 return false;
192 # endif
194 return true;
197 /* A memory allocation for an in-progress read_utmp. */
199 struct utmp_alloc
201 /* A pointer to a possibly-empty array of utmp entries,
202 followed by a possibly-empty sequence of unused bytes,
203 followed by a possibly-empty sequence of string bytes.
204 UTMP is either null or allocated by malloc. */
205 struct gl_utmp *utmp;
207 /* The number of utmp entries. */
208 idx_t filled;
210 /* The string byte sequence length. Strings are null-terminated. */
211 idx_t string_bytes;
213 /* The total number of bytes allocated. This equals
214 FILLED * sizeof *UTMP + [size of free area] + STRING_BYTES. */
215 idx_t alloc_bytes;
218 /* Use the memory allocation A, and if the read_utmp options OPTIONS
219 permit it, add a new entry with the given USER, etc. Grow A as
220 needed, reporting an error and exit on memory allocation failure.
221 Return the resulting memory allocation. */
223 static struct utmp_alloc
224 add_utmp (struct utmp_alloc a, int options,
225 char const *user, idx_t user_len,
226 char const *id, idx_t id_len,
227 char const *line, idx_t line_len,
228 char const *host, idx_t host_len,
229 pid_t pid, short type, struct timespec ts, long session,
230 int termination, int exit)
232 int entry_bytes = sizeof (struct gl_utmp);
233 idx_t avail = a.alloc_bytes - (entry_bytes * a.filled + a.string_bytes);
234 idx_t needed_string_bytes =
235 (user_len + 1) + (id_len + 1) + (line_len + 1) + (host_len + 1);
236 idx_t needed = entry_bytes + needed_string_bytes;
237 if (avail < needed)
239 idx_t old_string_offset = a.alloc_bytes - a.string_bytes;
240 void *new = xpalloc (a.utmp, &a.alloc_bytes, needed - avail, -1, 1);
241 idx_t new_string_offset = a.alloc_bytes - a.string_bytes;
242 a.utmp = new;
243 char *q = new;
244 memmove (q + new_string_offset, q + old_string_offset, a.string_bytes);
246 struct gl_utmp *ut = &a.utmp[a.filled];
247 char *stringlim = (char *) a.utmp + a.alloc_bytes;
248 char *p = stringlim - a.string_bytes;
249 *--p = '\0'; /* NUL-terminate ut->ut_user */
250 ut->ut_user = p = memcpy (p - user_len, user, user_len);
251 *--p = '\0'; /* NUL-terminate ut->ut_id */
252 ut->ut_id = p = memcpy (p - id_len, id, id_len);
253 *--p = '\0'; /* NUL-terminate ut->ut_line */
254 ut->ut_line = p = memcpy (p - line_len, line, line_len);
255 *--p = '\0'; /* NUL-terminate ut->ut_host */
256 ut->ut_host = memcpy (p - host_len, host, host_len);
257 ut->ut_ts = ts;
258 ut->ut_pid = pid;
259 ut->ut_session = session;
260 ut->ut_type = type;
261 ut->ut_exit.e_termination = termination;
262 ut->ut_exit.e_exit = exit;
263 if (desirable_utmp_entry (ut, options))
265 /* Now that UT has been checked, relocate its string slots to be
266 relative to the end of the allocated storage, so that these
267 slots survive realloc. The slots will be relocated back just
268 before read_utmp returns. */
269 ut->ut_user = (char *) (intptr_t) (ut->ut_user - stringlim);
270 ut->ut_id = (char *) (intptr_t) (ut->ut_id - stringlim);
271 ut->ut_line = (char *) (intptr_t) (ut->ut_line - stringlim);
272 ut->ut_host = (char *) (intptr_t) (ut->ut_host - stringlim);
273 a.filled++;
274 a.string_bytes += needed_string_bytes;
276 return a;
279 /* Relocate the string pointers in A back to their natural position. */
280 static struct utmp_alloc
281 finish_utmp (struct utmp_alloc a)
283 char *stringlim = (char *) a.utmp + a.alloc_bytes;
285 for (idx_t i = 0; i < a.filled; i++)
287 a.utmp[i].ut_user = (intptr_t) a.utmp[i].ut_user + stringlim;
288 a.utmp[i].ut_id = (intptr_t) a.utmp[i].ut_id + stringlim;
289 a.utmp[i].ut_line = (intptr_t) a.utmp[i].ut_line + stringlim;
290 a.utmp[i].ut_host = (intptr_t) a.utmp[i].ut_host + stringlim;
293 return a;
296 /* Determine whether A already contains an entry of type BOOT_TIME. */
297 _GL_ATTRIBUTE_MAYBE_UNUSED
298 static bool
299 have_boot_time (struct utmp_alloc a)
301 for (idx_t i = 0; i < a.filled; i++)
303 struct gl_utmp *ut = &a.utmp[i];
304 if (UT_TYPE_BOOT_TIME (ut))
305 return true;
307 return false;
310 #if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION
311 # if !HAVE_DECL_ENDUTENT /* Android */
312 void endutent (void);
313 # endif
314 #endif
316 static int
317 read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
318 int options)
320 if ((options & READ_UTMP_BOOT_TIME) != 0
321 && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0)
323 /* No entries can match the given options. */
324 *n_entries = 0;
325 *utmp_buf = NULL;
326 return 0;
329 struct utmp_alloc a = {0};
331 # if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H
333 # if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, IRIX, Solaris, Cygwin, Android */
335 /* Ignore the return value for now.
336 Solaris' utmpname returns 1 upon success -- which is contrary
337 to what the GNU libc version does. In addition, older GNU libc
338 versions are actually void. */
339 UTMP_NAME_FUNCTION ((char *) file);
341 SET_UTMP_ENT ();
343 # if (defined __linux__ && !defined __ANDROID__) || defined __minix
344 bool file_is_utmp = (strcmp (file, UTMP_FILE) == 0);
345 /* Timestamp of the "runlevel" entry, if any. */
346 struct timespec runlevel_ts = {0};
347 # endif
349 void const *entry;
351 while ((entry = GET_UTMP_ENT ()) != NULL)
353 struct UTMP_STRUCT_NAME const *ut = (struct UTMP_STRUCT_NAME const *) entry;
355 struct timespec ts =
356 #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
357 { .tv_sec = ut->ut_tv.tv_sec, .tv_nsec = ut->ut_tv.tv_usec * 1000 };
358 #else
359 { .tv_sec = ut->ut_time, .tv_nsec = 0 };
360 #endif
362 a = add_utmp (a, options,
363 UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE),
364 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
365 ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE),
366 #else
367 "", 0,
368 #endif
369 ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE),
370 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
371 ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE),
372 #else
373 "", 0,
374 #endif
375 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
376 ut->ut_pid,
377 #else
379 #endif
380 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
381 ut->ut_type,
382 #else
384 #endif
386 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
387 ut->ut_session,
388 #else
390 #endif
391 UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut)
393 # if defined __linux__ && !defined __ANDROID__
394 if (file_is_utmp
395 && memcmp (UT_USER (ut), "runlevel", strlen ("runlevel") + 1) == 0
396 && memcmp (ut->ut_line, "~", strlen ("~") + 1) == 0)
397 runlevel_ts = ts;
398 # endif
399 # if defined __minix
400 if (file_is_utmp
401 && UT_USER (ut)[0] == '\0'
402 && memcmp (ut->ut_line, "run-level ", strlen ("run-level ")) == 0)
403 runlevel_ts = ts;
404 # endif
407 END_UTMP_ENT ();
409 # if defined __linux__ && !defined __ANDROID__
410 /* On Alpine Linux, UTMP_FILE is not filled. It is always empty.
411 So, fake a BOOT_TIME entry, by getting the time stamp of a file that
412 gets touched only during the boot process.
414 On Raspbian, which runs on hardware without a real-time clock, during boot,
415 1. the clock gets set to 1970-01-01 00:00:00,
416 2. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
417 ut_user = "reboot", ut_line = "~", time = 1970-01-01 00:00:05 or so,
418 3. the clock gets set to a correct value through NTP,
419 4. an entry gets written into /var/run/utmp, with
420 ut_user = "runlevel", ut_line = "~", time = correct value.
421 In this case, copy the time from the "runlevel" entry to the "reboot"
422 entry. */
423 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
424 && file_is_utmp)
426 for (idx_t i = 0; i < a.filled; i++)
428 struct gl_utmp *ut = &a.utmp[i];
429 if (UT_TYPE_BOOT_TIME (ut))
431 /* Workaround for Raspbian: */
432 if (ut->ut_ts.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
433 ut->ut_ts = runlevel_ts;
434 break;
437 if (!have_boot_time (a))
439 /* Workaround for Alpine Linux: */
440 struct timespec boot_time;
441 if (get_linux_boot_time_fallback (&boot_time) >= 0)
442 a = add_utmp (a, options,
443 "reboot", strlen ("reboot"),
444 "", 0,
445 "~", strlen ("~"),
446 "", 0,
447 0, BOOT_TIME, boot_time, 0, 0, 0);
450 # endif
452 # if defined __ANDROID__
453 /* On Android, there is no /var, and normal processes don't have access
454 to system files. Therefore use the kernel's uptime counter, although
455 it produces wrong values after the date has been bumped in the running
456 system. */
457 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
458 && strcmp (file, UTMP_FILE) == 0
459 && !have_boot_time (a))
461 struct timespec boot_time;
462 if (get_android_boot_time (&boot_time) >= 0)
463 a = add_utmp (a, options,
464 "reboot", strlen ("reboot"),
465 "", 0,
466 "", 0,
467 "", 0,
468 0, BOOT_TIME, boot_time, 0, 0, 0);
470 # endif
472 # if defined __minix
473 /* On Minix, during boot,
474 1. an entry gets written into /var/run/utmp, with ut_type = BOOT_TIME,
475 ut_user = "", ut_line = "system boot", time = 1970-01-01 00:00:00,
476 2. an entry gets written into /var/run/utmp, with
477 ut_user = "", ut_line = "run-level m", time = correct value.
478 In this case, copy the time from the "run-level m" entry to the
479 "system boot" entry. */
480 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
481 && file_is_utmp)
483 for (idx_t i = 0; i < a.filled; i++)
485 struct gl_utmp *ut = &a.utmp[i];
486 if (UT_TYPE_BOOT_TIME (ut))
488 if (ut->ut_ts.tv_sec <= 60 && runlevel_ts.tv_sec != 0)
489 ut->ut_ts = runlevel_ts;
490 break;
494 # endif
496 # else /* old FreeBSD, OpenBSD, HP-UX, Haiku */
498 FILE *f = fopen (file, "re");
500 if (f != NULL)
502 for (;;)
504 struct UTMP_STRUCT_NAME ut;
506 if (fread (&ut, sizeof ut, 1, f) == 0)
507 break;
508 a = add_utmp (a, options,
509 UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE),
510 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID)
511 ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE),
512 #else
513 "", 0,
514 #endif
515 ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE),
516 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST)
517 ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE),
518 #else
519 "", 0,
520 #endif
521 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID)
522 ut.ut_pid,
523 #else
525 #endif
526 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE)
527 ut.ut_type,
528 #else
530 #endif
531 #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV)
532 (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 },
533 #else
534 (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 },
535 #endif
536 #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION)
537 ut.ut_session,
538 #else
540 #endif
541 UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut)
545 int saved_errno = ferror (f) ? errno : 0;
546 if (fclose (f) != 0)
547 saved_errno = errno;
548 if (saved_errno != 0)
550 free (a.utmp);
551 errno = saved_errno;
552 return -1;
555 else
557 if (strcmp (file, UTMP_FILE) != 0)
559 int saved_errno = errno;
560 free (a.utmp);
561 errno = saved_errno;
562 return -1;
566 # if defined __OpenBSD__
567 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
568 && strcmp (file, UTMP_FILE) == 0
569 && !have_boot_time (a))
571 struct timespec boot_time;
572 if (get_openbsd_boot_time (&boot_time) >= 0)
573 a = add_utmp (a, options,
574 "reboot", strlen ("reboot"),
575 "", 0,
576 "", 0,
577 "", 0,
578 0, BOOT_TIME, boot_time, 0, 0, 0);
580 # endif
582 # endif
584 # if defined __linux__ && !defined __ANDROID__
585 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
586 && strcmp (file, UTMP_FILE) == 0
587 && !have_boot_time (a))
589 struct timespec boot_time;
590 if (get_linux_boot_time_final_fallback (&boot_time) >= 0)
591 a = add_utmp (a, options,
592 "reboot", strlen ("reboot"),
593 "", 0,
594 "~", strlen ("~"),
595 "", 0,
596 0, BOOT_TIME, boot_time, 0, 0, 0);
599 # endif
601 # if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \
602 && defined CTL_KERN && defined KERN_BOOTTIME \
603 && !defined __minix
604 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
605 && strcmp (file, UTMP_FILE) == 0
606 && !have_boot_time (a))
608 struct timespec boot_time;
609 if (get_bsd_boot_time_final_fallback (&boot_time) >= 0)
610 a = add_utmp (a, options,
611 "reboot", strlen ("reboot"),
612 "", 0,
613 "", 0,
614 "", 0,
615 0, BOOT_TIME, boot_time, 0, 0, 0);
617 # endif
619 # if defined __HAIKU__
620 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
621 && strcmp (file, UTMP_FILE) == 0
622 && !have_boot_time (a))
624 struct timespec boot_time;
625 if (get_haiku_boot_time (&boot_time) >= 0)
626 a = add_utmp (a, options,
627 "reboot", strlen ("reboot"),
628 "", 0,
629 "", 0,
630 "", 0,
631 0, BOOT_TIME, boot_time, 0, 0, 0);
633 # endif
635 # if HAVE_OS_H /* BeOS, Haiku */
636 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
637 && strcmp (file, UTMP_FILE) == 0
638 && !have_boot_time (a))
640 struct timespec boot_time;
641 if (get_haiku_boot_time_final_fallback (&boot_time) >= 0)
642 a = add_utmp (a, options,
643 "reboot", strlen ("reboot"),
644 "", 0,
645 "", 0,
646 "", 0,
647 0, BOOT_TIME, boot_time, 0, 0, 0);
649 # endif
651 # endif
653 # if defined __CYGWIN__ || defined _WIN32
654 if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0
655 && strcmp (file, UTMP_FILE) == 0
656 && !have_boot_time (a))
658 struct timespec boot_time;
659 if (get_windows_boot_time (&boot_time) >= 0)
660 a = add_utmp (a, options,
661 "reboot", strlen ("reboot"),
662 "", 0,
663 "", 0,
664 "", 0,
665 0, BOOT_TIME, boot_time, 0, 0, 0);
667 # endif
669 a = finish_utmp (a);
671 *n_entries = a.filled;
672 *utmp_buf = a.utmp;
674 return 0;
677 # if READUTMP_USE_SYSTEMD
678 /* Use systemd and Linux /proc and kernel APIs. */
680 static struct timespec
681 get_boot_time_uncached (void)
683 /* Try to find the boot time in the /var/run/utmp file. */
685 idx_t n_entries = 0;
686 STRUCT_UTMP *utmp = NULL;
687 read_utmp_from_file (UTMP_FILE, &n_entries, &utmp, READ_UTMP_BOOT_TIME);
688 if (n_entries > 0)
690 struct timespec result = utmp[0].ut_ts;
691 free (utmp);
692 return result;
694 free (utmp);
697 /* We shouldn't get here. */
698 return (struct timespec) {0};
701 static struct timespec
702 get_boot_time (void)
704 static bool volatile cached;
705 static struct timespec volatile boot_time;
707 if (!cached)
709 boot_time = get_boot_time_uncached ();
710 cached = true;
712 return boot_time;
715 /* Guess the pty name that was opened for the given user right after
716 the given time AT. */
717 static char *
718 guess_pty_name (uid_t uid, const struct timespec at)
720 /* Traverse the entries of the /dev/pts/ directory, looking for devices
721 which are owned by UID and whose ctime is shortly after AT. */
722 DIR *dirp = opendir ("/dev/pts");
723 if (dirp != NULL)
725 /* Buffer containing /dev/pts/N. */
726 char name_buf[9 + 10 + 1];
727 memcpy (name_buf, "/dev/pts/", 9);
729 char best_name[9 + 10 + 1];
730 struct timespec best_time = { .tv_sec = 0, .tv_nsec = 0 };
732 for (;;)
734 struct dirent *dp = readdir (dirp);
735 if (dp == NULL)
736 break;
737 if (dp->d_name[0] != '.' && strlen (dp->d_name) <= 10)
739 /* Compose the absolute file name /dev/pts/N. */
740 strcpy (name_buf + 9, dp->d_name);
742 /* Find its owner and ctime. */
743 struct stat st;
744 if (stat (name_buf, &st) >= 0
745 && st.st_uid == uid
746 && (st.st_ctim.tv_sec > at.tv_sec
747 || (st.st_ctim.tv_sec == at.tv_sec
748 && st.st_ctim.tv_nsec >= at.tv_nsec)))
750 /* This entry has the owner UID and a ctime >= AT. */
751 /* Is this entry the best one so far? */
752 if ((best_time.tv_sec == 0 && best_time.tv_nsec == 0)
753 || (st.st_ctim.tv_sec < best_time.tv_sec
754 || (st.st_ctim.tv_sec == best_time.tv_sec
755 && st.st_ctim.tv_nsec < best_time.tv_nsec)))
757 strcpy (best_name, name_buf);
758 best_time = st.st_ctim;
764 closedir (dirp);
766 /* Did we find an entry owned by ID, and is it at most 5 seconds
767 after AT? */
768 if (!(best_time.tv_sec == 0 && best_time.tv_nsec == 0)
769 && (best_time.tv_sec < at.tv_sec + 5
770 || (best_time.tv_sec == at.tv_sec + 5
771 && best_time.tv_nsec <= at.tv_nsec)))
772 return xstrdup (best_name + 5);
775 return NULL;
778 static int
779 read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options)
781 /* Fill entries, simulating what a utmp file would contain. */
782 struct utmp_alloc a = {0};
784 /* Synthesize a BOOT_TIME entry. */
785 if (!(options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)))
786 a = add_utmp (a, options,
787 "reboot", strlen ("reboot"),
788 "", 0,
789 "~", strlen ("~"),
790 "", 0,
791 0, BOOT_TIME, get_boot_time (), 0, 0, 0);
793 /* Synthesize USER_PROCESS entries. */
794 if (!(options & READ_UTMP_BOOT_TIME))
796 char **sessions;
797 int num_sessions = sd_get_sessions (&sessions);
798 if (num_sessions >= 0 && sessions != NULL)
800 char **session_ptr;
801 for (session_ptr = sessions; *session_ptr != NULL; session_ptr++)
803 char *session = *session_ptr;
805 uint64_t start_usec;
806 if (sd_session_get_start_time (session, &start_usec) < 0)
807 start_usec = 0;
808 struct timespec start_ts;
809 start_ts.tv_sec = start_usec / 1000000;
810 start_ts.tv_nsec = start_usec % 1000000 * 1000;
812 char *seat;
813 if (sd_session_get_seat (session, &seat) < 0)
814 seat = NULL;
816 char missing[] = "";
818 char *type = NULL;
819 char *tty;
820 if (sd_session_get_tty (session, &tty) < 0)
822 tty = NULL;
823 /* Try harder to get a sensible value for the tty. */
824 if (sd_session_get_type (session, &type) < 0)
825 type = missing;
826 if (strcmp (type, "tty") == 0)
828 char *service;
829 if (sd_session_get_service (session, &service) < 0)
830 service = NULL;
832 uid_t uid;
833 char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL
834 : guess_pty_name (uid, start_ts));
836 if (service != NULL && pty != NULL)
838 tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1);
839 stpcpy (stpcpy (stpcpy (tty, service), " "), pty);
840 free (pty);
841 free (service);
843 else if (service != NULL)
844 tty = service;
845 else if (pty != NULL)
846 tty = pty;
850 /* Create up to two USER_PROCESS entries: one for the seat,
851 one for the tty. */
852 if (seat != NULL || tty != NULL)
854 char *user;
855 if (sd_session_get_username (session, &user) < 0)
856 user = missing;
858 pid_t leader_pid;
859 if (sd_session_get_leader (session, &leader_pid) < 0)
860 leader_pid = 0;
862 char *host;
863 char *remote_host;
864 if (sd_session_get_remote_host (session, &remote_host) < 0)
866 host = missing;
867 /* For backward compatibility, put the X11 display into the
868 host field. */
869 if (!type && sd_session_get_type (session, &type) < 0)
870 type = missing;
871 if (strcmp (type, "x11") == 0)
873 char *display;
874 if (sd_session_get_display (session, &display) < 0)
875 display = NULL;
876 /* Workaround: gdm "forgets" to pass the display to
877 systemd, thus display may be NULL here. */
878 if (display != NULL)
879 host = display;
882 else
884 char *remote_user;
885 if (sd_session_get_remote_user (session, &remote_user) < 0)
886 host = remote_host;
887 else
889 host = xmalloc (strlen (remote_user) + 1
890 + strlen (remote_host) + 1);
891 stpcpy (stpcpy (stpcpy (host, remote_user), "@"),
892 remote_host);
893 free (remote_user);
894 free (remote_host);
898 if (seat != NULL)
899 a = add_utmp (a, options,
900 user, strlen (user),
901 session, strlen (session),
902 seat, strlen (seat),
903 host, strlen (host),
904 leader_pid /* the best we have */,
905 USER_PROCESS, start_ts, leader_pid, 0, 0);
906 if (tty != NULL)
907 a = add_utmp (a, options,
908 user, strlen (user),
909 session, strlen (session),
910 tty, strlen (tty),
911 host, strlen (host),
912 leader_pid /* the best we have */,
913 USER_PROCESS, start_ts, leader_pid, 0, 0);
915 if (host != missing)
916 free (host);
917 if (user != missing)
918 free (user);
921 if (type != missing)
922 free (type);
923 free (tty);
924 free (seat);
925 free (session);
927 free (sessions);
931 a = finish_utmp (a);
933 *n_entries = a.filled;
934 *utmp_buf = a.utmp;
936 return 0;
939 # endif
942 read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
943 int options)
945 # if READUTMP_USE_SYSTEMD
946 if (strcmp (file, UTMP_FILE) == 0)
947 /* Imitate reading UTMP_FILE, using systemd and Linux APIs. */
948 return read_utmp_from_systemd (n_entries, utmp_buf, options);
949 # endif
951 return read_utmp_from_file (file, n_entries, utmp_buf, options);
954 #else /* dummy fallback */
957 read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf,
958 int options)
960 errno = ENOSYS;
961 return -1;
964 #endif