libink: declare signals in introspection XML

All three org.finit signals were invisible to generated proxies, and
org.freedesktop.DBus was missing from the standard interfaces even
though Hello, AddMatch, and RemoveMatch are answered.

Add a link_signal_t table to the vtable, emitted like methods and
properties, declare the Manager1 and Cond1 signals, and complete the
static XML with PropertiesChanged and org.freedesktop.DBus.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-08-16 22:03:39 +02:00
parent 9d4cc8e933
commit b743d15e35
5 changed files with 92 additions and 1 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ Every node implements the usual stock interfaces:
| Interface | Purpose |
|---------------------------------------|-------------------------------------------|
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` |
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` — on `/org/freedesktop/DBus` only |
| `org.freedesktop.DBus.Peer` | `Ping`, `GetMachineId` |
| `org.freedesktop.DBus.Introspectable` | `Introspect()` — XML description |
| `org.freedesktop.DBus.Properties` | `Get`, `GetAll`; nothing is writable |
+35
View File
@@ -152,6 +152,13 @@ static void emit_property(struct xbuf *x, const link_property_t *p)
p->name, p->sig ? p->sig : "s");
}
static void emit_signal(struct xbuf *x, const link_signal_t *s)
{
xprintf(x, " <signal name=\"%s\">\n", s->name);
emit_args(x, s->sig, "out");
xprintf(x, " </signal>\n");
}
static const char STANDARD_INTERFACES_XML[] =
" <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
" <method name=\"Introspect\">\n"
@@ -174,6 +181,28 @@ static const char STANDARD_INTERFACES_XML[] =
" <arg name=\"interface\" type=\"s\" direction=\"in\"/>\n"
" <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
" </method>\n"
" <signal name=\"PropertiesChanged\">\n"
" <arg name=\"interface\" type=\"s\"/>\n"
" <arg name=\"changed\" type=\"a{sv}\"/>\n"
" <arg name=\"invalidated\" type=\"as\"/>\n"
" </signal>\n"
" </interface>\n";
/*
* Hello/AddMatch/RemoveMatch are answered on the canonical
* /org/freedesktop/DBus object only, so declare them only there.
*/
static const char DBUS_DRIVER_XML[] =
" <interface name=\"org.freedesktop.DBus\">\n"
" <method name=\"Hello\">\n"
" <arg name=\"name\" type=\"s\" direction=\"out\"/>\n"
" </method>\n"
" <method name=\"AddMatch\">\n"
" <arg name=\"rule\" type=\"s\" direction=\"in\"/>\n"
" </method>\n"
" <method name=\"RemoveMatch\">\n"
" <arg name=\"rule\" type=\"s\" direction=\"in\"/>\n"
" </method>\n"
" </interface>\n";
/* Is `child` a path under `parent`? If so, write the first segment
@@ -219,6 +248,8 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
"<node>\n");
xprintf(&x, "%s", STANDARD_INTERFACES_XML);
if (!strcmp(path, "/org/freedesktop/DBus"))
xprintf(&x, "%s", DBUS_DRIVER_XML);
o = NULL;
{
@@ -238,6 +269,7 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
TAILQ_FOREACH(e, &o->vtables, link) {
const link_property_t *prop;
const link_signal_t *sig;
xprintf(&x, " <interface name=\"%s\">\n",
e->vt->interface);
@@ -247,6 +279,9 @@ static int handle_introspect(link_connection_t *conn, const struct link_msg *m)
if (e->vt->properties)
for (prop = e->vt->properties; prop->name; prop++)
emit_property(&x, prop);
if (e->vt->signals)
for (sig = e->vt->signals; sig->name; sig++)
emit_signal(&x, sig);
xprintf(&x, " </interface>\n");
}
}
+10
View File
@@ -247,10 +247,20 @@ typedef struct {
link_property_getter_fn getter;
} link_property_t;
/*
* Declares a signal for introspection only; emission is unchecked,
* see link_connection_emit_signal().
*/
typedef struct {
const char *name; /* member name */
const char *sig; /* D-Bus signature, e.g. "sss" */
} link_signal_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 */
const link_signal_t *signals; /* terminated by {NULL, ...}, or NULL */
} link_vtable_t;
/* Register one (interface, methods) at `path`. Calling repeatedly
+13
View File
@@ -623,10 +623,17 @@ static const link_method_t manager_methods[] = {
{ NULL, NULL, NULL, 0, NULL }
};
static const link_signal_t manager_signals[] = {
{ .name = "ServiceStateChanged", .sig = "sss" },
{ .name = "RunlevelChanged", .sig = "ss" },
{ NULL, NULL }
};
static const link_vtable_t manager_vtable = {
.interface = "org.finit.Manager1",
.methods = manager_methods,
.properties = manager_properties,
.signals = manager_signals,
};
/* ---------- org.finit.Service1 (one object per service) ----------
@@ -1251,9 +1258,15 @@ static const link_method_t cond_methods[] = {
{ NULL, NULL, NULL, 0, NULL }
};
static const link_signal_t cond_signals[] = {
{ .name = "ConditionChanged", .sig = "ss" },
{ NULL, NULL }
};
static const link_vtable_t cond_vtable = {
.interface = COND_INTERFACE,
.methods = cond_methods,
.signals = cond_signals,
};
/* ---------- signal emission: ConditionChanged ---------- */
+33
View File
@@ -29,3 +29,36 @@ case "$xml" in
fail "per-character <arg> fragment leaked" ;;
*) assert "no bracket/array fragments" 0 -eq 0 ;;
esac
# ---------- <signal> declarations ----------
say "Cond1 declares the ConditionChanged signal"
case "$xml" in
*'<signal name="ConditionChanged">'*) assert "ConditionChanged declared" 0 -eq 0 ;;
*) fail "ConditionChanged missing from Cond1 XML" ;;
esac
say "Manager1 declares ServiceStateChanged and RunlevelChanged"
mgr=$(texec "$CLIENT" introspect "$BUS" /org/finit/manager)
case "$mgr" in
*'<signal name="ServiceStateChanged">'*'<signal name="RunlevelChanged">'*)
assert "Manager1 signals declared" 0 -eq 0 ;;
*) fail "Manager1 signal declarations missing" ;;
esac
say "Properties declares PropertiesChanged, org.freedesktop.DBus is present"
case "$mgr" in
*'<signal name="PropertiesChanged">'*) assert "PropertiesChanged declared" 0 -eq 0 ;;
*) fail "PropertiesChanged missing from Properties XML" ;;
esac
# Hello/AddMatch/RemoveMatch are answered on the canonical object
# only, so they must be declared there and nowhere else.
drv=$(texec "$CLIENT" introspect "$BUS" /org/freedesktop/DBus)
case "$drv" in
*'<interface name="org.freedesktop.DBus">'*) assert "org.freedesktop.DBus declared on canonical object" 0 -eq 0 ;;
*) fail "org.freedesktop.DBus missing from /org/freedesktop/DBus" ;;
esac
case "$mgr" in
*'<interface name="org.freedesktop.DBus">'*) fail "org.freedesktop.DBus leaked onto /org/finit/manager" ;;
*) assert "org.freedesktop.DBus not advertised off-path" 0 -eq 0 ;;
esac