Files
finit/doc/dbus.md
T
Joachim Wiberg b55dada80b libink: a message bus is not a peer
libink was written against the only bus it had, its own, where the
peer on the other end is the client.  A broker is not: it routes for
senders it names itself, expects a DESTINATION on anything addressed
through it, and answers on its own schedule rather than next.

Runlevels go on the wire as S and N rather than the digits Finit
keeps internally, since that is what a caller outside Finit means by
one.

The library stays a convenience library, linked into finit and
initctl and installed nowhere: the ABI promise waits until libink is
its own project.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 09:28:26 +02:00

16 KiB
Raw Blame History

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 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 (Set not yet implemented)

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 reject any caller whose peer uid isn't 0. On the local bus the kernel's SO_PEERCRED socket option tells Finit exactly who's calling, so privilege escalation through the bus is impossible.

On the system bus, all incoming traffic is treated as unprivileged: it arrives through dbus-daemon (typically running as root) and Finit cannot yet ask the daemon for the real requester's uid via GetConnectionUnixUser. This means external tooling can freely Get/Introspect/ListServices, but every state-changing method returns org.freedesktop.DBus.Error.AccessDenied. Per-sender uid lookup is on the roadmap.

When a privileged method is rejected the error name is exactly org.freedesktop.DBus.Error.AccessDenied, and the body carries a short reason string (e.g. "permission denied: Start requires root").

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 as dbus-monitor, but scoped to Finit and with no need to pass --address.

  • initctl cond emits the standard Cond1.ConditionChanged signal 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:

dbus-monitor --address=unix:path=/run/finit/bus \
             "type='signal',interface='org.finit.Manager1'"

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.