keventd: add -S settle mode polling /sys/kernel/uevent_seqnum

Stop-gap "settle" equivalent of udevadm settle for migration scenarios.
Polls /sys/kernel/uevent_seqnum every 50ms and exits zero when the
sequence number has been stable for 200ms (or non-zero after -t SECONDS
timeout, default 30s).

This is racy by design -- a slow probe firing after we return still
races -- so the doc steers users toward dev/, class/, and bind/
conditions for any service they control.  Settle is for legacy boot
scripts and init transitions where condition wiring isn't feasible.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-08-16 22:03:34 +02:00
parent f3461ce2ba
commit c1c68054ea
2 changed files with 86 additions and 3 deletions
+23 -1
View File
@@ -301,7 +301,7 @@ battery:
Usage
-----
keventd [-cdGhnpv] [-g GROUP] [-r DIR]
keventd [-cdGhnpSv] [-g GROUP] [-r DIR] [-t SECONDS]
Options:
-c Run coldplug at startup
@@ -310,12 +310,34 @@ Usage
-G Disable netlink rebroadcast entirely
-h Show help text
-n Run in foreground (no daemon)
-p Passive mode: power supply events only
-r DIR Extra rules directory
-S Settle mode: wait for kernel uevent queue to quiet, then exit
-t SEC Settle timeout (default 30s, used with -S)
-v Show version
In normal operation, Finit starts keventd automatically via its system
configuration. The `-d` flag is useful for debugging device issues --
it runs keventd in the foreground and logs all received uevents.
`keventd -S` is a one-shot command, not a flag to the running daemon.
It is the `udevadm settle` equivalent for migration scenarios:
keventd -S -t 10 && start-graphical-session
It does not talk to the running keventd (or any device manager) -- it
simply opens `/sys/kernel/uevent_seqnum` and polls until the kernel's
sequence counter has been stable for 200ms, then exits zero. After
the `-t SECONDS` timeout (default 30) it exits non-zero instead.
Because `uevent_seqnum` is maintained by the kernel itself, `-S` works
regardless of which device manager is active, or even if none is.
Prefer the condition-based model (`<dev/X>`, `<class/...>`,
`<driver/...>`) over settle when you control the service definition --
settle is racy with slow probes that fire after the queue appears
quiet. It is provided for legacy boot scripts and init transitions
where condition wiring isn't feasible.
Debug logging can also be toggled at runtime by sending `SIGUSR1`:
kill -USR1 $(pidof keventd)
+63 -2
View File
@@ -44,6 +44,7 @@
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
@@ -536,10 +537,57 @@ static void uevent_cb(uev_t *w, void *arg, int events)
rebc_event(rebc_buf, len);
}
/*
* Stop-gap "settle" mode: poll /sys/kernel/uevent_seqnum until it has
* been stable for `stable_ms` (default 200ms), or until `timeout_s`
* elapses. Imperfect -- a slow probe firing after we return still
* races -- but matches what typical `udevadm settle` users want.
*
* The proper fix lives in the IPC/queue work (see audit doc).
*/
static int cmd_settle(int timeout_s, int stable_ms)
{
struct timespec start, last_change, now;
unsigned long long last_seq = 0, cur_seq = 0;
FILE *fp;
clock_gettime(CLOCK_MONOTONIC, &start);
last_change = start;
while (1) {
fp = fopen("/sys/kernel/uevent_seqnum", "r");
if (!fp) {
fprintf(stderr, "keventd: cannot read uevent_seqnum: %s\n",
strerror(errno));
return 1;
}
if (fscanf(fp, "%llu", &cur_seq) != 1)
cur_seq = last_seq;
fclose(fp);
clock_gettime(CLOCK_MONOTONIC, &now);
if (cur_seq != last_seq) {
last_seq = cur_seq;
last_change = now;
} else {
long dt_ms = (now.tv_sec - last_change.tv_sec) * 1000 +
(now.tv_nsec - last_change.tv_nsec) / 1000000;
if (dt_ms >= stable_ms)
return 0;
}
if (now.tv_sec - start.tv_sec >= timeout_s)
return 1;
usleep(50000); /* 50ms */
}
}
static int usage(int rc)
{
fprintf(stderr,
"Usage: keventd [-dGhnpv] [-c] [-g GROUP] [-r DIR]\n"
"Usage: keventd [-dGhnpSv] [-c] [-g GROUP] [-r DIR] [-t SECONDS]\n"
"\n"
"Options:\n"
" -c Run coldplug at startup\n"
@@ -550,6 +598,8 @@ static int usage(int rc)
" -n Run in foreground (no daemon)\n"
" -p Passive mode: power supply events only (no device management)\n"
" -r DIR Extra rules directory (in addition to standard udev paths)\n"
" -S Settle: wait until kernel uevent queue is quiet, then exit\n"
" -t SEC Settle timeout in seconds (default: 30, with -S)\n"
" -v Show version\n"
"\n", REBC_DEFAULT_NLGROUP);
@@ -577,6 +627,8 @@ int main(int argc, char *argv[])
uev_t netlink_watcher;
uev_ctx_t ctx;
int do_coldplug = 0;
int do_settle = 0;
int settle_timeout = 30;
int foreground = 0;
int nlfd;
int c;
@@ -586,7 +638,7 @@ int main(int argc, char *argv[])
* requested bits verbatim instead of masking them. */
umask(0);
while ((c = getopt(argc, argv, "cdg:Ghnpr:v")) != -1) {
while ((c = getopt(argc, argv, "cdg:Ghnpr:St:v")) != -1) {
switch (c) {
case 'c':
do_coldplug = 1;
@@ -612,6 +664,12 @@ int main(int argc, char *argv[])
case 'r':
rules_dir = optarg;
break;
case 'S':
do_settle = 1;
break;
case 't':
settle_timeout = atoi(optarg);
break;
case 'v':
printf("keventd v%s\n", KEVENTD_VERSION);
return 0;
@@ -620,6 +678,9 @@ int main(int argc, char *argv[])
}
}
if (do_settle)
return cmd_settle(settle_timeout, 200);
if (!foreground) {
openlog("keventd", LOG_PID, LOG_DAEMON);
set_logging(LOG_NOTICE);