mirror of
https://github.com/troglobit/finit.git
synced 2026-10-09 16:50:59 +07:00
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>
315 lines
8.4 KiB
C
315 lines
8.4 KiB
C
/* libink — built-in implementations of the well-known
|
|
* org.freedesktop.DBus.* interfaces (Hello, Peer, Introspectable).
|
|
*
|
|
* These run before object-tree lookup in the dispatcher; returning
|
|
* 0 means "handled, reply sent"; <0 means "not a built-in, fall
|
|
* through to user-registered handlers".
|
|
*
|
|
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "internal.h"
|
|
|
|
/* ---------- helpers ---------- */
|
|
|
|
static int member_is(const struct link_msg *m, const char *iface, const char *member)
|
|
{
|
|
if (!m->member || strcmp(m->member, member) != 0)
|
|
return 0;
|
|
if (m->interface && strcmp(m->interface, iface) != 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int send_string_reply(link_connection_t *conn, const struct link_msg *req,
|
|
const char *s)
|
|
{
|
|
struct link_writer w;
|
|
ssize_t blen;
|
|
|
|
__w_init(&w, conn->txbuf, sizeof(conn->txbuf));
|
|
__w_string(&w, s);
|
|
blen = __w_finish(&w);
|
|
if (blen < 0) {
|
|
errno = EMSGSIZE;
|
|
return -1;
|
|
}
|
|
return __send_method_return(conn, req, "s", conn->txbuf, (size_t)blen);
|
|
}
|
|
|
|
/* ---------- Hello ---------- */
|
|
|
|
static int handle_hello(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
if (!conn->unique_name[0]) {
|
|
uint32_t n = ++conn->server->next_unique_id;
|
|
|
|
snprintf(conn->unique_name, sizeof(conn->unique_name),
|
|
":1.%u", n);
|
|
}
|
|
return send_string_reply(conn, m, conn->unique_name);
|
|
}
|
|
|
|
/* ---------- Ping / GetMachineId ---------- */
|
|
|
|
static int handle_ping(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
return __send_method_return(conn, m, NULL, NULL, 0);
|
|
}
|
|
|
|
static int handle_get_machine_id(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
/* D-Bus mandates a 32-char hex machine-id. Use the per-server
|
|
* GUID-style identifier we already generate for each connection,
|
|
* promoted to a per-server constant on first call. Good enough
|
|
* for the brokerless case where clients use this only as a
|
|
* sanity hint. */
|
|
static char machine_id[33];
|
|
|
|
if (!machine_id[0])
|
|
__auth_generate_guid(machine_id);
|
|
return send_string_reply(conn, m, machine_id);
|
|
}
|
|
|
|
/* ---------- Introspect ---------- */
|
|
|
|
struct xbuf {
|
|
char *buf;
|
|
size_t cap;
|
|
size_t off;
|
|
int err;
|
|
};
|
|
|
|
static void xprintf(struct xbuf *x, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int n;
|
|
|
|
if (x->err)
|
|
return;
|
|
va_start(ap, fmt);
|
|
n = vsnprintf(x->buf + x->off, x->cap - x->off, fmt, ap);
|
|
va_end(ap);
|
|
if (n < 0 || (size_t)n >= x->cap - x->off) {
|
|
x->err = 1;
|
|
return;
|
|
}
|
|
x->off += (size_t)n;
|
|
}
|
|
|
|
/* Emit a single <method> stanza for one method definition. */
|
|
static void emit_method(struct xbuf *x, const link_method_t *m)
|
|
{
|
|
const char *p;
|
|
|
|
xprintf(x, " <method name=\"%s\">\n", m->name);
|
|
for (p = m->in_sig ? m->in_sig : ""; *p; p++)
|
|
xprintf(x, " <arg type=\"%c\" direction=\"in\"/>\n", *p);
|
|
for (p = m->out_sig ? m->out_sig : ""; *p; p++)
|
|
xprintf(x, " <arg type=\"%c\" direction=\"out\"/>\n", *p);
|
|
xprintf(x, " </method>\n");
|
|
}
|
|
|
|
/* Introspection limitation: emit_method prints one <arg> per
|
|
* character of the signature, which is wrong for compound types
|
|
* (an "a(ss)" arg appears as four args). Good enough for the
|
|
* "s", "u", "as" signatures we expose today; replace with a
|
|
* signature parser when the first compound argument lands. */
|
|
|
|
static const char STANDARD_INTERFACES_XML[] =
|
|
" <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
|
|
" <method name=\"Introspect\">\n"
|
|
" <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"
|
|
" </method>\n"
|
|
" </interface>\n"
|
|
" <interface name=\"org.freedesktop.DBus.Peer\">\n"
|
|
" <method name=\"Ping\"/>\n"
|
|
" <method name=\"GetMachineId\">\n"
|
|
" <arg name=\"machine_uuid\" type=\"s\" direction=\"out\"/>\n"
|
|
" </method>\n"
|
|
" </interface>\n";
|
|
|
|
/* Is `child` a path under `parent`? If so, write the first segment
|
|
* of the relative remainder into out (max outsz) and return 1. */
|
|
static int child_segment(const char *parent, const char *child,
|
|
char *out, size_t outsz)
|
|
{
|
|
size_t plen = strlen(parent);
|
|
const char *rest, *slash;
|
|
size_t seglen;
|
|
|
|
if (strncmp(parent, child, plen) != 0)
|
|
return 0;
|
|
/* Special case for "/" */
|
|
if (plen == 1 && parent[0] == '/')
|
|
rest = child + 1;
|
|
else if (child[plen] != '/')
|
|
return 0;
|
|
else
|
|
rest = child + plen + 1;
|
|
if (!*rest)
|
|
return 0;
|
|
|
|
slash = strchr(rest, '/');
|
|
seglen = slash ? (size_t)(slash - rest) : strlen(rest);
|
|
if (seglen + 1 > outsz)
|
|
return 0;
|
|
memcpy(out, rest, seglen);
|
|
out[seglen] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
static char xml[8192]; /* static keeps the stack small in PID 1 */
|
|
struct xbuf x = { .buf = xml, .cap = sizeof(xml) };
|
|
struct link_object *o;
|
|
const char *path = m->path;
|
|
|
|
xprintf(&x,
|
|
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
|
|
" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
|
|
"<node>\n");
|
|
|
|
xprintf(&x, "%s", STANDARD_INTERFACES_XML);
|
|
|
|
o = NULL;
|
|
{
|
|
struct link_object *p;
|
|
|
|
TAILQ_FOREACH(p, &conn->server->objects, link) {
|
|
if (strcmp(p->path, path) == 0) {
|
|
o = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (o) {
|
|
struct link_vtable_entry *e;
|
|
const link_method_t *meth;
|
|
|
|
TAILQ_FOREACH(e, &o->vtables, link) {
|
|
xprintf(&x, " <interface name=\"%s\">\n",
|
|
e->vt->interface);
|
|
if (e->vt->methods)
|
|
for (meth = e->vt->methods; meth->name; meth++)
|
|
emit_method(&x, meth);
|
|
xprintf(&x, " </interface>\n");
|
|
}
|
|
}
|
|
|
|
{
|
|
struct link_object *p;
|
|
char prev_seg[LINK_PATH_MAX] = { 0 };
|
|
char seg [LINK_PATH_MAX];
|
|
|
|
TAILQ_FOREACH(p, &conn->server->objects, link) {
|
|
if (!child_segment(path, p->path, seg, sizeof(seg)))
|
|
continue;
|
|
if (strcmp(prev_seg, seg) == 0)
|
|
continue;
|
|
xprintf(&x, " <node name=\"%s\"/>\n", seg);
|
|
memcpy(prev_seg, seg, sizeof(prev_seg));
|
|
}
|
|
}
|
|
|
|
xprintf(&x, "</node>\n");
|
|
|
|
if (x.err)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.Failed",
|
|
"Introspection XML overflow");
|
|
|
|
return send_string_reply(conn, m, xml);
|
|
}
|
|
|
|
/* ---------- AddMatch / RemoveMatch ---------- */
|
|
|
|
static int handle_add_match(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
const char *rule;
|
|
struct link_reader r;
|
|
|
|
if (!m->signature || strcmp(m->signature, "s") != 0)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"AddMatch takes a single string");
|
|
|
|
__r_init(&r, m->body, m->body_avail);
|
|
if (__r_string(&r, &rule) < 0)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"Malformed argument");
|
|
|
|
if (__match_add(conn, rule) < 0) {
|
|
if (errno == ENOSPC)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.LimitsExceeded",
|
|
"Too many active match rules");
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.MatchRuleInvalid",
|
|
"Unrecognised key or malformed rule");
|
|
}
|
|
return __send_method_return(conn, m, NULL, NULL, 0);
|
|
}
|
|
|
|
static int handle_remove_match(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
const char *rule;
|
|
struct link_reader r;
|
|
|
|
if (!m->signature || strcmp(m->signature, "s") != 0)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"RemoveMatch takes a single string");
|
|
|
|
__r_init(&r, m->body, m->body_avail);
|
|
if (__r_string(&r, &rule) < 0)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
|
"Malformed argument");
|
|
|
|
if (__match_remove(conn, rule) < 0)
|
|
return __send_error(conn, m,
|
|
"org.freedesktop.DBus.Error.MatchRuleNotFound",
|
|
"No such match rule on this connection");
|
|
|
|
return __send_method_return(conn, m, NULL, NULL, 0);
|
|
}
|
|
|
|
/* ---------- entry point ---------- */
|
|
|
|
int __handle_builtin(link_connection_t *conn, const struct link_msg *m)
|
|
{
|
|
if (member_is(m, "org.freedesktop.DBus", "Hello") &&
|
|
m->path && strcmp(m->path, "/org/freedesktop/DBus") == 0)
|
|
return handle_hello(conn, m);
|
|
|
|
if (member_is(m, "org.freedesktop.DBus", "AddMatch") &&
|
|
m->path && strcmp(m->path, "/org/freedesktop/DBus") == 0)
|
|
return handle_add_match(conn, m);
|
|
|
|
if (member_is(m, "org.freedesktop.DBus", "RemoveMatch") &&
|
|
m->path && strcmp(m->path, "/org/freedesktop/DBus") == 0)
|
|
return handle_remove_match(conn, m);
|
|
|
|
if (member_is(m, "org.freedesktop.DBus.Peer", "Ping"))
|
|
return handle_ping(conn, m);
|
|
|
|
if (member_is(m, "org.freedesktop.DBus.Peer", "GetMachineId"))
|
|
return handle_get_machine_id(conn, m);
|
|
|
|
if (member_is(m, "org.freedesktop.DBus.Introspectable", "Introspect"))
|
|
return handle_introspect(conn, m);
|
|
|
|
return -1; /* not a built-in */
|
|
}
|