Skip to content

WIP Finit 5.x - #490

Draft
troglobit wants to merge 31 commits into
masterfrom
next
Draft

WIP Finit 5.x#490
troglobit wants to merge 31 commits into
masterfrom
next

Conversation

@troglobit

Copy link
Copy Markdown
Collaborator

Initial work on what could become Finit 5.0 ...

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Verified protocol/behavior mismatches (D-Bus header flags, runlevel signal encoding, and error mapping) can break interoperability and/or cause incorrect runtime/test behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR introduces an in-tree, brokerless D-Bus implementation (“libink”) and wires Finit (PID 1) and initctl onto it, along with an accompanying smoke-test suite and documentation updates.

Changes:

  • Add libink (server+client) and a Finit-side D-Bus object tree (Manager1/Service1/Cond1) with signal emission hooks.
  • Update build system/configure logic to optionally enable D-Bus support and install a system-bus policy file.
  • Add D-Bus-focused smoke tests plus a small client used by the tests.
File summaries
File Description
test/src/Makefile.am Build the dbus-auth-client test helper when DBUS is enabled
test/src/dbus-auth-client.c New minimal client for libink smoke tests (auth/methods/signals)
test/src/.gitignore Ignore dbus-auth-client test binary
test/setup-sysroot.sh Copy dbus-auth-client into sysroot when present; reduce ldconfig verbosity
test/Makefile.am Add dbus-*.sh tests to EXTRA_DIST and TESTS under DBUS
test/lib/setup.sh Adjust sysroot freshness check for finit binary
test/lib/Makefile.am Distribute dbus-setup.sh helper
test/lib/dbus-setup.sh Shared D-Bus test preamble (locate client/bus socket, wait for bus)
test/dbus-service.sh Smoke test for Service1 object surface + ServiceStateChanged signal
test/dbus-manager.sh Smoke test for Manager1 methods/properties and authz behavior
test/dbus-introspect.sh Validate introspection XML handling of compound signatures
test/dbus-initctl.sh Verify initctl routes through D-Bus by observing emitted signals
test/dbus-cond.sh Smoke test for Cond1 methods, policy checks, and ConditionChanged signal
test/dbus-bus.sh Smoke test for org.freedesktop.DBus built-ins (Hello/AddMatch/Introspect)
test/dbus-auth.sh Smoke test for SASL AUTH EXTERNAL handshake behavior
test/check.sh Enable D-Bus in test configure invocation
src/svc.c Register/unregister per-service D-Bus objects on svc create/delete
src/sm.c Emit runlevel-changed notification over D-Bus
src/service.h Export compose_cmdline() and new service_reload() API
src/service.c Refactor reload into public service_reload() and internal apply helper; emit state-change notifications
src/private.h Add internal dbus_* prototypes guarded by HAVE_DBUS
src/Makefile.am Build/link dbus.c and libink into finit/initctl under DBUS
src/finit.h Add FINIT_BUS_SOCKET path constant
src/finit.c Initialize D-Bus listener during startup when enabled
src/dbus.c New: Finit↔libink glue, vtables, signal emission, optional system-bus attach
src/cond-w.c Emit ConditionChanged signal when conditions are set/cleared (guarded)
src/api.c Route legacy API reload through service_reload()
mkdocs.yml Add D-Bus Integration page to docs nav
Makefile.am Add libink + dbus-1 subdirs under DBUS, ensure build order
libink/server.c New: server socket creation/accept, peer uid capture, attach authenticated fds
libink/README.md New: library overview, layout, and testing notes
libink/proto.h New: message header parse/build API and constants
libink/proto.c New: message header parser/builders
libink/path.h New: object-path escaping API
libink/path.c New: systemd-style path segment escaping
libink/match.c New: AddMatch rule parsing and matching
libink/marshal.h New: internal marshal API for writer/reader
libink/marshal.c New: body marshalling/unmarshalling helpers
libink/Makefile.am New: build libink as a convenience libtool library
libink/link.h New: public libink API (server, client, marshal helpers)
libink/io.c New: shared EINTR-resilient read/write helpers
libink/internal.h New: internal types/constants and private APIs
libink/dispatch.c New: object registry + method dispatch + reply/signal send helpers
libink/connection.c New: per-connection state machine (auth→binary dispatch)
libink/client.c New: synchronous client implementation for method calls and signal wait
libink/builtin.c New: built-in org.freedesktop.DBus.* handlers (Hello/Introspect/Properties/AddMatch)
libink/auth.c New: SASL AUTH EXTERNAL implementation for server and client
libink/.gitignore Ignore build artifacts in libink
doc/dbus.md New: D-Bus Integration user guide
doc/ChangeLog.md Document D-Bus/libink feature and initctl routing
doc/build.md Document --disable-dbus and clarify dbus plugin vs built-in support
dbus-1/org.finit.conf New: system-bus policy restricting state-changing methods
dbus-1/Makefile.am Install dbus policy file when DBUS is enabled
dbus-1/.gitignore Ignore generated Makefile artifacts
configure.ac Add --disable-dbus, HAVE_DBUS define, and DBUS automake conditional
Review details

Suppressed comments (3)

src/dbus.c:421

  • manager_signal() also treats any non-zero dispatch_action() return as NoSuchService. If kill(2) fails for a matched/running service, this incorrectly reports a missing service instead of an operational failure.
    libink/proto.c:322
  • Similarly, ERROR replies should not set NO_REPLY_EXPECTED; reply frames should use flags=0 to match the D-Bus spec and maximize interop.
	return finalize_header(buf, cap, LINK_MSG_ERROR,
			       LINK_FLAG_NO_REPLY_EXPECTED,
			       body_len, serial, off);

libink/proto.c:349

  • Signal frames should also use flags=0; NO_REPLY_EXPECTED is a METHOD_CALL flag and setting it on SIGNALs is at best meaningless and at worst trips protocol validators.
	return finalize_header(buf, cap, LINK_MSG_SIGNAL,
			       LINK_FLAG_NO_REPLY_EXPECTED,
			       body_len, serial, off);
  • Files reviewed: 56/56 changed files
  • Comments generated: 4
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread test/lib/setup.sh
@@ -330,7 +330,6 @@ export SYSROOT
top_builddir="${top_builddir:-$TEST_DIR/..}"
sysroot_finit="$SYSROOT/sbin/finit"
built_finit="$top_builddir/src/finit"
Comment thread src/dbus.c Outdated
Comment on lines +904 to +905
snprintf(old_s, sizeof(old_s), "%d", old_level);
snprintf(new_s, sizeof(new_s), "%d", new_level);
Comment thread src/dbus.c
Comment on lines +307 to +316
const char *ident;

if (link_call_read_string(call, &ident) < 0)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"expected (s)");
if (dispatch_action(ident, action, NULL) != 0)
return link_call_reply_error(call,
"org.finit.Error.NoSuchService", ident);

Comment thread libink/proto.c
Comment on lines +293 to +295
return finalize_header(buf, cap, LINK_MSG_METHOD_RETURN,
LINK_FLAG_NO_REPLY_EXPECTED,
body_len, serial, off);
First step toward native D-Bus support.  libink is a small server
library written from scratch -- no broker dependency, Finit always
listens on its own socket at /run/finit/bus.  Lives at top-level
libink/ as a standalone-shape library, no Finit headers leak in.

This drop has the listening socket, SO_PEERCRED capture, and the
SASL AUTH EXTERNAL handshake.  Marshaller, dispatch and the object
tree follow.

Drive-by: setup-sysroot.sh now points ldd at src/.libs/finit (the
real ELF) -- libtool wraps src/finit when in-tree .la files link in,
and the shell wrapper has no DT_NEEDED for sysroot.mk to follow.
SUBDIRS reordered so libink builds before src.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The binary D-Bus message layer on top of AUTH EXTERNAL: header
parser and builder, writer-side body marshaller (byte/bool/u32/
string/path/signature/arrays/structs), and the object-tree
dispatcher.  Built-in interfaces -- Hello, Peer.Ping,
Peer.GetMachineId, Introspectable.Introspect -- run before user
handlers.

First Finit method lands too: Manager1.ListServices on
/org/finit/manager, returning every loaded service's identity.

Native little-endian only.  Header field count and total message
size capped so PID 1 isn't exposed to oversized inputs.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Mirror of the writer's primitives: __r_byte/_bool/_u32/_string/_path
with a per-call cursor wired into struct ink_call, public via
ink_call_read_*.

Manager1 grows the obvious service-control surface:
Start/Stop/Restart take "s" identity; Reload no args; SetRunlevel
"u"; Reboot/Poweroff/Halt no args.  Bogus identity returns
NoSuchService; runlevel ops while in S, 0 or 6 return WrongRunlevel.

Drive-by: Introspect XML buffer moved out of the stack (8 KB
static) so a growing interface can't push PID 1 toward its stack
limit.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
When a peer sent "BEGIN\r\n" and the first binary method call close
enough that the kernel delivered both in one read(), the auth path
correctly moved the post-BEGIN bytes into rxbuf and went to
AUTH_DONE -- but ink_connection_process returned without dispatching
them.  The libuev fd watcher then waited for more data that never
came; the peer deadlocked until the watchdog killed it.

Fix: after auth transitions to DONE, fall through to process_binary
before reading more bytes.  Whether the writes coalesced depended on
kernel scheduling, hence the intermittent dbus-auth.sh flake.

Drive-by:

* Reply scratch moved out of struct ink_call (was ~64 KB on stack
  in dispatch) into a per-connection txbuf; ink_call is ~700 B now.
* Drop a dead `otmp` in ink_server_free and a redundant trailing
  process_binary in ink_connection_process.
* Collapse the duplicate-branch ternary in dispatch.c's
  UnknownMethod error path.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
INK_METHOD_PRIVILEGED on ink_method_t; dispatch rejects a privileged
call with AccessDenied unless conn->peer_uid is 0.  The uid was
captured via SO_PEERCRED at accept time and AUTH EXTERNAL refuses a
claim that doesn't match it, so the value is trusted here.

State-changing Manager1 methods marked privileged:
Start, Stop, Restart, Reload, SetRunlevel, Reboot, Poweroff, Halt.
ListServices stays public (read-only).

Built-ins (Hello, Peer.Ping, Peer.GetMachineId, Introspect) run
before the authz check -- they're inherently read-only and never
reach user handlers.

Test: a "call-s-as-uid" mode in the test client setuid()s before
connecting, since the test namespace maps the outer user to inner
uid 0.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Per-service object surface so D-Bus clients can drive one service
directly instead of passing its identity as an argument every time.

Each loaded service gets a Service1 vtable at
/org/finit/service/<encoded-identity>, where the encoding is
systemd-style reversible hex escape: bytes outside [A-Za-z0-9]
become "_HH".  Methods Start/Stop/Restart/Reload all privileged.
Manager1.GetService(s) -> o looks up the canonical path so callers
never have to compute encodings themselves.

svc_new/svc_del drive dbus_register_service/_unregister so the
object tree tracks the service list.  dbus_init replays existing
services since early conf parsing runs before D-Bus is up.

Refactor: extracted service_reload(svc) so api.c (legacy initctl
path) and src/dbus.c (Service1.Reload) share the "mark dirty + step"
body.  Renamed the existing static service_reload helper to
service_reload_apply to free the cleaner name.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signal emission and per-peer subscription.  match.c parses the
filter grammar subset we need (type/interface/member/path,
single-quoted values); unknown keys are rejected so a peer learns
its filter didn't take rather than silently receiving everything.
AddMatch/RemoveMatch handlers in builtin.c gate on
/org/freedesktop/DBus.

ink_connection_emit_signal builds a SIGNAL header, checks the peer's
match rules, and sends if anything matches.  struct ink_writer is
now public so callers can stack-allocate one and marshal signal
bodies outside the dispatch path.

First user: Manager1.ServiceStateChanged(identity, old, new),
emitted from svc_set_state under HAVE_DBUS.  Coarse svc_state_t ->
string mapping with no `default:` -- a new SVC_*_STATE in svc.h
surfaces as -Wswitch rather than silently emitting "unknown".

TODO on send_all: a slow peer can stall PID 1 once kernel buffers
fill.  The peer cap of 64 hides the symptom for now; non-blocking
plus drop-on-EAGAIN is the proper fix.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Exposes Finit's condition engine on D-Bus at /org/finit/cond:

  Get   (s) -> s      state ("on"/"off"/"flux") for any condition
  Set   (s) -> ()     assert  a usr/* condition       [privileged]
  Clear (s) -> ()     deassert a usr/* condition      [privileged]
  List  ()  -> as     names of all asserted conditions
  Dump  ()  -> a(ss)  (name, state) pairs

Set/Clear are restricted to usr/* so clients can't twiddle
pid/sys/hook entries owned by Finit's state machine.  Names are
normalised the way initctl does it: bare "foo" becomes "usr/foo",
periods rejected, multi-slash tails rejected.

ConditionChanged fires from cond_set/cond_set_oneshot/cond_clear,
after the state is persisted and before cond_update notifies
dependents -- so subscribers see the new state before service-side
reactions land.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
initctl now talks to /run/finit/bus for the simple state-changing
commands when D-Bus is reachable, falling back automatically to the
legacy INIT_SOCKET when it isn't (--disable-dbus, or finit built
without HAVE_DBUS).  CLI surface unchanged.

Routes:
  initctl start/stop/restart <svc> -> Manager1.{Start,Stop,Restart}
  initctl reload                   -> Manager1.Reload  (no args)
  initctl reboot/halt/poweroff     -> Manager1.{Reboot,Halt,Poweroff}

The reboot/halt/poweroff path keeps the legacy sleep(5), since
/run/finit/bus is about to disappear once the shutdown is acted on.

Still on the legacy IPC: per-service reload (needs ink_path_encode),
suspend (no Manager1.Suspend yet), and the read-only commands.
Marked in the code.

Approach: rather than link libink into initctl, this commit adds a
minimal client wire implementation in src/dbus-client.{c,h}.  That
duplicates marshalling with libink and the test client; a follow-up
folds them together once libink grows a client side.

Test: dbus-auth.sh subscribes to Manager1.ServiceStateChanged in a
background monitor and runs initctl restart keventd -- only the
D-Bus path fires that signal, so observing it proves the route.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Mechanical sweep across libink, src/dbus.c, src/dbus-client.* and
test/src/dbus-auth-client.c.  No behaviour change.

The library is libink (linked with -link); the API operates on D-Bus
connections -- which are links between peers.  The link_* prefix
matches the linker-flag pun and reads more naturally at call sites.
ink_* was an artefact of the early naming discussion that didn't
survive the API growing.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
initctl drops its private wire implementation
(src/dbus-client.{c,h}, ~490 lines) and links libink directly.  The
earlier server-only framing pushed the same wire format into three
places: libink, the initctl client, and the test helper.  Lifting
it costs little and removes ~500 LOC of duplication.

New in libink:

* client.c -- link_client_open/close/call.  Synchronous: connect,
  AUTH EXTERNAL, BEGIN, send METHOD_CALL, read reply.
* io.c -- __io_read_full / __io_write_all consolidate the four
  EINTR-resilient blocking loops auth.c, dispatch.c and client.c
  all needed.
* auth.c gains __auth_client (mirror of __auth_process); proto.c
  gains __msg_build_method_call.

Naming settled at the same time: public API link_*, internal __*,
public header libink/link.h (was libink/ink.h), internal
libink/internal.h.

Fixed a real bug caught by simplify: read_one added wire-supplied
fields_len to 16 before bounds-checking it, so a peer sending
0xFFFFFFF0 wrapped past zero and bypassed the rxbuf guard.  Both
lengths now checked against rxbuf before the arithmetic.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Three additions to the public client API so callers can do more
than just check call success/error:

* struct link_reader moves from marshal.h to link.h, with
  link_reader_init + link_r_byte/_bool/_u32/_string/_path/_done
  public wrappers.  Mirrors the writer surface.

* link_reply_t + link_client_reply() expose a view of the most
  recent reply: type, signature, error_name (set on ERROR), and
  body bytes.  Pointers reference the client's rxbuf and are
  invalidated by the next call or close.

* link_client_call_v: sd-bus-style varargs wrapper over
  link_client_call.  Pass a signature ("s", "u", "", ...) and the
  args inline; marshals into a 1 KiB stack buffer.  Supports
  y/b/u/s/o today.

The err_buf out-param on link_client_call goes away -- callers now
read link_client_reply()->error_name instead.  initctl's
try_dbus_manager is updated to the new shape.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
* link_client_wait(c, timeout_ms) waits for the next inbound frame
  (typically a SIGNAL after AddMatch) and publishes it via the same
  link_client_reply() accessor link_client_call uses.
  timeout_ms < 0 blocks; == 0 polls; > 0 waits.

* link_reply_t gains path/interface/member so callers can identify
  the signal that fired.  NULL on method-returns and errors.

* link_r_pos(reader) exposes the reader cursor for walking arrays.

The D-Bus message-type codes (LINK_MSG_METHOD_RETURN/_ERROR/_SIGNAL,
_METHOD_CALL, _INVALID) move from internal proto.h to public link.h
so callers can interpret link_reply_t.type without an internal
header.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The test client originally reimplemented the entire D-Bus wire
format -- struct buf marshaller, struct reply parser, manual header
walk, custom "s"/"o"/"as" decoders -- because libink was
server-only.  With libink bidirectional and exposing the reader,
writer, message-type constants, link_client_wait and a
signal-aware reply view, all of that can go.

What still uses raw wire access:

* mode_auth exists *to* test the AUTH EXTERNAL handshake itself,
  including REJECTED cases driven by deliberately-wrong uids.
  libink's __auth_client won't do -- it swallows the reply line
  but the test needs to print it for the shell to grep.

* The setuid-as-uid modes drop the effective uid and then go
  through libink like everything else (AUTH EXTERNAL claims
  geteuid()).

Net: 861 -> 467 LOC in the test client.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The original test started as an AUTH-handshake smoke test and
accreted coverage for everything we shipped -- Manager1, Service1,
Cond1, signals, AddMatch, initctl routing -- to the point the name
was misleading and the file ran 315 LOC.

Replaced with six focused tests, each runnable in isolation:

  dbus-auth.sh        AUTH EXTERNAL + GUID uniqueness + socket mode
  dbus-bus.sh         org.freedesktop.DBus built-ins
  dbus-manager.sh     Manager1 vtable + per-method authorization
  dbus-service.sh     Service1 vtable + ServiceStateChanged
  dbus-cond.sh        Cond1 vtable + ConditionChanged + usr/ policy
  dbus-initctl.sh     initctl restart/reload routed via D-Bus

Common preamble (CLIENT/BUS vars, skip-if-not-built, retry until
the bus socket appears) lives in test/lib/dbus-setup.sh.  /tmp/*
filenames are unique per test so the parallel runner can't clobber
across them.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Two new D-Bus routes:

* initctl monitor streams every signal on the bus until ^C, one
  line per delivery: "HH:MM:SS iface.member(args)".  First real
  consumer of link_client_wait; decodes leading 's' args only.
  Exits with a clear error when the bus connection drops.

* initctl cond {get,set,clear} opens an org.finit.Cond1 client up
  front and routes each parsed condition arg through
  Cond1.{Get,Set,Clear} when reachable, falling back to the legacy
  filesystem symlink path otherwise.  Set now fires the
  ConditionChanged signal as a side effect.

cond_dbus_call owns the bus-handle lifecycle and the
LINK_CALL_{OK,ERROR,FAIL} fan-out so do_cond_act stays flat.

Test: dbus-initctl.sh asserts initctl cond set fires
ConditionChanged -- only the D-Bus path emits it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The standard Properties interface lets dbus-send, gdbus and other
D-Bus tooling introspect and read out Finit state in the canonical
way.

libink:

* String variant marshalling (link_w/r_variant_string) plus
  link_property_t and a .properties slot on link_vtable_t.
  Read-only properties only for now; non-string variants deferred
  until a property needs them.
* Properties.Get/GetAll handlers in builtin.c.  Introspection XML
  emits <property> entries and advertises the interface.
* link_r_align (public) for walking a{sv} reply bodies.

Finit:

* Manager1 declares Runlevel, PrevRunlevel and Version as
  read-only string properties.
* Manager1.RunlevelChanged(ss) -- (old, new) -- emitted from sm.c
  right after the runlevel global flips.
* dbus_emit_signal() consolidates the three near-identical signal
  fan-out helpers onto one.
* initctl runlevel reads via Properties.GetAll when reachable,
  falls back to runlevel_get otherwise.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
If a dbus-daemon is reachable at /var/run/dbus/system_bus_socket
when dbus_init() runs, libink claims "org.finit" on the system bus
and integrates the authenticated connection into the same peer
machinery that /run/finit/bus uses.  dbus-send, dbus-monitor, gdbus
and any binding that speaks D-Bus over AF_UNIX can then talk to
Finit without going through our custom socket.

Two new libink primitives make this work:

  link_server_attach(server, fd, peer_uid)
    Promote an externally-authenticated fd into a server-tracked
    connection (skips AUTH, sets CLOEXEC+NONBLOCK, owns the fd from
    entry).  peer_uid sets what privileged-method checks see;
    (uid_t)-1 makes all privileged methods reject by default --
    the safe default for external system-bus traffic until
    per-sender uid lookup (GetConnectionUnixUser) lands.

  link_client_steal_fd(c)
    Detach the authenticated socket from a libink client and free
    the container, so callers can hand the fd directly to
    link_server_attach.

src/dbus.c gains try_attach_system_bus() called once at the end of
dbus_init.  peer_register() is extracted from accept_cb so both
the accept path and the new attach path enforce DBUS_MAX_PEERS and
unwind correctly on alloc/uev_io failure.

Test coverage is manual -- the test fixture has no dbus-daemon.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Three small additions distilled from earlier review rounds.

* link_r_array_begin(r, &out_end) reads the u32 byte-length prefix
  of an "a<T>" payload and returns the end offset.  Callers loop
  while link_r_pos < *out_end.  Replaces the manual u32+pos walk
  in print_string_array (test client) and dbus_get_manager_props
  (initctl).

* link_reply_get_string(r, &out) and link_reply_get_u32(r, &out)
  wrap link_reader_init + link_r_*.  Five sites switch from a
  3-line dance to a one-liner.

* link_client_open_timeout(path, ms) sets SO_SNDTIMEO + SO_RCVTIMEO
  before AUTH.  Once the fd is handed to link_server_attach and
  flipped to non-blocking the timeouts are silently inert.
  try_attach_system_bus uses a 2-second budget, so a wedged
  dbus-daemon can no longer stall boot.

link_client_open(path) is preserved as an alias for
link_client_open_timeout(path, 0); no caller needs updating unless
it actually wants a budget.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The state-changing subcommands that still went exclusively through
INIT_SOCKET now have matching Manager1/Service1 methods and route
through them when reachable.

src/dbus.c:

* Manager1.SetDebug(): toggles the debug flag (log_debug()).
* Manager1.Signal(s, u): sends signal u (1..31) to every running
  service matching identity s.  signal_one() silently skips
  matched-but-not-running services so a multi-match ident doesn't
  spuriously fail.
* Manager1.Suspend(): sync(); suspend().  Maps EINVAL to
  "kernel does not support suspend to RAM".
* dispatch_action grew a udata pointer so signal_one can receive
  the signo.

initctl:

* toggle_debug, do_suspend and do_reload(<svc>) gain the standard
  try_dbus_manager / try_dbus_service preamble with legacy
  fallback.
* do_signal inlines its Manager1.Signal call (try_dbus_manager
  doesn't yet have a "su" body grammar).  The error-mapping pattern
  across all three sites is now a small map_dbus_err helper.
* try_dbus_service builds /org/finit/service/<encoded> via
  link_path_encode and dispatches Service1.<method>.
* Server-side signo bound is 1..31 to match initctl's existing
  range (str2sig + strtonum); both transports user-visible
  equivalent.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
doc/dbus.md is the user-facing reference for the D-Bus surface:
bus address, object tree, methods/properties/signals per interface,
identity encoding, authorization model, and the mapping table from
each initctl subcommand to the D-Bus method it routes through.

Examples use dbus-send and dbus-monitor (which ship with the dbus
reference implementation); gdbus and pure-Python bindings work the
same way.

Closes #396

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Use LD_LIBRARY_PATH=/tmp/lib only on the commands that actually need
libink.  The unit test step uses its own sysroot and is unaffected.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The dbus tests introspect only /, the manager, and one service
object, so nothing covered Cond1.Dump's a(ss), the only compound
signature on the bus.  Check it appears as a single <arg> and that
no per-character fragments leak into the XML.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
dbus_init() tried the system-bus attach exactly once, before
dbus-daemon -- usually a finit service itself -- had started, so
org.finit was never claimed on a normal boot, and a broker restart
lost the name for good.  Probe from the service and condition
notify paths instead: when the name is unclaimed and the broker
socket exists, schedule one coalesced attach attempt.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The frozen v5.0 Service1 interface had no readable state -- clients
could only observe transitions via ServiceStateChanged.  Add the
read-only properties Identity, Name, State, Pid, RestartCount,
Description, Command and Conditions.

The framework now emits the variant signature from the property
table and getters write the bare value, so the declared type is the
single source of truth.  State transitions also emit the standard
PropertiesChanged signal: State in the changed dictionary, Pid and
RestartCount invalidated.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add a getprop mode to the test client, dispatching on the variant
signature via link_r_variant_begin(), and exercise Identity, State
and Pid plus the property declarations in the service object's
introspection XML.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The summary table was still glued to the legacy INIT_SOCKET.  Fetch
it over the bus: ListServices, then Properties.GetAll per service,
object paths encoded locally.  Service1 grows a Runlevels bitmask
property for the fourth column, Command now returns the full command
line as documented, and the current runlevel comes from
Manager1.Runlevel, so the whole path works without the legacy socket
-- the test hides it to prove that.  The detail view and JSON mode
still use the socket, and the table falls back to it whenever the
bus is absent.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Only the status summary table had moved off the legacy socket; the
detail view, JSON output, -q exit codes, and ident still required
it.  Add the remaining Service1 properties -- Type, Origin,
Environment, PidFile, User, Group, Uptime, ExitStatus,
RestartsTotal, RestartMax, Starts, ManualStart, Forking, Started --
and render every status view from a transport-neutral svc_row that
the legacy path fills from the raw svc_t and the D-Bus path from
Properties.GetAll.  One set of printers serves both transports, so
they can no longer drift; the old per-svc_t printers are gone.

The wire dispatch is a descriptor table next to the struct, unknown
keys and basic value types are skipped for forward compatibility,
and initctl ident is a single ListServices round trip.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
libink installed as a public shared library -- versioned .so,
headers, pkg-config file -- an ABI commitment the plan never made.
The API is still settling, link_writer_t exposes its layout, and
bus clients don't need the library: the wire protocol is plain
D-Bus, any standard binding works.  Link it statically into finit
and initctl instead; a public install can come back when libink is
extracted into its own project.

With no uninstalled shared library in the link, libtool stops
wrapping finit and initctl in shell scripts, so the sysroot setup
loses its .libs/ detours and stale-wrapper guards, CI drops the
LD_LIBRARY_PATH workaround, and the debug scaffolding from chasing
the old loader problem goes with them.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The Runlevel and PrevRunlevel properties leaked Finit's internal
encoding: single-user came out as "10" and a missing previous level
as "0", leaving every bus client to know the magic numbers.  Emit
the runlevel(8) encoding instead -- digits, "S", "N" -- matching
what initctl runlevel has always printed.  initctl now passes the
strings straight through.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Reflow prose and tables, fix factual drift, e.g., build flags.

The User Guide is for end users, so the implementation notes move to
a libink/README.md, integrator-facing details stays in the guide.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants