D-Bus Integration¶
Finit ships with a built-in, brokerless D-Bus implementation, libink
(-link), that exposes the running init system as a peer on its own private
bus, and optionally on the system bus when dbus-daemon is available.
Everything initctl does is also reachable from any generic D-Bus tooling —
dbus-send, dbus-monitor, gdbus, language bindings, dashboards,
monitoring agents, etc.
Note
D-Bus support is enabled by default, opt out at build time with
--disable-dbus, see Building for details. When disabled,
initctl keeps using the legacy INIT_SOCKET transport and Finit
exposes no bus.
Bus address¶
| Bus | Address |
|---|---|
| Local (always) | unix:path=/run/finit/bus |
| System (opportunistic) | unix:path=/var/run/dbus/system_bus_socket |
The local bus is brokerless: clients connect straight to Finit over a
Unix-domain socket using the standard D-Bus SASL EXTERNAL handshake. No
dbus-daemon is required, which makes it suitable for embedded systems that
don't ship one.
The socket is 0660, owned by root and the group given to
--with-group at build time, the same gate as /run/finit/socket that
initctl falls back on. The bus reaches every operation initctl
does, so restricting one and not the other would leave the door open.
Members of that group may use it, see Authorization.
The system bus is best-effort: Finit probes for a running dbus-daemon
and, when reachable, claims the well-known name org.finit so that standard
tooling sees Finit just like any other system service:
dbus-send --system --print-reply --dest=org.finit /org/finit/manager \
org.finit.Manager1.ListServices
dbus-monitor --system "sender='org.finit'"
If no system bus is present (the common case on embedded targets), this step is silently skipped.
Object tree¶
/
├── org/
│ └── finit/
│ ├── manager Manager1
│ ├── cond Cond1
│ └── service/
│ ├── keventd Service1 (one per service)
│ ├── sshd
│ └── …
└── org/freedesktop/DBus Standard well-known interfaces
Every node implements the usual stock interfaces:
| Interface | Purpose |
|---|---|
org.freedesktop.DBus |
Hello, AddMatch, RemoveMatch |
org.freedesktop.DBus.Peer |
Ping, GetMachineId |
org.freedesktop.DBus.Introspectable |
Introspect() — XML description |
org.freedesktop.DBus.Properties |
Get, GetAll; nothing is writable |
Note: Hello, AddMatch, and RemoveMatch are answered on the canonical
/org/freedesktop/DBus object only, as per the D-Bus specification.
org.finit.Manager1¶
Lives at /org/finit/manager. Owns the global init operations
and the service registry.
Methods¶
| Method | In sig | Out sig | Priv. | Notes |
|---|---|---|---|---|
ListServices |
— | as |
no | Returns identities (name, name:id) of loaded services |
GetService |
s |
o |
no | Resolves an identity to its Service1 object path |
Start |
s |
— | yes | Start the service(s) matching the identity |
Stop |
s |
— | yes | Stop the service(s) matching the identity |
Restart |
s |
— | yes | Restart (stop + start) the service(s) |
Reload |
— | — | yes | Re-read all *.conf and apply changes |
SetRunlevel |
u |
— | yes | Transition to runlevel u (0–6) |
SetDebug |
— | — | yes | Toggle Finit's runtime debug flag |
Signal |
su |
— | yes | Send signal u (1–31) to services matching identity s |
Suspend |
— | — | yes | sync() + suspend-to-RAM |
Reboot, Halt, Poweroff |
— | — | yes | Trigger the corresponding shutdown sequence |
Properties¶
All read-only strings; observable via Properties.Get and
Properties.GetAll.
| Property | Type | Returns |
|---|---|---|
Runlevel |
s |
Current runlevel as a digit ("2", "3", …) or "S" |
PrevRunlevel |
s |
Previous runlevel, same encoding, "N" when none |
Version |
s |
Finit's version string (PACKAGE_VERSION) |
Signals¶
| Signal | Body | Fires when |
|---|---|---|
ServiceStateChanged |
sss — identity, old state, new state |
Service transitions |
RunlevelChanged |
ss — old level, new level |
System runlevel change |
State names emitted by ServiceStateChanged are stable wire strings:
halted, done, dead, cleanup, teardown, stopping, setup,
paused, waiting, starting, running. RunlevelChanged levels
use the same encoding as the Runlevel property: digits, "S", "N".
org.finit.Service1 (per-service objects)¶
Lives at /org/finit/service/<encoded>, one object per loaded service.
<encoded> is the service identity (name, or name:id for templated
services) put through systemd-style _HH hex escaping — ASCII alphanumerics
and _ pass through, anything else becomes _HH where HH is the hex byte.
Use Manager1.GetService(identity) to look up the exact path rather than
constructing it by hand.
| Method | In sig | Out sig | Priv. | Notes |
|---|---|---|---|---|
Start |
— | — | yes | Equivalent to Manager1.Start(<identity>) for this service |
Stop |
— | — | yes | … |
Restart |
— | — | yes | … |
Reload |
— | — | yes | Reload (SIGHUP if supported, else restart) |
Properties¶
All read-only; observable via Properties.Get and Properties.GetAll.
| Property | Type | Returns |
|---|---|---|
Identity |
s |
Service identity, name or name:id |
Name |
s |
Program name (basename of the command) |
State |
s |
Current status, same vocabulary as initctl status |
Pid |
u |
Current PID, 0 when not running |
RestartCount |
u |
Restarts since the last stable run |
Runlevels |
u |
Allowed runlevels as a bitmask, bit N = runlevel N, bit 10 = S |
Description |
s |
The service's description string |
Command |
s |
Full command line, arguments included |
Conditions |
s |
Declared conditions, raw .conf form |
Type |
s |
Unit type: service, task, run, sysv, tty, free |
Origin |
s |
Source .conf file, empty for built-ins |
Environment |
s |
The service's env setting, raw |
PidFile |
s |
Declared PID file, raw (! prefix included) |
User |
s |
User the service runs as |
Group |
s |
Group the service runs as |
Uptime |
u |
Seconds since start, 0 when not running |
ExitStatus |
u |
Raw waitpid(2) status from the last exit |
RestartsTotal |
u |
Restarts over the service's lifetime |
RestartMax |
u |
Restart limit before the service is blocked |
Starts |
u |
Times started, for manual-start units |
ManualStart |
b |
manual-start set in the .conf |
Forking |
b |
Daemon forks to background |
Started |
b |
Run/task completed successfully |
On every state transition the object also emits the standard
org.freedesktop.DBus.Properties.PropertiesChanged signal: State
in the changed dictionary, Pid and RestartCount invalidated (call
Get for fresh values).
The per-service surface lets generic tooling supply an object handle once and then invoke methods on it, instead of repeatedly passing the identity string.
org.finit.Cond1¶
Lives at /org/finit/cond. Exposes Finit's
condition system to bus clients.
Methods¶
| Method | In sig | Out sig | Priv. | Notes |
|---|---|---|---|---|
Get |
s |
s |
no | Returns "on", "off", or "flux" for the named condition |
Set |
s |
— | yes | Assert a usr/<name> condition |
Clear |
s |
— | yes | Deassert a usr/<name> condition |
List |
— | as |
no | Names of all known conditions |
Dump |
— | a(ss) |
no | (name, state) pairs for everything List returns |
Note: non-usr/* paths are rejected with InvalidArgs -- system
conditions belong to Finit's state machine.
Signals¶
| Signal | Body | Fires when |
|---|---|---|
ConditionChanged |
ss — name, new state |
A condition is asserted or deasserted |
Authorization¶
Privileged methods accept root, and any caller belonging to the group given
to --with-group at build time. That is the same set the socket mode already
admits, so the two gates agree instead of the socket letting the group in and
every method turning it away.
On the local bus the kernel settles this at connect(): SO_PEERCRED
gives Finit the caller's uid and SO_PEERGROUPS its group set, both straight
from the kernel. Finit matches the group against --with-group itself, so
the check never touches NSS -- getpwuid/getgrouplist can block on a slow
LDAP or SSSD backend, and PID 1 must never block. Because the group set is
the caller's real credentials, privilege escalation through the bus is
impossible.
On the system bus one connection carries every caller, so SO_PEERCRED
describes dbus-daemon rather than whoever asked. Finit asks the bus driver
GetConnectionUnixUser about the message sender and holds the call until the
answer arrives -- nothing blocks, the reply comes back through the same event
loop as everything else. The broker does not report the caller's groups in
that reply, so privileged methods over the system bus are root-only; group
membership is honoured on the local bus only. Lifting that is future work,
see libink/README.md.
Answers are cached per sender. A bus never reuses a unique name while it runs, so an answer holds for as long as that bus does; Finit empties the cache when the broker goes away, since a new one numbers its clients from scratch. A caller Finit cannot identify is refused, so the failure mode is a denial rather than an escalation.
When a privileged method is rejected the error name is exactly
org.freedesktop.DBus.Error.AccessDenied, and the body carries a short reason
string.
initctl integration¶
initctl transparently routes through D-Bus when the bus socket is present,
and falls back to the legacy INIT_SOCKET transport otherwise. Concretely,
the following subcommands use the bus first:
| Subcommand | Method |
|---|---|
initctl start |
Manager1.Start(svc) |
initctl stop |
Manager1.Stop(svc) |
initctl restart |
Manager1.Restart(svc) |
initctl reload |
Manager1.Reload() |
initctl reload S |
Service1.Reload() (per-svc) |
initctl reboot |
Manager1.Reboot() |
initctl halt |
Manager1.Halt() |
initctl poweroff |
Manager1.Poweroff() |
initctl suspend |
Manager1.Suspend() |
initctl debug |
Manager1.SetDebug() |
initctl signal |
Manager1.Signal(svc, signo) |
initctl runlevel |
Properties.Get(Manager1.Runlevel/PrevRunlevel) |
initctl cond set/get/clear |
Cond1.{Set,Get,Clear}, clr is an alias |
Two initctl subcommands are pure D-Bus features without legacy equivalents:
-
initctl monitor— subscribes to every signal on the local bus and prints one line per delivery (with timestamp, interface and member). Same idea asdbus-monitor, but scoped to Finit and with no need to pass--address. -
initctl condemits the standardCond1.ConditionChangedsignal as a side effect, so subscribers observe user-driven state changes the same way they observe service-driven ones.
Examples¶
The examples below use dbus-send and dbus-monitor, which ship as part of
the dbus reference implementation; they're widely packaged and don't pull
in any extra runtime. Any tool that speaks D-Bus over an AF_UNIX socket works
equally well — gdbus, Python's jeepney/dasbus, etc. — substitute their
syntax for setting the bus address. The wire protocol is the compatibility
surface: Finit's own libink is an internal implementation detail, external
clients should use any standard D-Bus library.
When org.finit is registered on the system bus you can replace
--address=unix:path=/run/finit/bus with --system in any example below.
List the running services:
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --print-reply --dest=org.finit \
/org/finit/manager \
org.finit.Manager1.ListServices
Read the current runlevel via the Properties interface:
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --print-reply --dest=org.finit \
/org/finit/manager \
org.freedesktop.DBus.Properties.Get \
string:org.finit.Manager1 string:Runlevel
Subscribe to every state change on the manager object:
Or use initctl monitor, which does the same without any address
plumbing.
Restart a service by its object path:
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --dest=org.finit \
/org/finit/service/sshd \
org.finit.Service1.Restart
Trigger a usr/-condition assertion that wakes any dependent service:
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --dest=org.finit \
/org/finit/cond \
org.finit.Cond1.Set string:"data-ready"
The --dest=org.finit argument is informational on the local
brokerless bus — Finit accepts any destination because there's no
broker to route by name — but dbus-send requires it syntactically.