| /* GNU's read utmp module. | |
| Copyright (C) 1992-2001, 2003-2006, 2009-2026 Free Software Foundation, Inc. | |
| This program 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 program 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 jla; revised by djm */ | |
| #include <config.h> | |
| #include "readutmp.h" | |
| #include <errno.h> | |
| #include <stdio.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <signal.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <stdint.h> | |
| #if defined __linux__ || defined __ANDROID__ | |
| # include <sys/sysinfo.h> | |
| # include <time.h> | |
| #endif | |
| #if READUTMP_USE_SYSTEMD | |
| # include <dirent.h> | |
| # include <systemd/sd-login.h> | |
| #endif | |
| #if HAVE_SYS_SYSCTL_H && !(defined __GLIBC__ && defined __linux__) && !defined __minix | |
| # if HAVE_SYS_PARAM_H | |
| # include <sys/param.h> | |
| # endif | |
| # include <sys/sysctl.h> | |
| #endif | |
| #if HAVE_OS_H | |
| # include <OS.h> | |
| #endif | |
| #if defined _WIN32 && ! defined __CYGWIN__ | |
| # define WIN32_LEAN_AND_MEAN | |
| # include <windows.h> | |
| # include <sys/time.h> | |
| #endif | |
| #include "stat-time.h" | |
| #include "xalloc.h" | |
| /* Each of the FILE streams in this file is only used in a single thread. */ | |
| #include "unlocked-io.h" | |
| /* Some helper functions. */ | |
| #include "boot-time-aux.h" | |
| /* The following macros describe the 'struct UTMP_STRUCT_NAME', | |
| *not* 'struct gl_utmp'. */ | |
| #undef UT_USER | |
| #undef UT_TIME_MEMBER | |
| #undef UT_PID | |
| #undef UT_TYPE_EQ | |
| #undef UT_TYPE_NOT_DEFINED | |
| #undef UT_EXIT_E_TERMINATION | |
| #undef UT_EXIT_E_EXIT | |
| /* 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 | |
| /* Accessor macro for the member of type time_t (or 'unsigned int'). */ | |
| #if HAVE_UTMPX_H || (HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_TV) | |
| # define UT_TIME_MEMBER(UT) ((UT)->ut_tv.tv_sec) | |
| #else | |
| # define UT_TIME_MEMBER(UT) ((UT)->ut_time) | |
| #endif | |
| /* Accessor macro for the member named ut_pid. */ | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID) | |
| # define UT_PID(UT) ((UT)->ut_pid) | |
| #else | |
| # define UT_PID(UT) 0 | |
| #endif | |
| /* Accessor macros for the member named ut_type. */ | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE) | |
| # define UT_TYPE_EQ(UT, V) ((UT)->ut_type == (V)) | |
| # define UT_TYPE_NOT_DEFINED 0 | |
| #else | |
| # define UT_TYPE_EQ(UT, V) 0 | |
| # define UT_TYPE_NOT_DEFINED 1 | |
| #endif | |
| #if HAVE_UTMPX_H | |
| # if HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION | |
| # define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) | |
| # else | |
| # define UT_EXIT_E_TERMINATION(UT) 0 | |
| # endif | |
| #elif HAVE_UTMP_H | |
| # if HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION | |
| # define UT_EXIT_E_TERMINATION(UT) ((UT)->ut_exit.e_termination) | |
| # else | |
| # define UT_EXIT_E_TERMINATION(UT) 0 | |
| # endif | |
| #endif | |
| #if HAVE_UTMPX_H | |
| # if HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT | |
| # define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) | |
| # else | |
| # define UT_EXIT_E_EXIT(UT) 0 | |
| # endif | |
| #elif HAVE_UTMP_H | |
| # if HAVE_STRUCT_UTMP_UT_EXIT_E_EXIT | |
| # define UT_EXIT_E_EXIT(UT) ((UT)->ut_exit.e_exit) | |
| # else | |
| # define UT_EXIT_E_EXIT(UT) 0 | |
| # endif | |
| #endif | |
| /* Size of the UT_USER (ut) member. */ | |
| #define UT_USER_SIZE sizeof UT_USER ((struct UTMP_STRUCT_NAME *) 0) | |
| /* Size of the ut->ut_id member. */ | |
| #define UT_ID_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_id) | |
| /* Size of the ut->ut_line member. */ | |
| #define UT_LINE_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_line) | |
| /* Size of the ut->ut_host member. */ | |
| #define UT_HOST_SIZE sizeof (((struct UTMP_STRUCT_NAME *) 0)->ut_host) | |
| #if _GL_GNUC_PREREQ (8, 0) | |
| # pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess" | |
| #endif | |
| /* Copy UT->ut_user into storage obtained from malloc. Then remove any | |
| trailing spaces from the copy, NUL terminate it, and return the copy. */ | |
| char * | |
| extract_trimmed_name (const STRUCT_UTMP *ut) | |
| { | |
| char const *name = ut->ut_user; | |
| idx_t len = strlen (name); | |
| char const *p; | |
| for (p = name + len; name < p && p[-1] == ' '; p--) | |
| continue; | |
| return ximemdup0 (name, p - name); | |
| } | |
| #if READ_UTMP_SUPPORTED | |
| /* Is the utmp entry UT desired by the user who asked for OPTIONS? */ | |
| static bool | |
| desirable_utmp_entry (STRUCT_UTMP const *ut, int options) | |
| { | |
| # if defined __OpenBSD__ && !HAVE_UTMPX_H | |
| /* Eliminate entirely empty entries. */ | |
| if (ut->ut_ts.tv_sec == 0 && ut->ut_user[0] == '\0' | |
| && ut->ut_line[0] == '\0' && ut->ut_host[0] == '\0') | |
| return false; | |
| # endif | |
| bool boot_time = UT_TYPE_BOOT_TIME (ut); | |
| if ((options & READ_UTMP_BOOT_TIME) && !boot_time) | |
| return false; | |
| if ((options & READ_UTMP_NO_BOOT_TIME) && boot_time) | |
| return false; | |
| bool user_proc = IS_USER_PROCESS (ut); | |
| if ((options & READ_UTMP_USER_PROCESS) && !user_proc) | |
| return false; | |
| # if !(defined __CYGWIN__ || defined _WIN32) | |
| if ((options & READ_UTMP_CHECK_PIDS) | |
| && user_proc | |
| && 0 < UT_PID (ut) | |
| && (kill (UT_PID (ut), 0) < 0 && errno == ESRCH)) | |
| return false; | |
| # endif | |
| return true; | |
| } | |
| /* A memory allocation for an in-progress read_utmp. */ | |
| struct utmp_alloc | |
| { | |
| /* A pointer to a possibly-empty array of utmp entries, | |
| followed by a possibly-empty sequence of unused bytes, | |
| followed by a possibly-empty sequence of string bytes. | |
| UTMP is either null or allocated by malloc. */ | |
| struct gl_utmp *utmp; | |
| /* The number of utmp entries. */ | |
| idx_t filled; | |
| /* The string byte sequence length. Strings are null-terminated. */ | |
| idx_t string_bytes; | |
| /* The total number of bytes allocated. This equals | |
| FILLED * sizeof *UTMP + [size of free area] + STRING_BYTES. */ | |
| idx_t alloc_bytes; | |
| }; | |
| /* Use the memory allocation A, and if the read_utmp options OPTIONS | |
| permit it, add a new entry with the given USER, etc. Grow A as | |
| needed, reporting an error and exit on memory allocation failure. | |
| Return the resulting memory allocation. */ | |
| static struct utmp_alloc | |
| add_utmp (struct utmp_alloc a, int options, | |
| char const *user, idx_t user_len, | |
| char const *id, idx_t id_len, | |
| char const *line, idx_t line_len, | |
| char const *host, idx_t host_len, | |
| pid_t pid, short type, struct timespec ts, long session, | |
| int termination, int exit) | |
| { | |
| int entry_bytes = sizeof (struct gl_utmp); | |
| idx_t avail = a.alloc_bytes - (entry_bytes * a.filled + a.string_bytes); | |
| idx_t needed_string_bytes = | |
| (user_len + 1) + (id_len + 1) + (line_len + 1) + (host_len + 1); | |
| idx_t needed = entry_bytes + needed_string_bytes; | |
| if (avail < needed) | |
| { | |
| idx_t old_string_offset = a.alloc_bytes - a.string_bytes; | |
| void *new = xpalloc (a.utmp, &a.alloc_bytes, needed - avail, -1, 1); | |
| idx_t new_string_offset = a.alloc_bytes - a.string_bytes; | |
| a.utmp = new; | |
| char *q = new; | |
| memmove (q + new_string_offset, q + old_string_offset, a.string_bytes); | |
| } | |
| struct gl_utmp *ut = &a.utmp[a.filled]; | |
| char *stringlim = (char *) a.utmp + a.alloc_bytes; | |
| char *p = stringlim - a.string_bytes; | |
| *--p = '\0'; /* NUL-terminate ut->ut_user */ | |
| ut->ut_user = p = memcpy (p - user_len, user, user_len); | |
| *--p = '\0'; /* NUL-terminate ut->ut_id */ | |
| ut->ut_id = p = memcpy (p - id_len, id, id_len); | |
| *--p = '\0'; /* NUL-terminate ut->ut_line */ | |
| ut->ut_line = p = memcpy (p - line_len, line, line_len); | |
| *--p = '\0'; /* NUL-terminate ut->ut_host */ | |
| ut->ut_host = memcpy (p - host_len, host, host_len); | |
| ut->ut_ts = ts; | |
| ut->ut_pid = pid; | |
| ut->ut_session = session; | |
| ut->ut_type = type; | |
| ut->ut_exit.e_termination = termination; | |
| ut->ut_exit.e_exit = exit; | |
| if (desirable_utmp_entry (ut, options)) | |
| { | |
| /* Now that UT has been checked, relocate its string slots to be | |
| relative to the end of the allocated storage, so that these | |
| slots survive realloc. The slots will be relocated back just | |
| before read_utmp returns. */ | |
| ut->ut_user = (char *) (intptr_t) (ut->ut_user - stringlim); | |
| ut->ut_id = (char *) (intptr_t) (ut->ut_id - stringlim); | |
| ut->ut_line = (char *) (intptr_t) (ut->ut_line - stringlim); | |
| ut->ut_host = (char *) (intptr_t) (ut->ut_host - stringlim); | |
| a.filled++; | |
| a.string_bytes += needed_string_bytes; | |
| } | |
| return a; | |
| } | |
| /* Relocate the string pointers in A back to their natural position. */ | |
| static struct utmp_alloc | |
| finish_utmp (struct utmp_alloc a) | |
| { | |
| char *stringlim = (char *) a.utmp + a.alloc_bytes; | |
| for (idx_t i = 0; i < a.filled; i++) | |
| { | |
| a.utmp[i].ut_user = (intptr_t) a.utmp[i].ut_user + stringlim; | |
| a.utmp[i].ut_id = (intptr_t) a.utmp[i].ut_id + stringlim; | |
| a.utmp[i].ut_line = (intptr_t) a.utmp[i].ut_line + stringlim; | |
| a.utmp[i].ut_host = (intptr_t) a.utmp[i].ut_host + stringlim; | |
| } | |
| return a; | |
| } | |
| /* Determine whether A already contains an entry of type BOOT_TIME. */ | |
| _GL_ATTRIBUTE_MAYBE_UNUSED | |
| static bool | |
| have_boot_time (struct utmp_alloc a) | |
| { | |
| for (idx_t i = 0; i < a.filled; i++) | |
| { | |
| struct gl_utmp *ut = &a.utmp[i]; | |
| if (UT_TYPE_BOOT_TIME (ut)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| #if !HAVE_UTMPX_H && HAVE_UTMP_H && defined UTMP_NAME_FUNCTION | |
| # if !HAVE_DECL_ENDUTENT /* Android */ | |
| void endutent (void); | |
| # endif | |
| #endif | |
| static int | |
| read_utmp_from_file (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, | |
| int options) | |
| { | |
| if ((options & READ_UTMP_BOOT_TIME) != 0 | |
| && (options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) != 0) | |
| { | |
| /* No entries can match the given options. */ | |
| *n_entries = 0; | |
| *utmp_buf = NULL; | |
| return 0; | |
| } | |
| struct utmp_alloc a = { NULL, 0, 0, 0 }; | |
| # if READUTMP_USE_SYSTEMD || HAVE_UTMPX_H || HAVE_UTMP_H | |
| # if defined UTMP_NAME_FUNCTION /* glibc, musl, macOS, FreeBSD, NetBSD, Minix, AIX, 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 *) file); | |
| SET_UTMP_ENT (); | |
| # if (defined __linux__ && !defined __ANDROID__) || defined __minix | |
| bool file_is_utmp = streq (file, UTMP_FILE); | |
| /* 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 | |
| a = add_utmp (a, options, | |
| UT_USER (ut), strnlen (UT_USER (ut), UT_USER_SIZE), | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID) | |
| ut->ut_id, strnlen (ut->ut_id, UT_ID_SIZE), | |
| #else | |
| "", 0, | |
| #endif | |
| ut->ut_line, strnlen (ut->ut_line, UT_LINE_SIZE), | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST) | |
| ut->ut_host, strnlen (ut->ut_host, UT_HOST_SIZE), | |
| #else | |
| "", 0, | |
| #endif | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID) | |
| ut->ut_pid, | |
| #else | |
| 0, | |
| #endif | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE) | |
| ut->ut_type, | |
| #else | |
| 0, | |
| #endif | |
| ts, | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION) | |
| ut->ut_session, | |
| #else | |
| 0, | |
| #endif | |
| UT_EXIT_E_TERMINATION (ut), UT_EXIT_E_EXIT (ut) | |
| ); | |
| # if defined __linux__ && !defined __ANDROID__ | |
| if (file_is_utmp | |
| && memeq (UT_USER (ut), "runlevel", strlen ("runlevel") + 1) | |
| && memeq (ut->ut_line, "~", strlen ("~") + 1)) | |
| runlevel_ts = ts; | |
| # endif | |
| # if defined __minix | |
| if (file_is_utmp | |
| && UT_USER (ut)[0] == '\0' | |
| && memeq (ut->ut_line, "run-level ", strlen ("run-level "))) | |
| runlevel_ts = ts; | |
| # endif | |
| } | |
| END_UTMP_ENT (); | |
| # if defined __linux__ && !defined __ANDROID__ | |
| /* On Alpine Linux, UTMP_FILE is not filled. It is always empty. | |
| So, fake a BOOT_TIME entry, by getting the time stamp of a file that | |
| gets touched only during the boot process. | |
| 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, copy the time from the "runlevel" entry to the "reboot" | |
| entry. */ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && file_is_utmp) | |
| { | |
| for (idx_t i = 0; i < a.filled; i++) | |
| { | |
| struct gl_utmp *ut = &a.utmp[i]; | |
| if (UT_TYPE_BOOT_TIME (ut)) | |
| { | |
| /* Workaround for Raspbian: */ | |
| if (ut->ut_ts.tv_sec <= 60 && runlevel_ts.tv_sec != 0) | |
| ut->ut_ts = runlevel_ts; | |
| break; | |
| } | |
| } | |
| if (!have_boot_time (a)) | |
| { | |
| /* Workaround for Alpine Linux: */ | |
| 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 | |
| # if defined __ANDROID__ | |
| /* 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. */ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| 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 | |
| # 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 ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && file_is_utmp) | |
| { | |
| for (idx_t i = 0; i < a.filled; i++) | |
| { | |
| struct gl_utmp *ut = &a.utmp[i]; | |
| if (UT_TYPE_BOOT_TIME (ut)) | |
| { | |
| if (ut->ut_ts.tv_sec <= 60 && runlevel_ts.tv_sec != 0) | |
| ut->ut_ts = runlevel_ts; | |
| break; | |
| } | |
| } | |
| } | |
| # endif | |
| # else /* old FreeBSD, OpenBSD, HP-UX, Haiku */ | |
| FILE *f = fopen (file, "re"); | |
| if (f != NULL) | |
| { | |
| for (;;) | |
| { | |
| struct UTMP_STRUCT_NAME ut; | |
| if (fread (&ut, sizeof ut, 1, f) == 0) | |
| break; | |
| a = add_utmp (a, options, | |
| UT_USER (&ut), strnlen (UT_USER (&ut), UT_USER_SIZE), | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_ID : HAVE_STRUCT_UTMP_UT_ID) | |
| ut.ut_id, strnlen (ut.ut_id, UT_ID_SIZE), | |
| #else | |
| "", 0, | |
| #endif | |
| ut.ut_line, strnlen (ut.ut_line, UT_LINE_SIZE), | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_HOST : HAVE_STRUCT_UTMP_UT_HOST) | |
| ut.ut_host, strnlen (ut.ut_host, UT_HOST_SIZE), | |
| #else | |
| "", 0, | |
| #endif | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_PID : HAVE_STRUCT_UTMP_UT_PID) | |
| ut.ut_pid, | |
| #else | |
| 0, | |
| #endif | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_TYPE : HAVE_STRUCT_UTMP_UT_TYPE) | |
| ut.ut_type, | |
| #else | |
| 0, | |
| #endif | |
| #if (HAVE_UTMPX_H ? 1 : HAVE_STRUCT_UTMP_UT_TV) | |
| (struct timespec) { .tv_sec = ut.ut_tv.tv_sec, .tv_nsec = ut.ut_tv.tv_usec * 1000 }, | |
| #else | |
| (struct timespec) { .tv_sec = ut.ut_time, .tv_nsec = 0 }, | |
| #endif | |
| #if (HAVE_UTMPX_H ? HAVE_STRUCT_UTMPX_UT_SESSION : HAVE_STRUCT_UTMP_UT_SESSION) | |
| ut.ut_session, | |
| #else | |
| 0, | |
| #endif | |
| UT_EXIT_E_TERMINATION (&ut), UT_EXIT_E_EXIT (&ut) | |
| ); | |
| } | |
| int saved_errno = ferror (f) ? errno : 0; | |
| if (fclose (f) != 0) | |
| saved_errno = errno; | |
| if (saved_errno != 0) | |
| { | |
| free (a.utmp); | |
| errno = saved_errno; | |
| return -1; | |
| } | |
| } | |
| else | |
| { | |
| if (!streq (file, UTMP_FILE)) | |
| { | |
| int saved_errno = errno; | |
| free (a.utmp); | |
| errno = saved_errno; | |
| return -1; | |
| } | |
| } | |
| # if defined __OpenBSD__ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| struct timespec boot_time; | |
| if (get_openbsd_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 | |
| # endif | |
| # if defined __linux__ && !defined __ANDROID__ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| struct timespec boot_time; | |
| if (get_linux_boot_time_final_fallback (&boot_time) >= 0) | |
| a = add_utmp (a, options, | |
| "reboot", strlen ("reboot"), | |
| "", 0, | |
| "~", strlen ("~"), | |
| "", 0, | |
| 0, BOOT_TIME, boot_time, 0, 0, 0); | |
| } | |
| # endif | |
| # if HAVE_SYS_SYSCTL_H && HAVE_SYSCTL \ | |
| && defined CTL_KERN && defined KERN_BOOTTIME \ | |
| && !defined __minix | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| struct timespec boot_time; | |
| if (get_bsd_boot_time_final_fallback (&boot_time) >= 0) | |
| a = add_utmp (a, options, | |
| "reboot", strlen ("reboot"), | |
| "", 0, | |
| "", 0, | |
| "", 0, | |
| 0, BOOT_TIME, boot_time, 0, 0, 0); | |
| } | |
| # endif | |
| # if defined __HAIKU__ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| struct timespec boot_time; | |
| if (get_haiku_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 | |
| # if HAVE_OS_H /* BeOS, Haiku */ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| struct timespec boot_time; | |
| if (get_haiku_boot_time_final_fallback (&boot_time) >= 0) | |
| a = add_utmp (a, options, | |
| "reboot", strlen ("reboot"), | |
| "", 0, | |
| "", 0, | |
| "", 0, | |
| 0, BOOT_TIME, boot_time, 0, 0, 0); | |
| } | |
| # endif | |
| # endif | |
| # if defined __CYGWIN__ || defined _WIN32 | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| 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 | |
| # if defined _WIN32 && ! defined __CYGWIN__ | |
| if ((options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME)) == 0 | |
| && streq (file, UTMP_FILE) | |
| && !have_boot_time (a)) | |
| { | |
| struct timespec boot_time; | |
| if (get_windows_boot_time_fallback (&boot_time) >= 0) | |
| a = add_utmp (a, options, | |
| "reboot", strlen ("reboot"), | |
| "", 0, | |
| "", 0, | |
| "", 0, | |
| 0, BOOT_TIME, boot_time, 0, 0, 0); | |
| } | |
| # endif | |
| a = finish_utmp (a); | |
| *n_entries = a.filled; | |
| *utmp_buf = a.utmp; | |
| return 0; | |
| } | |
| # if READUTMP_USE_SYSTEMD | |
| /* Use systemd and Linux /proc and kernel APIs. */ | |
| static struct timespec | |
| get_boot_time_uncached (void) | |
| { | |
| /* Try to find the boot time in the /var/run/utmp file. */ | |
| { | |
| idx_t n_entries = 0; | |
| STRUCT_UTMP *utmp = NULL; | |
| read_utmp_from_file (UTMP_FILE, &n_entries, &utmp, READ_UTMP_BOOT_TIME); | |
| if (n_entries > 0) | |
| { | |
| struct timespec result = utmp[0].ut_ts; | |
| free (utmp); | |
| return result; | |
| } | |
| free (utmp); | |
| } | |
| /* We shouldn't get here. */ | |
| return (struct timespec) {0}; | |
| } | |
| static struct timespec | |
| get_boot_time (void) | |
| { | |
| static bool volatile cached; | |
| static struct timespec volatile boot_time; | |
| if (!cached) | |
| { | |
| boot_time = get_boot_time_uncached (); | |
| cached = true; | |
| } | |
| return boot_time; | |
| } | |
| /* Guess the pty name that was opened for the given user right after | |
| the given time AT. */ | |
| static char * | |
| guess_pty_name (uid_t uid, const struct timespec at) | |
| { | |
| /* Traverse the entries of the /dev/pts/ directory, looking for devices | |
| which are owned by UID and whose ctime is shortly after AT. */ | |
| DIR *dirp = opendir ("/dev/pts"); | |
| if (dirp != NULL) | |
| { | |
| /* Buffer containing /dev/pts/N. */ | |
| char name_buf[9 + 10 + 1]; | |
| memcpy (name_buf, "/dev/pts/", 9); | |
| char best_name[9 + 10 + 1]; | |
| struct timespec best_time = { .tv_sec = 0, .tv_nsec = 0 }; | |
| for (;;) | |
| { | |
| struct dirent *dp = readdir (dirp); | |
| if (dp == NULL) | |
| break; | |
| if (dp->d_name[0] != '.' && strlen (dp->d_name) <= 10) | |
| { | |
| /* Compose the absolute file name /dev/pts/N. */ | |
| strcpy (name_buf + 9, dp->d_name); | |
| /* Find its owner and ctime. */ | |
| struct stat st; | |
| if (stat (name_buf, &st) >= 0 | |
| && st.st_uid == uid | |
| && (st.st_ctim.tv_sec > at.tv_sec | |
| || (st.st_ctim.tv_sec == at.tv_sec | |
| && st.st_ctim.tv_nsec >= at.tv_nsec))) | |
| { | |
| /* This entry has the owner UID and a ctime >= AT. */ | |
| /* Is this entry the best one so far? */ | |
| if ((best_time.tv_sec == 0 && best_time.tv_nsec == 0) | |
| || (st.st_ctim.tv_sec < best_time.tv_sec | |
| || (st.st_ctim.tv_sec == best_time.tv_sec | |
| && st.st_ctim.tv_nsec < best_time.tv_nsec))) | |
| { | |
| strcpy (best_name, name_buf); | |
| best_time = st.st_ctim; | |
| } | |
| } | |
| } | |
| } | |
| closedir (dirp); | |
| /* Did we find an entry owned by ID, and is it at most 5 seconds | |
| after AT? */ | |
| if (!(best_time.tv_sec == 0 && best_time.tv_nsec == 0) | |
| && (best_time.tv_sec < at.tv_sec + 5 | |
| || (best_time.tv_sec == at.tv_sec + 5 | |
| && best_time.tv_nsec <= at.tv_nsec))) | |
| return xstrdup (best_name + 5); | |
| } | |
| return NULL; | |
| } | |
| static int | |
| read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) | |
| { | |
| /* Fill entries, simulating what a utmp file would contain. */ | |
| struct utmp_alloc a = {0}; | |
| /* Synthesize a BOOT_TIME entry. */ | |
| if (!(options & (READ_UTMP_USER_PROCESS | READ_UTMP_NO_BOOT_TIME))) | |
| a = add_utmp (a, options, | |
| "reboot", strlen ("reboot"), | |
| "", 0, | |
| "~", strlen ("~"), | |
| "", 0, | |
| 0, BOOT_TIME, get_boot_time (), 0, 0, 0); | |
| /* Synthesize USER_PROCESS entries. */ | |
| if (!(options & READ_UTMP_BOOT_TIME)) | |
| { | |
| char **sessions; | |
| int num_sessions = sd_get_sessions (&sessions); | |
| if (num_sessions >= 0 && sessions != NULL) | |
| { | |
| for (char **session_ptr = sessions; *session_ptr != NULL; session_ptr++) | |
| { | |
| char *session = *session_ptr; | |
| uint64_t start_usec; | |
| if (sd_session_get_start_time (session, &start_usec) < 0) | |
| start_usec = 0; | |
| struct timespec start_ts; | |
| start_ts.tv_sec = start_usec / 1000000; | |
| start_ts.tv_nsec = start_usec % 1000000 * 1000; | |
| char *seat; | |
| if (sd_session_get_seat (session, &seat) < 0) | |
| seat = NULL; | |
| char missing[] = ""; | |
| char *type = NULL; | |
| char *tty; | |
| if (sd_session_get_tty (session, &tty) < 0) | |
| { | |
| tty = NULL; | |
| /* Try harder to get a sensible value for the tty. */ | |
| if (sd_session_get_type (session, &type) < 0) | |
| type = missing; | |
| if (streq (type, "tty")) | |
| { | |
| char *service; | |
| if (sd_session_get_service (session, &service) < 0) | |
| service = NULL; | |
| uid_t uid; | |
| char *pty = (sd_session_get_uid (session, &uid) < 0 ? NULL | |
| : guess_pty_name (uid, start_ts)); | |
| if (service != NULL && pty != NULL) | |
| { | |
| tty = xmalloc (strlen (service) + 1 + strlen (pty) + 1); | |
| stpcpy (stpcpy (stpcpy (tty, service), " "), pty); | |
| free (pty); | |
| free (service); | |
| } | |
| else if (service != NULL) | |
| tty = service; | |
| else if (pty != NULL) | |
| tty = pty; | |
| } | |
| else if (streq (type, "web")) | |
| { | |
| char *service; | |
| if (sd_session_get_service (session, &service) < 0) | |
| service = NULL; | |
| tty = service; | |
| } | |
| } | |
| /* Create up to two USER_PROCESS or LOGIN_PROCESS entries: | |
| one for the seat, one for the tty. */ | |
| if (seat != NULL || tty != NULL) | |
| { | |
| char *user; | |
| if (sd_session_get_username (session, &user) < 0) | |
| user = missing; | |
| pid_t leader_pid; | |
| if (sd_session_get_leader (session, &leader_pid) < 0) | |
| leader_pid = 0; | |
| char *clasz; | |
| if (sd_session_get_class (session, &clasz) < 0) | |
| clasz = missing; | |
| short ctype = | |
| (strncmp (clasz, "manager", 7) == 0 ? LOGIN_PROCESS : | |
| USER_PROCESS); | |
| char *host; | |
| char *remote_host; | |
| if (sd_session_get_remote_host (session, &remote_host) < 0) | |
| { | |
| host = missing; | |
| /* For backward compatibility, put the X11 display into the | |
| host field. */ | |
| if (!type && sd_session_get_type (session, &type) < 0) | |
| type = missing; | |
| if (streq (type, "x11")) | |
| { | |
| char *display; | |
| if (sd_session_get_display (session, &display) < 0) | |
| display = NULL; | |
| /* Workaround: gdm "forgets" to pass the display to | |
| systemd, thus display may be NULL here. */ | |
| if (display != NULL) | |
| host = display; | |
| } | |
| } | |
| else | |
| { | |
| char *remote_user; | |
| if (sd_session_get_remote_user (session, &remote_user) < 0) | |
| host = remote_host; | |
| else | |
| { | |
| host = xmalloc (strlen (remote_user) + 1 | |
| + strlen (remote_host) + 1); | |
| stpcpy (stpcpy (stpcpy (host, remote_user), "@"), | |
| remote_host); | |
| free (remote_user); | |
| free (remote_host); | |
| } | |
| } | |
| if (seat != NULL) | |
| a = add_utmp (a, options, | |
| user, strlen (user), | |
| session, strlen (session), | |
| seat, strlen (seat), | |
| host, strlen (host), | |
| leader_pid /* the best we have */, | |
| ctype, start_ts, leader_pid, 0, 0); | |
| if (tty != NULL) | |
| a = add_utmp (a, options, | |
| user, strlen (user), | |
| session, strlen (session), | |
| tty, strlen (tty), | |
| host, strlen (host), | |
| leader_pid /* the best we have */, | |
| ctype, start_ts, leader_pid, 0, 0); | |
| if (host != missing) | |
| free (host); | |
| if (clasz != missing) | |
| free (clasz); | |
| if (user != missing) | |
| free (user); | |
| } | |
| if (type != missing) | |
| free (type); | |
| free (tty); | |
| free (seat); | |
| free (session); | |
| } | |
| free (sessions); | |
| } | |
| } | |
| a = finish_utmp (a); | |
| *n_entries = a.filled; | |
| *utmp_buf = a.utmp; | |
| return 0; | |
| } | |
| # endif | |
| int | |
| read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, | |
| int options) | |
| { | |
| # if READUTMP_USE_SYSTEMD | |
| if (streq (file, UTMP_FILE)) | |
| /* Imitate reading UTMP_FILE, using systemd and Linux APIs. */ | |
| return read_utmp_from_systemd (n_entries, utmp_buf, options); | |
| # endif | |
| return read_utmp_from_file (file, n_entries, utmp_buf, options); | |
| } | |
| #else /* dummy fallback */ | |
| int | |
| read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, | |
| int options) | |
| { | |
| errno = ENOSYS; | |
| return -1; | |
| } | |
| #endif |