mirror of
https://github.com/troglobit/finit.git
synced 2026-10-02 05:52:48 +07:00
libink was written peer to peer, where one connection is one client and one principal. Attaching to a message bus breaks both halves of that, and two things followed from it. Signals never reached the system bus. Fan-out is gated on the peer having sent AddMatch, but a broker subscribes for its own clients and never sends us one, so every ServiceStateChanged was dropped on the floor. A connection attached with LINK_ATTACH_BROKER gets them all. Hello, AddMatch and RemoveMatch write per-connection state. Shared by every caller behind a broker, that lets one sender exhaust the match cap or drop another's rule, so we leave all three to the bus, whose job they are. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1419 lines
39 KiB
C
1419 lines
39 KiB
C
/* 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 <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.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"
|
|
#include "util.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 struct peer *sysbus_peer;
|
|
|
|
static void sysbus_probe(void);
|
|
|
|
static void peer_drop(struct peer *p)
|
|
{
|
|
int was_sysbus = p == sysbus_peer;
|
|
|
|
uev_io_stop(&p->watcher);
|
|
link_connection_close(p->conn);
|
|
TAILQ_REMOVE(&peers, p, link);
|
|
peer_count--;
|
|
free(p);
|
|
|
|
/* broker gone; the notify paths probe for its return */
|
|
if (was_sysbus)
|
|
sysbus_peer = NULL;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* Wrap an authenticated connection in a struct peer, insert into the
|
|
* peer list, and register an event-loop watcher. Enforces
|
|
* DBUS_MAX_PEERS. Closes the connection and returns NULL on failure.
|
|
* Used by both the accept path and the system-bus attach path. */
|
|
static struct peer *peer_register(uev_ctx_t *ctx, link_connection_t *conn)
|
|
{
|
|
struct peer *p;
|
|
|
|
if (peer_count >= DBUS_MAX_PEERS) {
|
|
logit(LOG_WARNING, "D-Bus peer cap reached (%zu), dropping",
|
|
peer_count);
|
|
link_connection_close(conn);
|
|
return NULL;
|
|
}
|
|
|
|
p = calloc(1, sizeof(*p));
|
|
if (!p) {
|
|
link_connection_close(conn);
|
|
return NULL;
|
|
}
|
|
|
|
p->conn = conn;
|
|
TAILQ_INSERT_TAIL(&peers, p, link);
|
|
peer_count++;
|
|
|
|
if (uev_io_init(ctx, &p->watcher, peer_cb, p,
|
|
link_connection_get_fd(conn), UEV_READ)) {
|
|
peer_drop(p);
|
|
return NULL;
|
|
}
|
|
return 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;
|
|
|
|
if (link_server_accept(server, &conn) < 0) {
|
|
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
|
err(1, "Failed accepting D-Bus client");
|
|
break;
|
|
}
|
|
|
|
if (!peer_register(w->ctx, conn))
|
|
continue; /* logged inside */
|
|
}
|
|
}
|
|
|
|
/* ---------- 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 *);
|
|
void *udata;
|
|
int matched;
|
|
};
|
|
|
|
static int dispatch_found(svc_t *svc, void *udata)
|
|
{
|
|
struct dispatch_ctx *ctx = udata;
|
|
|
|
ctx->matched++;
|
|
return ctx->action(svc, ctx->udata);
|
|
}
|
|
|
|
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(svc, udata)` 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 *), void *udata)
|
|
{
|
|
char buf[MAX_IDENT_LEN];
|
|
struct dispatch_ctx ctx = { .action = action, .udata = udata };
|
|
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;
|
|
int rc;
|
|
|
|
if (link_call_read_string(call, &ident) < 0)
|
|
return link_call_reply_error(call,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"expected (s)");
|
|
|
|
rc = dispatch_action(ident, action, NULL);
|
|
if (rc < 0)
|
|
return link_call_reply_error(call,
|
|
"org.finit.Error.NoSuchService", ident);
|
|
if (rc)
|
|
return link_call_reply_error(call,
|
|
"org.finit.Error.Failed",
|
|
"failed on matched service(s)");
|
|
|
|
(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 int manager_set_debug(link_call_t *call, void *u)
|
|
{
|
|
(void)u;
|
|
log_debug();
|
|
(void)link_call_reply(call);
|
|
return 0;
|
|
}
|
|
|
|
static int signal_one(svc_t *svc, void *udata)
|
|
{
|
|
int signo = *(int *)udata;
|
|
|
|
/* Silently skip stopped services -- a multi-match ident
|
|
* (e.g. "sshd:*") should not fail the whole call just because
|
|
* one of the matches happens to be in a halted state. */
|
|
if (!svc_is_running(svc))
|
|
return 0;
|
|
return !!kill(svc->pid, signo);
|
|
}
|
|
|
|
static int manager_signal(link_call_t *call, void *u)
|
|
{
|
|
const char *ident;
|
|
uint32_t signo;
|
|
int sig, rc;
|
|
|
|
(void)u;
|
|
if (link_call_read_string(call, &ident) < 0 ||
|
|
link_call_read_u32 (call, &signo) < 0)
|
|
return link_call_reply_error(call,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"expected (s, u)");
|
|
/* Match the upper bound `initctl signal` allows (1..31). RT
|
|
* signals are a future story; keep both sides in lockstep so
|
|
* users see the same range regardless of transport. */
|
|
if (signo == 0 || signo > 31)
|
|
return link_call_reply_error(call,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"signal out of range (1..31)");
|
|
|
|
sig = (int)signo;
|
|
rc = dispatch_action(ident, signal_one, &sig);
|
|
if (rc < 0)
|
|
return link_call_reply_error(call,
|
|
"org.finit.Error.NoSuchService", ident);
|
|
if (rc)
|
|
return link_call_reply_error(call,
|
|
"org.finit.Error.Failed",
|
|
"failed signalling matched service(s)");
|
|
|
|
(void)link_call_reply(call);
|
|
return 0;
|
|
}
|
|
|
|
static int manager_suspend(link_call_t *call, void *u)
|
|
{
|
|
(void)u;
|
|
sync();
|
|
if (suspend() < 0) {
|
|
const char *msg = (errno == EINVAL)
|
|
? "Kernel does not support suspend to RAM"
|
|
: strerror(errno);
|
|
return link_call_reply_error(call,
|
|
"org.finit.Error.Failed", msg);
|
|
}
|
|
(void)link_call_reply(call);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------- Manager1 properties ----------
|
|
*
|
|
* Read-only string properties exposed via the standard
|
|
* org.freedesktop.DBus.Properties interface. Getters write a
|
|
* variant containing a single string. */
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
char buf[8];
|
|
|
|
(void)u;
|
|
link_w_string(w, runlevel_encode(runlevel, buf, sizeof(buf)));
|
|
return 0;
|
|
}
|
|
|
|
static int prop_prevrunlevel(link_writer_t *w, void *u)
|
|
{
|
|
char buf[8];
|
|
|
|
(void)u;
|
|
if (prevlevel <= 0 || prevlevel > 9)
|
|
strlcpy(buf, "N", sizeof(buf));
|
|
else
|
|
snprintf(buf, sizeof(buf), "%d", prevlevel);
|
|
link_w_string(w, buf);
|
|
return 0;
|
|
}
|
|
|
|
static int prop_version(link_writer_t *w, void *u)
|
|
{
|
|
(void)u;
|
|
link_w_string(w, PACKAGE_VERSION);
|
|
return 0;
|
|
}
|
|
|
|
static const link_property_t manager_properties[] = {
|
|
{ .name = "Runlevel", .sig = "s", .getter = prop_runlevel },
|
|
{ .name = "PrevRunlevel", .sig = "s", .getter = prop_prevrunlevel },
|
|
{ .name = "Version", .sig = "s", .getter = prop_version },
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
|
|
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 },
|
|
{ .name = "Suspend", .in_sig = "", .out_sig = "",
|
|
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_suspend },
|
|
{ .name = "SetDebug", .in_sig = "", .out_sig = "",
|
|
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_set_debug },
|
|
{ .name = "Signal", .in_sig = "su", .out_sig = "",
|
|
.flags = LINK_METHOD_PRIVILEGED, .handler = manager_signal },
|
|
{ NULL, NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static const link_vtable_t manager_vtable = {
|
|
.interface = "org.finit.Manager1",
|
|
.methods = manager_methods,
|
|
.properties = manager_properties,
|
|
};
|
|
|
|
/* ---------- 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 }
|
|
};
|
|
|
|
/*
|
|
* Getters write the bare value; the framework emits the variant
|
|
* signature from the table below. `State` deliberately uses the
|
|
* initctl status vocabulary from svc_status(), not the coarser
|
|
* ServiceStateChanged strings -- a client that only tracks edges
|
|
* has the signal, a client that asks gets the full story.
|
|
*/
|
|
#define SVC_PROP_STR(fn, field) \
|
|
static int fn(link_writer_t *w, void *arg) \
|
|
{ \
|
|
link_w_string(w, ((svc_t *)arg)->field); \
|
|
return 0; \
|
|
}
|
|
|
|
#define SVC_PROP_U32(fn, field) \
|
|
static int fn(link_writer_t *w, void *arg) \
|
|
{ \
|
|
link_w_u32(w, (uint32_t)((svc_t *)arg)->field); \
|
|
return 0; \
|
|
}
|
|
|
|
#define SVC_PROP_BOOL(fn, field) \
|
|
static int fn(link_writer_t *w, void *arg) \
|
|
{ \
|
|
link_w_bool(w, ((svc_t *)arg)->field); \
|
|
return 0; \
|
|
}
|
|
|
|
static int svc_prop_identity(link_writer_t *w, void *arg)
|
|
{
|
|
link_w_string(w, svc_ident(arg, NULL, 0));
|
|
return 0;
|
|
}
|
|
|
|
static int svc_prop_state(link_writer_t *w, void *arg)
|
|
{
|
|
link_w_string(w, svc_status(arg));
|
|
return 0;
|
|
}
|
|
|
|
static int svc_prop_type(link_writer_t *w, void *arg)
|
|
{
|
|
link_w_string(w, svc_typestr((svc_t *)arg));
|
|
return 0;
|
|
}
|
|
|
|
static int svc_prop_command(link_writer_t *w, void *arg)
|
|
{
|
|
svc_t *svc = arg;
|
|
char buf[512];
|
|
|
|
compose_cmdline(svc, buf, sizeof(buf));
|
|
if (svc_is_sysv(svc)) {
|
|
strlcat(buf, " ", sizeof(buf));
|
|
strlcat(buf, svc->state == SVC_HALTED_STATE
|
|
? "stop" : "start", sizeof(buf));
|
|
}
|
|
|
|
link_w_string(w, buf);
|
|
return 0;
|
|
}
|
|
|
|
static int svc_prop_pid(link_writer_t *w, void *arg)
|
|
{
|
|
svc_t *svc = arg;
|
|
|
|
link_w_u32(w, svc->pid > 0 ? (uint32_t)svc->pid : 0);
|
|
return 0;
|
|
}
|
|
|
|
static int svc_prop_restarts(link_writer_t *w, void *arg)
|
|
{
|
|
svc_t *svc = arg;
|
|
|
|
link_w_u32(w, svc->restart_cnt > 0 ? (uint32_t)svc->restart_cnt : 0);
|
|
return 0;
|
|
}
|
|
|
|
static int svc_prop_uptime(link_writer_t *w, void *arg)
|
|
{
|
|
svc_t *svc = arg;
|
|
long up = 0;
|
|
|
|
if (svc->pid > 0) {
|
|
up = jiffies() - svc->start_time;
|
|
if (up < 0)
|
|
up = 0;
|
|
}
|
|
link_w_u32(w, (uint32_t)up);
|
|
return 0;
|
|
}
|
|
|
|
SVC_PROP_STR (svc_prop_name, name)
|
|
SVC_PROP_STR (svc_prop_desc, desc)
|
|
SVC_PROP_STR (svc_prop_conditions, cond)
|
|
SVC_PROP_STR (svc_prop_origin, file)
|
|
SVC_PROP_STR (svc_prop_environ, env)
|
|
SVC_PROP_STR (svc_prop_pidfile, pidfile)
|
|
SVC_PROP_STR (svc_prop_user, username)
|
|
SVC_PROP_STR (svc_prop_group, group)
|
|
SVC_PROP_U32 (svc_prop_runlevels, runlevels)
|
|
SVC_PROP_U32 (svc_prop_exitstatus, status)
|
|
SVC_PROP_U32 (svc_prop_restarts_tot, restart_tot)
|
|
SVC_PROP_U32 (svc_prop_restart_max, restart_max)
|
|
SVC_PROP_U32 (svc_prop_starts, once)
|
|
SVC_PROP_BOOL(svc_prop_manual, manual)
|
|
SVC_PROP_BOOL(svc_prop_forking, forking)
|
|
SVC_PROP_BOOL(svc_prop_started, started)
|
|
|
|
static const link_property_t service_properties[] = {
|
|
{ .name = "Identity", .sig = "s", .getter = svc_prop_identity },
|
|
{ .name = "Name", .sig = "s", .getter = svc_prop_name },
|
|
{ .name = "State", .sig = "s", .getter = svc_prop_state },
|
|
{ .name = "Pid", .sig = "u", .getter = svc_prop_pid },
|
|
{ .name = "RestartCount", .sig = "u", .getter = svc_prop_restarts },
|
|
{ .name = "Runlevels", .sig = "u", .getter = svc_prop_runlevels },
|
|
{ .name = "Description", .sig = "s", .getter = svc_prop_desc },
|
|
{ .name = "Command", .sig = "s", .getter = svc_prop_command },
|
|
{ .name = "Conditions", .sig = "s", .getter = svc_prop_conditions },
|
|
{ .name = "Type", .sig = "s", .getter = svc_prop_type },
|
|
{ .name = "Origin", .sig = "s", .getter = svc_prop_origin },
|
|
{ .name = "Environment", .sig = "s", .getter = svc_prop_environ },
|
|
{ .name = "PidFile", .sig = "s", .getter = svc_prop_pidfile },
|
|
{ .name = "User", .sig = "s", .getter = svc_prop_user },
|
|
{ .name = "Group", .sig = "s", .getter = svc_prop_group },
|
|
{ .name = "Uptime", .sig = "u", .getter = svc_prop_uptime },
|
|
{ .name = "ExitStatus", .sig = "u", .getter = svc_prop_exitstatus },
|
|
{ .name = "RestartsTotal",.sig = "u", .getter = svc_prop_restarts_tot },
|
|
{ .name = "RestartMax", .sig = "u", .getter = svc_prop_restart_max },
|
|
{ .name = "Starts", .sig = "u", .getter = svc_prop_starts },
|
|
{ .name = "ManualStart", .sig = "b", .getter = svc_prop_manual },
|
|
{ .name = "Forking", .sig = "b", .getter = svc_prop_forking },
|
|
{ .name = "Started", .sig = "b", .getter = svc_prop_started },
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
|
|
static const link_vtable_t service_vtable = {
|
|
.interface = "org.finit.Service1",
|
|
.methods = service_methods,
|
|
.properties = service_properties,
|
|
};
|
|
|
|
/* 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 fan-out helper ----------
|
|
*
|
|
* Fan out a pre-marshalled signal body to every connected peer,
|
|
* letting each connection apply its AddMatch filter. Short-circuits
|
|
* when no peers are connected so dbus_notify_* callers don't have
|
|
* to inspect that state themselves. */
|
|
static void dbus_emit_signal(const char *path,
|
|
const char *interface,
|
|
const char *member,
|
|
const char *signature,
|
|
const uint8_t *body, size_t body_len)
|
|
{
|
|
struct peer *p, *tmp;
|
|
|
|
if (!server || TAILQ_EMPTY(&peers))
|
|
return;
|
|
TAILQ_FOREACH_SAFE(p, &peers, link, tmp) {
|
|
if (link_connection_emit_signal(p->conn,
|
|
path, interface, member,
|
|
signature, body, body_len) < 0) {
|
|
/* nothing hit the wire, and same for every peer */
|
|
if (errno == EMSGSIZE || errno == EINVAL)
|
|
break;
|
|
logit(LOG_WARNING, "D-Bus peer fd %d write failed: "
|
|
"%s, dropping",
|
|
link_connection_get_fd(p->conn),
|
|
strerror(errno));
|
|
peer_drop(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------- 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;
|
|
char ident[MAX_IDENT_LEN];
|
|
char path[FINIT_SVC_PATH_MAX];
|
|
ssize_t blen;
|
|
|
|
if (!svc)
|
|
return;
|
|
|
|
sysbus_probe();
|
|
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
|
|
link_writer_init(&w, body, sizeof(body));
|
|
link_w_string(&w, ident);
|
|
link_w_string(&w, state_name((svc_state_t)old_state));
|
|
link_w_string(&w, state_name((svc_state_t)new_state));
|
|
blen = link_writer_finish(&w);
|
|
if (blen < 0)
|
|
return;
|
|
|
|
dbus_emit_signal("/org/finit/manager", "org.finit.Manager1",
|
|
"ServiceStateChanged", "sss", body, (size_t)blen);
|
|
|
|
/*
|
|
* Dual emission: Properties-aware clients track one object via
|
|
* the standard PropertiesChanged instead of filtering the
|
|
* manager-wide signal. Volatile numerics are invalidated, not
|
|
* marshalled -- interested clients re-Get.
|
|
*/
|
|
if (service_path_for(svc, path, sizeof(path)) < 0)
|
|
return;
|
|
|
|
link_writer_init(&w, body, sizeof(body));
|
|
link_w_string(&w, "org.finit.Service1");
|
|
link_w_array_begin(&w, '{');
|
|
link_w_struct_begin(&w);
|
|
link_w_string(&w, "State");
|
|
link_w_variant_string(&w, svc_status(svc));
|
|
link_w_struct_end(&w);
|
|
link_w_array_end(&w);
|
|
link_w_array_begin(&w, 's');
|
|
link_w_string(&w, "Pid");
|
|
link_w_string(&w, "RestartCount");
|
|
link_w_array_end(&w);
|
|
blen = link_writer_finish(&w);
|
|
if (blen < 0)
|
|
return;
|
|
|
|
dbus_emit_signal(path, "org.freedesktop.DBus.Properties",
|
|
"PropertiesChanged", "sa{sv}as",
|
|
body, (size_t)blen);
|
|
}
|
|
|
|
/* ---------- signal emission: RunlevelChanged ----------
|
|
*
|
|
* Fired by sm.c right after the runlevel global flips. Body is
|
|
* (old, new) in the same runlevel(8) encoding as the Manager1
|
|
* Runlevel property: digits, "S", or "N". */
|
|
void dbus_notify_runlevel_change(int old_level, int new_level)
|
|
{
|
|
uint8_t body[64];
|
|
link_writer_t w;
|
|
char old_s[8], new_s[8];
|
|
ssize_t blen;
|
|
|
|
runlevel_encode(old_level, old_s, sizeof(old_s));
|
|
runlevel_encode(new_level, new_s, sizeof(new_s));
|
|
|
|
link_writer_init(&w, body, sizeof(body));
|
|
link_w_string(&w, old_s);
|
|
link_w_string(&w, new_s);
|
|
blen = link_writer_finish(&w);
|
|
if (blen < 0)
|
|
return;
|
|
|
|
dbus_emit_signal("/org/finit/manager", "org.finit.Manager1",
|
|
"RunlevelChanged", "ss", 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;
|
|
ssize_t blen;
|
|
|
|
if (!name || !state)
|
|
return;
|
|
|
|
sysbus_probe();
|
|
|
|
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;
|
|
|
|
dbus_emit_signal(COND_PATH_OBJECT, COND_INTERFACE,
|
|
"ConditionChanged", "ss", body, (size_t)blen);
|
|
}
|
|
|
|
/* ---------- system-bus attach (opportunistic) ----------
|
|
*
|
|
* If /var/run/dbus/system_bus_socket is reachable, libink connects to
|
|
* the system bus as a regular client, claims org.finit as a well-known
|
|
* name, then promotes the authenticated fd into a server-attached
|
|
* peer so the same vtables serve incoming method calls and outgoing
|
|
* signal fan-out reaches the system bus.
|
|
*
|
|
* peer_uid is set to (uid_t)-1 so LINK_METHOD_PRIVILEGED methods
|
|
* reject by default -- per-request sender uid lookup via
|
|
* GetConnectionUnixUser is a follow-up. Read-only methods
|
|
* (ListServices, Properties.Get, Introspect, ...) work as expected.
|
|
*
|
|
* A bounded SO_SNDTIMEO/SO_RCVTIMEO budget is applied via
|
|
* link_client_open_timeout so a hung dbus-daemon can't stall boot;
|
|
* once the connection is attached and flipped to non-blocking, those
|
|
* timeouts are silently inert. */
|
|
|
|
#define SYSTEM_BUS_PATH "/var/run/dbus/system_bus_socket"
|
|
#define FINIT_BUS_NAME "org.finit"
|
|
/* Budget for the synchronous AUTH + Hello + RequestName round-trips.
|
|
* If the system bus is alive but the daemon is wedged we'd rather
|
|
* give up after a couple of seconds than stall the rest of dbus_init
|
|
* (and through it, boot). */
|
|
#define SYSTEM_BUS_TIMEOUT_MS 2000
|
|
/* DBUS_NAME_FLAG_DO_NOT_QUEUE: fail fast if the name is taken
|
|
* (something else owns org.finit -- shouldn't happen and we'd
|
|
* rather log than silently sit in the queue). */
|
|
#define DBUS_NAME_FLAG_DO_NOT_QUEUE 0x04
|
|
|
|
/* sysbus_probe() re-runs on every service and condition change, so a
|
|
* broker that keeps refusing would repeat itself for every event, and
|
|
* before syslog is up each line is an open/write/close on /dev/kmsg.
|
|
* Say it once, then trace, until an attach succeeds. */
|
|
static int sysbus_warned;
|
|
|
|
#define sysbus_level() (sysbus_warned ? LOG_DEBUG : LOG_WARNING)
|
|
|
|
/* What the broker said, for the log. A refused call carries an error
|
|
* name; anything that failed below that has nothing to quote. */
|
|
static const char *sysbus_errstr(link_client_t *c)
|
|
{
|
|
const link_reply_t *r = link_client_reply(c);
|
|
|
|
return (r && r->error_name) ? r->error_name : "transport or parse failure";
|
|
}
|
|
|
|
static int sysbus_request_name(link_client_t *c)
|
|
{
|
|
const char *reason;
|
|
uint32_t result;
|
|
int rc;
|
|
|
|
rc = link_client_call_v(c, "/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus", "RequestName",
|
|
"su", FINIT_BUS_NAME,
|
|
(uint32_t)DBUS_NAME_FLAG_DO_NOT_QUEUE);
|
|
if (rc != LINK_CALL_OK)
|
|
reason = sysbus_errstr(c);
|
|
else if (link_reply_get_u32(link_client_reply(c), &result) < 0)
|
|
reason = "malformed RequestName reply";
|
|
else if (result != 1) /* 2/3/4 mean we did not get the name */
|
|
reason = "name already owned";
|
|
else
|
|
return 0;
|
|
|
|
logit(sysbus_level(), "Failed to claim %s on system bus: %s",
|
|
FINIT_BUS_NAME, reason);
|
|
sysbus_warned = 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int try_attach_system_bus(uev_ctx_t *ctx)
|
|
{
|
|
link_client_t *c;
|
|
link_connection_t *conn;
|
|
struct peer *p;
|
|
int rc;
|
|
|
|
c = link_client_open_timeout(SYSTEM_BUS_PATH, SYSTEM_BUS_TIMEOUT_MS);
|
|
if (!c) {
|
|
dbg("System bus unavailable at %s; skipping registration",
|
|
SYSTEM_BUS_PATH);
|
|
return -1;
|
|
}
|
|
|
|
/* Unlike the local bus, a broker routes by destination, and it
|
|
* drops anything not addressed to the driver before Hello. */
|
|
link_client_set_destination(c, "org.freedesktop.DBus");
|
|
|
|
rc = link_client_call_v(c, "/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus", "Hello", NULL);
|
|
if (rc != LINK_CALL_OK) {
|
|
logit(sysbus_level(), "System-bus Hello failed: %s",
|
|
sysbus_errstr(c));
|
|
sysbus_warned = 1;
|
|
link_client_close(c);
|
|
return -1;
|
|
}
|
|
|
|
if (sysbus_request_name(c) < 0) {
|
|
link_client_close(c);
|
|
return -1;
|
|
}
|
|
|
|
/* link_server_attach owns the fd from this point on whether it
|
|
* succeeds or fails, so the steal-then-attach pair has no leak
|
|
* window. */
|
|
conn = link_server_attach(server, link_client_steal_fd(c), (uid_t)-1,
|
|
LINK_ATTACH_BROKER);
|
|
if (!conn)
|
|
return -1;
|
|
|
|
p = peer_register(ctx, conn);
|
|
if (!p) {
|
|
logit(LOG_WARNING, "Failed registering system-bus peer");
|
|
return -1;
|
|
}
|
|
|
|
sysbus_peer = p;
|
|
sysbus_warned = 0; /* arm the warning for a later broker restart */
|
|
logit(LOG_NOTICE, "Registered %s on system bus", FINIT_BUS_NAME);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The broker is usually not up yet when dbus_init() runs -- it is
|
|
* typically a finit service itself -- and it may restart at any
|
|
* time. The service and condition notify paths call sysbus_probe()
|
|
* on every event: when org.finit is unclaimed and the broker's
|
|
* socket exists, one coalesced attach attempt is scheduled. This
|
|
* stays daemon-agnostic -- only the socket is probed, never a
|
|
* service name -- and the broker's own service transitions are what
|
|
* trigger it.
|
|
*/
|
|
static uev_t sysbus_tmr;
|
|
static int sysbus_tmr_up;
|
|
|
|
static void sysbus_probe_cb(uev_t *w, void *arg, int events)
|
|
{
|
|
(void)arg;
|
|
(void)events;
|
|
|
|
if (!sysbus_peer)
|
|
(void)try_attach_system_bus(w->ctx);
|
|
}
|
|
|
|
static void sysbus_probe(void)
|
|
{
|
|
if (sysbus_peer || !server)
|
|
return;
|
|
if (access(SYSTEM_BUS_PATH, F_OK))
|
|
return;
|
|
|
|
/* coalesce bursts to a single probe */
|
|
if (!sysbus_tmr_up)
|
|
sysbus_tmr_up = !uev_timer_init(ctx, &sysbus_tmr,
|
|
sysbus_probe_cb, NULL,
|
|
200, 0);
|
|
else
|
|
uev_timer_set(&sysbus_tmr, 200, 0);
|
|
}
|
|
|
|
/* ---------- 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);
|
|
}
|
|
|
|
(void)try_attach_system_bus(ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dbus_exit(void)
|
|
{
|
|
struct peer *p;
|
|
|
|
if (sysbus_tmr_up) {
|
|
uev_timer_stop(&sysbus_tmr);
|
|
sysbus_tmr_up = 0;
|
|
}
|
|
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:
|
|
*/
|