Conversation
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Evolve keventd from a power_supply-only monitor into a full device
manager capable of replacing mdev/mdevd on embedded systems. This
is the first step towards Finit v5.0 where keventd absorbs devmon.
New capabilities:
- Parse all uevent actions (add, remove, change, bind, unbind)
- Create and remove /dev nodes with subsystem-aware permissions
- Create persistent symlinks in /dev/disk/by-{id,path} and
/dev/input/by-{id,path}, tracked for cleanup on device removal
- Load firmware from /lib/firmware/ via the sysfs loading protocol
- Spawn modprobe for MODALIAS events (async, non-blocking)
- Coldplug support via -c flag (walks /sys/devices to replay events)
- Set dev/* conditions for Finit's service dependency system
The original power_supply monitoring and sys/pwr/ac condition are
preserved.
New files: keventd.h (structures/API), uevent.c (all device logic).
The receive buffer is increased to 8K with a 1MB socket buffer to
reduce event loss during coldplug bursts.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Rewrite doc/keventd.md from a 14-line stub into comprehensive documentation covering all features of the new unified keventd: device node creation, persistent symlinks, firmware loading, module loading, coldplug, conditions, and command-line usage. Update doc/conditions.md to list keventd as the primary provider of dev/* and sys/pwr/* conditions, with devmon as fallback when an external device manager is used instead. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
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: #451 (comment) See: https://github.com/illiliti/libudev-zero Suggested-by: Aaron Andersen <aaron@fosslib.net> Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This is a backwards compatible mode for users upgrading and not noticing that keventd is now build by default. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Transform keventd from a power-supply monitor + basic hotplug handler into
a full udev-compatible device manager.
Rules engine (rules.c):
- Full .rules file parser covering all udev key types: ACTION, KERNEL,
SUBSYSTEM, DEVPATH, ENV, ATTR, SYSCTL, TAG, RESULT, PROGRAM, TEST,
parent-chain KERNELS/SUBSYSTEMS/ATTRS/DRIVERS, and more
- Pattern matching: plain string, fnmatch glob, and pipe-separated alternatives
- Operators: ==, !=, =, +=, -=, :=
- Assignments: NAME=, MODE=, OWNER=, GROUP=, SYMLINK+=, ENV{k}=, TAG+=, RUN+=
- IMPORT{program|file|builtin|parent|cmdline|db}=
- PROGRAM= with stdout capture for subsequent RESULT== matching
- GOTO=/LABEL= flow control
- Loads *.rules from /lib/udev/rules.d, /run/udev/rules.d, /etc/udev/rules.d
and an optional extra directory (-r DIR); reloads on SIGHUP
Builtin framework (builtin.c):
- kmod: load module by MODALIAS or explicit alias
- hwdb: match device against *.hwdb text files in udev hwdb dirs; builds
correct lookup key per subsystem — evdev:input:b*v*p*e* for input,
usb:v*p* for USB, raw modalias for PCI/platform
- path_id: build stable ID_PATH / ID_PATH_TAG from sysfs topology (PCI, USB,
ATA, NVMe, platform, ACPI, virtio)
- usb_id: read idVendor/idProduct/bcdDevice/serial from sysfs; look up
ID_VENDOR_FROM_DATABASE and ID_MODEL_FROM_DATABASE from usb.ids
(hwdata package) when available; silent fallback when absent
- input_id: classify input devices (keyboard, mouse, joystick, touchscreen,
touchpad) from evdev capability bitmasks in sysfs
- net_id: generate predictable names — MAC-based enx<mac> and PCI-slot-based
enp<bus>s<dev>[f<func>]
- blkid: probe filesystem type, UUID, and label via libblkid; sets ID_FS_*
and ID_PART_TABLE_* properties
Network interface renaming (uevent.c):
- netdev_add() renames interfaces via SIOCSIFNAME when a NAME= rule matched,
then sets the Finit dev/ condition on the final name; and any setup using
persistent interface naming via udev rules
Device node and symlink improvements (uevent.c):
- NAME=, MODE=, OWNER=, GROUP= overrides from matched rules applied at
mknod/chown time, falling back to the built-in permission table
- SYMLINK+= links from rules applied alongside built-in by-id/by-path links
Device property database (udevdb.c):
- Persist per-device E:/S:/I: records to /run/udev/data/ on ADD/CHANGE,
delete on REMOVE; IMPORT{db}= restores saved properties into event env
Build:
- libblkid (util-linux) is now required for keventd
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add 27 stock rules installed to /lib/udev/rules.d/. Rules invoking /lib/udev/<helper> fall back to keventd builtins (path_id, usb_id, blkid, hwdb, kmod, net_id, input_id) when the helper binary is absent; user-supplied helpers in /lib/udev/ still take precedence. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
@troglobit this is amazing! this code looks like it is pretty close to providing a full replacement for if you get to a point where you think this is good enough i'm more than happy to test it out, just let me know! |
Glad to hear you like it! 😊
It should be fairly complete, except for libudev, which some packages need. You can use libudev-zero as a stand-in though. That's how I've been testing in Infix OS. I should put something like this in the docs to help:
I'll probably do some commit housekeeping before I port it to the |
actually there is a fork which added this functionality, but it doesn't exist upstream. i hope upstream would welcome a PR implementing this. obviously this is an important step for us.
i would love to hear more about your user cases and testing! |
|
i booted into a graphical environment with do you have any suggestions for |
|
Three good points! libudev-zero: you are right, I was mislead by "a common friend" 😅 I'll fix the docs. The Gardenhouse fork that adds Zombies from missing helpers: bug. I'll make sure to refactor to handle this better, thanks for taking the time to test and report! Regarding settle: I think we have a really good abstraction with conditions in Finit, so my idea has been to to declare device requirements in the service file. For example: Good point about settle being discouraged, I had missed that and just tried to build something that wouldn't require it. Honestly, I didn't quite understand how it worked anyway. Investigating it a bit more it seems they've deprecated it because it races and slows (serializes) the boot process. Unknowingly I went my own merry way that turned out to be right. E.g., I've added
For your greetd case, One more thing about keventd's design: it folds the common udev helpers ( |
i think you have a very concrete way to solve my problem, i understood this and maybe should have elaborated... what happens if my graphics device flip flops between
very cool!
ahh! so this is a consistent design in keventd... wonderful! i had noticed it for device ownership and permission assignment, though not for these common helpers - very cool! i think i will go through the code again a bit deeper... all sorts of wonderful things exist in there 😃 thanks @troglobit! |
Yeah that sounds like you should match on a devpath or similar and create a symlink in an udev rule. We do similar things for gps devices that can be plugged into any USB port, or the other way around provide usb-to-ttl converters with a fixed device name based on the USB port they are plugged into.
Glad you like it! |
|
still running one question: did you want |
Replace the raw poll() + global running/reload_rules flag dance with libuev. All four signals (SIGUSR1, SIGTERM, SIGHUP, SIGCHLD) are now handled via uev_signal_init() -- libuev uses signalfd internally so the handlers run in normal main-loop context, not async signal context. The SIGCHLD handler waitpid(-1, WNOHANG) reaps any subprocess (modprobe loads, rule RUN+= helpers) instead of the old SIG_IGN auto-reap trick. Synchronous waitpid() in modprobe_load() and run_program() still works because signalfd queues the signal -- it only reaches sigchld_cb on the next main-loop iteration. The netlink receive loop becomes uevent_cb() registered via uev_io_init(), with the receive buffer passed via the watcher's arg slot (avoids file-static state). ENOBUFS and EINTR/EAGAIN are still tolerated, any other recv() error still panic()s. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
modprobe_load() and run_program() used to temporarily restore the default SIGCHLD handler around fork+waitpid because the main loop set SIGCHLD=SIG_IGN. With the libuev conversion, sigchld_cb in keventd.c handles reaping via signalfd -- and signalfd does not interfere with synchronous waitpid(pid, ...) -- so the dance is dead code. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
When a rule references an absolute helper path (e.g. /lib/udev/fido_id, /lib/udev/scsi_id) that the system does not ship and we have no matching builtin either, return 1 from try_builtin_fallback() so the caller does not fork /bin/sh on a binary that's known to be missing. Previously each such uevent left a zombie 127 child for keventd's sigchld_cb to reap -- harmless but noisy and wasteful at coldplug. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
dev/<X> only fires when a device gets a /dev node, which leaves a lot of
embedded-relevant hardware uncoverable: DSA switch cores, IIO sensors,
LEDs, backlight, PHYs, regulators -- all live purely under sysfs.
Two new condition namespaces:
class/<subsystem>/<sysname> asserted on every sysfs class device add
(e.g. <class/leds/blue>)
bind/<driver> asserted on driver bind, cleared on unbind
(e.g. <bind/mt7530>)
dev_cond() is generalized into a static cond_emit(prefix, rel, set) so
class_cond() and bind_cond() share the same mkpath + symlink/erase code.
The name avoids colliding with src/cond.h's public cond_path() helper
(unrelated function that returns a condition's filesystem path).
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
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>
- <dev/IFACE> works for network interfaces too -- the condition
fires when the kernel device appears, before any link state.
Add an example next to <dev/sda> and cross-reference the
netlink plugin's parallel net/<iface>/{exist,up,running}
- The Usage block was missing -p (passive mode) and -r DIR (extra
rules dir). Add them so the synopsis matches `keventd -h`
- Clarify that `keventd -S` is a one-shot command, not a flag to
the running daemon. It does not talk to keventd, it just polls
the kernel's uevent_seqnum. Spell out the typical usage and the
rationale for preferring conditions over settle
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
When started with -c (the default when keventd is the device manager), gate the pidfile on the kernel's uevent_seqnum having been stable for 200ms. Up to now ready signaling with the pidfile was done right after coldplug() triggered the kernel to re-emit events, but before uev_run() had drained any of them, so <pid/keventd> really only meant "listening on netlink". With the gate, services that depend on <pid/keventd> can now assume /dev is populated and persistent symlinks are live. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Wow, by now you've probably logged more miles than me on it 😅 Any chance you can share the libudev-zero patch? I'd love to pull it into my Infix branch.
Good question, the idea was to provide a reliable hand-over point " In Infix I lean on Finit conditions to set up "barriers". Most services depend on Separately, I've added a |
sure! i only added tag filtering from the udev database... i think if someone were to fill in more udev function stubs there is better ways to structure this code though... 😅 did you implement any other udev functions? i'll try the new version and report back - thanks! |
Thanks 👍
Nothing more than the two new condition classes I mentioned before, 5a86ff8 covers that.
Much appreciated! |

The changes to Finit's
keventdon this branch, in conjunction with libudev-zero, should prove to be a good-enough replacement for many systems.We might adopted libudev-zero to the project in case that's needed, but hopefully we can bring it some new fresh blood instead 🧛
Important
This branch is still an active work in progress, and it may even be dropped and its feature set be moved to the
nextbranch, which is the canonical branch for all Finit 5 work.