udevdb_read_parent() contained the general capability, keyed from
sysfs when no uevent is in hand. Split it out as
udevdb_read_devpath() for the D-Bus Info method; parent lookup
becomes a wrapper.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
coldplug_trigger(action, subsystem-glob) replays events for a subset
of devices, the D-Bus Trigger method needs both knobs; coldplug() is
now the ("add", NULL) case. nftw() has no user cookie, so the
parameters ride in file statics.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The park machinery was welded to broker uid resolution; a handler
that cannot answer yet, like a device-settle call waiting for the
event queue to drain, had no way to defer. link_call_park() holds
the request, link_call_resume() re-runs the handler with
link_call_resumed() reading true, and the expire sweep remains the
backstop for a resume that never comes.
Resume also no longer drops a local caller's kernel group set in the
privileged re-check: group source now keys on broker-ness.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Device seqnums are 64-bit; the writer and reader stopped at u32.
Adds t/x/d to the skip path so a{sv} consumers tolerate them.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
dbus.md was orphaned: not in dist_docs_DATA, not linked from the
user guide. Wire it into the doc dist and link it from the index
features list, features.md, initctl.md, and plugins.md, where the
dbus.so plugin entry now disambiguates the external system bus from
the built-in org.finit API.
Document the 64-peer cap, the supported AddMatch keys, the busconfig
policy file, the legacy-parity edge semantics with the deliberate
SetRunlevel InvalidArgs divergence, the reload-signal behavior of
Service1.Reload, and the reboot family timeout. Refresh the stale
initctl.md usage paste, add monitor and the D-Bus transport to
initctl(8), add /run/finit/bus to the filesystem layout, and flatten
the ChangeLog D-Bus entry to house style.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The D-Bus support is default-enabled, so the HAVE_DBUS paths only
bit-rot silently without this: the leg caught initctl failing to
build with --disable-dbus on its first local run. Also asserts the
binaries carry no bus references and smoke-runs one non-dbus test.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
initctl_CPPFLAGS is only assigned under the DBUS conditional, but an
automake per-target variable exists even when its conditional is
false, so a --disable-dbus build dropped AM_CPPFLAGS entirely:
util.c:319:16: error: invalid use of undefined type 'struct FTW'
Assign the base flags unconditionally and append under DBUS.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The path encoding in libink/path.c was untested for identities with
separators; dbus-service.sh only used the bare keventd identity.
Declare a dhcp-client:eth1 service and verify the escaped path, that
the object introspects, and that Identity round-trips.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
All three org.finit signals were invisible to generated proxies, and
org.freedesktop.DBus was missing from the standard interfaces even
though Hello, AddMatch, and RemoveMatch are answered.
Add a link_signal_t table to the vtable, emitted like methods and
properties, declare the Manager1 and Cond1 signals, and complete the
static XML with PropertiesChanged and org.freedesktop.DBus.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The D-Bus methods carried byte-for-byte copies of api.c's static
start/stop/restart helpers. Promote them to service.c alongside
service_reload(), which already serves both callers, and reduce both
sides to svc_parse_jobstr-style adapters.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Regression tests for the recent handler fixes: bogus SetRunlevel is
InvalidArgs, Signal on a stopped service is Failed, and the reboot
family declares the timeout argument. Reboot cannot be invoked
without taking down the sandbox, so the latter is asserted via
introspection. New call-u and call-su modes in dbus-auth-client.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
initctl -t N reboot arms an emergency bypass timer over the legacy
socket, but the bus methods took no argument, so the timeout was
silently dropped whenever D-Bus was up.
Reboot, Halt, and Poweroff now take a timeout in seconds, 0 for
none, armed via the same shutdown_bypass() the legacy path uses.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Service1 Start/Stop/Restart discarded the action return value, their
Manager1 twins map it to org.finit.Error.Failed. Cond1 Set/Clear
replied success even when the condition symlink operation failed,
where legacy initctl exits 73. Verify the resulting condition state
with cond_get() rather than the noupdate return values, which report
no-change, not failure, and would reject an idempotent re-set.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Manager1.Signal silently skipped stopped services, so the same
command gave different exit codes depending on transport: the legacy
INIT_CMD_SIGNAL path fails when the service is not running. Mirror
the legacy behavior.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The bus method called sm_runlevel() unconditionally. In runlevel 0
and 6 that aborts an in-flight shutdown, which INIT_CMD_RUNLVL
refuses with a warning, and during bootstrap it switches immediately
where the legacy path defers via cfglevel to the end of runlevel S.
Port both. A bad runlevel argument still returns InvalidArgs, where
the legacy protocol acks silently: a typed interface rejects garbage.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The legacy INIT_CMD_SUSPEND is refused in runlevel S, 0, and 6, the
bus method suspended unconditionally, even mid-shutdown. Add the
same guard, replying WrongRunlevel like the reboot family.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Once it gives up it forks the post:script and reports that PID as the
service's, so a slay still waiting for the service to come back killed
the script instead, and crashing.sh lost the /tmp/post it checks for.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The unified keventd has no automated coverage, only the devmon
fallback is exercised by the test suite. Network interfaces are
the one device class an unprivileged test can hotplug: the sandbox
has its own network namespace, so 'ip link add' makes the kernel
emit genuine uevents.
Verify keventd readiness, <class/net/IFNAME> driving a service --
and <dev/IFNAME> NOT asserted, interfaces are not device nodes --
libudev-compatible n<ifindex> keying in /run/udev/data, conditions
surviving initctl reload, and cleanup on interface remove.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
A service with a <class/net/eth0>, or any other keventd-provided,
condition is never started when the device appears. keventd asserts
the condition file, but devmon only watches /dev, so no cond_update()
ever reaches affected services. Reload made it worse:
devmon_reconf() clears any registered dev/ condition without a /dev
node behind it.
Watch the dev/, class/, and driver/ condition directories, like the
sys and usr plugins do for their namespaces, and treat an existing
condition file as device presence in devmon_reconf().
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
keventd links libblkid, which finit itself does not, but sysroot.mk
only copied the libraries finit links. Inside the sysroot keventd
then fails to start:
Service keventd[18] died (exit status: 127)
Collect libraries from finit and everything installed under
libexec/finit/ instead.
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>
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/<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>)
driver/<name> asserted while the driver is bound to at
least one device (e.g. <driver/mt7530>)
A driver can bind to several devices, so driver/ conditions are
refcounted: asserted on first bind, cleared when the last device is
unbound.
dev_cond() is generalized into a static cond_emit(prefix, rel, set) so
class_cond() and driver_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>
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>
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>
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>
keventd is the only thing in the tree that links libblkid, so a tree
that used to build now stops in configure with no hint of which package
to install. Say so where people look for dependencies, and give CI the
package it now needs.
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>
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>
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>
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>
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>
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>
A pass over the whole branch before merge, mostly in libink since
that is the new code and the part exposed to the wire. Grouped here
rather than scattered so the review is easy to read in one place.
libink parser and dispatch:
- Bound reader lengths so a 32-bit size_t can't wrap a wire length
past the guard and read out of bounds. Reachable pre-auth on any
bus, so it matters on the 32-bit targets Finit runs on.
- Drop a peer when a reply send fails instead of limping on with a
half-written frame; a built-in whose send failed used to fall
through and put a second frame on the wire.
initctl:
- Copy a D-Bus error name out of the reply before closing the client;
the reply points into memory the close frees. Both error paths now
share one helper so this can't creep back.
Authorization:
- Take the caller's groups from the kernel (SO_PEERCRED plus
SO_PEERGROUPS) rather than getpwuid()/getgrouplist(), which go
through NSS and can block PID 1 on a slow LDAP or SSSD backend.
The check is now a lookup against the group resolved once at init,
with no NSS and no 256 KiB array on the stack. A caller reaching
us through a broker carries no group set, so system-bus privileged
methods are root-only; the local bus keeps group support. See
libink/README.md for the note on lifting that.
Shutdown:
- Call dbus_exit() from the shutdown path so the server, its peers,
and the socket are let go cleanly. The teardown existed but nobody
called it.
Tests, CI, docs:
- A fuzz target for the message parser, run as a quick sweep in the
suite and properly under libFuzzer in CI, with the corpus carried
between runs. The -as-uid tests drop groups the way a login does
so SO_PEERGROUPS sees the right set, and widen the test socket to
reach the per-method check behind the 0660 gate. Bring the GitHub
actions up to versions that run on Node 24, and tidy a few small
things a /simplify pass turned up.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
__msg_parse() turns bytes off a socket into pointers, before anything
has vouched for the peer, and it is the only place in libink that
does. It had no test of its own beyond whatever the other tests
happened to send it, all of it well-formed.
The target checks the parser's contract, not merely that it survived.
A header field must point into the header field array, and terminate
inside it, and the parse must never claim more bytes than it was
handed. Crash-only would pass a parser that walked into the body and
returned fields from there, since those bytes were handed over too.
The expected bounds are derived from the raw header rather than from
the parser, so the two have to agree independently.
Every input is copied into an allocation sized to it first. Reading
past the end of a roomy buffer stays inside the allocation and the
sanitizer never sees it; against an exact one the same read is a
fault, which is where the sharpest findings come from.
Under libFuzzer it is an ordinary fuzz target and named files replay,
which is how a find gets reproduced. With no arguments it runs a
fixed sweep -- every truncation, every single-byte corruption, every
value of the length that decides where the header ends, and seeded
garbage -- so the suite covers the same contract on every build,
without clang or a corpus in the tree. It takes 40 ms.
CI fuzzes it properly on every pull request, keeps the crashers, and
carries the corpus between runs so it reaches deeper over time than
any single run can. Note that clang links the fuzzer runtime against
the newest GCC tree it finds, so the libstdc++ headers have to match
that one and not the default compiler, which is worth saying since
installing the obvious package leaves you exactly where you started.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
A rule carrying sender, destination, or eavesdrop was refused whole,
and a peer whose AddMatch fails gets no signals at all. That is a
poor trade for keys clients attach as a matter of course: better a
filter wider than asked for than a subscription that never happened.
They are accepted and ignored rather than honoured. Widening costs
nothing here since Finit is the only sender on this bus, and what it
emits through the match table is state any peer that got this far may
already read.
argN and argNpath still take the whole rule down. Honouring them
means parsing message bodies, and nothing asks for them yet.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
"Set not yet implemented" reads as a promise. Finit exposes no
writable property and has no use for one: everything a caller might
want to change is a Manager1 method, where the authorization lives.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Only the file header said we refuse 'B' messages, and nobody reads a
header comment when they are looking at why a parse failed. Put it at
the check, with an XXX so it turns up in a grep for known gaps.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
A call is parked until the resolver says who sent it, and an outbound
call sits in a pending slot until its reply lands. Neither had a way
to give up. A broker that answers GetConnectionUnixUser slowly, or
not at all, leaves the caller waiting forever and keeps the slot; four
of those and every later privileged call is refused with
LimitsExceeded until Finit restarts.
libink cannot time itself out, it has no event loop, so the deadline
is the embedder's to keep. One sweep per connection covers both, and
the ordering between them stays in the library rather than in each
embedder: calls first, because one timing out usually resolves the
park it was made for, and AccessDenied tells that caller more than a
bare timeout.
The sweep is armed when a resolve is deferred and stops rearming as
soon as nothing is outstanding, so a system that never meets a broker
never wakes up for it.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Parked calls and outbound calls awaiting a reply only ever happen on a
connection talking to a broker, but the parked array sat on the server
and the pending array on every connection. A server with no broker
carried 4 KiB of slots it could never fill, and both were reachable
from code paths that have no business in them.
Move both behind one struct, allocated on the first park or call and
freed with the connection. link_server_t goes from 4400 to 168 bytes;
link_connection_t barely moves, its buffers dominate, but an ordinary
peer no longer carries reply-tracking it never uses.
Tokens are now per bus rather than per server, so link_uid_resolved()
takes the connection the answer is about. Every resolver already has
it: it is the first argument to both the resolver and the reply
callback.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
It still described treating every system-bus caller as unprivileged as
the state of things, which stopped being true when Finit learned to ask
the broker who sent a call.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>