keventd: add netlink uevent rebroadcast for libudev-zero

After keventd processes a uevent (creating device nodes, loading
modules, etc.), rebroadcast the original event to netlink group
0x4 so that libudev-zero consumers -- graphical applications,
Wayland/X11 compositors, libinput, and anything else using libudev
to monitor device hotplug -- can receive device events.

Rebroadcast is enabled by default.  Use -g to override the target
netlink group mask, or -G to disable rebroadcast entirely.  Bit 0
(kernel group) is always masked out to prevent feedback loops.

Ref: https://github.com/finit-project/finit/issues/451#issuecomment-3817233886
See: https://github.com/illiliti/libudev-zero

Suggested-by: Aaron Andersen <aaron@fosslib.net>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-08-16 21:43:27 +02:00
parent 120c8e6002
commit 155d21cee7
+105 -3
View File
@@ -67,6 +67,9 @@
#define KEVENTD_VERSION "5.0"
#define _PATH_SYSFS_PWR "/sys/class/power_supply"
/* Default netlink group for uevent rebroadcast (libudev-zero convention) */
#define REBC_DEFAULT_NLGROUP 4
static int num_ac_online;
static int num_ac;
@@ -92,6 +95,74 @@ void logit(int prio, const char *fmt, ...)
#define panic(fmt, args...) { logit(LOG_CRIT, fmt ":%s", ##args, strerror(errno)); exit(1); }
#define warn(fmt, args...) { logit(LOG_WARNING, fmt ":%s", ##args, strerror(errno)); }
/*
* Netlink rebroadcast support.
*
* The Linux kernel sends uevents to netlink multicast group 1 (bit 0)
* of NETLINK_KOBJECT_UEVENT. Only the device manager should listen on
* this raw kernel group. Userspace consumers (e.g., applications using
* libudev) expect to receive processed events on a separate group --
* conventionally group 4 (bit 2), established by systemd/udevd.
*
* libudev-zero (https://github.com/illiliti/libudev-zero), a daemonless
* replacement for libudev, listens on group 0x4 for these rebroadcast
* events. Without rebroadcast, graphical applications, Wayland/X11
* compositors, libinput, and anything else using libudev to monitor
* device hotplug will never see any events.
*
* Rebroadcast is enabled by default to group 0x4. Use -g to override
* the group mask, or -G to disable rebroadcast entirely. Bit 0 is
* always masked out to prevent a feedback loop with the kernel group.
*/
static int rebc_fd = -1;
static unsigned int rebc_nlgroups;
static void rebc_init(unsigned int nlgroups)
{
/* Mask out bit 0 (kernel group) to prevent feedback loop */
if (nlgroups & 1) {
logit(LOG_WARNING, "rebroadcast group mask 0x%x includes kernel group (bit 0), masking it out", nlgroups);
nlgroups &= ~1U;
}
if (!nlgroups) {
logit(LOG_WARNING, "no valid rebroadcast groups remaining, rebroadcast disabled");
return;
}
rebc_fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
if (rebc_fd == -1) {
warn("failed creating rebroadcast socket");
return;
}
rebc_nlgroups = nlgroups;
logit(LOG_NOTICE, "rebroadcasting uevents to netlink group(s) 0x%x", nlgroups);
}
static void rebc_event(char *buf, size_t len)
{
struct sockaddr_nl sa = { 0 };
struct msghdr hdr = { 0 };
struct iovec iov;
if (rebc_fd == -1)
return;
iov.iov_base = buf;
iov.iov_len = len;
sa.nl_family = AF_NETLINK;
sa.nl_groups = rebc_nlgroups;
hdr.msg_name = &sa;
hdr.msg_namelen = sizeof(sa);
hdr.msg_iov = &iov;
hdr.msg_iovlen = 1;
if (sendmsg(rebc_fd, &hdr, 0) == -1)
logit(LOG_DEBUG, "rebroadcast failed: %s", strerror(errno));
}
static void sys_cond(const char *cond, int set)
{
char oneshot[256];
@@ -330,15 +401,17 @@ static void shut_down(int signo)
static int usage(int rc)
{
fprintf(stderr,
"Usage: keventd [-dhnv] [-c]\n"
"Usage: keventd [-dGhnv] [-c] [-g GROUP]\n"
"\n"
"Options:\n"
" -c Run coldplug at startup\n"
" -d Enable debug mode (foreground, verbose)\n"
" -g GROUP Override netlink rebroadcast group (default: %d)\n"
" -G Disable netlink rebroadcast entirely\n"
" -h Show this help text\n"
" -n Run in foreground (no daemon)\n"
" -v Show version\n"
"\n");
"\n", REBC_DEFAULT_NLGROUP);
return rc;
}
@@ -360,6 +433,7 @@ int main(int argc, char *argv[])
struct sockaddr_nl nls = { 0 };
struct pollfd pfd;
char buf[UEVENT_BUFFER_SIZE];
unsigned int nlgroups = REBC_DEFAULT_NLGROUP;
int do_coldplug = 0;
int foreground = 0;
int c;
@@ -369,7 +443,7 @@ int main(int argc, char *argv[])
* requested bits verbatim instead of masking them. */
umask(0);
while ((c = getopt(argc, argv, "cdhnv")) != -1) {
while ((c = getopt(argc, argv, "cdg:Ghnv")) != -1) {
switch (c) {
case 'c':
do_coldplug = 1;
@@ -378,6 +452,12 @@ int main(int argc, char *argv[])
debug = 1;
foreground = 1;
break;
case 'g':
nlgroups = (unsigned int)atoi(optarg);
break;
case 'G':
nlgroups = 0;
break;
case 'h':
return usage(0);
case 'n':
@@ -425,6 +505,10 @@ int main(int argc, char *argv[])
setsockopt(pfd.fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
}
/* Initialize rebroadcast socket (default on, -G to disable) */
if (nlgroups)
rebc_init(nlgroups);
/* Run coldplug if requested */
if (do_coldplug)
coldplug();
@@ -432,6 +516,7 @@ int main(int argc, char *argv[])
logit(LOG_NOTICE, "keventd v%s started, waiting for events...", KEVENTD_VERSION);
while (running) {
char rebc_buf[UEVENT_BUFFER_SIZE];
int len;
if (-1 == poll(&pfd, 1, -1)) {
@@ -459,9 +544,26 @@ int main(int argc, char *argv[])
if (!strncmp(buf, "libudev", 7))
continue;
/*
* Save raw buffer before handle_uevent() -- uevent_parse()
* modifies the buffer in-place (splits @ and = separators).
* Rebroadcast needs the original kernel format intact.
*/
if (rebc_fd != -1)
memcpy(rebc_buf, buf, len);
handle_uevent(buf, len);
/*
* Rebroadcast after processing so that device nodes and
* symlinks exist by the time consumers receive the event.
*/
if (rebc_fd != -1)
rebc_event(rebc_buf, len);
}
if (rebc_fd != -1)
close(rebc_fd);
close(pfd.fd);
logit(LOG_NOTICE, "keventd shutting down");