From: Bruno Haible Date: Tue, 4 Aug 2026 15:03:56 +0000 (+0200) Subject: readutmp: Add option to make use of wtmpdb. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;ds=sidebyside;p=thirdparty%2Fgnulib.git readutmp: Add option to make use of wtmpdb. * m4/wtmpdb.m4: New file. * m4/readutmp.m4 (gl_READUTMP): Invoke gl_WTMPDB_CHOICE. Define READUTMP_USE_WTMPDB. Take it into account for READUTMP_LIB. * lib/readutmp.h (WTMPDB_FILE): New macro. * lib/readutmp.c: Include . (wtmpdb_time_to_timespec): New function. (struct wtmpdb_locals): New type. (wtmpdb_callback, read_utmp_from_wtmpdb): New functions. (read_utmp): For WTMP_FILE, conditionally use read_utmp_from_wtmpdb. * modules/readutmp (Files): Add m4/wtmpdb.m4. (Depends-on): Add str_endswith. * DEPENDENCIES: Mention libwtmpdb. --- diff --git a/ChangeLog b/ChangeLog index 0443b14d30..521e657631 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2026-08-04 Bruno Haible + + readutmp: Add option to make use of wtmpdb. + * m4/wtmpdb.m4: New file. + * m4/readutmp.m4 (gl_READUTMP): Invoke gl_WTMPDB_CHOICE. Define + READUTMP_USE_WTMPDB. Take it into account for READUTMP_LIB. + * lib/readutmp.h (WTMPDB_FILE): New macro. + * lib/readutmp.c: Include . + (wtmpdb_time_to_timespec): New function. + (struct wtmpdb_locals): New type. + (wtmpdb_callback, read_utmp_from_wtmpdb): New functions. + (read_utmp): For WTMP_FILE, conditionally use read_utmp_from_wtmpdb. + * modules/readutmp (Files): Add m4/wtmpdb.m4. + (Depends-on): Add str_endswith. + * DEPENDENCIES: Mention libwtmpdb. + 2026-08-04 Bruno Haible readutmp: Fix naming of configure option. diff --git a/DEPENDENCIES b/DEPENDENCIES index 155101349b..e8276a8b96 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -416,6 +416,16 @@ For modules 'readutmp' and 'boot-time': - On Debian and Debian-based systems: libsystemd-dev, - On Red Hat distributions: systemd-devel. - Other: https://repology.org/project/systemd/versions + * wtmpdb libraries + + Recommended if wtmpdb is present on the system. + Needed for fetching login, logout, and boot times from + /var/log/wtmp.db instead of from the file /var/run/wtmp. + + Homepage: + https://github.com/thkukuk/wtmpdb + + Pre-built package name: + - On Debian and Debian-based systems: libwtmpdb-dev, + - On Red Hat distributions: --. + - Other: https://repology.org/project/wtmpdb/versions For modules 'terminfo' and 'termcap': * GNU ncurses (preferred) diff --git a/lib/readutmp.c b/lib/readutmp.c index 6e43b5e956..8e0fdcb0ea 100644 --- a/lib/readutmp.c +++ b/lib/readutmp.c @@ -19,6 +19,15 @@ #include +#if READUTMP_USE_WTMPDB +# include +/* Kill macro definitions that conflict with and . */ +# undef EMPTY +# undef BOOT_TIME +# undef RUNLEVEL +# undef USER_PROCESS +#endif + #include "readutmp.h" #include @@ -972,6 +981,166 @@ read_utmp_from_systemd (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) # endif +# if READUTMP_USE_WTMPDB + +static int +wtmpdb_time_to_timespec (const char *value, struct timespec *ts) +{ + if (value != NULL) + { + char *endptr; + unsigned long long login_usec = strtoull (value, &endptr, 10); + if (endptr != value && *endptr == '\0') + { + ts->tv_sec = login_usec / 1000000; + ts->tv_nsec = (login_usec % 1000000) * 1000; + return 0; + } + } + return -1; +} + +struct wtmpdb_locals +{ + int options; + struct utmp_alloc a; +}; + +static int +wtmpdb_callback (void *data, int argc, char **argv, char **column_names) +{ + struct wtmpdb_locals *l = (struct wtmpdb_locals *) data; + + /* column_names is typically + [ "ID", "Type", "User", "Login", "Logout", "TTY", "RemoteHost", "Service" ] + */ + + int id_index = -1; + int type_index = -1; + int user_index = -1; + int login_index = -1; + int logout_index = -1; + int tty_index = -1; + int remote_host_index = -1; + int service_index = -1; + + for (int i = 0; i < argc; i++) + { + if (streq (column_names[i], "ID")) + id_index = i; + if (streq (column_names[i], "Type")) + type_index = i; + if (streq (column_names[i], "User")) + user_index = i; + if (streq (column_names[i], "Login")) + login_index = i; + if (streq (column_names[i], "Logout")) + logout_index = i; + if (streq (column_names[i], "TTY")) + tty_index = i; + if (streq (column_names[i], "RemoteHost")) + remote_host_index = i; + if (streq (column_names[i], "Service")) + service_index = i; + } + + const char *id = (id_index >= 0 ? argv[id_index] : NULL); + const char *type = (type_index >= 0 ? argv[type_index] : NULL); + const char *user = (user_index >= 0 ? argv[user_index] : NULL); + const char *login = (login_index >= 0 ? argv[login_index] : NULL); + const char *logout = (logout_index >= 0 ? argv[logout_index] : NULL); + const char *tty = (tty_index >= 0 ? argv[tty_index] : NULL); + const char *host = (remote_host_index >= 0 ? argv[remote_host_index] : NULL); + const char *service = (service_index >= 0 ? argv[service_index] : NULL); + + if (type != NULL && streq (type, "1" /* BOOT_TIME */)) + { + struct timespec login_ts; + if (wtmpdb_time_to_timespec (login, &login_ts) == 0) + { + l->a = add_utmp (l->a, l->options, + "reboot", strlen ("reboot"), + id, strlen (id), + tty, strlen (tty), + host, strlen (host), + 0, BOOT_TIME, + login_ts, 0, 0, 0); + + struct timespec logout_ts; + if (wtmpdb_time_to_timespec (logout, &logout_ts) == 0) + { + l->a = add_utmp (l->a, l->options, + "shutdown", strlen ("shutdown"), + id, strlen (id), + tty, strlen (tty), + host, strlen (host), + 0, EMPTY, + logout_ts, 0, 0, 0); + } + } + } + + if (type != NULL && streq (type, "3" /* USER_PROCESS */) + && login != NULL) + { + struct timespec login_ts; + if (wtmpdb_time_to_timespec (login, &login_ts) == 0) + { + short ctype = + (service != NULL && str_endswith (service, "-greeter") + ? LOGIN_PROCESS + : USER_PROCESS); + l->a = add_utmp (l->a, l->options, + user, strlen (user), + id, strlen (id), + tty, strlen (tty), + host, strlen (host), + 0, ctype, + login_ts, 0, 0, 0); + + struct timespec logout_ts; + if (wtmpdb_time_to_timespec (logout, &logout_ts) == 0) + { + l->a = add_utmp (l->a, l->options, + "", 0, + id, strlen (id), + tty, strlen (tty), + host, strlen (host), + 0, DEAD_PROCESS, + logout_ts, 0, 0, 0); + } + } + } + + return 0; +} + +static int +read_utmp_from_wtmpdb (idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) +{ + /* Fill entries, simulating what a utmp file would contain. */ + struct wtmpdb_locals locals = { options, { NULL, 0, 0, 0 } }; + + char *err = NULL; + int ret = wtmpdb_read_all_v2 (WTMPDB_FILE, wtmpdb_callback, &locals, &err); + free (err); + if (ret >= 0) + { + locals.a = finish_utmp (locals.a); + if (locals.a.filled > 0) + { + *n_entries = locals.a.filled; + *utmp_buf = locals.a.utmp; + return 0; + } + free (locals.a.utmp); + } + + return -1; +} + +# endif + int read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, int options) @@ -982,6 +1151,17 @@ read_utmp (char const *file, idx_t *n_entries, STRUCT_UTMP **utmp_buf, return read_utmp_from_systemd (n_entries, utmp_buf, options); # endif +# if READUTMP_USE_WTMPDB + if (streq (file, WTMP_FILE)) + { + /* Imitate reading WTMP_FILE, using wtmpdb APIs. */ + int ret = read_utmp_from_wtmpdb (n_entries, utmp_buf, options); + if (ret >= 0) + return ret; + /* Could not read /var/log/wtmp.db. Fall back to /var/log/wtmp. */ + } +# endif + return read_utmp_from_file (file, n_entries, utmp_buf, options); } diff --git a/lib/readutmp.h b/lib/readutmp.h index badbbc4331..31a27c1dda 100644 --- a/lib/readutmp.h +++ b/lib/readutmp.h @@ -246,7 +246,7 @@ struct utmpx32 #endif /* Definition of WTMP_FILE. - On glibc systems, UTMP_FILE is "/var/log/wtmp". */ + On glibc systems, WTMP_FILE is "/var/log/wtmp". */ #if !defined WTMP_FILE && defined _PATH_WTMP # define WTMP_FILE _PATH_WTMP #endif @@ -258,6 +258,12 @@ struct utmpx32 # define WTMP_FILE "/etc/wtmp" #endif +/* Definition of WTMPDB_FILE. + On Debian and Devuan systems, WTMPDB_FILE is "/var/log/wtmp.db". */ +#ifndef WTMPDB_FILE +# define WTMPDB_FILE "/var/log/wtmp.db" +#endif + /* In early versions of Android, did not define BOOT_TIME or LOGIN_PROCESS, only USER_PROCESS. We need to use the value that is defined in newer versions of Android. */ diff --git a/m4/readutmp.m4 b/m4/readutmp.m4 index 3eeec0d081..787fe4eaa1 100644 --- a/m4/readutmp.m4 +++ b/m4/readutmp.m4 @@ -1,5 +1,5 @@ # readutmp.m4 -# serial 32 +# serial 33 dnl Copyright (C) 2002-2026 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -9,11 +9,13 @@ dnl This file is offered as-is, without any warranty. AC_DEFUN([gl_READUTMP], [ AC_REQUIRE([gl_SYSTEMD_CHOICE]) + AC_REQUIRE([gl_WTMPDB_CHOICE]) - dnl Set READUTMP_LIB to '-lsystemd' or '', depending on whether use of - dnl systemd APIs is possible and desired (only the systemd login API, here). + dnl Set READUTMP_LIB to '-lsystemd' or '-lwtmpdb' or '', depending on whether + dnl use of systemd APIs is possible and desired (only the systemd login API, + dnl here) and whether use of wtmpdb APIs is possible and desired. dnl AC_LIB_LINKFLAGS_BODY would be overkill here, since few people install - dnl libsystemd in non-system directories. + dnl libsystemd or libwtmpdb in non-system directories. READUTMP_LIB= if test "$SYSTEMD_CHOICE" = yes; then AC_CHECK_HEADER([systemd/sd-login.h]) @@ -42,6 +44,14 @@ AC_DEFUN([gl_READUTMP], fi fi fi + if test "$WTMPDB_CHOICE" = yes; then + AC_CHECK_HEADER([wtmpdb.h]) + if test $ac_cv_header_wtmpdb_h = yes; then + AC_DEFINE([READUTMP_USE_WTMPDB], [1], + [Define if the readutmp module should use the wtmpdb API.]) + READUTMP_LIB=$READUTMP_LIB${READUTMP_LIB:+ }'-lwtmpdb' + fi + fi AC_SUBST([READUTMP_LIB]) gl_PREREQ_READUTMP_H diff --git a/m4/wtmpdb.m4 b/m4/wtmpdb.m4 new file mode 100644 index 0000000000..159524ef86 --- /dev/null +++ b/m4/wtmpdb.m4 @@ -0,0 +1,25 @@ +# wtmpdb.m4 +# serial 1 +dnl Copyright (C) 2026 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. +dnl This file is offered as-is, without any warranty. + +# Sets WTMPDB_CHOICE to 'yes' or 'no', depending on the preferred use of +# wtmpdb APIs. +AC_DEFUN([gl_WTMPDB_CHOICE], +[ + AC_MSG_CHECKING([whether to use wtmpdb APIs]) + AC_ARG_WITH([wtmpdb], + [ --with-wtmpdb use wtmpdb APIs], + [WTMPDB_CHOICE="$withval"], + [WTMPDB_CHOICE=no]) + AC_MSG_RESULT([$WTMPDB_CHOICE]) + AC_SUBST([WTMPDB_CHOICE]) +]) + +# Pre-built package name for the libwtmpdb library: +# - On Debian and Debian-based systems: libwtmpdb-dev, +# - On Red Hat distributions: --. +# - Other: https://repology.org/project/wtmpdb/versions diff --git a/modules/readutmp b/modules/readutmp index f74c533f78..0ce43d8972 100644 --- a/modules/readutmp +++ b/modules/readutmp @@ -7,6 +7,7 @@ lib/readutmp.c lib/boot-time-aux.h m4/readutmp.m4 m4/systemd.m4 +m4/wtmpdb.m4 Depends-on: extensions @@ -19,6 +20,7 @@ stdint-h memeq streq strnlen +str_endswith time-h unlocked-io-internal xalloc