mirror of
https://github.com/troglobit/finit.git
synced 2026-11-09 16:21:24 +00:00
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>
This commit is contained in:
1 parent
6310d9e760
commit
b55dada80b
16 files changed
+340
-250
No files matched your search
@@ -62,16 +62,16 @@ jobs:
|
|||||||
tree /tmp || true
|
tree /tmp || true
|
||||||
- name: Check dependencies
|
- name: Check dependencies
|
||||||
run: |
|
run: |
|
||||||
LD_LIBRARY_PATH=/tmp/lib ldd /tmp/sbin/finit
|
ldd /tmp/sbin/finit
|
||||||
size /tmp/sbin/finit
|
size /tmp/sbin/finit
|
||||||
LD_LIBRARY_PATH=/tmp/lib ldd /tmp/sbin/initctl
|
ldd /tmp/sbin/initctl
|
||||||
size /tmp/sbin/initctl
|
size /tmp/sbin/initctl
|
||||||
LD_LIBRARY_PATH=/tmp/lib ldd /tmp/sbin/reboot
|
ldd /tmp/sbin/reboot
|
||||||
size /tmp/sbin/reboot
|
size /tmp/sbin/reboot
|
||||||
- name: Verify starting and showing usage text
|
- name: Verify starting and showing usage text
|
||||||
run: |
|
run: |
|
||||||
sudo env LD_LIBRARY_PATH=/tmp/lib /tmp/sbin/finit -h
|
sudo /tmp/sbin/finit -h
|
||||||
sudo env LD_LIBRARY_PATH=/tmp/lib /tmp/sbin/initctl -h
|
sudo /tmp/sbin/initctl -h
|
||||||
- name: Enable unprivileged userns (unshare)
|
- name: Enable unprivileged userns (unshare)
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
|
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ AC_CONFIG_FILES([Makefile
|
|||||||
contrib/void/Makefile contrib/void/finit.d/Makefile contrib/void/finit.d/available/Makefile
|
contrib/void/Makefile contrib/void/finit.d/Makefile contrib/void/finit.d/available/Makefile
|
||||||
doc/Makefile doc/config/Makefile
|
doc/Makefile doc/config/Makefile
|
||||||
dbus-1/Makefile
|
dbus-1/Makefile
|
||||||
libink/Makefile libink/libink.pc
|
libink/Makefile
|
||||||
libsystemd/Makefile libsystemd/libsystemd.pc
|
libsystemd/Makefile libsystemd/libsystemd.pc
|
||||||
man/Makefile
|
man/Makefile
|
||||||
plugins/Makefile
|
plugins/Makefile
|
||||||
|
|||||||
+8
-1
@@ -50,9 +50,16 @@ Below are a few of the main switches to configure:
|
|||||||
`/proc/cmdline`, this is *not recommended* since Finit may be running as the
|
`/proc/cmdline`, this is *not recommended* since Finit may be running as the
|
||||||
init for container apps that can see the host's `/proc` filesystem
|
init for container apps that can see the host's `/proc` filesystem
|
||||||
|
|
||||||
|
* `--disable-dbus`: Opt out of Finit's built-in D-Bus support, enabled by
|
||||||
|
default. See [D-Bus Integration](dbus.md) for what it provides. Not to
|
||||||
|
be confused with `--enable-dbus-plugin` below, which only starts an
|
||||||
|
external `dbus-daemon`
|
||||||
|
|
||||||
* `--enable-alsa-utils-plugin`: Enable the optional `alsa-utils.so` sound plugin.
|
* `--enable-alsa-utils-plugin`: Enable the optional `alsa-utils.so` sound plugin.
|
||||||
|
|
||||||
* `--enable-dbus-plugin`: Enable the optional D-Bus `dbus.so` plugin.
|
* `--enable-dbus-plugin`: Enable the optional D-Bus `dbus.so` plugin, which
|
||||||
|
launches `dbus-daemon` at boot. Unrelated to the built-in bus, see
|
||||||
|
`--disable-dbus` above.
|
||||||
|
|
||||||
* `--enable-resolvconf-plugin`: Enable the `resolvconf.so` optional plugin.
|
* `--enable-resolvconf-plugin`: Enable the `resolvconf.so` optional plugin.
|
||||||
|
|
||||||
|
|||||||
+152
-166
@@ -1,45 +1,45 @@
|
|||||||
D-Bus Integration
|
D-Bus Integration
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Finit ships with a built-in, brokerless [D-Bus][] implementation,
|
Finit ships with a built-in, brokerless [D-Bus][] implementation, **libink**
|
||||||
**libink**, that exposes the running init system as a peer on its own
|
(`-link`), that exposes the running init system as a peer on its own private
|
||||||
private bus, and optionally on the system bus when `dbus-daemon` is
|
bus, and optionally on the system bus when `dbus-daemon` is available.
|
||||||
available. Everything `initctl` does is also reachable from any
|
Everything `initctl` does is also reachable from any generic D-Bus tooling —
|
||||||
generic D-Bus tooling — `dbus-send`, `dbus-monitor`, `gdbus`,
|
`dbus-send`, `dbus-monitor`, `gdbus`, language bindings, dashboards,
|
||||||
language bindings, dashboards, monitoring agents, etc.
|
monitoring agents, etc.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> D-Bus support is enabled at build time with `--enable-dbus`. See
|
> D-Bus support is enabled by default, opt out at build time with
|
||||||
> [Building](build.md) for details. When disabled, `initctl` keeps
|
> `--disable-dbus`, see [Building](build.md) for details. When disabled,
|
||||||
> using the legacy `INIT_SOCKET` transport and Finit exposes no bus.
|
> `initctl` keeps using the legacy `INIT_SOCKET` transport and Finit
|
||||||
|
> exposes no bus.
|
||||||
|
|
||||||
Bus address
|
Bus address
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
| Bus | Address |
|
| Bus | Address |
|
||||||
| --- | --- |
|
|------------------------|---------------------------------------------|
|
||||||
| Local (always) | `unix:path=/run/finit/bus` |
|
| Local (always) | `unix:path=/run/finit/bus` |
|
||||||
| System (opportunistic) | `unix:path=/var/run/dbus/system_bus_socket`, well-known name `org.finit` |
|
| System (opportunistic) | `unix:path=/var/run/dbus/system_bus_socket` |
|
||||||
|
|
||||||
The **local** bus is brokerless: clients connect straight to Finit
|
The **local** bus is brokerless: clients connect straight to Finit over a
|
||||||
over a Unix-domain socket using the standard D-Bus SASL EXTERNAL
|
Unix-domain socket using the standard D-Bus SASL EXTERNAL handshake. No
|
||||||
handshake. No `dbus-daemon` is required, which makes it suitable for
|
`dbus-daemon` is required, which makes it suitable for embedded systems that
|
||||||
embedded systems that don't ship one.
|
don't ship one.
|
||||||
|
|
||||||
The **system** bus is best-effort: at start-up Finit probes for a
|
The **system** bus is best-effort: Finit probes for a running `dbus-daemon`
|
||||||
running `dbus-daemon` and, if reachable, registers `org.finit` so that
|
and, when reachable, claims the well-known name `org.finit` so that standard
|
||||||
standard tooling sees Finit just like any other system service:
|
tooling sees Finit just like any other system service:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dbus-send --system --print-reply --dest=org.finit \
|
dbus-send --system --print-reply --dest=org.finit /org/finit/manager \
|
||||||
/org/finit/manager \
|
|
||||||
org.finit.Manager1.ListServices
|
org.finit.Manager1.ListServices
|
||||||
|
|
||||||
dbus-monitor --system "sender='org.finit'"
|
dbus-monitor --system "sender='org.finit'"
|
||||||
```
|
```
|
||||||
|
|
||||||
If no system bus is present (the common case on embedded targets),
|
If no system bus is present (the common case on embedded targets), this step
|
||||||
this step is silently skipped.
|
is silently skipped.
|
||||||
|
|
||||||
Object tree
|
Object tree
|
||||||
-----------
|
-----------
|
||||||
@@ -59,12 +59,15 @@ Object tree
|
|||||||
|
|
||||||
Every node implements the usual stock interfaces:
|
Every node implements the usual stock interfaces:
|
||||||
|
|
||||||
| Interface | Purpose |
|
| Interface | Purpose |
|
||||||
| ------------------------------------ | ------- |
|
|---------------------------------------|-------------------------------------------|
|
||||||
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` (on `/org/freedesktop/DBus`) |
|
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` |
|
||||||
| `org.freedesktop.DBus.Peer` | `Ping`, `GetMachineId` |
|
| `org.freedesktop.DBus.Peer` | `Ping`, `GetMachineId` |
|
||||||
| `org.freedesktop.DBus.Introspectable`| `Introspect()` — XML description |
|
| `org.freedesktop.DBus.Introspectable` | `Introspect()` — XML description |
|
||||||
| `org.freedesktop.DBus.Properties` | `Get`, `GetAll` (Set not yet implemented) |
|
| `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`
|
`org.finit.Manager1`
|
||||||
--------------------
|
--------------------
|
||||||
@@ -74,88 +77,89 @@ and the service registry.
|
|||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
| Method | In sig | Out sig | Privileged | Notes |
|
| Method | In sig | Out sig | Priv. | Notes |
|
||||||
| ----------------------- | ------ | ------- | ---------- | ----- |
|
|------------------------------|--------|---------|-------|-----------------------------------------------------------|
|
||||||
| `ListServices` | — | `as` | no | Returns the identities (`name`, `name:id`) of every loaded service. |
|
| `ListServices` | — | `as` | no | Returns identities (`name`, `name:id`) of loaded services |
|
||||||
| `GetService` | `s` | `o` | no | Resolves a service identity to its `Service1` object path. |
|
| `GetService` | `s` | `o` | no | Resolves an identity to its `Service1` object path |
|
||||||
| `Start` | `s` | — | yes | Start the service(s) matching the identity. |
|
| `Start` | `s` | — | yes | Start the service(s) matching the identity |
|
||||||
| `Stop` | `s` | — | yes | Stop 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). |
|
| `Restart` | `s` | — | yes | Restart (stop + start) the service(s) |
|
||||||
| `Reload` | — | — | yes | Re-read all `*.conf` and apply changes (same as `initctl reload`). |
|
| `Reload` | — | — | yes | Re-read all `*.conf` and apply changes |
|
||||||
| `SetRunlevel` | `u` | — | yes | Transition to runlevel `u` (0–6). |
|
| `SetRunlevel` | `u` | — | yes | Transition to runlevel `u` (0–6) |
|
||||||
| `SetDebug` | — | — | yes | Toggle Finit's runtime debug flag. |
|
| `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. |
|
| `Signal` | `su` | — | yes | Send signal `u` (1–31) to services matching identity `s` |
|
||||||
| `Suspend` | — | — | yes | `sync()` + suspend-to-RAM. |
|
| `Suspend` | — | — | yes | `sync()` + suspend-to-RAM |
|
||||||
| `Reboot` / `Halt` / `Poweroff` | — | — | yes | Trigger the corresponding shutdown sequence. |
|
| `Reboot`, `Halt`, `Poweroff` | — | — | yes | Trigger the corresponding shutdown sequence |
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
All read-only strings; observable via `Properties.Get` and
|
All read-only strings; observable via `Properties.Get` and
|
||||||
`Properties.GetAll`.
|
`Properties.GetAll`.
|
||||||
|
|
||||||
| Property | Type | Returns |
|
| Property | Type | Returns |
|
||||||
| -------------- | ---- | ------- |
|
|----------------|------|--------------------------------------------------------|
|
||||||
| `Runlevel` | `s` | Current runlevel as a digit (`"2"`, `"3"`, …) or `"S"`. |
|
| `Runlevel` | `s` | Current runlevel as a digit (`"2"`, `"3"`, …) or `"S"` |
|
||||||
| `PrevRunlevel` | `s` | Previous runlevel, same encoding. |
|
| `PrevRunlevel` | `s` | Previous runlevel, same encoding, `"N"` when none |
|
||||||
| `Version` | `s` | Finit's version string (`PACKAGE_VERSION`). |
|
| `Version` | `s` | Finit's version string (`PACKAGE_VERSION`) |
|
||||||
|
|
||||||
### Signals
|
### Signals
|
||||||
|
|
||||||
| Signal | Body | Fires when |
|
| Signal | Body | Fires when |
|
||||||
| ----------------------- | ---- | ---------- |
|
|-----------------------|----------------------------------------|------------------------|
|
||||||
| `ServiceStateChanged` | `(sss)` — identity, old state, new state | A service transitions between supervisor states. |
|
| `ServiceStateChanged` | `sss` — identity, old state, new state | Service transitions |
|
||||||
| `RunlevelChanged` | `(ss)` — old level, new level | The system enters a new runlevel. |
|
| `RunlevelChanged` | `ss` — old level, new level | System runlevel change |
|
||||||
|
|
||||||
State names emitted by `ServiceStateChanged` are stable wire strings:
|
State names emitted by `ServiceStateChanged` are stable wire strings:
|
||||||
`halted`, `done`, `dead`, `cleanup`, `teardown`, `stopping`, `setup`,
|
`halted`, `done`, `dead`, `cleanup`, `teardown`, `stopping`, `setup`,
|
||||||
`paused`, `waiting`, `starting`, `running`.
|
`paused`, `waiting`, `starting`, `running`. `RunlevelChanged` levels
|
||||||
|
use the same encoding as the `Runlevel` property: digits, `"S"`, `"N"`.
|
||||||
|
|
||||||
`org.finit.Service1` (per-service objects)
|
`org.finit.Service1` (per-service objects)
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
Lives at **`/org/finit/service/<encoded>`**, one object per loaded
|
Lives at **`/org/finit/service/<encoded>`**, one object per loaded service.
|
||||||
service. `<encoded>` is the service identity (name, or `name:id` for
|
`<encoded>` is the service identity (name, or `name:id` for templated
|
||||||
templated services) put through systemd-style `_HH` hex escaping —
|
services) put through systemd-style `_HH` hex escaping — ASCII alphanumerics
|
||||||
ASCII alphanumerics and `_` pass through, anything else becomes `_HH`
|
and `_` pass through, anything else becomes `_HH` where `HH` is the hex byte.
|
||||||
where `HH` is the hex byte. Use `Manager1.GetService(identity)` to
|
Use `Manager1.GetService(identity)` to look up the exact path rather than
|
||||||
look up the exact path rather than constructing it by hand.
|
constructing it by hand.
|
||||||
|
|
||||||
| Method | In sig | Out sig | Privileged | Notes |
|
| Method | In sig | Out sig | Priv. | Notes |
|
||||||
| --------- | ------ | ------- | ---------- | ----- |
|
|-----------|--------|---------|-------|-------------------------------------------------------------|
|
||||||
| `Start` | — | — | yes | Equivalent to `Manager1.Start(<identity>)` for this service. |
|
| `Start` | — | — | yes | Equivalent to `Manager1.Start(<identity>)` for this service |
|
||||||
| `Stop` | — | — | yes | … |
|
| `Stop` | — | — | yes | … |
|
||||||
| `Restart` | — | — | yes | … |
|
| `Restart` | — | — | yes | … |
|
||||||
| `Reload` | — | — | yes | Reload (SIGHUP if supported, else restart). |
|
| `Reload` | — | — | yes | Reload (SIGHUP if supported, else restart) |
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
All read-only; observable via `Properties.Get` and `Properties.GetAll`.
|
All read-only; observable via `Properties.Get` and `Properties.GetAll`.
|
||||||
|
|
||||||
| Property | Type | Returns |
|
| Property | Type | Returns |
|
||||||
| -------------- | ---- | ------- |
|
|-----------------|------|----------------------------------------------------------------|
|
||||||
| `Identity` | `s` | Service identity, `name` or `name:id`. |
|
| `Identity` | `s` | Service identity, `name` or `name:id` |
|
||||||
| `Name` | `s` | Program name (basename of the command). |
|
| `Name` | `s` | Program name (basename of the command) |
|
||||||
| `State` | `s` | Current status, same vocabulary as `initctl status` (richer than the coarse `ServiceStateChanged` strings). |
|
| `State` | `s` | Current status, same vocabulary as `initctl status` |
|
||||||
| `Pid` | `u` | Current PID, 0 when not running. |
|
| `Pid` | `u` | Current PID, 0 when not running |
|
||||||
| `RestartCount` | `u` | Restarts since the last stable run. |
|
| `RestartCount` | `u` | Restarts since the last stable run |
|
||||||
| `Runlevels` | `u` | Allowed runlevels as a bitmask, bit N = runlevel N, bit 10 = S. |
|
| `Runlevels` | `u` | Allowed runlevels as a bitmask, bit N = runlevel N, bit 10 = S |
|
||||||
| `Description` | `s` | The service's `description` string. |
|
| `Description` | `s` | The service's `description` string |
|
||||||
| `Command` | `s` | Full command line, arguments included. |
|
| `Command` | `s` | Full command line, arguments included |
|
||||||
| `Conditions` | `s` | Declared conditions, raw `.conf` form. |
|
| `Conditions` | `s` | Declared conditions, raw `.conf` form |
|
||||||
| `Type` | `s` | Unit type: `service`, `task`, `run`, `sysv`, `tty`, `free`. |
|
| `Type` | `s` | Unit type: `service`, `task`, `run`, `sysv`, `tty`, `free` |
|
||||||
| `Origin` | `s` | Source `.conf` file, empty for built-ins. |
|
| `Origin` | `s` | Source `.conf` file, empty for built-ins |
|
||||||
| `Environment` | `s` | The service's `env` setting, raw. |
|
| `Environment` | `s` | The service's `env` setting, raw |
|
||||||
| `PidFile` | `s` | Declared PID file, raw (`!` prefix included). |
|
| `PidFile` | `s` | Declared PID file, raw (`!` prefix included) |
|
||||||
| `User` | `s` | User the service runs as. |
|
| `User` | `s` | User the service runs as |
|
||||||
| `Group` | `s` | Group the service runs as. |
|
| `Group` | `s` | Group the service runs as |
|
||||||
| `Uptime` | `u` | Seconds since start, 0 when not running. |
|
| `Uptime` | `u` | Seconds since start, 0 when not running |
|
||||||
| `ExitStatus` | `u` | Raw `waitpid(2)` status from the last exit. |
|
| `ExitStatus` | `u` | Raw `waitpid(2)` status from the last exit |
|
||||||
| `RestartsTotal`| `u` | Restarts over the service's lifetime. |
|
| `RestartsTotal` | `u` | Restarts over the service's lifetime |
|
||||||
| `RestartMax` | `u` | Restart limit before the service is blocked. |
|
| `RestartMax` | `u` | Restart limit before the service is blocked |
|
||||||
| `Starts` | `u` | Times started, for `manual-start` units. |
|
| `Starts` | `u` | Times started, for `manual-start` units |
|
||||||
| `ManualStart` | `b` | `manual-start` set in the `.conf`. |
|
| `ManualStart` | `b` | `manual-start` set in the `.conf` |
|
||||||
| `Forking` | `b` | Daemon forks to background. |
|
| `Forking` | `b` | Daemon forks to background |
|
||||||
| `Started` | `b` | Run/task completed successfully. |
|
| `Started` | `b` | Run/task completed successfully |
|
||||||
|
|
||||||
On every state transition the object also emits the standard
|
On every state transition the object also emits the standard
|
||||||
`org.freedesktop.DBus.Properties.PropertiesChanged` signal: `State`
|
`org.freedesktop.DBus.Properties.PropertiesChanged` signal: `State`
|
||||||
@@ -174,89 +178,87 @@ Lives at **`/org/finit/cond`**. Exposes Finit's
|
|||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
| Method | In sig | Out sig | Privileged | Notes |
|
| Method | In sig | Out sig | Priv. | Notes |
|
||||||
| -------- | ------ | ------- | ---------- | ----- |
|
|---------|--------|---------|-------|--------------------------------------------------------------|
|
||||||
| `Get` | `s` | `s` | no | Returns `"on"`, `"off"`, or `"flux"` for the named condition. |
|
| `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). |
|
| `Set` | `s` | — | yes | Assert a `usr/<name>` condition |
|
||||||
| `Clear` | `s` | — | yes | Deassert a `usr/<name>` condition. |
|
| `Clear` | `s` | — | yes | Deassert a `usr/<name>` condition |
|
||||||
| `List` | — | `as` | no | Names of all known conditions. |
|
| `List` | — | `as` | no | Names of all known conditions |
|
||||||
| `Dump` | — | `a(ss)` | no | `(name, state)` pairs for everything `List` returns. |
|
| `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
|
### Signals
|
||||||
|
|
||||||
| Signal | Body | Fires when |
|
| Signal | Body | Fires when |
|
||||||
| ------------------- | ---- | ---------- |
|
|--------------------|------------------------|---------------------------------------|
|
||||||
| `ConditionChanged` | `(ss)` — name, new state | A condition is asserted or deasserted. |
|
| `ConditionChanged` | `ss` — name, new state | A condition is asserted or deasserted |
|
||||||
|
|
||||||
Authorization
|
Authorization
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Privileged methods reject any caller whose peer `uid` isn't 0.
|
Privileged methods reject any caller whose peer `uid` isn't 0. On the
|
||||||
On the **local** bus the kernel's `SO_PEERCRED` socket option tells
|
**local** bus the kernel's `SO_PEERCRED` socket option tells Finit exactly
|
||||||
Finit exactly who's calling, so privilege escalation through the bus
|
who's calling, so privilege escalation through the bus is impossible.
|
||||||
is impossible.
|
|
||||||
|
|
||||||
On the **system** bus, all incoming traffic is treated as
|
On the **system** bus, all incoming traffic is treated as unprivileged: it
|
||||||
unprivileged: it arrives through `dbus-daemon` (typically running as
|
arrives through `dbus-daemon` (typically running as root) and Finit cannot yet
|
||||||
root) and Finit cannot yet ask the daemon for the real requester's
|
ask the daemon for the real requester's uid via `GetConnectionUnixUser`. This
|
||||||
uid via `GetConnectionUnixUser`. This means external tooling can
|
means external tooling can freely `Get`/`Introspect`/`ListServices`, but every
|
||||||
freely `Get`/`Introspect`/`ListServices`, but every state-changing
|
state-changing method returns `org.freedesktop.DBus.Error.AccessDenied`.
|
||||||
method returns `org.freedesktop.DBus.Error.AccessDenied`. Per-sender
|
Per-sender uid lookup is on the roadmap.
|
||||||
uid lookup is on the roadmap.
|
|
||||||
|
|
||||||
When a privileged method is rejected the error name is exactly
|
When a privileged method is rejected the error name is exactly
|
||||||
`org.freedesktop.DBus.Error.AccessDenied`, and the body carries a
|
`org.freedesktop.DBus.Error.AccessDenied`, and the body carries a short reason
|
||||||
short reason string (e.g. `"permission denied: Start requires root"`).
|
string (e.g. `"permission denied: Start requires root"`).
|
||||||
|
|
||||||
`initctl` integration
|
`initctl` integration
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
`initctl` transparently routes through D-Bus when the bus socket is
|
`initctl` transparently routes through D-Bus when the bus socket is present,
|
||||||
present, and falls back to the legacy `INIT_SOCKET` transport
|
and falls back to the legacy `INIT_SOCKET` transport otherwise. Concretely,
|
||||||
otherwise. Concretely, the following subcommands use the bus first:
|
the following subcommands use the bus first:
|
||||||
|
|
||||||
| Subcommand | Method |
|
| Subcommand | Method |
|
||||||
| ------------------- | ------------------------------- |
|
|----------------------------|--------------------------------------------------|
|
||||||
| `initctl start` | `Manager1.Start(svc)` |
|
| `initctl start` | `Manager1.Start(svc)` |
|
||||||
| `initctl stop` | `Manager1.Stop(svc)` |
|
| `initctl stop` | `Manager1.Stop(svc)` |
|
||||||
| `initctl restart` | `Manager1.Restart(svc)` |
|
| `initctl restart` | `Manager1.Restart(svc)` |
|
||||||
| `initctl reload` | `Manager1.Reload()` |
|
| `initctl reload` | `Manager1.Reload()` |
|
||||||
| `initctl reload S` | `Service1.Reload()` (per-svc) |
|
| `initctl reload S` | `Service1.Reload()` (per-svc) |
|
||||||
| `initctl reboot` | `Manager1.Reboot()` |
|
| `initctl reboot` | `Manager1.Reboot()` |
|
||||||
| `initctl halt` | `Manager1.Halt()` |
|
| `initctl halt` | `Manager1.Halt()` |
|
||||||
| `initctl poweroff` | `Manager1.Poweroff()` |
|
| `initctl poweroff` | `Manager1.Poweroff()` |
|
||||||
| `initctl suspend` | `Manager1.Suspend()` |
|
| `initctl suspend` | `Manager1.Suspend()` |
|
||||||
| `initctl debug` | `Manager1.SetDebug()` |
|
| `initctl debug` | `Manager1.SetDebug()` |
|
||||||
| `initctl signal` | `Manager1.Signal(svc, signo)` |
|
| `initctl signal` | `Manager1.Signal(svc, signo)` |
|
||||||
| `initctl runlevel` | `Properties.Get(Manager1.Runlevel/PrevRunlevel)` |
|
| `initctl runlevel` | `Properties.Get(Manager1.Runlevel/PrevRunlevel)` |
|
||||||
| `initctl cond set/get/clr` | `Cond1.{Set,Get,Clear}` |
|
| `initctl cond set/get/clear` | `Cond1.{Set,Get,Clear}`, `clr` is an alias |
|
||||||
|
|
||||||
Two `initctl` subcommands are pure D-Bus features without legacy
|
Two `initctl` subcommands are pure D-Bus features without legacy equivalents:
|
||||||
equivalents:
|
|
||||||
|
|
||||||
* `initctl monitor` — subscribes to every signal on the local bus and
|
* `initctl monitor` — subscribes to every signal on the local bus and prints
|
||||||
prints one line per delivery (with timestamp, interface and
|
one line per delivery (with timestamp, interface and member). Same idea as
|
||||||
member). Same idea as `dbus-monitor`, but scoped to Finit and with
|
`dbus-monitor`, but scoped to Finit and with no need to pass `--address`.
|
||||||
no need to pass `--address`.
|
|
||||||
|
|
||||||
* `initctl cond` (when D-Bus is reachable) emits the standard
|
* `initctl cond` emits the standard `Cond1.ConditionChanged` signal as a side
|
||||||
`Cond1.ConditionChanged` signal as a side effect, so subscribers
|
effect, so subscribers observe user-driven state changes the same way they
|
||||||
observe user-driven state changes the same way they observe
|
observe service-driven ones.
|
||||||
service-driven ones.
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The examples below use `dbus-send` and `dbus-monitor`, which ship as
|
The examples below use `dbus-send` and `dbus-monitor`, which ship as part of
|
||||||
part of the [dbus][] reference implementation; they're widely
|
the [dbus][] reference implementation; they're widely packaged and don't pull
|
||||||
packaged and don't pull in any extra runtime. Any tool that speaks
|
in any extra runtime. Any tool that speaks D-Bus over an AF_UNIX socket works
|
||||||
D-Bus over an AF_UNIX socket works equally well — `gdbus`, Python's
|
equally well — `gdbus`, Python's `jeepney`/`dasbus`, etc. — substitute their
|
||||||
`jeepney`/`dasbus`, etc. — substitute their syntax for setting the
|
syntax for setting the bus address. The wire protocol is the compatibility
|
||||||
bus address.
|
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
|
When `org.finit` is registered on the system bus you can replace
|
||||||
`--address=unix:path=/run/finit/bus` with `--system` in any example
|
`--address=unix:path=/run/finit/bus` with `--system` in any example below.
|
||||||
below.
|
|
||||||
|
|
||||||
List the running services:
|
List the running services:
|
||||||
|
|
||||||
@@ -309,21 +311,5 @@ The `--dest=org.finit` argument is informational on the local
|
|||||||
brokerless bus — Finit accepts any destination because there's no
|
brokerless bus — Finit accepts any destination because there's no
|
||||||
broker to route by name — but `dbus-send` requires it syntactically.
|
broker to route by name — but `dbus-send` requires it syntactically.
|
||||||
|
|
||||||
[dbus]: https://gitlab.freedesktop.org/dbus/dbus
|
[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
|
[D-Bus]: https://dbus.freedesktop.org/doc/dbus-specification.html
|
||||||
@@ -3,6 +3,5 @@
|
|||||||
.dirstamp
|
.dirstamp
|
||||||
*.lo
|
*.lo
|
||||||
libink.*
|
libink.*
|
||||||
!libink.pc.in
|
|
||||||
Makefile
|
Makefile
|
||||||
Makefile.in
|
Makefile.in
|
||||||
+7
-11
@@ -1,5 +1,10 @@
|
|||||||
# libink — brokerless D-Bus library (server + client), born inside Finit
|
# libink — brokerless D-Bus library (server + client), born inside Finit
|
||||||
lib_LTLIBRARIES = libink.la
|
#
|
||||||
|
# Internal convenience library: linked statically into finit and
|
||||||
|
# initctl, nothing installed. Public install (and the ABI promise
|
||||||
|
# that comes with it) is deferred until libink is extracted into its
|
||||||
|
# own project.
|
||||||
|
noinst_LTLIBRARIES = libink.la
|
||||||
libink_la_SOURCES = server.c auth.c connection.c \
|
libink_la_SOURCES = server.c auth.c connection.c \
|
||||||
proto.c proto.h \
|
proto.c proto.h \
|
||||||
marshal.c marshal.h \
|
marshal.c marshal.h \
|
||||||
@@ -7,16 +12,7 @@ libink_la_SOURCES = server.c auth.c connection.c \
|
|||||||
match.c \
|
match.c \
|
||||||
path.c \
|
path.c \
|
||||||
client.c io.c \
|
client.c io.c \
|
||||||
internal.h
|
link.h path.h internal.h
|
||||||
|
|
||||||
libink_la_LDFLAGS = -version-info 0:0:0
|
|
||||||
libink_la_CPPFLAGS = -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_BSD_SOURCE
|
libink_la_CPPFLAGS = -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_BSD_SOURCE
|
||||||
libink_la_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
|
libink_la_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
|
||||||
|
|
||||||
# pkg-config support
|
|
||||||
pkgconfigdir = $(libdir)/pkgconfig
|
|
||||||
pkgconfig_DATA = libink.pc
|
|
||||||
|
|
||||||
# Public headers install to $(includedir)/ink/
|
|
||||||
inkdir = $(includedir)/ink
|
|
||||||
ink_HEADERS = link.h path.h
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
libink — brokerless D-Bus for Finit
|
||||||
|
===================================
|
||||||
|
|
||||||
|
libink is a small C library implementing the [D-Bus wire protocol][spec],
|
||||||
|
both the server and the client side, without a broker and without any
|
||||||
|
dependency on `libdbus`, `sd-bus`, or GIO. It was born inside Finit to
|
||||||
|
let PID 1 be a bus of its own: clients connect straight to the listening
|
||||||
|
socket, authenticate with the standard SASL EXTERNAL handshake, and get
|
||||||
|
kernel-authenticated credentials via `SO_PEERCRED`.
|
||||||
|
|
||||||
|
For what the bus exposes and how to talk to it, see the User Guide,
|
||||||
|
[D-Bus Integration](../doc/dbus.md). This file covers the library
|
||||||
|
itself.
|
||||||
|
|
||||||
|
Status
|
||||||
|
------
|
||||||
|
|
||||||
|
libink is an internal implementation detail of Finit: built as a libtool
|
||||||
|
convenience library, linked statically into `finit` and `initctl`,
|
||||||
|
nothing installed. There is deliberately no ABI promise yet — that
|
||||||
|
comes if/when libink is extracted into a project of its own. External
|
||||||
|
D-Bus clients need none of this; the wire protocol is the compatibility
|
||||||
|
surface, any standard D-Bus library works.
|
||||||
|
|
||||||
|
Layout
|
||||||
|
------
|
||||||
|
|
||||||
|
| File | Contents |
|
||||||
|
|-----------------|-------------------------------------------------------|
|
||||||
|
| `server.c` | Listening socket, accept, peer credential capture |
|
||||||
|
| `auth.c` | SASL EXTERNAL handshake, uid verification |
|
||||||
|
| `connection.c` | Per-peer state machine, message framing |
|
||||||
|
| `proto.c` | Wire header parse/build |
|
||||||
|
| `marshal.c` | Body (de)marshalling: basic types, arrays, variants |
|
||||||
|
| `dispatch.c` | Object tree, vtable registration, method dispatch |
|
||||||
|
| `builtin.c` | `org.freedesktop.DBus.*` stock interfaces |
|
||||||
|
| `match.c` | AddMatch/RemoveMatch rule parsing and signal filter |
|
||||||
|
| `path.c` | systemd-style `_HH` object path encoding |
|
||||||
|
| `client.c` | Outgoing connections, method calls, reply/signal wait |
|
||||||
|
| `io.c` | Shared EINTR-resilient read/write loops |
|
||||||
|
|
||||||
|
Public API symbols carry the `link_*` prefix (`link.h`), internal ones
|
||||||
|
`__*` (`internal.h`). Method handlers are registered as vtables of
|
||||||
|
`link_method_t`/`link_property_t`; the framework emits variant
|
||||||
|
signatures from the property table so the declared type is the single
|
||||||
|
source of truth.
|
||||||
|
|
||||||
|
The boundary to Finit is deliberate: nothing under `libink/` includes a
|
||||||
|
Finit header. All glue lives in `src/dbus.c` — object registration,
|
||||||
|
signal emission from the service/condition/runlevel hook points, and
|
||||||
|
the uev event loop bridge. `initctl` uses the client half of the same
|
||||||
|
library, so one wire-format implementation serves both ends. If libink
|
||||||
|
is ever spun out, that file is the cut line.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
|
||||||
|
The `test/dbus-*.sh` suite exercises the library end to end against a
|
||||||
|
live Finit in a namespace, driven by `test/src/dbus-auth-client.c`.
|
||||||
|
Wire-format conformance against third-party tools (`dbus-send`,
|
||||||
|
`dbus-monitor`) and fuzzing of the parsers are tracked as pre-merge
|
||||||
|
work — this is PID 1's attack surface.
|
||||||
|
|
||||||
|
[spec]: https://dbus.freedesktop.org/doc/dbus-specification.html
|
||||||
+44
-1
@@ -24,6 +24,7 @@
|
|||||||
struct link_client {
|
struct link_client {
|
||||||
int fd;
|
int fd;
|
||||||
uint32_t next_serial;
|
uint32_t next_serial;
|
||||||
|
const char *destination; /* not owned; NULL when brokerless */
|
||||||
link_reply_t reply; /* most recent reply view (points into rxbuf) */
|
link_reply_t reply; /* most recent reply view (points into rxbuf) */
|
||||||
/* Distinct from "reply.type == 0": LINK_MSG_INVALID is 0, which
|
/* Distinct from "reply.type == 0": LINK_MSG_INVALID is 0, which
|
||||||
* is a wire-valid (if malformed) type, so we need an out-of-band
|
* is a wire-valid (if malformed) type, so we need an out-of-band
|
||||||
@@ -87,6 +88,12 @@ link_client_t *link_client_open(const char *path)
|
|||||||
return link_client_open_timeout(path, 0);
|
return link_client_open_timeout(path, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void link_client_set_destination(link_client_t *c, const char *destination)
|
||||||
|
{
|
||||||
|
if (c)
|
||||||
|
c->destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
void link_client_close(link_client_t *c)
|
void link_client_close(link_client_t *c)
|
||||||
{
|
{
|
||||||
if (!c)
|
if (!c)
|
||||||
@@ -187,6 +194,41 @@ static void clear_reply(link_client_t *c)
|
|||||||
c->have_reply = 0;
|
c->have_reply = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A broker interleaves traffic of its own with our replies: claiming a
|
||||||
|
* name makes it emit NameAcquired, and it arrives before the reply to
|
||||||
|
* the call that caused it. Read past anything that is not the reply
|
||||||
|
* we are waiting for. On a brokerless link nothing is interleaved and
|
||||||
|
* the first message read is always the one we want.
|
||||||
|
*
|
||||||
|
* Bounded so a chatty or hostile broker cannot stall PID 1 here; each
|
||||||
|
* read is bounded in turn by SO_RCVTIMEO when the caller asked for a
|
||||||
|
* timeout at open. */
|
||||||
|
#define LINK_CALL_MAX_SKIP 16
|
||||||
|
|
||||||
|
static int read_reply(link_client_t *c, uint32_t serial)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < LINK_CALL_MAX_SKIP; i++) {
|
||||||
|
struct link_msg msg;
|
||||||
|
|
||||||
|
if (read_one(c, &msg) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Not a reply at all, or a reply to something else. */
|
||||||
|
if (msg.type != LINK_MSG_METHOD_RETURN && msg.type != LINK_MSG_ERROR)
|
||||||
|
continue;
|
||||||
|
if (msg.reply_serial != serial)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
publish_reply(c, &msg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = EPROTO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Wait up to timeout_ms (-1 = forever) for one full inbound frame
|
/* Wait up to timeout_ms (-1 = forever) for one full inbound frame
|
||||||
* and publish it. Returns 0 on success, 1 on timeout, -1 on error. */
|
* and publish it. Returns 0 on success, 1 on timeout, -1 on error. */
|
||||||
static int read_and_publish(link_client_t *c, int timeout_ms)
|
static int read_and_publish(link_client_t *c, int timeout_ms)
|
||||||
@@ -235,6 +277,7 @@ int link_client_call(link_client_t *c,
|
|||||||
serial = c->next_serial++;
|
serial = c->next_serial++;
|
||||||
hlen = __msg_build_method_call(hdr, sizeof(hdr), serial,
|
hlen = __msg_build_method_call(hdr, sizeof(hdr), serial,
|
||||||
obj_path, interface, member,
|
obj_path, interface, member,
|
||||||
|
c->destination,
|
||||||
signature, (uint32_t)body_len);
|
signature, (uint32_t)body_len);
|
||||||
if (hlen < 0)
|
if (hlen < 0)
|
||||||
return LINK_CALL_FAIL;
|
return LINK_CALL_FAIL;
|
||||||
@@ -244,7 +287,7 @@ int link_client_call(link_client_t *c,
|
|||||||
if (body_len > 0 && send_all(c->fd, body, body_len) < 0)
|
if (body_len > 0 && send_all(c->fd, body, body_len) < 0)
|
||||||
return LINK_CALL_FAIL;
|
return LINK_CALL_FAIL;
|
||||||
|
|
||||||
if (read_and_publish(c, -1) != 0)
|
if (read_reply(c, serial) < 0)
|
||||||
return LINK_CALL_FAIL;
|
return LINK_CALL_FAIL;
|
||||||
|
|
||||||
if (c->reply.type == LINK_MSG_METHOD_RETURN)
|
if (c->reply.type == LINK_MSG_METHOD_RETURN)
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
prefix=@prefix@
|
|
||||||
exec_prefix=@exec_prefix@
|
|
||||||
libdir=@libdir@
|
|
||||||
includedir=@includedir@
|
|
||||||
|
|
||||||
Name: libink
|
|
||||||
Description: Brokerless D-Bus server library, born inside Finit
|
|
||||||
Version: @PACKAGE_VERSION@
|
|
||||||
Libs: -L${libdir} -link
|
|
||||||
Cflags: -I${includedir}
|
|
||||||
@@ -226,6 +226,13 @@ link_client_t *link_client_open_timeout(const char *path, int timeout_ms);
|
|||||||
|
|
||||||
void link_client_close(link_client_t *c);
|
void link_client_close(link_client_t *c);
|
||||||
|
|
||||||
|
/* Address subsequent calls on `c` to a well-known name. Needed when
|
||||||
|
* a broker routes the message, e.g. "org.freedesktop.DBus" to reach
|
||||||
|
* the bus driver itself; a brokerless link has a single peer and
|
||||||
|
* needs no destination, which is the default. `destination` is not
|
||||||
|
* copied, so it must outlive the client. */
|
||||||
|
void link_client_set_destination(link_client_t *c, const char *destination);
|
||||||
|
|
||||||
/* Detach the authenticated socket from the client and return the raw
|
/* Detach the authenticated socket from the client and return the raw
|
||||||
* fd; subsequent link_client_close on `c` is invalid because the
|
* fd; subsequent link_client_close on `c` is invalid because the
|
||||||
* structure has already been freed. Used by callers (e.g. system-bus
|
* structure has already been freed. Used by callers (e.g. system-bus
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
|||||||
const char *path,
|
const char *path,
|
||||||
const char *interface,
|
const char *interface,
|
||||||
const char *member,
|
const char *member,
|
||||||
|
const char *destination,
|
||||||
const char *signature,
|
const char *signature,
|
||||||
uint32_t body_len)
|
uint32_t body_len)
|
||||||
{
|
{
|
||||||
@@ -369,6 +370,9 @@ ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
|||||||
return -1;
|
return -1;
|
||||||
if (put_field_string(buf, cap, &off, LINK_HDR_MEMBER, 's', member) < 0)
|
if (put_field_string(buf, cap, &off, LINK_HDR_MEMBER, 's', member) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
if (destination &&
|
||||||
|
put_field_string(buf, cap, &off, LINK_HDR_DESTINATION, 's', destination) < 0)
|
||||||
|
return -1;
|
||||||
if (signature && *signature &&
|
if (signature && *signature &&
|
||||||
put_field_string(buf, cap, &off, LINK_HDR_SIGNATURE, 'g', signature) < 0)
|
put_field_string(buf, cap, &off, LINK_HDR_SIGNATURE, 'g', signature) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
+3
-1
@@ -89,12 +89,14 @@ ssize_t __msg_build_signal(uint8_t *buf, size_t cap,
|
|||||||
const char *member,
|
const char *member,
|
||||||
const char *signature, uint32_t body_len);
|
const char *signature, uint32_t body_len);
|
||||||
|
|
||||||
/* Build a method-call header (client side). */
|
/* Build a method-call header (client side). `destination` is NULL
|
||||||
|
* when no broker routes the message. */
|
||||||
ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
||||||
uint32_t serial,
|
uint32_t serial,
|
||||||
const char *path,
|
const char *path,
|
||||||
const char *interface,
|
const char *interface,
|
||||||
const char *member,
|
const char *member,
|
||||||
|
const char *destination,
|
||||||
const char *signature,
|
const char *signature,
|
||||||
uint32_t body_len);
|
uint32_t body_len);
|
||||||
|
|
||||||
|
|||||||
+28
-9
@@ -457,15 +457,31 @@ static int manager_suspend(link_call_t *call, void *u)
|
|||||||
* org.freedesktop.DBus.Properties interface. Getters write a
|
* org.freedesktop.DBus.Properties interface. Getters write a
|
||||||
* variant containing a single string. */
|
* variant containing a single string. */
|
||||||
|
|
||||||
/* Two distinct getters because the property table is static const --
|
/*
|
||||||
* we can't bind &runlevel/&prevlevel through userdata. */
|
* Two distinct getters because the property table is static const --
|
||||||
|
* we can't bind &runlevel/&prevlevel through userdata. The values
|
||||||
|
* use the same encoding as the runlevel(8) command and `initctl
|
||||||
|
* runlevel`: "S" for single-user, "N" for no previous runlevel --
|
||||||
|
* the internal digit is not a wire format.
|
||||||
|
*/
|
||||||
|
static const char *runlevel_encode(int level, char *buf, size_t len)
|
||||||
|
{
|
||||||
|
if (level == INIT_LEVEL)
|
||||||
|
strlcpy(buf, "S", len);
|
||||||
|
else if (level >= 0 && level <= 9)
|
||||||
|
snprintf(buf, len, "%d", level);
|
||||||
|
else
|
||||||
|
strlcpy(buf, "N", len);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
static int prop_runlevel(link_writer_t *w, void *u)
|
static int prop_runlevel(link_writer_t *w, void *u)
|
||||||
{
|
{
|
||||||
char buf[8];
|
char buf[8];
|
||||||
|
|
||||||
(void)u;
|
(void)u;
|
||||||
snprintf(buf, sizeof(buf), "%d", runlevel);
|
link_w_string(w, runlevel_encode(runlevel, buf, sizeof(buf)));
|
||||||
link_w_string(w, buf);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +490,10 @@ static int prop_prevrunlevel(link_writer_t *w, void *u)
|
|||||||
char buf[8];
|
char buf[8];
|
||||||
|
|
||||||
(void)u;
|
(void)u;
|
||||||
snprintf(buf, sizeof(buf), "%d", prevlevel);
|
if (prevlevel <= 0 || prevlevel > 9)
|
||||||
|
strlcpy(buf, "N", sizeof(buf));
|
||||||
|
else
|
||||||
|
snprintf(buf, sizeof(buf), "%d", prevlevel);
|
||||||
link_w_string(w, buf);
|
link_w_string(w, buf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -893,8 +912,8 @@ void dbus_notify_service_state(svc_t *svc, int old_state, int new_state)
|
|||||||
/* ---------- signal emission: RunlevelChanged ----------
|
/* ---------- signal emission: RunlevelChanged ----------
|
||||||
*
|
*
|
||||||
* Fired by sm.c right after the runlevel global flips. Body is
|
* Fired by sm.c right after the runlevel global flips. Body is
|
||||||
* (old, new) as one-digit strings, matching the format that
|
* (old, new) in the same runlevel(8) encoding as the Manager1
|
||||||
* Manager1.Runlevel (the property) returns. */
|
* Runlevel property: digits, "S", or "N". */
|
||||||
void dbus_notify_runlevel_change(int old_level, int new_level)
|
void dbus_notify_runlevel_change(int old_level, int new_level)
|
||||||
{
|
{
|
||||||
uint8_t body[64];
|
uint8_t body[64];
|
||||||
@@ -902,8 +921,8 @@ void dbus_notify_runlevel_change(int old_level, int new_level)
|
|||||||
char old_s[8], new_s[8];
|
char old_s[8], new_s[8];
|
||||||
ssize_t blen;
|
ssize_t blen;
|
||||||
|
|
||||||
snprintf(old_s, sizeof(old_s), "%d", old_level);
|
runlevel_encode(old_level, old_s, sizeof(old_s));
|
||||||
snprintf(new_s, sizeof(new_s), "%d", new_level);
|
runlevel_encode(new_level, new_s, sizeof(new_s));
|
||||||
|
|
||||||
link_writer_init(&w, body, sizeof(body));
|
link_writer_init(&w, body, sizeof(body));
|
||||||
link_w_string(&w, old_s);
|
link_w_string(&w, old_s);
|
||||||
|
|||||||
+13
-8
@@ -308,12 +308,8 @@ static int do_runlevel(char *arg)
|
|||||||
|
|
||||||
if (dbus_get_manager_props(wanted, out, sizeof(curr_buf)) == 0 &&
|
if (dbus_get_manager_props(wanted, out, sizeof(curr_buf)) == 0 &&
|
||||||
curr_buf[0] && prev_buf[0]) {
|
curr_buf[0] && prev_buf[0]) {
|
||||||
int cl = atoi(curr_buf);
|
/* already in runlevel(8) encoding: digits, S, N */
|
||||||
int pl = atoi(prev_buf);
|
printf("%s %s\n", prev_buf, curr_buf);
|
||||||
|
|
||||||
curr = (cl == INIT_LEVEL) ? 'S' : (char)(cl + '0');
|
|
||||||
prev = (pl > 0 && pl <= 9) ? (char)(pl + '0') : 'N';
|
|
||||||
printf("%c %c\n", prev, curr);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -2181,6 +2177,15 @@ fail:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* wire encoding is runlevel(8) style: digits, S, N */
|
||||||
|
static int runlevel_from_str(const char *s)
|
||||||
|
{
|
||||||
|
if (!strcmp(s, "S"))
|
||||||
|
return INIT_LEVEL;
|
||||||
|
|
||||||
|
return atoi(s);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* All show_status() views over D-Bus, self-contained (the current
|
* All show_status() views over D-Bus, self-contained (the current
|
||||||
* runlevel comes from Manager1, not the legacy socket). Returns 0
|
* runlevel comes from Manager1, not the legacy socket). Returns 0
|
||||||
@@ -2216,7 +2221,7 @@ static int dbus_show_status(char *arg, int *retval)
|
|||||||
/* runlevel feeds the detail view's Runlevels line */
|
/* runlevel feeds the detail view's Runlevels line */
|
||||||
if (dbus_get_manager_props(wanted, outv, sizeof(curr)) < 0)
|
if (dbus_get_manager_props(wanted, outv, sizeof(curr)) < 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
runlevel = atoi(curr);
|
runlevel = runlevel_from_str(curr);
|
||||||
if (json) {
|
if (json) {
|
||||||
*retval = json_status_one(stdout, &rows[0], "", 0);
|
*retval = json_status_one(stdout, &rows[0], "", 0);
|
||||||
puts("");
|
puts("");
|
||||||
@@ -2230,7 +2235,7 @@ static int dbus_show_status(char *arg, int *retval)
|
|||||||
|
|
||||||
if (dbus_get_manager_props(wanted, outv, sizeof(curr)) < 0)
|
if (dbus_get_manager_props(wanted, outv, sizeof(curr)) < 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
runlevel = atoi(curr);
|
runlevel = runlevel_from_str(curr);
|
||||||
|
|
||||||
filter = (arg && arg[0]) ? arg : NULL;
|
filter = (arg && arg[0]) ? arg : NULL;
|
||||||
if (json) {
|
if (json) {
|
||||||
|
|||||||
@@ -330,7 +330,6 @@ export SYSROOT
|
|||||||
top_builddir="${top_builddir:-$TEST_DIR/..}"
|
top_builddir="${top_builddir:-$TEST_DIR/..}"
|
||||||
sysroot_finit="$SYSROOT/sbin/finit"
|
sysroot_finit="$SYSROOT/sbin/finit"
|
||||||
built_finit="$top_builddir/src/finit"
|
built_finit="$top_builddir/src/finit"
|
||||||
[ -x "$top_builddir/src/.libs/finit" ] && built_finit="$top_builddir/src/.libs/finit"
|
|
||||||
|
|
||||||
if [ -x "$built_finit" ] && [ -e "$sysroot_finit" ] && ! cmp -s "$built_finit" "$sysroot_finit"; then
|
if [ -x "$built_finit" ] && [ -e "$sysroot_finit" ] && ! cmp -s "$built_finit" "$sysroot_finit"; then
|
||||||
fail "Stale $sysroot_finit, run 'make -C test setup-chroot' or use 'make check'"
|
fail "Stale $sysroot_finit, run 'make -C test setup-chroot' or use 'make check'"
|
||||||
|
|||||||
+4
-35
@@ -2,41 +2,17 @@
|
|||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
echo "=== Finit Test Sysroot Setup ==="
|
|
||||||
echo "Date: $(date)"
|
|
||||||
echo "SYSROOT: $SYSROOT"
|
|
||||||
echo "top_builddir: $top_builddir"
|
|
||||||
echo "srcdir: $srcdir"
|
|
||||||
echo "================================"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
make -C "$top_builddir" DESTDIR="$SYSROOT" install
|
make -C "$top_builddir" DESTDIR="$SYSROOT" install
|
||||||
|
|
||||||
mkdir -p "$SYSROOT/sbin/"
|
mkdir -p "$SYSROOT/sbin/"
|
||||||
cp "$top_builddir/test/src/serv" "$SYSROOT/sbin/"
|
cp "$top_builddir/test/src/serv" "$SYSROOT/sbin/"
|
||||||
# Prefer the real ELF in .libs/ over the libtool wrapper script --
|
if [ -x "$top_builddir/test/src/dbus-auth-client" ]; then
|
||||||
# since the test client links libink.la, libtool wraps the top-level
|
cp "$top_builddir/test/src/dbus-auth-client" "$SYSROOT/sbin/"
|
||||||
# dbus-auth-client as a shell script that re-execs the real binary
|
|
||||||
# via its own RPATH, which falls apart inside the test namespace.
|
|
||||||
if [ -x "$top_builddir/test/src/.libs/dbus-auth-client" ]; then
|
|
||||||
cp "$top_builddir/test/src/.libs/dbus-auth-client" "$SYSROOT/sbin/"
|
|
||||||
elif [ -x "$top_builddir/test/src/dbus-auth-client" ]; then
|
|
||||||
cp "$top_builddir/test/src/dbus-auth-client" "$SYSROOT/sbin/"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
# Prefer the real ELF in .libs/ over the libtool wrapper script at
|
FINITBIN="$(pwd)/$top_builddir/src/finit" DEST="$SYSROOT" make -f "$srcdir/lib/sysroot.mk"
|
||||||
# $top_builddir/src/finit. Libtool generates a shell wrapper when
|
|
||||||
# the binary depends on an in-tree convenience library (e.g. libink),
|
|
||||||
# and `ldd <wrapper>` returns "not a dynamic executable", which
|
|
||||||
# silently makes sysroot.mk copy zero host libs into the sysroot.
|
|
||||||
if [ -f "$top_builddir/src/.libs/finit" ]; then
|
|
||||||
finitbin_for_ldd="$(pwd)/$top_builddir/src/.libs/finit"
|
|
||||||
else
|
|
||||||
finitbin_for_ldd="$(pwd)/$top_builddir/src/finit"
|
|
||||||
fi
|
|
||||||
FINITBIN="$finitbin_for_ldd" DEST="$SYSROOT" make -f "$srcdir/lib/sysroot.mk"
|
|
||||||
|
|
||||||
# Drop plugins we don't need in test, only causes confusing FAIL in logs.
|
# Drop plugins we don't need in test, only causes confusing FAIL in logs.
|
||||||
for plugin in tty.so urandom.so rtc.so modprobe.so; do
|
for plugin in tty.so urandom.so rtc.so modprobe.so; do
|
||||||
@@ -50,11 +26,4 @@ for conf in 10-hotplug.conf; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Update dynamic linker cache for /usr/local/lib libraries
|
# Update dynamic linker cache for /usr/local/lib libraries
|
||||||
echo "Running ldconfig in sysroot: $SYSROOT"
|
ldconfig -r "$SYSROOT" || echo "Warning: ldconfig failed with exit code $?"
|
||||||
echo "Contents of $SYSROOT/etc/ld.so.conf:"
|
|
||||||
cat "$SYSROOT/etc/ld.so.conf" || echo "Warning: ld.so.conf not found"
|
|
||||||
echo "Libraries in $SYSROOT/usr/local/lib:"
|
|
||||||
ls -la "$SYSROOT/usr/local/lib/" 2>/dev/null || echo "Warning: /usr/local/lib not found in sysroot"
|
|
||||||
ldconfig -v -r "$SYSROOT" || echo "Warning: ldconfig failed with exit code $?"
|
|
||||||
echo "Verifying ldconfig cache was created:"
|
|
||||||
ls -la "$SYSROOT/etc/ld.so.cache" || echo "Warning: ld.so.cache not created"
|
|
||||||
Reference in new issue
Block a user