mirror of
https://github.com/troglobit/finit.git
synced 2026-10-18 10:59:34 +00:00
libink/finit: properties, and org.finit on the system bus
Runlevel and version are state, not actions, so they belong behind org.freedesktop.DBus.Properties rather than another method each. Finit also claims org.finit on the system bus when it finds one, so ordinary D-Bus clients can reach it without knowing about /run/finit/bus. Opportunistic on purpose: no dbus-daemon is a normal state for the systems Finit runs on, not an error to report. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
1 parent
dd1390a6c2
commit
ebc0ef62e6
22 files changed
+1474
-154
No files matched your search
@@ -20,6 +20,55 @@ All relevant changes are documented in this file.
|
||||
same service, `command = { "/lib/systemd/systemd-udevd", "-udevd" }`,
|
||||
and Finit starts the first one it finds. The line-based format could
|
||||
only express this by repeating the whole stanza per candidate
|
||||
- Finit now ships with a built-in brokerless D-Bus implementation,
|
||||
**libink**, exposing the running init system as a peer on its own
|
||||
private bus at `/run/finit/bus`, and -- opportunistically --
|
||||
registering `org.finit` on the standard system bus when a
|
||||
`dbus-daemon` is reachable. No external `libdbus`/`sd-bus`/`GIO`
|
||||
dependency.
|
||||
|
||||
The bus implements the stock `org.freedesktop.DBus`,
|
||||
`org.freedesktop.DBus.Peer`, `org.freedesktop.DBus.Introspectable`,
|
||||
and `org.freedesktop.DBus.Properties` interfaces, plus three
|
||||
Finit-specific ones:
|
||||
|
||||
* `org.finit.Manager1` at `/org/finit/manager` --
|
||||
`ListServices`, `GetService`, `Start`/`Stop`/`Restart`/`Reload`,
|
||||
`SetRunlevel`, `SetDebug`, `Signal`, `Suspend`, and the
|
||||
`Reboot`/`Halt`/`Poweroff` triplet. Read-only properties
|
||||
`Runlevel`, `PrevRunlevel`, `Version`. Signals
|
||||
`ServiceStateChanged (sss)` and `RunlevelChanged (ss)`.
|
||||
|
||||
* `org.finit.Service1` at `/org/finit/service/<encoded>` -- one
|
||||
object per loaded service, with `Start`/`Stop`/`Restart`/`Reload`
|
||||
for working off an object handle rather than passing the
|
||||
identity string around.
|
||||
|
||||
* `org.finit.Cond1` at `/org/finit/cond` -- `Get`, `Set`, `Clear`,
|
||||
`List`, `Dump` for [user-defined conditions](conditions.md),
|
||||
with a `ConditionChanged (ss)` signal.
|
||||
|
||||
Privileged methods reject non-root callers based on the kernel-
|
||||
authenticated peer uid (`SO_PEERCRED`); read-only methods are open.
|
||||
See [D-Bus Integration](dbus.md) for the full surface, build flag,
|
||||
and `dbus-send`/`dbus-monitor` examples.
|
||||
|
||||
- `initctl` now transparently routes through D-Bus when the bus is
|
||||
reachable, with the legacy `INIT_SOCKET` transport as a fallback:
|
||||
`start`, `stop`, `restart`, `reload`, `reload <svc>`, `reboot`,
|
||||
`halt`, `poweroff`, `suspend`, `debug`, `signal`, `runlevel`, and
|
||||
`cond {get,set,clr}` all use the new path. Two new subcommands
|
||||
show up that have no legacy equivalent:
|
||||
|
||||
* `initctl monitor` -- streams every signal on the bus to the
|
||||
terminal, one line per delivery (`HH:MM:SS iface.member(args)`),
|
||||
until interrupted. Same idea as `dbus-monitor`, but scoped to
|
||||
Finit and with no address plumbing required.
|
||||
|
||||
* Issuing `initctl cond set/clr` over D-Bus also fires the
|
||||
`Cond1.ConditionChanged` signal, so observers see user-driven
|
||||
state changes the same way they see service-driven ones.
|
||||
|
||||
- Restart log now spells out the signal name and flags core dumps,
|
||||
e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in
|
||||
place of the bare numeric `by signal: N`. Gives operators a much
|
||||
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
D-Bus Integration
|
||||
=================
|
||||
|
||||
Finit ships with a built-in, brokerless [D-Bus][] implementation,
|
||||
**libink**, 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 at build time with `--enable-dbus`. See
|
||||
> [Building](build.md) 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`, well-known name `org.finit` |
|
||||
|
||||
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: at start-up Finit probes for a
|
||||
running `dbus-daemon` and, if reachable, registers `org.finit` so that
|
||||
standard tooling sees Finit just like any other system service:
|
||||
|
||||
```sh
|
||||
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` (on `/org/freedesktop/DBus`) |
|
||||
| `org.freedesktop.DBus.Peer` | `Ping`, `GetMachineId` |
|
||||
| `org.freedesktop.DBus.Introspectable`| `Introspect()` — XML description |
|
||||
| `org.freedesktop.DBus.Properties` | `Get`, `GetAll` (Set not yet implemented) |
|
||||
|
||||
`org.finit.Manager1`
|
||||
--------------------
|
||||
|
||||
Lives at **`/org/finit/manager`**. Owns the global init operations
|
||||
and the service registry.
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | In sig | Out sig | Privileged | Notes |
|
||||
| ----------------------- | ------ | ------- | ---------- | ----- |
|
||||
| `ListServices` | — | `as` | no | Returns the identities (`name`, `name:id`) of every loaded service. |
|
||||
| `GetService` | `s` | `o` | no | Resolves a service 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 (same as `initctl reload`). |
|
||||
| `SetRunlevel` | `u` | — | yes | Transition to runlevel `u` (0–6). |
|
||||
| `SetDebug` | — | — | yes | Toggle Finit's runtime debug flag. |
|
||||
| `Signal` | `su` | — | yes | Send signal number `u` (1–31) to every running service matching identity `s`. Halted matches are silently skipped. |
|
||||
| `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. |
|
||||
| `Version` | `s` | Finit's version string (`PACKAGE_VERSION`). |
|
||||
|
||||
### Signals
|
||||
|
||||
| Signal | Body | Fires when |
|
||||
| ----------------------- | ---- | ---------- |
|
||||
| `ServiceStateChanged` | `(sss)` — identity, old state, new state | A service transitions between supervisor states. |
|
||||
| `RunlevelChanged` | `(ss)` — old level, new level | The system enters a new runlevel. |
|
||||
|
||||
State names emitted by `ServiceStateChanged` are stable wire strings:
|
||||
`halted`, `done`, `dead`, `cleanup`, `teardown`, `stopping`, `setup`,
|
||||
`paused`, `waiting`, `starting`, `running`.
|
||||
|
||||
`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 | Privileged | Notes |
|
||||
| --------- | ------ | ------- | ---------- | ----- |
|
||||
| `Start` | — | — | yes | Equivalent to `Manager1.Start(<identity>)` for this service. |
|
||||
| `Stop` | — | — | yes | … |
|
||||
| `Restart` | — | — | yes | … |
|
||||
| `Reload` | — | — | yes | Reload (SIGHUP if supported, else restart). |
|
||||
|
||||
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](conditions.md) to bus clients.
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | In sig | Out sig | Privileged | Notes |
|
||||
| -------- | ------ | ------- | ---------- | ----- |
|
||||
| `Get` | `s` | `s` | no | Returns `"on"`, `"off"`, or `"flux"` for the named condition. |
|
||||
| `Set` | `s` | — | yes | Assert a `usr/<name>` condition. Non-`usr/*` paths are rejected with `InvalidArgs` (system conditions belong to Finit's state machine). |
|
||||
| `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. |
|
||||
|
||||
### 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/clr` | `Cond1.{Set,Get,Clear}` |
|
||||
|
||||
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` (when D-Bus is reachable) 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.
|
||||
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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.
|
||||
|
||||
[dbus]: https://gitlab.freedesktop.org/dbus/dbus
|
||||
|
||||
Implementation notes
|
||||
--------------------
|
||||
|
||||
The D-Bus server library lives in `libink/`. It speaks the binary
|
||||
D-Bus 1.0 wire format directly, has no `libdbus`/`sd-bus`/`GIO`
|
||||
dependency, and is tiny — a few thousand lines of C. The Finit-side
|
||||
glue in `src/dbus.c` registers vtables for Manager1/Service1/Cond1,
|
||||
emits the four signals from the appropriate hook points (state
|
||||
transitions, runlevel transitions, condition flips), and bridges the
|
||||
event loop to the libink server.
|
||||
|
||||
The `initctl` client uses the same library — `link_client_open`,
|
||||
`link_client_call_v`, `link_client_reply`, `link_reader_*` — so the
|
||||
single wire-format implementation serves both ends.
|
||||
|
||||
[D-Bus]: https://dbus.freedesktop.org/doc/dbus-specification.html
|
||||
Reference in new issue
Block a user