As mentioned in the thread starting at
https://lists.gnu.org/archive/html/bug-gnulib/2023-09/msg00064.html
and then again in
https://lists.gnu.org/archive/html/bug-gnulib/2026-07/msg00145.html
https://lists.gnu.org/archive/html/bug-gnulib/2026-07/msg00281.html
it would be useful to optionally make use of wtmpdb in the Gnulib module
'readutmp', for the sake of the coreutils programs 'who' and 'users'.
This patch does it: it uses the wtmpdb library when the users asks for
the results of /var/log/wtmp.
Find attached a corresponding patch for coreutils.
Here are the effects on some coreutils program invocations (as seen on
a Devuan 6 system):
$ src/who /var/log/wtmp
bruno tty7 2026-07-31 23:11 (:0)
bruno tty7 2026-08-03 20:53 (:0)
→
$ src/who /var/log/wtmp
bruno tty7 2026-08-03 20:53 (:0)
bruno tty7 2026-07-31 23:11 (:0)
$ src/who --all /var/log/wtmp
2026-07-31 23:11 229 id=si term=0
exit=0
system boot 2026-07-31 23:11
run-level 2 2026-07-31 23:11 last=S
2026-07-31 23:11 1365 id=l2
2026-07-31 23:11 1365 id=l2 term=0
exit=0
2026-07-31 23:11 1616 id=1
2026-07-31 23:11 1617 id=2
2026-07-31 23:11 1620 id=5
LOGIN tty2 2026-07-31 23:11 1617 id=2
LOGIN tty1 2026-07-31 23:11 1616 id=1
2026-07-31 23:11 1618 id=3
LOGIN tty3 2026-07-31 23:11 1618 id=3
2026-07-31 23:11 1619 id=4
LOGIN tty4 2026-07-31 23:11 1619 id=4
LOGIN tty5 2026-07-31 23:11 1620 id=5
2026-07-31 23:11 1621 id=6
LOGIN tty6 2026-07-31 23:11 1621 id=6
bruno + tty7 2026-07-31 23:11 20:40 1644 (:0)
run-level 2026-08-01 00:45
run-level 0 2026-08-01 00:45 last=2
tty1 2026-08-01 00:45 1616 id=1 term=0
exit=0
tty2 2026-08-01 00:45 1617 id=2 term=0
exit=0
tty3 2026-08-01 00:45 1618 id=3 term=0
exit=0
tty4 2026-08-01 00:45 1619 id=4 term=0
exit=0
tty5 2026-08-01 00:45 1620 id=5 term=0
exit=0
tty6 2026-08-01 00:45 1621 id=6 term=0
exit=0
tty1 2026-08-01 00:45 0 id=1 term=0
exit=0
tty2 2026-08-01 00:45 0 id=2 term=0
exit=0
tty3 2026-08-01 00:45 0 id=3 term=0
exit=0
tty4 2026-08-01 00:45 0 id=4 term=0
exit=0
tty5 2026-08-01 00:45 0 id=5 term=0
exit=0
tty6 2026-08-01 00:45 0 id=6 term=0
exit=0
2026-08-01 00:45 15341 id=l0
tty7 2026-08-01 00:45 0 id=:0 term=0
exit=0
run-level 2026-08-01 00:45
2026-08-03 20:09 227 id=si term=0
exit=0
system boot 2026-08-03 20:09
run-level 2 2026-08-03 20:09 last=S
2026-08-03 20:09 1365 id=l2
2026-08-03 20:10 1365 id=l2 term=0
exit=0
2026-08-03 20:10 1882 id=1
2026-08-03 20:10 1883 id=2
2026-08-03 20:10 1885 id=4
LOGIN tty1 2026-08-03 20:10 1882 id=1
LOGIN tty2 2026-08-03 20:10 1883 id=2
2026-08-03 20:10 1886 id=5
LOGIN tty5 2026-08-03 20:10 1886 id=5
2026-08-03 20:10 1884 id=3
LOGIN tty4 2026-08-03 20:10 1885 id=4
2026-08-03 20:10 1887 id=6
LOGIN tty6 2026-08-03 20:10 1887 id=6
LOGIN tty3 2026-08-03 20:10 1884 id=3
bruno + tty7 2026-08-03 20:53 old 2099 (:0)
→
$ src/who --all /var/log/wtmp
bruno + tty7 2026-08-03 20:53 20:41 0 (:0)
LOGIN tty7 2026-08-03 20:09 0 id=5
tty7 2026-08-03 20:53 0 id=5 term=0
exit=0
system boot 2026-08-03 20:09
bruno + tty7 2026-07-31 23:11 20:41 0 (:0)
tty7 2026-08-01 00:45 0 id=3 term=0
exit=0
LOGIN tty7 2026-07-31 23:11 0 id=2
tty7 2026-07-31 23:11 0 id=2 term=0
exit=0
system boot 2026-07-31 23:11
$ src/who --boot /var/log/wtmp
system boot 2026-07-31 23:11
system boot 2026-08-03 20:09
→
$ src/who --boot /var/log/wtmp
system boot 2026-08-03 20:09
system boot 2026-07-31 23:11
$ src/users /var/log/wtmp
bruno bruno
(unchanged)
2026-08-04 Bruno Haible <[email protected]>
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.
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 <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>
@@ -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, <utmp.h> 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 @@
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
>From 90b2857cd6bf9ec273d06f157f0eb60e769d0dc9 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Tue, 4 Aug 2026 17:17:27 +0200
Subject: [PATCH] maint: Document --with-wtmpdb option
* DEPENDENCIES: Mention wtmpdb.
* NEWS: Mention the effects of --with-wtmpdb.
---
DEPENDENCIES | 12 ++++++++++++
NEWS | 4 ++++
2 files changed, 16 insertions(+)
diff --git a/DEPENDENCIES b/DEPENDENCIES
index b501ffe4a..4dfd6c0c8 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -15,6 +15,18 @@
- 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.
+ Related 'configure' option: --with-wtmpdb.
+ + 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
+
* libselinux
+ Mandatory on Linux systems that support SELinux.
Needed so that the file utilities respect the security contexts,
diff --git a/NEWS b/NEWS
index ae34bab0d..002722f1f 100644
--- a/NEWS
+++ b/NEWS
@@ -82,6 +82,10 @@ GNU coreutils NEWS -*- outline -*-
** Improvements
+ When built with the configure option '--with-wtmpdb', invocations of
+ 'who /var/log/wtmp' and 'users /var/log/wtmp' use the wtmpdb database
+ instead of the file /var/log/wtmp. This makes them Y2038-safe.
+
'df', 'du', 'ls', 'od', 'pr', and 'sort' now escape invalid arguments in error
messages for options expecting an integer.
--
2.53.0