mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-25 23:21:33 +03:00
bus: add sd_bus_emit_object_{added/removed}()
This implements two new helpers, discussed on systemd-devel about 1 year ago: sd_bus_emit_object_added() sd_bus_emit_object_removed() Both calls are equivalent to their respective counterpart sd_bus_emit_interfaces_{added/removed}(), but can figure out the list of interfaces themselves, instead of requiring the caller to provide them. Furthermore, both calls properly deal with builtin interfaces provided via org.freedesktop.DBus.* and alike. Both calls simply traverse a node and all its parent nodes to figure out a list of all interfaces registered as vtable or fallback. It then appends each of them, similar to the interfaces_{added/removed}() helpers. Note that interfaces_{added/removed}() runs a parent traversal for *each* passed interface. Therefore, it can simply bail out, once it found a parent node that implements a given interface. With object_{added/removed}() we cannot know the registered interfaces in advance, thus, we cannot run one traversal per node. Instead, we run a single traversal and remember all interfaces that we added. Therefore, a child-interface overrides all conflicting parent-interfaces. We keep a "Set *s" context to track those while climbing up the tree.
This commit is contained in:
parent
7d9fcc2bf6
commit
d95eb43e90
@ -2111,6 +2111,375 @@ _public_ int sd_bus_emit_properties_changed(
|
||||
return sd_bus_emit_properties_changed_strv(bus, path, interface, names);
|
||||
}
|
||||
|
||||
static int object_added_append_all_prefix(
|
||||
sd_bus *bus,
|
||||
sd_bus_message *m,
|
||||
Set *s,
|
||||
const char *prefix,
|
||||
const char *path,
|
||||
bool require_fallback) {
|
||||
|
||||
const char *previous_interface = NULL;
|
||||
struct node_vtable *c;
|
||||
struct node *n;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(m);
|
||||
assert(s);
|
||||
assert(prefix);
|
||||
assert(path);
|
||||
|
||||
n = hashmap_get(bus->nodes, prefix);
|
||||
if (!n)
|
||||
return 0;
|
||||
|
||||
LIST_FOREACH(vtables, c, n->vtables) {
|
||||
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
void *u = NULL;
|
||||
|
||||
if (require_fallback && !c->is_fallback)
|
||||
continue;
|
||||
|
||||
r = node_vtable_get_userdata(bus, path, c, &u, &error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
if (!streq_ptr(c->interface, previous_interface)) {
|
||||
/* If a child-node already handled this interface, we
|
||||
* skip it on any of its parents. The child vtables
|
||||
* always fully override any conflicting vtables of
|
||||
* any parent node. */
|
||||
if (set_get(s, c->interface))
|
||||
continue;
|
||||
|
||||
r = set_put(s, c->interface);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (previous_interface) {
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_bus_message_open_container(m, 'e', "sa{sv}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "s", c->interface);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_open_container(m, 'a', "{sv}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
previous_interface = c->interface;
|
||||
}
|
||||
|
||||
r = vtable_append_all_properties(bus, m, path, c, u, &error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (previous_interface) {
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path) {
|
||||
_cleanup_set_free_ Set *s = NULL;
|
||||
char *prefix;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(m);
|
||||
assert(path);
|
||||
|
||||
/*
|
||||
* This appends all interfaces registered on path @path. We first add
|
||||
* the builtin interfaces, which are always available and handled by
|
||||
* sd-bus. Then, we add all interfaces registered on the exact node,
|
||||
* followed by all fallback interfaces registered on any parent prefix.
|
||||
*
|
||||
* If an interface is registered multiple times on the same node with
|
||||
* different vtables, we merge all the properties across all vtables.
|
||||
* However, if a child node has the same interface registered as one of
|
||||
* its parent nodes has as fallback, we make the child overwrite the
|
||||
* parent instead of extending it. Therefore, we keep a "Set" of all
|
||||
* handled interfaces during parent traversal, so we skip interfaces on
|
||||
* a parent that were overwritten by a child.
|
||||
*/
|
||||
|
||||
s = set_new(&string_hash_ops);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = object_added_append_all_prefix(bus, m, s, path, path, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
|
||||
prefix = alloca(strlen(path) + 1);
|
||||
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
|
||||
r = object_added_append_all_prefix(bus, m, s, prefix, path, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_bus_emit_object_added(sd_bus *bus, const char *path) {
|
||||
BUS_DONT_DESTROY(bus);
|
||||
|
||||
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
|
||||
int r;
|
||||
|
||||
/*
|
||||
* This emits an InterfacesAdded signal on the given path, by iterating
|
||||
* all registered vtables and fallback vtables on the path. All
|
||||
* properties are queried and included in the signal.
|
||||
* This call is equivalent to sd_bus_emit_interfaces_added() with an
|
||||
* explicit list of registered interfaces. However, unlike
|
||||
* interfaces_added(), this call can figure out the list of supported
|
||||
* interfaces itself. Furthermore, it properly adds the builtin
|
||||
* org.freedesktop.DBus.* interfaces.
|
||||
*/
|
||||
|
||||
assert_return(bus, -EINVAL);
|
||||
assert_return(object_path_is_valid(path), -EINVAL);
|
||||
assert_return(!bus_pid_changed(bus), -ECHILD);
|
||||
|
||||
if (!BUS_IS_OPEN(bus->state))
|
||||
return -ENOTCONN;
|
||||
|
||||
do {
|
||||
bus->nodes_modified = false;
|
||||
m = sd_bus_message_unref(m);
|
||||
|
||||
r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_append_basic(m, 'o', path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "{sa{sv}}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = object_added_append_all(bus, m, path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (bus->nodes_modified)
|
||||
continue;
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
} while (bus->nodes_modified);
|
||||
|
||||
return sd_bus_send(bus, m, NULL);
|
||||
}
|
||||
|
||||
static int object_removed_append_all_prefix(
|
||||
sd_bus *bus,
|
||||
sd_bus_message *m,
|
||||
Set *s,
|
||||
const char *prefix,
|
||||
const char *path,
|
||||
bool require_fallback) {
|
||||
|
||||
const char *previous_interface = NULL;
|
||||
struct node_vtable *c;
|
||||
struct node *n;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(m);
|
||||
assert(s);
|
||||
assert(prefix);
|
||||
assert(path);
|
||||
|
||||
n = hashmap_get(bus->nodes, prefix);
|
||||
if (!n)
|
||||
return 0;
|
||||
|
||||
LIST_FOREACH(vtables, c, n->vtables) {
|
||||
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
void *u = NULL;
|
||||
|
||||
if (require_fallback && !c->is_fallback)
|
||||
continue;
|
||||
if (streq_ptr(c->interface, previous_interface))
|
||||
continue;
|
||||
|
||||
/* If a child-node already handled this interface, we
|
||||
* skip it on any of its parents. The child vtables
|
||||
* always fully override any conflicting vtables of
|
||||
* any parent node. */
|
||||
if (set_get(s, c->interface))
|
||||
continue;
|
||||
|
||||
r = node_vtable_get_userdata(bus, path, c, &u, &error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
r = set_put(s, c->interface);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_append(m, "s", c->interface);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
previous_interface = c->interface;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path) {
|
||||
_cleanup_set_free_ Set *s = NULL;
|
||||
char *prefix;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(m);
|
||||
assert(path);
|
||||
|
||||
/* see sd_bus_emit_object_added() for details */
|
||||
|
||||
s = set_new(&string_hash_ops);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Peer");
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Introspectable");
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Properties");
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.ObjectManager");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = object_removed_append_all_prefix(bus, m, s, path, path, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
|
||||
prefix = alloca(strlen(path) + 1);
|
||||
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
|
||||
r = object_removed_append_all_prefix(bus, m, s, prefix, path, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (bus->nodes_modified)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_bus_emit_object_removed(sd_bus *bus, const char *path) {
|
||||
BUS_DONT_DESTROY(bus);
|
||||
|
||||
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
|
||||
int r;
|
||||
|
||||
/*
|
||||
* This is like sd_bus_emit_object_added(), but emits an
|
||||
* InterfacesRemoved signal on the given path. This only includes any
|
||||
* registered interfaces but skips the properties. Note that this will
|
||||
* call into the find() callbacks of any registered vtable. Therefore,
|
||||
* you must call this function before destroying/unlinking your object.
|
||||
* Otherwise, the list of interfaces will be incomplete. However, note
|
||||
* that this will *NOT* call into any property callback. Therefore, the
|
||||
* object might be in an "destructed" state, as long as we can find it.
|
||||
*/
|
||||
|
||||
assert_return(bus, -EINVAL);
|
||||
assert_return(object_path_is_valid(path), -EINVAL);
|
||||
assert_return(!bus_pid_changed(bus), -ECHILD);
|
||||
|
||||
if (!BUS_IS_OPEN(bus->state))
|
||||
return -ENOTCONN;
|
||||
|
||||
do {
|
||||
bus->nodes_modified = false;
|
||||
m = sd_bus_message_unref(m);
|
||||
|
||||
r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_append_basic(m, 'o', path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "s");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = object_removed_append_all(bus, m, path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (bus->nodes_modified)
|
||||
continue;
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
} while (bus->nodes_modified);
|
||||
|
||||
return sd_bus_send(bus, m, NULL);
|
||||
}
|
||||
|
||||
static int interfaces_added_append_one_prefix(
|
||||
sd_bus *bus,
|
||||
sd_bus_message *m,
|
||||
|
@ -176,6 +176,28 @@ static int emit_interfaces_removed(sd_bus *bus, sd_bus_message *m, void *userdat
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int emit_object_added(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||
int r;
|
||||
|
||||
assert_se(sd_bus_emit_object_added(bus, m->path) >= 0);
|
||||
|
||||
r = sd_bus_reply_method_return(m, NULL);
|
||||
assert_se(r >= 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int emit_object_removed(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||
int r;
|
||||
|
||||
assert_se(sd_bus_emit_object_removed(bus, m->path) >= 0);
|
||||
|
||||
r = sd_bus_reply_method_return(m, NULL);
|
||||
assert_se(r >= 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const sd_bus_vtable vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_METHOD("AlterSomething", "s", "s", something_handler, 0),
|
||||
@ -186,6 +208,8 @@ static const sd_bus_vtable vtable[] = {
|
||||
SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0),
|
||||
SD_BUS_METHOD("EmitInterfacesAdded", NULL, NULL, emit_interfaces_added, 0),
|
||||
SD_BUS_METHOD("EmitInterfacesRemoved", NULL, NULL, emit_interfaces_removed, 0),
|
||||
SD_BUS_METHOD("EmitObjectAdded", NULL, NULL, emit_object_added, 0),
|
||||
SD_BUS_METHOD("EmitObjectRemoved", NULL, NULL, emit_object_removed, 0),
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
@ -456,6 +480,30 @@ static int client(struct context *c) {
|
||||
sd_bus_message_unref(reply);
|
||||
reply = NULL;
|
||||
|
||||
r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectAdded", &error, NULL, "");
|
||||
assert_se(r >= 0);
|
||||
|
||||
r = sd_bus_process(bus, &reply);
|
||||
assert_se(r > 0);
|
||||
|
||||
assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"));
|
||||
bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
|
||||
|
||||
sd_bus_message_unref(reply);
|
||||
reply = NULL;
|
||||
|
||||
r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectRemoved", &error, NULL, "");
|
||||
assert_se(r >= 0);
|
||||
|
||||
r = sd_bus_process(bus, &reply);
|
||||
assert_se(r > 0);
|
||||
|
||||
assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"));
|
||||
bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
|
||||
|
||||
sd_bus_message_unref(reply);
|
||||
reply = NULL;
|
||||
|
||||
r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, "");
|
||||
assert_se(r >= 0);
|
||||
|
||||
|
@ -310,6 +310,8 @@ int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, con
|
||||
int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names);
|
||||
int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_;
|
||||
|
||||
int sd_bus_emit_object_added(sd_bus *bus, const char *path);
|
||||
int sd_bus_emit_object_removed(sd_bus *bus, const char *path);
|
||||
int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces);
|
||||
int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_;
|
||||
int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces);
|
||||
|
Loading…
Reference in New Issue
Block a user