mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 13:33:09 +07:00
The D-Bus socket was bound world read/write, on the reasoning that SO_PEERCRED authorizes each method anyway. That leaves the read-only surface open to every local user, and it quietly ignores --with-group: a system that restricts initctl to the wheel group still handed the same service state to anyone who asked over the bus. Bind it 0660 and chown it to the configured group, the same gate the fallback socket has always had. libink takes the mode as an argument rather than assuming one, since who may connect is the embedder's policy, not the library's. The mode is applied at bind(), so there is no window where the socket is more permissive than intended. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1425 lines
40 KiB
C
1425 lines
40 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);
|
|
|
|
/* Same access policy as INIT_SOCKET: the bus reaches every
|
|
* service operation initctl does, so --with-group has to gate
|
|
* both or it gates neither. */
|
|
if (link_server_new(&server, FINIT_BUS_SOCKET, 0660) < 0) {
|
|
err(1, "Failed binding D-Bus socket %s", FINIT_BUS_SOCKET);
|
|
return 1;
|
|
}
|
|
|
|
if (chown(FINIT_BUS_SOCKET, geteuid(), getgroup(DEFGROUP)))
|
|
err(1, "Failed setting group %s on %s", DEFGROUP, FINIT_BUS_SOCKET);
|
|
|
|
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:
|
|
*/
|