Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = man plugins src system tmpfiles.d

if KEVENTD
SUBDIRS += keventd
endif
dist_doc_DATA = README.md LICENSE contrib/finit.conf

if CONTRIB
Expand Down
17 changes: 13 additions & 4 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ AC_CONFIG_FILES([Makefile
man/Makefile
plugins/Makefile
src/Makefile
keventd/Makefile
system/Makefile system/10-hotplug.conf
test/test.env test/Makefile
test/lib/Makefile test/src/Makefile
Expand Down Expand Up @@ -112,7 +113,7 @@ AC_PLUGIN([resolvconf], [no], [Setup necessary files for resolvconf])
AC_PLUGIN([x11-common], [no], [Console setup (for X)])
AC_PLUGIN([netlink], [yes], [Basic netlink plugin for IFUP/IFDN and GW events. Can be replaced with externally built plugin that links with libnl or similar.])
AC_PLUGIN([hook-scripts], [no], [Trigger script execution from hook points])
AC_PLUGIN([hotplug], [yes], [Start udevd or mdev kernel event datamon])
AC_PLUGIN([hotplug], [no], [Start udevd or mdev kernel event datamon])
AC_PLUGIN([rtc], [yes], [Save and restore RTC using hwclock])
AC_PLUGIN([tty], [yes], [Automatically activate new TTYs, e.g. USB-to-serial])
AC_PLUGIN([urandom], [yes], [Setup and save random seed at boot/shutdown])
Expand Down Expand Up @@ -165,7 +166,10 @@ AC_ARG_WITH(random-seed,
[random_seed=$withval], [random_seed=yes])

AC_ARG_WITH(keventd,
AS_HELP_STRING([--with-keventd], [Enable built-in keventd, default: no]),, [with_keventd=no])
AS_HELP_STRING([--with-keventd], [Enable built-in keventd device manager, default: yes]),, [with_keventd=yes])

AC_ARG_WITH(udev-rules,
AS_HELP_STRING([--without-udev-rules], [Skip install of curated udev rules under /lib/udev/rules.d, default: yes when keventd enabled]),, [with_udev_rules=yes])

AC_ARG_WITH(sulogin,
AS_HELP_STRING([--with-sulogin@<:@=USER@:>@], [Enable built-in sulogin, optional USER to request password for (default root), default: no.]),[sulogin=$withval],[with_sulogin=no])
Expand Down Expand Up @@ -312,7 +316,11 @@ AS_IF([test "x$rtc_file" != "xno"], [
AC_DEFINE_UNQUOTED(RTC_FILE, "$rtcfile_path", [Save and restore system time from this file if /dev/rtc is missing.])],[
AC_DEFINE_UNQUOTED(RTC_FILE, NULL)])

AS_IF([test "x$with_keventd" != "xno"], [with_keventd=yes])
AS_IF([test "x$with_keventd" != "xno"], [
with_keventd=yes
PKG_CHECK_MODULES([blkid], [blkid],, [
AC_MSG_ERROR([libblkid (util-linux) is required to build keventd.
Install the dev package (e.g. libblkid-dev) or disable keventd with --without-keventd])])])

AS_IF([test "x$with_sulogin" != "xno"], [
AS_IF([test "x$sulogin" = "xyes"], [
Expand Down Expand Up @@ -345,6 +353,7 @@ AS_IF([test "x$with_plugin_path" != "xno"], [
AM_CONDITIONAL(BASH, [test "x$bash_dir" != "xno"])
AM_CONDITIONAL(STATIC, [test "x$enable_static" = "xyes"])
AM_CONDITIONAL(KEVENTD, [test "x$with_keventd" != "xno"])
AM_CONDITIONAL(UDEV_RULES, [test "x$with_keventd" != "xno" -a "x$with_udev_rules" != "xno"])
AM_CONDITIONAL(SULOGIN, [test "x$with_sulogin" != "xno"])
AM_CONDITIONAL(WATCHDOGD, [test "x$with_watchdog" != "xno"])
AM_CONDITIONAL(LIBSYSTEMD, [test "x$with_libsystemd" != "xno"])
Expand Down Expand Up @@ -428,7 +437,7 @@ Optional features:
Install doc/..........: $enable_doc
Install contrib/......: $enable_contrib
Bash completion.......: $bash_completion_dir
Built-in keventd......: $with_keventd
Built-in keventd......: $with_keventd (blkid: $blkid_LIBS)
Built-in sulogin......: $with_sulogin $sulogin
Built-in watchdogd....: $with_watchdog $watchdog
Built-in logrotate....: $enable_logrotate
Expand Down
26 changes: 15 additions & 11 deletions doc/conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,26 +135,30 @@ Built-in Conditions

Finit comes with a set of plugins for conditions:

- `devmon` (built-in)
- `netlink`
- `pidfile`
- `keventd`: provides `<dev/...>` and `<sys/pwr/...>`
- `devmon` (built-in fallback for `<dev/...>` without keventd)
- `netlink`: provides `<net/...>`
- `pidfile`: provides `<pid/...>`
- `sys`
- `usr`

The `devmon` (built-in) plugin monitors `/dev` and `/dev/dir` for device
nodes being created and removed. It is active only when a run, task, or
service has declared a `<dev/foo>` or `<dev/dir/bar>` condition.
The `dev/` conditions are provided by `keventd`, the built-in device
manager. When keventd creates a device node in `/dev`, it also asserts
the corresponding `dev/` condition. When a device is removed, the
condition is cleared. If keventd is not in use (an external device
manager like udevd is used instead), the `devmon` built-in provides the
same conditions by monitoring `/dev` and `/dev/dir` with inotify.

The `pidfile` plugin (recursively) watches `/run/` (recursively) for PID
files created by the monitored services, and sets a corresponding
condition in the `pid/` namespace.
The `pidfile` plugin (recursively) watches `/run/` for PID files created
by the monitored services, and sets a corresponding condition in the
`pid/` namespace.

Similarly, the `netlink` plugin provides basic conditions for when an
interface is brought up/down and when a default route (gateway) is set,
in the `net/` namespace.

The `sys` and `usr` plugins monitor are passive condition monitors where
the action is provided by `keventd`, signal handlers, and in the case of
The `sys` and `usr` plugins are passive condition monitors where the
action is provided by `keventd`, signal handlers, and in the case of
`usr`, the end-user via the `initctl` tool.

Additionally, the various states of a run/task/sysv/service can also be
Expand Down
261 changes: 250 additions & 11 deletions doc/keventd.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,253 @@
keventd
=======
Bundled Device Manager
======================

The kernel event daemon bundled with Finit is a simple uevent monitor
for `/sys/class/power_supply`. It provides the `sys/pwr/ac` condition,
which can be useful to prevent power hungry services like anacron to run
when a laptop is only running on battery, for instance.
The kernel event daemon `keventd` is a built-in device manager bundled
with Finit. It replaces the need for external device managers like
mdev, mdevd, or udevd on systems where a lighter-weight solution is
preferred, particularly on embedded systems.

Since keventd is not an integral part of Finit yet it is not enabled by
default. Enable it using `./configure --with-keventd`. The bundled
`contrib/` build scripts for Debian, Alpine, and Void have this enabled.
It is enabled by default since Finit v5. To disable it and use an
external device manager instead:

This daemon is planned to be extended with monitoring of other uevents,
patches and ideas are welcome in the issue tracker.
./configure --without-keventd


Features
--------

When started, keventd listens on a `NETLINK_KOBJECT_UEVENT` socket for
kernel events and handles:

- **Device node creation**: creates and removes `/dev` nodes with
correct permissions on device add/remove events
- **Persistent symlinks**: creates `/dev/disk/by-id/`, `/dev/disk/by-path/`,
and `/dev/input/by-id/`, `/dev/input/by-path/` symlinks for stable
device naming
- **Firmware loading**: responds to kernel firmware requests by searching
`/lib/firmware/` and writing firmware data to sysfs
- **Module loading**: parses `MODALIAS` from uevents and spawns `modprobe`
to load the appropriate kernel module
- **Coldplug**: with the `-c` flag, walks `/sys/devices` and triggers
add events for all devices present at boot
- **Power supply monitoring**: tracks AC power status and provides the
`sys/pwr/ac` condition
- **Device conditions**: sets `dev/*` conditions in the Finit condition
system when device nodes appear or disappear
- **Netlink rebroadcast**: rebroadcasts processed uevents to netlink
group 0x4 for [libudev-zero][] consumers (enabled by default)


Device Nodes
------------

On receiving an `add` event with `MAJOR`, `MINOR`, and `DEVNAME`
fields, keventd creates the corresponding device node in `/dev` using
`mknod()`. Parent directories are created automatically (e.g.,
`/dev/input/` for `/dev/input/event0`).

On `remove` events, the device node and its associated symlinks and
conditions are cleaned up.

### Default Permissions

keventd applies permissions based on built-in rules that match on
device subsystem and name:

| **Subsystem** | **Pattern** | **Mode** | **Owner:Group** |
|---------------|--------------------------------------------|----------|-----------------|
| block | sd*, vd*, nvme*, mmcblk*, loop*, dm-*, md* | 0660 | root:disk |
| tty | tty[0-9]* | 0620 | root:tty |
| tty | ttyS*, ttyUSB*, ttyACM* | 0660 | root:dialout |
| input | event*, mouse*, mice | 0660 | root:root |
| sound | * | 0660 | root:audio |
| video4linux | * | 0660 | root:video |
| drm | card*, render* | 0660 | root:video |
| (any) | null, zero, full, random, urandom | 0666 | root:root |
| (any) | console | 0600 | root:root |
| (default) | | 0660 | root:root |


Persistent Symlinks
-------------------

For block devices, keventd creates symlinks under `/dev/disk/`:

- **by-id**: based on the device serial number and model, read from
sysfs attributes (`/sys/.../device/vendor`, `model`, `serial`)
- **by-path**: based on the device topology path

For input devices, symlinks are created under `/dev/input/`:

- **by-id**: based on the device name from sysfs
- **by-path**: based on the physical device path

These symlinks are tracked internally and automatically removed when the
corresponding device is unplugged.


Firmware Loading
----------------

When a kernel driver requests firmware (via `request_firmware()`), the
kernel sends a uevent with a `FIRMWARE=` field. keventd handles this
by:

1. Searching for the firmware file in order:
- `/lib/firmware/updates/<kernel-version>/<name>`
- `/lib/firmware/updates/<name>`
- `/lib/firmware/<kernel-version>/<name>`
- `/lib/firmware/<name>`
2. Writing `1` to `/sys/<devpath>/loading` to signal start
3. Copying the firmware data to `/sys/<devpath>/data`
4. Writing `0` to `/sys/<devpath>/loading` on success (or `-1` on failure)

This is particularly important early in boot when drivers for graphics
cards, network adapters, and other hardware need firmware before they
can operate.


Module Loading
--------------

When a device add event includes a `MODALIAS` field, keventd spawns
`modprobe -bq <modalias>` to load the matching kernel module. Module
loading is done asynchronously (keventd does not wait for modprobe to
complete) to avoid blocking other event processing.


Coldplug
--------

To handle devices that were present before keventd started, it supports
a coldplug mode activated with the `-c` flag. This walks the entire
`/sys/devices` tree and writes `add` to each `uevent` file, causing the
kernel to re-emit add events for all existing devices.

This replaces the separate `coldplug` script previously used with mdev.


Netlink Rebroadcast
-------------------

The Linux kernel sends uevents to netlink multicast group 1 (bit 0) of
`NETLINK_KOBJECT_UEVENT`. Only the device manager listens on this raw
kernel group. Userspace consumers — applications using libudev —
expect to receive processed events on a separate netlink group.

systemd/udevd established the convention of rebroadcasting processed
events to a separate group, and [libudev-zero][], a daemonless drop-in
replacement for libudev, listens on group `0x4` for these events.
Without a device manager rebroadcasting, graphical applications,
Wayland/X11 compositors, libinput, and anything else using libudev to
monitor device hotplug will never see any events.

keventd rebroadcasts by default to netlink group 4 (`0x4`). A second
netlink socket is created at startup, and after each uevent has been
fully processed (device nodes created, symlinks set up, modules loaded),
the original raw event is sent to the configured group(s).
Rebroadcasting after processing ensures that device nodes and symlinks
already exist by the time consumers receive the notification.

Use `-g GROUP` to override the default group mask, or `-G` to disable
rebroadcast entirely. Bit 0 of the group mask is always forced off to
prevent a feedback loop with the kernel's own multicast group.

### Background

The netlink uevent architecture uses separate multicast groups to
isolate the kernel-to-device-manager channel from the device-manager-to-
application channel:

| **Group** | **Bit** | **Purpose** |
|-----------|---------|---------------------------------------------|
| 1 | 0 | Kernel events (device manager listens here) |
| 4 | 2 | Processed events (libudev consumers listen) |

This two-group design was established by systemd/udevd and is the de
facto standard. [mdevd][] implements the same mechanism via its `-O`
flag, and keventd follows the same convention.

For more details, see:

- [libudev-zero][] — daemonless replacement for libudev
- [mdevd][] — mdev-compatible device manager with rebroadcast support


Conditions
----------

keventd provides conditions in two namespaces:

### Device Conditions (`dev/`)

When a device node is created, keventd asserts a corresponding condition
in `/run/finit/cond/dev/`. This allows services to wait for specific
devices:

service [2345] <dev/sda> /usr/sbin/mdadm --monitor /dev/md0 -- RAID monitor
service [2345] <dev/ttyUSB0> /usr/sbin/gps-daemon -- GPS daemon

When the device is removed, the condition is cleared and Finit stops
the dependent services.

### Power Supply Conditions (`sys/pwr/`)

keventd monitors the `power_supply` subsystem and provides:

- `sys/pwr/ac` — asserted when AC power is connected

This is useful for preventing power-hungry services from running on
battery:

service [2345] <sys/pwr/ac,pid/syslogd> cron -f -- Cron daemon


Usage
-----

keventd [-cdGhnv] [-g GROUP]

Options:
-c Run coldplug at startup
-d Enable debug mode (foreground, verbose)
-g GROUP Override netlink rebroadcast group (default: 4)
-G Disable netlink rebroadcast entirely
-h Show help text
-n Run in foreground (no daemon)
-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.

Debug logging can also be toggled at runtime by sending `SIGUSR1`:

kill -USR1 $(pidof keventd)


Integration with Finit
----------------------

keventd is a standalone daemon started by Finit as an internal service.
It communicates with Finit exclusively through the filesystem-based
condition system — creating and removing symlinks in `/run/finit/cond/`.

This means keventd can also be tested independently:

# Run in debug mode to see all kernel events
keventd -d

# Run with coldplug to populate /dev from scratch
keventd -c -n

# Run without rebroadcast (e.g., headless embedded system)
keventd -c -G

When keventd is enabled, it conflicts with external device managers.
Only one device manager should be active at a time. The system
configuration uses the `conflict:` directive to enforce this:

service conflict:udevd,mdevd,mdev [...] keventd -c -- Finit device manager

[libudev-zero]: https://github.com/illiliti/libudev-zero
[mdevd]: https://skarnet.org/software/mdevd/
Loading
Loading