diff --git a/doc/dbus.md b/doc/dbus.md
index 49afcc6b..a1a9ac09 100644
--- a/doc/dbus.md
+++ b/doc/dbus.md
@@ -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 |
diff --git a/libink/builtin.c b/libink/builtin.c
index 000290c3..06f1e1e6 100644
--- a/libink/builtin.c
+++ b/libink/builtin.c
@@ -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, " \n", s->name);
+ emit_args(x, s->sig, "out");
+ xprintf(x, " \n");
+}
+
static const char STANDARD_INTERFACES_XML[] =
" \n"
" \n"
@@ -174,6 +181,28 @@ static const char STANDARD_INTERFACES_XML[] =
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \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[] =
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \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)
"\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, " \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, " \n");
}
}
diff --git a/libink/link.h b/libink/link.h
index 59754869..7a70ce2b 100644
--- a/libink/link.h
+++ b/libink/link.h
@@ -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
diff --git a/src/dbus.c b/src/dbus.c
index f828289c..827e05c9 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -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 ---------- */
diff --git a/test/dbus-introspect.sh b/test/dbus-introspect.sh
index 08e4927c..0d15457c 100755
--- a/test/dbus-introspect.sh
+++ b/test/dbus-introspect.sh
@@ -29,3 +29,36 @@ case "$xml" in
fail "per-character fragment leaked" ;;
*) assert "no bracket/array fragments" 0 -eq 0 ;;
esac
+
+# ---------- declarations ----------
+
+say "Cond1 declares the ConditionChanged signal"
+case "$xml" in
+ *''*) 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
+ *''*''*)
+ 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
+ *''*) 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
+ *''*) assert "org.freedesktop.DBus declared on canonical object" 0 -eq 0 ;;
+ *) fail "org.freedesktop.DBus missing from /org/freedesktop/DBus" ;;
+esac
+case "$mgr" in
+ *''*) fail "org.freedesktop.DBus leaked onto /org/finit/manager" ;;
+ *) assert "org.freedesktop.DBus not advertised off-path" 0 -eq 0 ;;
+esac