doc: describe the udev rules keventd actually implements

The engine covers most of the udev grammar but not all of it, and the
gaps are invisible until a rule silently does nothing.  Write down what
is implemented and where it parts ways with udev(7), rather than
leaving people to infer it from a ruleset that happens to work.  The
man page gets the directory precedence and a pointer to udev(7) and
the User's Guide, which hold the details.

Rename the menu entry to Device Manager while here, matching how the
watchdog daemon is listed.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-29 09:27:26 +02:00
1 parent 59945b87f7
commit c512d57df4
3 files changed
+143 -6

No files matched your search

+111 -1
View File
@@ -74,6 +74,115 @@ device subsystem and name:
| (default) | | 0660 | root:root | | (default) | | 0660 | root:root |
udev Rules
----------
Rules are read from `/lib/udev/rules.d/`, `/run/udev/rules.d/`, and
`/etc/udev/rules.d/`, plus any directory passed with `-r`. Files need a
`.rules` suffix. Like udev, all files sort together by filename,
whichever directory they live in, and a file masks one of the same name
in an earlier directory: `/etc/` overrides `/run/`, which overrides
`/lib/`, and the `-r` directory overrides them all. A symlink to
`/dev/null` in `/etc/udev/rules.d/` disables the `/lib/` file of the
same name.
The syntax is the one described in [udev(7)][]. What follows is the
subset keventd understands, not a second copy of the grammar. To pick up
edited rules, call `RulesReload` on
[`org.finit.Device1`](#d-bus-interface-orgfinitdevice1). `initctl reload`
does not touch them.
### Match Keys
| **Key** | **Matches** |
|---------------------------------------------------|--------------------------------------------------------------------------|
| `ACTION` | `add`, `remove`, `change`, `move`, `bind`, `unbind`, `online`, `offline` |
| `DEVPATH` | kernel device path, without the `/sys` prefix |
| `KERNEL` | sysfs name of the device |
| `NAME` | device node name |
| `SUBSYSTEM`, `DRIVER` | subsystem and bound driver |
| `ATTR{file}` | sysfs attribute of the device |
| `SYSCTL{param}` | value under `/proc/sys/` |
| `ENV{key}` | uevent property, including ones set by earlier rules |
| `CONST{key}` | `arch`; `virt` reports `container` or nothing |
| `TAG` | tag set by an earlier `TAG+=` |
| `TEST{octal}` | file exists, optionally with the given permission bits |
| `PROGRAM` | exit status of a program, output kept for `RESULT` and `%c` |
| `RESULT` | output of the last `PROGRAM` |
| `KERNELS`, `SUBSYSTEMS`, `DRIVERS`, `ATTRS{file}` | the device or any of its parents, see below |
Values match literally, as an `fnmatch()` glob, or as `a|b|c` alternatives.
Both `==` and `!=` work everywhere.
`SECLABEL`, `OPTIONS`, and `TAGS` parse but do nothing. Rules using them load
without complaint and the key is skipped, so `OPTIONS+="static_node=..."`,
`link_priority=`, `string_escape=`, and `watch` have no effect, and a `TAGS==`
match never succeeds.
### The Parent Chain
`ATTR{}` reads an attribute of the device the event fired for. `ATTRS{}` also
looks at the parents, walking up the sysfs tree until one matches. For a
keyboard behind a hub:
/sys/devices/.../usb2/ <- root hub
usb2/2-1/ <- hub
usb2/2-1/2-1.4/ <- keyboard, event fires here
`ATTRS{authorized_default}=="1"` finds no such attribute on the keyboard or
the hub, reaches the root hub, and matches there. `KERNELS`, `SUBSYSTEMS`,
and `DRIVERS` do the same for a parent's name, subsystem, and driver.
Each of these keys walks the chain on its own. udev requires all the parent
keys in one rule to match on the *same* parent, so a rule pairing
`SUBSYSTEMS=="usb"` with `ATTRS{idVendor}=="1d6b"` is looser here: the two may
land on different ancestors. Where that matters, match on one attribute
specific enough to identify the device by itself.
### Assignments
| **Key** | **Effect** |
|-----------------------------|--------------------------------------------------------------------------|
| `NAME=` | name of the device node |
| `SYMLINK=`, `+=` | symlinks to create, space separated, relative to `/dev` |
| `OWNER=`, `GROUP=`, `MODE=` | ownership and permissions of the node |
| `ENV{key}=`, `+=`, `-=` | set, append to, or clear a property |
| `TAG+=`, `-=` | tags for later `TAG==` matching |
| `RUN+=` | program to run once the event is handled, `RUN{builtin}+=` for a builtin |
| `IMPORT{type}` | `program`, `file`, `db`, `builtin`, `parent`, `cmdline` |
| `ATTR{file}=` | write to a sysfs attribute |
| `SYSCTL{param}=` | write to `/proc/sys/` |
| `LABEL=`, `GOTO=` | skip ahead to a label |
`:=` locks `MODE`, `OWNER`, and `GROUP` against later rules the way udev
does. On `NAME` and `ENV{}` it behaves like plain `=` and locks nothing,
and `SYMLINK:=` is dropped without a word.
`IMPORT{builtin}` and `RUN{builtin}` can call `blkid`, `hwdb`,
`input_id`, `kmod`, `net_id`, `path_id`, and `usb_id`.
### Substitutions
| **Long** | **Short** | **Expands to** |
|--------------------|---------------|------------------------------------|
| `$kernel` | `%k` | sysfs name of the device |
| `$devpath` | `%p` | kernel device path |
| `$name` | `%N`, `%D` | device node name |
| `$major`, `$minor` | `%M`, `%m` | device numbers |
| `$driver` | `%d` | bound driver |
| `$attr{file}` | `%s{file}` | sysfs attribute of the device |
| `$env{key}` | | uevent property |
| `$result` | `%c`, `%c{N}` | `PROGRAM` output, or its Nth field |
| `$root` | | `/dev` |
| `$sys` | | `/sys` |
| | `%n` | trailing digits of the sysfs name |
| | `%b` | `major:minor` |
udev's `$id`, `$parent`, `$links`, and `%E{}` are not implemented, and `%b`
here is the device number pair rather than udev's parent bus id. An unknown
specifier is left in the string as written.
Persistent Symlinks Persistent Symlinks
------------------- -------------------
@@ -319,7 +428,7 @@ Usage
-h Show help text -h Show help text
-n Run in foreground (no daemon) -n Run in foreground (no daemon)
-p Passive mode: power supply events only -p Passive mode: power supply events only
-r DIR Extra rules directory -r DIR Extra rules directory, overrides the standard udev paths
-S Settle mode: wait for kernel uevent queue to quiet, then exit -S Settle mode: wait for kernel uevent queue to quiet, then exit
-t SEC Settle timeout (default 30s, used with -S) -t SEC Settle timeout (default 30s, used with -S)
-v Show version -v Show version
@@ -425,3 +534,4 @@ system device manager (`keventd -c`).
[libudev-zero]: https://github.com/illiliti/libudev-zero [libudev-zero]: https://github.com/illiliti/libudev-zero
[mdevd]: https://skarnet.org/software/mdevd/ [mdevd]: https://skarnet.org/software/mdevd/
[udev(7)]: https://man7.org/linux/man-pages/man7/udev.7.html
+31 -4
View File
@@ -2,7 +2,7 @@
.\" First parameter, NAME, should be all caps .\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1) .\" other parameters are allowed: see man(7), man(1)
.Dd Aug 16, 2026 .Dd Aug 29, 2026
.\" Please adjust this date whenever revising the manpage. .\" Please adjust this date whenever revising the manpage.
.Dt KEVENTD 8 SMM .Dt KEVENTD 8 SMM
.Os Linux .Os Linux
@@ -89,7 +89,10 @@ stderr instead of syslog.
Passive mode: monitor power supply events only, leaving device Passive mode: monitor power supply events only, leaving device
management to an external device manager. management to an external device manager.
.It Fl r Ar dir .It Fl r Ar dir
Extra udev rules directory, searched after the standard paths. Extra udev rules directory. Takes precedence over the standard paths, a
file here masks one of the same name in any of them. See
.Sx UDEV RULES
below.
.It Fl S .It Fl S
Settle: wait until the device event queue is quiet, then exit 0, or Settle: wait until the device event queue is quiet, then exit 0, or
non-zero on timeout. Asks the running non-zero on timeout. Asks the running
@@ -132,6 +135,29 @@ owned by root:disk, TTY devices to root:tty, and common devices like
and and
.Pa /dev/zero .Pa /dev/zero
are world-readable (0666). are world-readable (0666).
.Sh UDEV RULES
Rules files need a
.Pa .rules
suffix. Like
.Xr udev 7 ,
all files sort together by filename, whichever directory they live in,
and a file masks one of the same name in an earlier directory:
.Pa /etc/udev/rules.d
overrides
.Pa /run/udev/rules.d ,
which overrides
.Pa /lib/udev/rules.d ,
and the
.Fl r
directory overrides them all. A symlink to
.Pa /dev/null
disables the file it shadows.
.Pp
The rules syntax is described in
.Xr udev 7 .
.Nm
implements a subset; the Finit User's Guide details what is supported
and where it differs.
.Sh PERSISTENT SYMLINKS .Sh PERSISTENT SYMLINKS
For block devices, For block devices,
.Nm .Nm
@@ -350,7 +376,7 @@ and
.It Pa /run/finit/cond/sys/pwr/ .It Pa /run/finit/cond/sys/pwr/
Power supply condition symlinks. Power supply condition symlinks.
.It Pa /lib/udev/rules.d/ , Pa /run/udev/rules.d/ , Pa /etc/udev/rules.d/ .It Pa /lib/udev/rules.d/ , Pa /run/udev/rules.d/ , Pa /etc/udev/rules.d/
udev rules, searched in this order. udev rules, a later directory overrides an earlier one.
.It Pa /run/udev/data/ .It Pa /run/udev/data/
Per-device property database, libudev compatible. Per-device property database, libudev compatible.
.It Pa /run/keventd/bus .It Pa /run/keventd/bus
@@ -363,6 +389,7 @@ D-Bus socket.
.Xr finit.conf 5 , .Xr finit.conf 5 ,
.Xr initctl 8 , .Xr initctl 8 ,
.Xr mknod 2 , .Xr mknod 2 ,
.Xr modprobe 8 .Xr modprobe 8 ,
.Xr udev 7
.Sh AUTHORS .Sh AUTHORS
.An Joachim Wiberg Aq Mt troglobit@gmail.com .An Joachim Wiberg Aq Mt troglobit@gmail.com
+1 -1
View File
@@ -55,7 +55,7 @@ nav:
- Runparts & rc.local: runparts.md - Runparts & rc.local: runparts.md
- Plugins: plugins.md - Plugins: plugins.md
- Watchdog: watchdog.md - Watchdog: watchdog.md
- keventd: keventd.md - Device Manager: keventd.md
- D-Bus Integration: dbus.md - D-Bus Integration: dbus.md
- Service State Machine: state-machine.md - Service State Machine: state-machine.md
- Distributions: distro.md - Distributions: distro.md