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