libink: a brokerless D-Bus implementation for Finit

Finit had no way to answer the question every service manager gets
asked: what is running, and change it.  D-Bus is how the rest of
userspace asks, but linking libdbus, sd-bus or GIO into PID 1 buys a
dependency, an allocator and a main loop we do not control.

So libink: the wire format, an object tree, and a bus of Finit's own
at /run/finit/bus, gated like INIT_SOCKET.  It speaks the standard
org.freedesktop.DBus, .Peer, .Introspectable interfaces, and Finit's
own Manager1, Service1 and Cond1 on top.  Methods that change
something are marked privileged and answered only for a caller the
kernel vouched for, via SO_PEERCRED.

Server and client both, since initctl is the first thing that needs
to talk to it, and its Start/Stop/Restart/Reload now go over the bus
rather than the legacy socket.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-13 09:28:14 +02:00
1 parent ae9a24f44f
commit 0a269f3298
39 files changed
+5530 -49

No files matched your search

+11
View File
@@ -81,6 +81,9 @@ finit_SOURCES = api.c cgroup.c cgroup.h \
if LOGROTATE
finit_SOURCES += logrotate.c
endif
if DBUS
finit_SOURCES += dbus.c
endif
pkginclude_HEADERS = cgroup.h cond.h conf.h finit.h helpers.h log.h \
plugin.h svc.h service.h
@@ -94,6 +97,10 @@ finit_LDADD += ../plugins/libplug.la
else
finit_LDADD += -ldl
endif
if DBUS
finit_CPPFLAGS += -I$(top_srcdir)/libink
finit_LDADD += $(top_builddir)/libink/libink.la
endif
initctl_SOURCES = initctl.c initctl.h cgutil.c cgutil.h \
client.c client.h cond.c cond.h reboot.c \
@@ -101,6 +108,10 @@ initctl_SOURCES = initctl.c initctl.h cgutil.c cgutil.h \
initctl_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
initctl_CFLAGS += $(lite_CFLAGS) $(uev_CFLAGS)
initctl_LDADD = $(lite_LIBS) $(uev_LIBS)
if DBUS
initctl_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/libink
initctl_LDADD += $(top_builddir)/libink/libink.la
endif
INIT_LNKS = init telinit
REBOOT_LNKS = reboot shutdown halt poweroff suspend
+1 -32
View File
@@ -113,38 +113,7 @@ static int restart(svc_t *svc, void *user_data)
static int reload(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
if (svc_is_blocked(svc))
svc_start(svc);
else
service_timeout_cancel(svc);
/*
* Clear conditions before reload to ensure dependent services
* are properly updated. Only needed when the service does NOT
* support SIGHUP (noreload), because then it will be stopped
* and restarted, so conditions genuinely go away. When the
* service handles SIGHUP, its PID and pidfile persist, so the
* condition stays valid and dependents should not be disrupted.
*
* Note: only clear 'ready' for services where the pidfile
* inotify handler reasserts it (pid/none). For s6/systemd
* services readiness relies on their respective notification
* mechanism which may not re-trigger on SIGHUP.
*/
if (svc_is_noreload(svc)) {
svc_cond_clear(svc);
if (svc->notify == SVC_NOTIFY_PID || svc->notify == SVC_NOTIFY_NONE)
service_ready(svc, 0);
}
svc_mark_dirty(svc);
service_step(svc);
return 0;
return service_reload(svc);
}
static int do_stop (char *buf, size_t len) { return call(stop, buf, len); }
+16
View File
@@ -37,6 +37,13 @@
#include "service.h"
#include "sm.h"
#ifdef HAVE_DBUS
/* Forward-declared locally to keep cond-w.c independent of the
* daemon's private.h (which pulls in svc/plugin headers). The full
* prototype lives in private.h for callers in finit's main loop. */
void dbus_notify_condition_change(const char *name, const char *state);
#endif
struct cond_boot {
TAILQ_ENTRY(cond_boot) link;
char *name;
@@ -353,6 +360,9 @@ void cond_set(const char *name)
if (cond_set_noupdate(name))
return;
#ifdef HAVE_DBUS
dbus_notify_condition_change(name, "on");
#endif
cond_update(name);
}
@@ -383,6 +393,9 @@ void cond_set_oneshot(const char *name)
if (cond_set_oneshot_noupdate(name))
return;
#ifdef HAVE_DBUS
dbus_notify_condition_change(name, "on");
#endif
cond_update(name);
}
@@ -404,6 +417,9 @@ void cond_clear(const char *name)
if (cond_clear_noupdate(name))
return;
#ifdef HAVE_DBUS
dbus_notify_condition_change(name, "off");
#endif
cond_update(name);
}
+869
View File
@@ -0,0 +1,869 @@
/* Finit-side glue between the event loop and libink.
*
* Owns the libink server, accepts new peers, drives each peer's
* state machine, and registers the Finit-specific D-Bus object
* tree (org.finit.Manager1 et al). Nothing in libink/ depends on
* finit-internal types: the boundary lives in this file, by design.
*
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#ifdef HAVE_DBUS
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <uev/uev.h>
#include <ftw.h>
#include "link.h"
#include "path.h"
#include "finit.h"
#include "cond.h"
#include "conf.h"
#include "log.h"
#include "private.h"
#include "service.h"
#include "sig.h"
#include "sm.h"
#include "svc.h"
#define DBUS_MAX_PEERS 64
struct peer {
uev_t watcher;
link_connection_t *conn;
TAILQ_ENTRY(peer) link;
};
static TAILQ_HEAD(, peer) peers = TAILQ_HEAD_INITIALIZER(peers);
static link_server_t *server;
static uev_t accept_watcher;
static size_t peer_count;
static void peer_drop(struct peer *p)
{
uev_io_stop(&p->watcher);
link_connection_close(p->conn);
TAILQ_REMOVE(&peers, p, link);
peer_count--;
free(p);
}
static void peer_cb(uev_t *w, void *arg, int events)
{
struct peer *p = arg;
(void)w;
if (UEV_ERROR == events) {
peer_drop(p);
return;
}
if (link_connection_process(p->conn) < 0)
peer_drop(p);
}
static void accept_cb(uev_t *w, void *arg, int events)
{
(void)arg;
if (UEV_ERROR == events) {
err(1, "D-Bus accept watcher error");
return;
}
for (;;) {
link_connection_t *conn = NULL;
struct peer *p;
if (link_server_accept(server, &conn) < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
err(1, "Failed accepting D-Bus client");
break;
}
if (peer_count >= DBUS_MAX_PEERS) {
logit(LOG_WARNING, "D-Bus peer cap reached (%zu), dropping",
peer_count);
link_connection_close(conn);
continue;
}
p = calloc(1, sizeof(*p));
if (!p) {
link_connection_close(conn);
err(1, "Out of memory accepting D-Bus client");
break;
}
p->conn = conn;
TAILQ_INSERT_TAIL(&peers, p, link);
peer_count++;
if (uev_io_init(w->ctx, &p->watcher, peer_cb, p,
link_connection_get_fd(conn), UEV_READ)) {
err(1, "Failed registering D-Bus peer watcher");
peer_drop(p);
}
}
}
/* ---------- org.finit.Manager1 ---------- */
/* Forward decl + buffer-size constant — both consumed by Manager1
* handlers below, defined in the Service1 block further down. */
#define SERVICE_PATH_PREFIX "/org/finit/service/"
#define SERVICE_PATH_PREFIX_LEN (sizeof(SERVICE_PATH_PREFIX) - 1)
#define FINIT_SVC_PATH_MAX 512
static int service_path_for(svc_t *svc, char *buf, size_t bufsz);
static int manager_list_services(link_call_t *call, void *userdata)
{
link_writer_t *w;
svc_t *iter = NULL;
svc_t *svc;
(void)userdata;
w = link_call_reply(call);
if (!w)
return -1;
link_w_array_begin(w, 's');
for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) {
char ident[MAX_IDENT_LEN];
svc_ident(svc, ident, sizeof(ident));
link_w_string(w, ident);
}
link_w_array_end(w);
return 0;
}
static int manager_get_service(link_call_t *call, void *userdata)
{
const char *ident;
svc_t *svc;
char path[FINIT_SVC_PATH_MAX];
link_writer_t *w;
(void)userdata;
if (link_call_read_string(call, &ident) < 0)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"expected (s)");
svc = svc_find_by_str(ident);
if (!svc)
return link_call_reply_error(call,
"org.finit.Error.NoSuchService", ident);
if (service_path_for(svc, path, sizeof(path)) < 0)
return link_call_reply_error(call,
"org.finit.Error.Failed",
"Path encoding overflow");
w = link_call_reply(call);
if (!w)
return -1;
link_w_path(w, path);
return 0;
}
/* Service-control helpers used by Start/Stop/Restart/Reload. These
* mirror the static helpers in api.c — kept private here so api.c
* stays untouched in this increment. */
static int dbus_apply_stop(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_stop(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
}
static int dbus_apply_start(svc_t *svc, void *user_data)
{
(void)user_data;
if (!svc)
return 1;
service_timeout_cancel(svc);
svc_start(svc);
service_step(svc);
if (!IS_RESERVED_RUNLEVEL(runlevel))
service_step_all(SVC_TYPE_ANY);
return 0;
}
static int dbus_apply_restart(svc_t *svc, void *user_data)
{
if (!svc)
return 1;
if (!svc_is_running(svc))
return dbus_apply_start(svc, user_data);
service_timeout_cancel(svc);
service_stop(svc);
service_step(svc);
return 0;
}
struct dispatch_ctx {
int (*action)(svc_t *, void *);
int matched;
};
static int dispatch_found(svc_t *svc, void *udata)
{
struct dispatch_ctx *ctx = udata;
ctx->matched++;
return ctx->action(svc, NULL);
}
static int dispatch_missing(char *job, char *id, void *udata)
{
(void)job; (void)id; (void)udata;
return 0; /* don't penalise the return; we'll check ->matched */
}
/* Apply `action` to every service matched by `ident`. Returns 0 if
* at least one service matched and the action succeeded on all;
* -1 if no service matched the identity (caller sends NoSuchService). */
static int dispatch_action(const char *ident,
int (*action)(svc_t *, void *))
{
char buf[MAX_IDENT_LEN];
struct dispatch_ctx ctx = { .action = action };
int rc;
if (!ident || !*ident || strlen(ident) >= sizeof(buf))
return -1;
memcpy(buf, ident, strlen(ident) + 1);
rc = svc_parse_jobstr(buf, sizeof(buf), &ctx,
dispatch_found, dispatch_missing);
if (ctx.matched == 0)
return -1;
return rc;
}
static int manager_take_string_method(link_call_t *call,
int (*action)(svc_t *, void *))
{
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) != 0)
return link_call_reply_error(call,
"org.finit.Error.NoSuchService", ident);
(void)link_call_reply(call); /* empty reply */
return 0;
}
static int manager_start (link_call_t *call, void *u) { (void)u; return manager_take_string_method(call, dbus_apply_start); }
static int manager_stop (link_call_t *call, void *u) { (void)u; return manager_take_string_method(call, dbus_apply_stop); }
static int manager_restart(link_call_t *call, void *u) { (void)u; return manager_take_string_method(call, dbus_apply_restart); }
static int manager_reload(link_call_t *call, void *userdata)
{
(void)userdata;
/*
* Same semantics as api.c: harmless no-op during bootstrap
* and shutdown, the client still sees success.
*/
if (IS_RESERVED_RUNLEVEL(runlevel))
warnx("Ignoring reload in runlevel S and 6/0.");
else
sm_reload();
(void)link_call_reply(call);
return 0;
}
static int manager_set_runlevel(link_call_t *call, void *userdata)
{
uint32_t lvl;
(void)userdata;
if (link_call_read_u32(call, &lvl) < 0)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"expected (u)");
if (lvl > 9 || lvl == INIT_LEVEL)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"runlevel must be 0-9 (excluding internal levels)");
if (lvl == 0) halt = SHUT_OFF;
if (lvl == 6) halt = SHUT_REBOOT;
sm_runlevel((int)lvl);
(void)link_call_reply(call);
return 0;
}
static int dbus_shutdown(link_call_t *call, shutop_t target, int level)
{
if (IS_RESERVED_RUNLEVEL(runlevel))
return link_call_reply_error(call,
"org.finit.Error.WrongRunlevel",
"Already in shutdown");
halt = target;
sm_runlevel(level);
(void)link_call_reply(call);
return 0;
}
static int manager_reboot (link_call_t *c, void *u) { (void)u; return dbus_shutdown(c, SHUT_REBOOT, 6); }
static int manager_poweroff(link_call_t *c, void *u) { (void)u; return dbus_shutdown(c, SHUT_OFF, 0); }
static int manager_halt (link_call_t *c, void *u) { (void)u; return dbus_shutdown(c, SHUT_HALT, 0); }
static const link_method_t manager_methods[] = {
{ .name = "ListServices", .in_sig = "", .out_sig = "as",
.handler = manager_list_services },
{ .name = "GetService", .in_sig = "s", .out_sig = "o",
.handler = manager_get_service },
{ .name = "Start", .in_sig = "s", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_start },
{ .name = "Stop", .in_sig = "s", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_stop },
{ .name = "Restart", .in_sig = "s", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_restart },
{ .name = "Reload", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reload },
{ .name = "SetRunlevel", .in_sig = "u", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_set_runlevel },
{ .name = "Reboot", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_reboot },
{ .name = "Poweroff", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_poweroff },
{ .name = "Halt", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_halt },
{ NULL, NULL, NULL, 0, NULL }
};
static const link_vtable_t manager_vtable = {
.interface = "org.finit.Manager1",
.methods = manager_methods,
};
/* ---------- org.finit.Service1 (one object per service) ----------
*
* Per-service object at /org/finit/service/<encoded-identity>.
* The vtable's `userdata` is the svc_t * for the specific service.
* Registration is driven dynamically from svc_new()/svc_del() via
* dbus_register_service() / dbus_unregister_service() below. */
/* SERVICE_PATH_PREFIX / SERVICE_PATH_PREFIX_LEN / FINIT_SVC_PATH_MAX
* defined near the top of the file so Manager1.GetService can refer
* to them. */
static int service_action_method(link_call_t *call, void *userdata,
int (*action)(svc_t *, void *))
{
svc_t *svc = userdata;
if (!svc)
return link_call_reply_error(call,
"org.finit.Error.NoSuchService",
"Service object no longer valid");
action(svc, NULL);
(void)link_call_reply(call);
return 0;
}
static int service1_start (link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_start); }
static int service1_stop (link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_stop); }
static int service1_restart(link_call_t *c, void *u) { return service_action_method(c, u, dbus_apply_restart); }
static int service1_reload(link_call_t *call, void *userdata)
{
svc_t *svc = userdata;
if (!svc)
return link_call_reply_error(call,
"org.finit.Error.NoSuchService",
"Service object no longer valid");
service_reload(svc);
(void)link_call_reply(call);
return 0;
}
static const link_method_t service_methods[] = {
{ .name = "Start", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_start },
{ .name = "Stop", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_stop },
{ .name = "Restart", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_restart },
{ .name = "Reload", .in_sig = "", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = service1_reload },
{ NULL, NULL, NULL, 0, NULL }
};
static const link_vtable_t service_vtable = {
.interface = "org.finit.Service1",
.methods = service_methods,
};
/* Build the canonical object path for a service. Identity is
* "name" for single-instance services, "name:id" otherwise. */
static int service_path_for(svc_t *svc, char *buf, size_t bufsz)
{
char ident[MAX_IDENT_LEN];
size_t plen = SERVICE_PATH_PREFIX_LEN;
int enc;
if (bufsz <= plen)
return -1;
memcpy(buf, SERVICE_PATH_PREFIX, plen);
svc_ident(svc, ident, sizeof(ident));
enc = link_path_encode(ident, buf + plen, bufsz - plen);
if (enc < 0)
return -1;
return (int)plen + enc;
}
void dbus_register_service(svc_t *svc)
{
char path[FINIT_SVC_PATH_MAX];
if (!server || !svc)
return;
if (service_path_for(svc, path, sizeof(path)) < 0)
return;
if (link_server_add_object(server, path, &service_vtable, svc) < 0)
logit(LOG_WARNING, "dbus: failed registering %s", path);
}
void dbus_unregister_service(svc_t *svc)
{
char path[FINIT_SVC_PATH_MAX];
if (!server || !svc)
return;
if (service_path_for(svc, path, sizeof(path)) < 0)
return;
(void)link_server_remove_object(server, path);
}
/* ---------- signal emission: ServiceStateChanged ---------- */
/*
* Coarse svc_state_t -> string. svc_status() in svc.h returns a
* richer string that also considers svc->block, but emitting just
* the state-machine state is enough for clients to track lifecycle
* transitions. Keep the strings stable -- they're a wire-API
* commitment once shipped.
*
* No `default:` on purpose: a new SVC_*_STATE added to svc.h must
* also pick a wire name here, and -Wall (-Wswitch) flags the
* missing case.
*/
static const char *state_name(svc_state_t s)
{
switch (s) {
case SVC_HALTED_STATE: return "halted";
case SVC_DONE_STATE: return "done";
case SVC_DEAD_STATE: return "dead";
case SVC_CLEANUP_STATE: return "cleanup";
case SVC_TEARDOWN_STATE: return "teardown";
case SVC_STOPPING_STATE: return "stopping";
case SVC_SETUP_STATE: return "setup";
case SVC_PAUSED_STATE: return "paused";
case SVC_WAITING_STATE: return "waiting";
case SVC_STARTING_STATE: return "starting";
case SVC_RUNNING_STATE: return "running";
}
return "unknown";
}
void dbus_notify_service_state(svc_t *svc, int old_state, int new_state)
{
uint8_t body[256];
link_writer_t w;
struct peer *p;
char ident[MAX_IDENT_LEN];
ssize_t blen;
svc_state_t o = (svc_state_t)old_state;
svc_state_t n = (svc_state_t)new_state;
if (!server || !svc)
return;
if (TAILQ_EMPTY(&peers))
return; /* nobody could possibly be listening */
svc_ident(svc, ident, sizeof(ident));
link_writer_init(&w, body, sizeof(body));
link_w_string(&w, ident);
link_w_string(&w, state_name(o));
link_w_string(&w, state_name(n));
blen = link_writer_finish(&w);
if (blen < 0)
return;
TAILQ_FOREACH(p, &peers, link)
(void)link_connection_emit_signal(p->conn,
"/org/finit/manager",
"org.finit.Manager1",
"ServiceStateChanged",
"sss",
body, (size_t)blen);
}
/* ---------- org.finit.Cond1 ---------- */
#define COND_PATH_OBJECT "/org/finit/cond"
#define COND_INTERFACE "org.finit.Cond1"
/* Cond1.Set/Clear refuse anything that isn't a usr/ condition --
* pid/, sys/, hook/ are owned by Finit's state machine and giving
* clients write access there would let them corrupt service state.
* Bare names ("foo") are normalised to "usr/foo" the same way
* initctl does. The returned pointer is valid for the duration
* of the caller's stack frame (`buf` must be at least 128 bytes). */
static const char *normalise_usr_cond(const char *name, char *buf, size_t bufsz)
{
const char *tail;
if (!name || !*name)
return NULL;
if (strchr(name, '.'))
return NULL;
if (strchr(name, '/')) {
if (strncmp(name, "usr/", 4) != 0)
return NULL;
tail = name + 4;
/* Match initctl's policy: no further slashes in the tail,
* and no empty tail ("usr/" alone). */
if (!*tail || strchr(tail, '/'))
return NULL;
if (strlen(name) >= bufsz)
return NULL;
memcpy(buf, name, strlen(name) + 1);
return buf;
}
if ((size_t)snprintf(buf, bufsz, "usr/%s", name) >= bufsz)
return NULL;
return buf;
}
/* Reject names that would escape /run/finit/cond/. cond_get(name)
* boils down to fopen(_PATH_COND + name), so without this check any
* caller can make PID 1 open arbitrary files -- a path traversal
* primitive that also stalls PID 1 if pointed at a FIFO or a slow
* device. Legal cond names look like "usr/foo", "pid/sshd",
* "service/keventd/ready"; no leading slash, no ".." segment. */
static int cond_name_valid(const char *name)
{
const char *p;
if (!name || !*name || *name == '/')
return 0;
for (p = name; *p; p++) {
if (*p == '.' && p[1] == '.' &&
(p[2] == '\0' || p[2] == '/'))
return 0;
}
return 1;
}
static int cond1_get(link_call_t *call, void *userdata)
{
const char *name;
link_writer_t *w;
(void)userdata;
if (link_call_read_string(call, &name) < 0)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"expected (s)");
if (!cond_name_valid(name))
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"invalid condition name");
w = link_call_reply(call);
if (!w)
return -1;
link_w_string(w, condstr(cond_get(name)));
return 0;
}
static int cond1_set_or_clear(link_call_t *call, int do_set)
{
const char *name;
char buf[128];
const char *full;
if (link_call_read_string(call, &name) < 0)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"expected (s)");
full = normalise_usr_cond(name, buf, sizeof(buf));
if (!full)
return link_call_reply_error(call,
"org.freedesktop.DBus.Error.InvalidArgs",
"Set/Clear is restricted to usr/* conditions");
if (do_set)
/* cond_set_oneshot, not cond_set: a user-asserted condition
* is a symlink to _PATH_RECONF, so it tracks the reconf
* generation automatically and stays "on" across reloads
* and runlevel switches. cond_set() writes a fixed
* generation that goes "flux" on the next reload -- wrong
* semantics for user conditions, and what initctl cond set
* has done forever via the filesystem path. */
cond_set_oneshot(full);
else
cond_clear(full);
(void)link_call_reply(call);
return 0;
}
static int cond1_set (link_call_t *c, void *u) { (void)u; return cond1_set_or_clear(c, 1); }
static int cond1_clear(link_call_t *c, void *u) { (void)u; return cond1_set_or_clear(c, 0); }
/* nftw() can't pass user data so a single static handle ferries the
* writer into the callback. Safe because dispatch is single-threaded. */
static link_writer_t *cond_walk_writer;
static int cond_walk_dump;
static int cond_walk_cb(const char *fpath, const struct stat *sb,
int tflag, struct FTW *ftwbuf)
{
const char *name;
const char *state;
size_t prefix_len;
(void)sb;
(void)ftwbuf;
if (tflag != FTW_F)
return 0;
if (!strcmp(fpath, _PATH_RECONF))
return 0;
prefix_len = strlen(_PATH_COND);
if (strlen(fpath) <= prefix_len)
return 0;
name = fpath + prefix_len;
if (cond_walk_dump) {
state = condstr(cond_get_path(fpath));
link_w_struct_begin(cond_walk_writer);
link_w_string(cond_walk_writer, name);
link_w_string(cond_walk_writer, state);
link_w_struct_end(cond_walk_writer);
} else {
link_w_string(cond_walk_writer, name);
}
return 0;
}
static int cond1_list(link_call_t *call, void *userdata)
{
link_writer_t *w;
(void)userdata;
w = link_call_reply(call);
if (!w)
return -1;
link_w_array_begin(w, 's');
cond_walk_writer = w;
cond_walk_dump = 0;
(void)nftw(_PATH_COND, cond_walk_cb, 20, 0);
cond_walk_writer = NULL;
link_w_array_end(w);
return 0;
}
static int cond1_dump(link_call_t *call, void *userdata)
{
link_writer_t *w;
(void)userdata;
w = link_call_reply(call);
if (!w)
return -1;
link_w_array_begin(w, '(');
cond_walk_writer = w;
cond_walk_dump = 1;
(void)nftw(_PATH_COND, cond_walk_cb, 20, 0);
cond_walk_writer = NULL;
link_w_array_end(w);
return 0;
}
static const link_method_t cond_methods[] = {
{ .name = "Get", .in_sig = "s", .out_sig = "s",
.handler = cond1_get },
{ .name = "Set", .in_sig = "s", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = cond1_set },
{ .name = "Clear", .in_sig = "s", .out_sig = "",
.flags = LINK_METHOD_PRIVILEGED, .handler = cond1_clear },
{ .name = "List", .in_sig = "", .out_sig = "as",
.handler = cond1_list },
{ .name = "Dump", .in_sig = "", .out_sig = "a(ss)",
.handler = cond1_dump },
{ NULL, NULL, NULL, 0, NULL }
};
static const link_vtable_t cond_vtable = {
.interface = COND_INTERFACE,
.methods = cond_methods,
};
/* ---------- signal emission: ConditionChanged ---------- */
void dbus_notify_condition_change(const char *name, const char *state)
{
uint8_t body[256];
link_writer_t w;
struct peer *p;
ssize_t blen;
if (!server || !name || !state)
return;
if (TAILQ_EMPTY(&peers))
return;
link_writer_init(&w, body, sizeof(body));
link_w_string(&w, name);
link_w_string(&w, state);
blen = link_writer_finish(&w);
if (blen < 0)
return;
TAILQ_FOREACH(p, &peers, link)
(void)link_connection_emit_signal(p->conn,
COND_PATH_OBJECT,
COND_INTERFACE,
"ConditionChanged",
"ss",
body, (size_t)blen);
}
/* ---------- init / exit ---------- */
int dbus_init(uev_ctx_t *ctx)
{
dbg("Setting up D-Bus listening socket at %s ...", FINIT_BUS_SOCKET);
if (link_server_new(&server, FINIT_BUS_SOCKET) < 0) {
err(1, "Failed binding D-Bus socket %s", FINIT_BUS_SOCKET);
return 1;
}
if (link_server_add_object(server, "/org/finit/manager",
&manager_vtable, NULL) < 0) {
err(1, "Failed registering Manager1 object");
link_server_free(server);
server = NULL;
return 1;
}
if (link_server_add_object(server, COND_PATH_OBJECT,
&cond_vtable, NULL) < 0) {
err(1, "Failed registering Cond1 object");
link_server_free(server);
server = NULL;
return 1;
}
if (uev_io_init(ctx, &accept_watcher, accept_cb, NULL,
link_server_get_fd(server), UEV_READ)) {
err(1, "Failed registering D-Bus accept watcher");
link_server_free(server);
server = NULL;
return 1;
}
/* Register Service1 objects for every service already loaded.
* Subsequent svc_new()/svc_del() calls into
* dbus_register_service()/dbus_unregister_service(). */
{
svc_t *iter = NULL;
svc_t *svc;
for (svc = svc_iterator(&iter, 1); svc;
svc = svc_iterator(&iter, 0))
dbus_register_service(svc);
}
return 0;
}
int dbus_exit(void)
{
struct peer *p;
uev_io_stop(&accept_watcher);
while ((p = TAILQ_FIRST(&peers)))
peer_drop(p);
if (server) {
link_server_free(server);
server = NULL;
}
return 0;
}
#endif /* HAVE_DBUS */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+5
View File
@@ -780,6 +780,11 @@ int main(int argc, char *argv[])
dbg("Starting initctl API responder ...");
api_init(&loop);
#ifdef HAVE_DBUS
dbg("Starting D-Bus listener ...");
dbus_init(&loop);
#endif
dbg("Starting service interval monitor ...");
service_init(&loop);
+1
View File
@@ -71,6 +71,7 @@
#define BUF_SIZE 4096
#define INIT_SOCKET _PATH_VARRUN "finit/socket"
#define FINIT_BUS_SOCKET _PATH_VARRUN "finit/bus"
#define INIT_MAGIC 0x03091969
#define INIT_LEVEL 10
+130 -6
View File
@@ -268,19 +268,105 @@ static int do_startstop(int cmd, char *arg)
return do_svc(cmd, arg);
}
static int do_start (char *arg) { return do_startstop(INIT_CMD_START_SVC, arg); }
static int do_stop (char *arg) { return do_startstop(INIT_CMD_STOP_SVC, arg); }
#ifdef HAVE_DBUS
#include "link.h"
/* Try the D-Bus path for a Manager1 method. Returns:
* 0 succeeded via D-Bus
* 1 D-Bus replied with an error -- callers should error out
* -1 D-Bus not reachable -- callers should fall back to the
* legacy INIT_SOCKET transport
*
* On D-Bus error replies the function maps the org.* error name to
* the same exit code initctl historically printed for that case
* (e.g. NoSuchService -> 69 with the legacy message). */
static int try_dbus_manager(const char *method, const char *arg_sig,
const char *arg)
{
link_client_t *c;
const char *err;
int rc;
c = link_client_open(FINIT_BUS_SOCKET);
if (!c)
return -1;
/* "s" methods take the service identity (arg may be NULL ->
* empty string); void methods pass no body. */
if (arg_sig && !strcmp(arg_sig, "s"))
rc = link_client_call_v(c, "/org/finit/manager",
"org.finit.Manager1", method,
"s", arg ? arg : "");
else if (!arg_sig || !*arg_sig)
rc = link_client_call_v(c, "/org/finit/manager",
"org.finit.Manager1", method, NULL);
else
rc = LINK_CALL_FAIL;
if (rc == LINK_CALL_ERROR) {
const link_reply_t *r = link_client_reply(c);
err = (r && r->error_name) ? r->error_name : "";
/* Exact match on the fully-qualified error name; substring
* matching would misfire on a future name that contains
* one of these as a substring. */
if (!strcmp(err, "org.finit.Error.NoSuchService")) {
link_client_close(c);
ERRX(noerr ? 0 : 69, "no such task or service(s): %s",
arg ? arg : "");
}
if (!strcmp(err, "org.freedesktop.DBus.Error.AccessDenied")) {
link_client_close(c);
ERRX(1, "permission denied: %s requires root", method);
}
link_client_close(c);
ERRX(1, "%s: %s", method, *err ? err : "D-Bus error");
}
link_client_close(c);
if (rc == LINK_CALL_OK)
return 0;
return -1; /* LINK_CALL_FAIL or anything else: fall back */
}
#endif /* HAVE_DBUS */
static int do_start (char *arg)
{
#ifdef HAVE_DBUS
int rc = try_dbus_manager("Start", "s", arg);
if (rc >= 0) return rc;
#endif
return do_startstop(INIT_CMD_START_SVC, arg);
}
static int do_stop (char *arg)
{
#ifdef HAVE_DBUS
int rc = try_dbus_manager("Stop", "s", arg);
if (rc >= 0) return rc;
#endif
return do_startstop(INIT_CMD_STOP_SVC, arg);
}
static int do_reload (char *arg)
{
if (!arg || !arg[0])
if (!arg || !arg[0]) {
#ifdef HAVE_DBUS
int rc = try_dbus_manager("Reload", "", NULL);
if (rc >= 0) return rc;
#endif
return do_svc(INIT_CMD_RELOAD, NULL);
}
return do_startstop(INIT_CMD_RELOAD_SVC, arg);
}
static int do_restart(char *arg)
{
#ifdef HAVE_DBUS
int rc = try_dbus_manager("Restart", "s", arg);
if (rc == 0) return 0;
if (rc == 1) ERRX(noerr ? 0 : 7, "failed restarting %s", arg);
#endif
if (do_startstop(INIT_CMD_RESTART_SVC, arg))
ERRX(noerr ? 0 : 7, "failed restarting %s", arg);
@@ -643,9 +729,47 @@ static int do_cmd(int cmd)
return 0;
}
int do_reboot (char *arg) { return do_cmd(INIT_CMD_REBOOT); }
int do_halt (char *arg) { return do_cmd(INIT_CMD_HALT); }
int do_poweroff(char *arg) { return do_cmd(INIT_CMD_POWEROFF); }
#ifdef HAVE_DBUS
static int do_reboot_dbus(const char *method)
{
int rc = try_dbus_manager(method, "", NULL);
if (rc == 0) {
sleep(5); /* match legacy: wait for finit to shut down */
return 0;
}
return rc; /* 1 = error, -1 = fall back */
}
#endif
int do_reboot (char *arg)
{
#ifdef HAVE_DBUS
int rc = do_reboot_dbus("Reboot");
if (rc >= 0) return rc;
#endif
return do_cmd(INIT_CMD_REBOOT);
}
int do_halt (char *arg)
{
#ifdef HAVE_DBUS
int rc = do_reboot_dbus("Halt");
if (rc >= 0) return rc;
#endif
return do_cmd(INIT_CMD_HALT);
}
int do_poweroff(char *arg)
{
#ifdef HAVE_DBUS
int rc = do_reboot_dbus("Poweroff");
if (rc >= 0) return rc;
#endif
return do_cmd(INIT_CMD_POWEROFF);
}
/* Suspend has no Manager1 equivalent yet; uses the legacy IPC. */
int do_suspend (char *arg) { return do_cmd(INIT_CMD_SUSPEND); }
/**
+9
View File
@@ -45,6 +45,15 @@ extern uev_ctx_t *ctx;
int api_init (uev_ctx_t *ctx);
int api_exit (void);
#ifdef HAVE_DBUS
int dbus_init (uev_ctx_t *ctx);
int dbus_exit (void);
void dbus_register_service (svc_t *svc);
void dbus_unregister_service (svc_t *svc);
void dbus_notify_service_state (svc_t *svc, int old_state, int new_state);
void dbus_notify_condition_change(const char *name, const char *state);
#endif
void conf_flush_events(void);
void service_monitor (pid_t lost, int status);
+59 -7
View File
@@ -1443,17 +1443,65 @@ int service_stop(svc_t *svc)
}
/**
* service_reload - Reload a service
* service_reload - Request reload of a service, driven by the state machine
* @svc: Service to reload
*
* This function does some basic checks of the runtime state of Finit
* and a sanity check of the @svc before sending %SIGHUP or calling
* the reload:script command.
* Mark a service dirty so the state machine will (re-)apply its
* configuration, then advance the state machine. For services that
* don't handle SIGHUP this also clears the readiness condition and
* the pidfile/none-notify readiness flag so dependents are properly
* notified once the service comes back.
*
* Returns:
* POSIX OK(0) on success, non-zero if @svc is NULL.
*/
int service_reload(svc_t *svc)
{
if (!svc)
return 1;
if (svc_is_blocked(svc))
svc_start(svc);
else
service_timeout_cancel(svc);
/*
* Clear conditions before reload to ensure dependent services
* are properly updated. Only needed when the service does NOT
* support SIGHUP (noreload), because then it will be stopped
* and restarted, so conditions genuinely go away. When the
* service handles SIGHUP, its PID and pidfile persist, so the
* condition stays valid and dependents should not be disrupted.
*
* Note: only clear 'ready' for services where the pidfile
* inotify handler reasserts it (pid/none). For s6/systemd
* services readiness relies on their respective notification
* mechanism which may not re-trigger on SIGHUP.
*/
if (svc_is_noreload(svc)) {
svc_cond_clear(svc);
if (svc->notify == SVC_NOTIFY_PID || svc->notify == SVC_NOTIFY_NONE)
service_ready(svc, 0);
}
svc_mark_dirty(svc);
service_step(svc);
return 0;
}
/**
* service_reload_apply - Perform the actual reload of a running service
* @svc: Service to reload
*
* Low-level reload step: sends %SIGHUP or runs the reload:script
* command. Called by the state machine when a service marked
* reload-pending by service_reload() reaches the right state.
*
* Returns:
* POSIX OK(0) or non-zero on error.
*/
static int service_reload(svc_t *svc)
static int service_reload_apply(svc_t *svc)
{
const char *id = svc_ident(svc, NULL, 0);
int do_progress = 1;
@@ -3015,6 +3063,10 @@ static void svc_set_state(svc_t *svc, svc_state_t new_state)
return;
*state = new_state;
#ifdef HAVE_DBUS
dbus_notify_service_state(svc, old_state, new_state);
#endif
/*
* The unit has stopped: HALTED comes after any post:/cleanup:
* script, DONE is a completed run/task, where remain-after-exit
@@ -3384,7 +3436,7 @@ restart:
if (sm_in_reload())
break;
service_reload(svc);
service_reload_apply(svc);
}
svc_mark_clean(svc);
@@ -3422,7 +3474,7 @@ restart:
if (svc_is_noreload(svc))
service_stop(svc);
else
service_reload(svc);
service_reload_apply(svc);
break;
}
+1
View File
@@ -59,6 +59,7 @@ void service_forked (svc_t *svc);
void service_ready (svc_t *svc, int ready);
int service_stop (svc_t *svc);
int service_reload (svc_t *svc);
int service_step (svc_t *svc);
void service_step_all (int types);
void service_worker (void *unused);
+9
View File
@@ -38,6 +38,7 @@
#include "finit.h"
#include "conf.h"
#include "private.h"
#include "svc.h"
#include "helpers.h"
#include "pid.h"
@@ -153,6 +154,10 @@ svc_t *svc_new(char *cmd, char *name, char *id, int type)
TAILQ_INSERT_TAIL(&svc_list, svc, link);
#ifdef HAVE_DBUS
dbus_register_service(svc);
#endif
return svc;
}
@@ -170,6 +175,10 @@ static struct wq work = {
*/
int svc_del(svc_t *svc)
{
#ifdef HAVE_DBUS
dbus_unregister_service(svc);
#endif
TAILQ_REMOVE(&svc_list, svc, link);
TAILQ_INSERT_TAIL(&gc_list, svc, link);