libink/finit: properties, and org.finit on the system bus

Runlevel and version are state, not actions, so they belong behind
org.freedesktop.DBus.Properties rather than another method each.

Finit also claims org.finit on the system bus when it finds one, so
ordinary D-Bus clients can reach it without knowing about
/run/finit/bus.  Opportunistic on purpose: no dbus-daemon is a normal
state for the systems Finit runs on, not an error to report.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-13 09:28:14 +02:00
1 parent dd1390a6c2
commit ebc0ef62e6
22 files changed
+1474 -154

No files matched your search

+185 -11
View File
@@ -104,24 +104,53 @@ static void xprintf(struct xbuf *x, const char *fmt, ...)
x->off += (size_t)n;
}
/*
* Advance past one single complete type in a D-Bus signature:
* a basic type code, 'a' + element type, or a bracketed group.
* Signatures come from our own vtables, so trust them; an
* unterminated group just stops at NUL.
*/
static const char *sig_next(const char *p)
{
while (*p == 'a') /* array prefixes, then element type */
p++;
if (*p == '(' || *p == '{') {
char close = *p == '(' ? ')' : '}';
for (p++; *p && *p != close; p = sig_next(p))
;
}
return *p ? p + 1 : p; /* NUL: unterminated group, stop here */
}
static void emit_args(struct xbuf *x, const char *sig, const char *dir)
{
const char *p, *e;
for (p = sig; p && *p; p = e) {
e = sig_next(p);
xprintf(x, " <arg type=\"%.*s\" direction=\"%s\"/>\n",
(int)(e - p), p, dir);
}
}
/* 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);
emit_args(x, m->in_sig, "in");
emit_args(x, m->out_sig, "out");
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 void emit_property(struct xbuf *x, const link_property_t *p)
{
/* Setters are not implemented, so every property advertises
* access="read" today. When Properties.Set lands, switch on
* a writable flag. */
xprintf(x, " <property name=\"%s\" type=\"%s\" access=\"read\"/>\n",
p->name, p->sig ? p->sig : "s");
}
static const char STANDARD_INTERFACES_XML[] =
" <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
@@ -134,6 +163,17 @@ static const char STANDARD_INTERFACES_XML[] =
" <method name=\"GetMachineId\">\n"
" <arg name=\"machine_uuid\" type=\"s\" direction=\"out\"/>\n"
" </method>\n"
" </interface>\n"
" <interface name=\"org.freedesktop.DBus.Properties\">\n"
" <method name=\"Get\">\n"
" <arg name=\"interface\" type=\"s\" direction=\"in\"/>\n"
" <arg name=\"property\" type=\"s\" direction=\"in\"/>\n"
" <arg name=\"value\" type=\"v\" direction=\"out\"/>\n"
" </method>\n"
" <method name=\"GetAll\">\n"
" <arg name=\"interface\" type=\"s\" direction=\"in\"/>\n"
" <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
" </method>\n"
" </interface>\n";
/* Is `child` a path under `parent`? If so, write the first segment
@@ -197,11 +237,16 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
const link_method_t *meth;
TAILQ_FOREACH(e, &o->vtables, link) {
const link_property_t *prop;
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);
if (e->vt->properties)
for (prop = e->vt->properties; prop->name; prop++)
emit_property(&x, prop);
xprintf(&x, " </interface>\n");
}
}
@@ -231,6 +276,129 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
return send_string_reply(conn, m, xml);
}
/* ---------- Properties.Get / GetAll ---------- */
/* Find the (object, vtable-entry) pair matching `path` and `interface`.
* Returns NULL if the path is unknown or the interface isn't exposed
* on it. */
static struct link_vtable_entry *
find_vtable(link_connection_t *conn, const char *path, const char *interface)
{
struct link_object *o;
if (!path || !interface)
return NULL;
TAILQ_FOREACH(o, &conn->server->objects, link) {
struct link_vtable_entry *e;
if (strcmp(o->path, path) != 0)
continue;
TAILQ_FOREACH(e, &o->vtables, link) {
if (strcmp(e->vt->interface, interface) == 0)
return e;
}
}
return NULL;
}
static int handle_properties_get(link_connection_t *conn, const struct link_msg *m)
{
const char *iface, *prop_name;
struct link_reader r;
struct link_writer w;
struct link_vtable_entry *e;
const link_property_t *p;
ssize_t blen;
if (!m->signature || strcmp(m->signature, "ss") != 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.InvalidArgs",
"Properties.Get takes (interface, property)");
__r_init(&r, m->body, m->body_avail);
if (__r_string(&r, &iface) < 0 || __r_string(&r, &prop_name) < 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.InvalidArgs",
"Malformed argument");
e = find_vtable(conn, m->path, iface);
if (!e || !e->vt->properties)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.UnknownInterface",
"No such interface on this object");
for (p = e->vt->properties; p->name; p++) {
if (strcmp(p->name, prop_name) != 0)
continue;
if (!p->getter)
break;
__w_init(&w, conn->txbuf, sizeof(conn->txbuf));
if (p->getter(&w, e->userdata) != 0 || (blen = __w_finish(&w)) < 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.Failed",
"Property getter failed");
return __send_method_return(conn, m, "v",
conn->txbuf, (size_t)blen);
}
return __send_error(conn, m,
"org.freedesktop.DBus.Error.UnknownProperty",
"No such property on this interface");
}
static int handle_properties_get_all(link_connection_t *conn, const struct link_msg *m)
{
const char *iface;
struct link_reader r;
struct link_writer w;
struct link_vtable_entry *e;
const link_property_t *p;
ssize_t blen;
if (!m->signature || strcmp(m->signature, "s") != 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.InvalidArgs",
"Properties.GetAll takes one string");
__r_init(&r, m->body, m->body_avail);
if (__r_string(&r, &iface) < 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.InvalidArgs",
"Malformed argument");
e = find_vtable(conn, m->path, iface);
if (!e)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.UnknownInterface",
"No such interface on this object");
__w_init(&w, conn->txbuf, sizeof(conn->txbuf));
__w_array_begin(&w, '{');
if (e->vt->properties) {
for (p = e->vt->properties; p->name; p++) {
if (!p->getter)
continue;
__w_struct_begin(&w);
__w_string(&w, p->name);
if (p->getter(&w, e->userdata) != 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.Failed",
"Property getter failed");
__w_struct_end(&w);
}
}
__w_array_end(&w);
blen = __w_finish(&w);
if (blen < 0)
return __send_error(conn, m,
"org.freedesktop.DBus.Error.Failed",
"Reply too large");
return __send_method_return(conn, m, "a{sv}",
conn->txbuf, (size_t)blen);
}
/* ---------- AddMatch / RemoveMatch ---------- */
static int handle_add_match(link_connection_t *conn, const struct link_msg *m)
@@ -310,5 +478,11 @@ int __handle_builtin(link_connection_t *conn, const struct link_msg *m)
if (member_is(m, "org.freedesktop.DBus.Introspectable", "Introspect"))
return handle_introspect(conn, m);
if (member_is(m, "org.freedesktop.DBus.Properties", "Get"))
return handle_properties_get(conn, m);
if (member_is(m, "org.freedesktop.DBus.Properties", "GetAll"))
return handle_properties_get_all(conn, m);
return -1; /* not a built-in */
}
+57 -1
View File
@@ -15,6 +15,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>
@@ -35,7 +36,7 @@ struct link_client {
size_t rxlen;
};
link_client_t *link_client_open(const char *path)
link_client_t *link_client_open_timeout(const char *path, int timeout_ms)
{
struct sockaddr_un sun = { .sun_family = AF_UNIX };
link_client_t *c;
@@ -48,6 +49,20 @@ link_client_t *link_client_open(const char *path)
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return NULL;
if (timeout_ms > 0) {
struct timeval tv = {
.tv_sec = timeout_ms / 1000,
.tv_usec = (timeout_ms % 1000) * 1000,
};
/* Cover both directions so the AUTH write and the
* subsequent read both honour the budget. setsockopt
* failure is non-fatal -- the bus may still respond
* quickly enough; we just lose the safety net. */
(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
(void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
close(fd);
return NULL;
@@ -67,6 +82,11 @@ link_client_t *link_client_open(const char *path)
return c;
}
link_client_t *link_client_open(const char *path)
{
return link_client_open_timeout(path, 0);
}
void link_client_close(link_client_t *c)
{
if (!c)
@@ -76,6 +96,18 @@ void link_client_close(link_client_t *c)
free(c);
}
int link_client_steal_fd(link_client_t *c)
{
int fd;
if (!c)
return -1;
fd = c->fd;
c->fd = -1;
free(c);
return fd;
}
/* read_full / send_all live in libink/io.c. */
#define read_full(fd, buf, len) __io_read_full ((fd), (buf), (len))
#define send_all(fd, buf, len) __io_write_all((fd), (buf), (len))
@@ -229,6 +261,30 @@ const link_reply_t *link_client_reply(link_client_t *c)
return &c->reply;
}
int link_reply_get_string(const link_reply_t *r, const char **out)
{
link_reader_t reader;
if (out)
*out = NULL;
if (!r || !r->body || !out)
return -1;
link_reader_init(&reader, r->body, r->body_len);
return link_r_string(&reader, out);
}
int link_reply_get_u32(const link_reply_t *r, uint32_t *out)
{
link_reader_t reader;
if (out)
*out = 0;
if (!r || !r->body || !out)
return -1;
link_reader_init(&reader, r->body, r->body_len);
return link_r_u32(&reader, out);
}
/* Marshal varargs into `body` (capacity `cap`) according to `sig`.
* Returns the marshalled length on success, -1 on overflow or
* unsupported type code. */
+4
View File
@@ -298,6 +298,7 @@ void link_w_bool (link_writer_t *w, int v) { __w_bool(w, v); }
void link_w_u32 (link_writer_t *w, uint32_t v) { __w_u32(w, v); }
void link_w_string (link_writer_t *w, const char *s) { __w_string(w, s); }
void link_w_path (link_writer_t *w, const char *s) { __w_path(w, s); }
void link_w_variant_string(link_writer_t *w, const char *s) { __w_variant_string(w, s); }
void link_w_array_begin (link_writer_t *w, char ec) { __w_array_begin(w, ec); }
void link_w_array_end (link_writer_t *w) { __w_array_end(w); }
void link_w_struct_begin(link_writer_t *w) { __w_struct_begin(w); }
@@ -311,6 +312,9 @@ int link_r_bool (link_reader_t *r, int *o) { return __r_bool (r, o);
int link_r_u32 (link_reader_t *r, uint32_t *o) { return __r_u32 (r, o); }
int link_r_string(link_reader_t *r, const char **o) { return __r_string(r, o); }
int link_r_path (link_reader_t *r, const char **o) { return __r_path (r, o); }
int link_r_variant_string(link_reader_t *r, const char **o) { return __r_variant_string(r, o); }
int link_r_align (link_reader_t *r, size_t n) { return __r_align (r, n); }
int link_r_array_begin(link_reader_t *r, size_t *e) { return __r_array_begin(r, e); }
int link_r_done (const link_reader_t *r) { return __r_done (r); }
size_t link_r_pos (const link_reader_t *r) { return r->off; }
+64 -6
View File
@@ -96,6 +96,18 @@ int link_server_get_fd(const link_server_t *server);
int link_server_accept(link_server_t *server, link_connection_t **conn);
/* Insert an externally-authenticated fd into the server's connection
* set. Used to integrate an outbound peer (e.g. a client-side
* handshake against an external dbus-daemon) so the same dispatch +
* signal-fan-out machinery covers it. `peer_uid` becomes what
* privileged-method checks see; pass (uid_t)-1 to make all
* LINK_METHOD_PRIVILEGED methods reject by default.
*
* On success the connection takes ownership of `fd`. On any failure
* `fd` is closed before the function returns NULL, so callers never
* have to track partial state. */
link_connection_t *link_server_attach(link_server_t *server, int fd, uid_t peer_uid);
int link_connection_get_fd (const link_connection_t *conn);
uid_t link_connection_get_uid (const link_connection_t *conn);
int link_connection_process (link_connection_t *conn);
@@ -116,9 +128,22 @@ typedef struct {
link_method_fn handler;
} link_method_t;
/* A read-only property descriptor. Set via the Properties.Set side
* is not yet implemented; only Get and GetAll are. The getter writes
* the property's value as a D-Bus variant (use link_w_variant_string
* for "s"-typed properties) into the provided writer. */
typedef int (*link_property_getter_fn)(link_writer_t *w, void *userdata);
typedef struct {
const char *interface; /* e.g. "org.finit.Manager1" */
const link_method_t *methods; /* terminated by {NULL, ...} */
const char *name; /* property name */
const char *sig; /* D-Bus signature, e.g. "s" */
link_property_getter_fn getter;
} link_property_t;
typedef struct {
const char *interface; /* e.g. "org.finit.Manager1" */
const link_method_t *methods; /* terminated by {NULL, ...}, or NULL */
const link_property_t *properties; /* terminated by {NULL, ...}, or NULL */
} link_vtable_t;
/* Register one (interface, methods) at `path`. Calling repeatedly
@@ -189,8 +214,23 @@ int link_connection_emit_signal(link_connection_t *conn,
* Returns NULL on any failure (caller can fall back to another
* transport if it has one). */
link_client_t *link_client_open(const char *path);
/* As link_client_open but applies SO_SNDTIMEO + SO_RCVTIMEO before
* the connect/AUTH handshake. After link_server_attach flips the fd
* to non-blocking the timeout is silently inert; it only protects
* the synchronous open path against a hung peer. timeout_ms == 0
* disables the budget (same behaviour as link_client_open). */
link_client_t *link_client_open_timeout(const char *path, int timeout_ms);
void link_client_close(link_client_t *c);
/* Detach the authenticated socket from the client and return the raw
* fd; subsequent link_client_close on `c` is invalid because the
* structure has already been freed. Used by callers (e.g. system-bus
* integration) that want to promote an outbound client connection
* into a server-attached peer via link_server_attach(). */
int link_client_steal_fd(link_client_t *c);
/* Status codes returned by link_client_call(_v). */
#define LINK_CALL_OK 0 /* method-return received */
#define LINK_CALL_ERROR 1 /* server replied with an error */
@@ -234,6 +274,14 @@ int link_client_call_v(link_client_t *c,
const link_reply_t *link_client_reply(link_client_t *c);
/* Convenience accessors for the common case where a reply carries
* exactly one string ("s" or "o") or one u32 ("u"). They wrap the
* link_reader_init + link_r_* pattern; on success return 0 and
* populate *out, on parse failure or missing body return -1. Use
* link_client_reply + link_reader_init directly for richer payloads. */
int link_reply_get_string(const link_reply_t *r, const char **out);
int link_reply_get_u32 (const link_reply_t *r, uint32_t *out);
/* Wait up to `timeout_ms` milliseconds for the next inbound message
* (typically a SIGNAL delivered after an AddMatch subscription), and
* populate the same view returned by link_client_reply().
@@ -264,6 +312,7 @@ void link_w_bool (link_writer_t *w, int v);
void link_w_u32 (link_writer_t *w, uint32_t v);
void link_w_string (link_writer_t *w, const char *s); /* "s" */
void link_w_path (link_writer_t *w, const char *s); /* "o" */
void link_w_variant_string(link_writer_t *w, const char *s); /* "v" containing "s" */
void link_w_array_begin (link_writer_t *w, char element_sig);
void link_w_array_end (link_writer_t *w);
void link_w_struct_begin(link_writer_t *w);
@@ -280,12 +329,21 @@ int link_r_bool (link_reader_t *r, int *out);
int link_r_u32 (link_reader_t *r, uint32_t *out);
int link_r_string (link_reader_t *r, const char **out); /* "s" */
int link_r_path (link_reader_t *r, const char **out); /* "o" */
int link_r_variant_string(link_reader_t *r, const char **out); /* "v" containing "s" */
int link_r_align (link_reader_t *r, size_t n); /* skip to next n-byte boundary */
int link_r_done (const link_reader_t *r);
/* Byte offset of the next read inside the original body buffer. Used
* to detect end-of-array when walking "a<T>" payloads: read the array
* byte-length prefix with link_r_u32 first, record (pos+length) as the
* end, then loop while link_r_pos < end. */
/* Begin reading an "a<T>" array. On success returns 0 and sets
* *out_end to the absolute reader offset at which the array ends;
* caller loops while link_r_pos < *out_end. For dict-entry arrays
* ("a{T}") call link_r_align(r, 8) at the top of each iteration --
* the element-alignment skip from the array prefix only covers the
* first entry. */
int link_r_array_begin(link_reader_t *r, size_t *out_end);
/* Byte offset of the next read inside the original body buffer.
* Use together with the *out_end returned by link_r_array_begin to
* walk the elements of an "a<T>" payload. */
size_t link_r_pos (const link_reader_t *r);
#ifdef __cplusplus
+61
View File
@@ -102,6 +102,14 @@ void __w_string(struct link_writer *w, const char *s) { write_lenprefixed(w, s,
void __w_path (struct link_writer *w, const char *s) { write_lenprefixed(w, s, 0); }
void __w_sig (struct link_writer *w, const char *s) { write_lenprefixed(w, s, 1); }
/* Variant "v" containing a string. Wire form:
* 1-byte sig length (1), 's', NUL, then the string per __w_string. */
void __w_variant_string(struct link_writer *w, const char *s)
{
__w_sig (w, "s");
__w_string(w, s);
}
static size_t element_align(char c)
{
switch (c) {
@@ -254,7 +262,60 @@ static int read_string_like(struct link_reader *r, const char **out)
int __r_string(struct link_reader *r, const char **out) { return read_string_like(r, out); }
int __r_path (struct link_reader *r, const char **out) { return read_string_like(r, out); }
/* Read a variant "v" expected to contain a string. Fails if the
* inner signature is anything other than "s" (returns -1, *out set
* to NULL). */
int __r_variant_string(struct link_reader *r, const char **out)
{
uint8_t sig_len;
uint32_t slen;
*out = NULL;
/* signature is "g" wire form: 1-byte length, bytes, NUL */
if (r_skip_align(r, 1) < 0 || r->off + 1 > r->cap) { r->err = 1; return -1; }
sig_len = r->base[r->off++];
if (sig_len != 1 || r->off + 2 > r->cap) { r->err = 1; return -1; }
if (r->base[r->off] != 's' || r->base[r->off + 1] != 0) { r->err = 1; return -1; }
r->off += 2;
/* now a normal string */
if (__r_u32(r, &slen) < 0) return -1;
if (r->off + (size_t)slen + 1 > r->cap || r->base[r->off + slen] != 0) {
r->err = 1;
return -1;
}
*out = (const char *)(r->base + r->off);
r->off += (size_t)slen + 1;
return 0;
}
int __r_done(const struct link_reader *r)
{
return !r->err && r->off == r->cap;
}
int __r_align(struct link_reader *r, size_t n)
{
return r_skip_align(r, n);
}
/* Begin reading an "a<T>" array. Reads the u32 byte-length prefix
* and sets *out_end to the absolute reader offset at which the array
* ends. Caller loops while r->off < *out_end. Returns -1 on a
* truncated or oversized array length. */
int __r_array_begin(struct link_reader *r, size_t *out_end)
{
uint32_t array_bytes;
size_t end;
if (__r_u32(r, &array_bytes) < 0)
return -1;
end = r->off + (size_t)array_bytes;
if (end > r->cap) {
r->err = 1;
return -1;
}
*out_end = end;
return 0;
}
+4
View File
@@ -23,6 +23,7 @@ void __w_u32 (struct link_writer *w, uint32_t v);
void __w_string (struct link_writer *w, const char *s); /* "s" */
void __w_path (struct link_writer *w, const char *s); /* "o" */
void __w_sig (struct link_writer *w, const char *s); /* "g" */
void __w_variant_string(struct link_writer *w, const char *s); /* "v" containing "s" */
/* element_sig_first_char drives the alignment padding inserted
* between the array length prefix and the first element. */
@@ -43,6 +44,9 @@ int __r_bool (struct link_reader *r, int *out);
int __r_u32 (struct link_reader *r, uint32_t *out);
int __r_string(struct link_reader *r, const char **out); /* "s" */
int __r_path (struct link_reader *r, const char **out); /* "o" */
int __r_variant_string(struct link_reader *r, const char **out); /* "v" containing "s" */
int __r_align (struct link_reader *r, size_t n); /* skip to n-byte boundary */
int __r_array_begin(struct link_reader *r, size_t *out_end);
int __r_done (const struct link_reader *r);
#endif /* LIBINK_MARSHAL_H_ */
+42
View File
@@ -5,6 +5,7 @@
*/
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
@@ -160,3 +161,44 @@ int link_server_accept(link_server_t *srv, link_connection_t **out)
*out = conn;
return 0;
}
link_connection_t *link_server_attach(link_server_t *srv, int fd, uid_t peer_uid)
{
link_connection_t *conn;
int flags;
/* On entry we always own `fd` -- close it on every failure path
* so callers don't have to track whether we touched fcntl state. */
if (!srv || fd < 0) {
if (fd >= 0)
close_save_errno(fd);
errno = EINVAL;
return NULL;
}
/* Match server_accept's fd setup: CLOEXEC first (so a fork-and-
* exec between the two calls cannot leak the fd), then NONBLOCK
* so process_binary's read loop can drain without hanging. */
flags = fcntl(fd, F_GETFD, 0);
if (flags < 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
goto err_close;
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
goto err_close;
conn = calloc(1, sizeof(*conn));
if (!conn)
goto err_close;
conn->fd = fd;
conn->auth = LINK_AUTH_DONE; /* caller already handshook */
conn->server = srv;
conn->peer_uid = peer_uid;
__auth_generate_guid(conn->guid);
return conn;
err_close:
close_save_errno(fd);
return NULL;
}