+2026-08-04 Bruno Haible <bruno@clisp.org>
+
+ 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.h>.
+ (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 <bruno@clisp.org>
readutmp: Fix naming of configure option.
#include <config.h>
+#if READUTMP_USE_WTMPDB
+# include <wtmpdb.h>
+/* Kill macro definitions that conflict with <utmp.h> and <utmpx.h>. */
+# undef EMPTY
+# undef BOOT_TIME
+# undef RUNLEVEL
+# undef USER_PROCESS
+#endif
+
#include "readutmp.h"
#include <errno.h>
# 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)
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);
}
# 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,
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])
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
--- /dev/null
+# 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