Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Initial work on what could become Finit 5.0 ...