Systemd/src/libsystemd/sd-bus/bus-objects.c

3033 lines
96 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "bus-internal.h"
#include "bus-introspect.h"
#include "bus-message.h"
#include "bus-objects.h"
#include "bus-signature.h"
#include "bus-slot.h"
#include "bus-type.h"
#include "bus-util.h"
#include "missing_capability.h"
#include "set.h"
#include "string-util.h"
#include "strv.h"
static int node_vtable_get_userdata(
sd_bus *bus,
const char *path,
struct node_vtable *c,
void **userdata,
sd_bus_error *error) {
sd_bus_slot *s;
void *u, *found_u = NULL;
int r;
assert(bus);
assert(path);
assert(c);
s = container_of(c, sd_bus_slot, node_vtable);
u = s->userdata;
if (c->find) {
bus->current_slot = sd_bus_slot_ref(s);
bus->current_userdata = u;
r = c->find(bus, path, c->interface, u, &found_u, error);
bus->current_userdata = NULL;
bus->current_slot = sd_bus_slot_unref(s);
if (r < 0)
return r;
if (sd_bus_error_is_set(error))
return -sd_bus_error_get_errno(error);
if (r == 0)
return r;
} else
found_u = u;
if (userdata)
*userdata = found_u;
return 1;
}
static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) {
assert(p);
if (!u || FLAGS_SET(p->flags, SD_BUS_VTABLE_ABSOLUTE_OFFSET))
return SIZE_TO_PTR(p->x.method.offset); /* don't add offset on NULL, to make ubsan happy */
return (uint8_t*) u + p->x.method.offset;
}
static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) {
assert(p);
if (!u || FLAGS_SET(p->flags, SD_BUS_VTABLE_ABSOLUTE_OFFSET))
return SIZE_TO_PTR(p->x.property.offset); /* as above */
return (uint8_t*) u + p->x.property.offset;
}
static int vtable_property_get_userdata(
sd_bus *bus,
const char *path,
struct vtable_member *p,
void **userdata,
sd_bus_error *error) {
void *u;
int r;
assert(bus);
assert(path);
assert(p);
assert(userdata);
r = node_vtable_get_userdata(bus, path, p->parent, &u, error);
if (r <= 0)
return r;
if (bus->nodes_modified)
return 0;
*userdata = vtable_property_convert_userdata(p->vtable, u);
return 1;
}
static int add_enumerated_to_set(
sd_bus *bus,
const char *prefix,
struct node_enumerator *first,
Set *s,
sd_bus_error *error) {
struct node_enumerator *c;
int r;
assert(bus);
assert(prefix);
assert(s);
LIST_FOREACH(enumerators, c, first) {
char **children = NULL, **k;
sd_bus_slot *slot;
if (bus->nodes_modified)
return 0;
slot = container_of(c, sd_bus_slot, node_enumerator);
bus->current_slot = sd_bus_slot_ref(slot);
bus->current_userdata = slot->userdata;
r = c->callback(bus, prefix, slot->userdata, &children, error);
bus->current_userdata = NULL;
bus->current_slot = sd_bus_slot_unref(slot);
if (r < 0)
return r;
if (sd_bus_error_is_set(error))
return -sd_bus_error_get_errno(error);
STRV_FOREACH(k, children) {
if (r < 0) {
free(*k);
continue;
}
if (!object_path_is_valid(*k)) {
free(*k);
r = -EINVAL;
continue;
}
if (!object_path_startswith(*k, prefix)) {
free(*k);
continue;
}
r = set_consume(s, *k);
if (r == -EEXIST)
r = 0;
}
free(children);
if (r < 0)
return r;
}
return 0;
}
sd-bus: make introspection data non-recursive Currently, our introspection data looks like this: <node> <interface name="org.freedesktop.DBus.Peer"> ... </interface> <interface name="org.freedesktop.DBus.Introspectable"> ... </interface> <interface name="org.freedesktop.DBus.Properties"> ... </interface> <node name="org"/> <node name="org/freedesktop"/> <node name="org/freedesktop/login1"/> <node name="org/freedesktop/login1/user"/> <node name="org/freedesktop/login1/user/self"/> <node name="org/freedesktop/login1/user/_1000"/> <node name="org/freedesktop/login1/seat"/> <node name="org/freedesktop/login1/seat/self"/> <node name="org/freedesktop/login1/seat/seat0"/> <node name="org/freedesktop/login1/session"/> <node name="org/freedesktop/login1/session/self"/> <node name="org/freedesktop/login1/session/c1"/> </node> (ordered alphabetically for better visibility) This is grossly incorrect. The spec says that we're allowed to return non-directed children, however, it does not allow us to return data recursively in multiple parents. If we return "org", then we must not return anything else that starts with "org/". It is unclear, whether we can include child-nodes as a tree. Moreover, it is usually not what the caller wants. Hence, this patch changes sd-bus to never return introspection data recursively. Instead, only a single child-layer is returned. This patch relies on enumerators to never return hierarchies. If someone registers an enumerator via sd_bus_add_enumerator, they better register sub-enumerators if they support *TRUE* hierarchies. Each enumerator is treated as a single layer and not filtered. Enumerators are still allowed to return nested data. However, that data is still required to be a single hierarchy. For instance, returning "/org/foo" and "/com/bar" is fine, but including "/com" or "/org" in that dataset is not. This should be the default for enumerators and I see no reason to filter in sd-bus. Moreover, filtering that data-set would require to sort the strv by path and then do prefix-filtering. This is O(n log n), which would be fine, but still better to avoid. Fixes #664.
2015-09-05 19:43:29 +02:00
enum {
/* if set, add_subtree() works recursively */
CHILDREN_RECURSIVE = 1 << 0,
sd-bus: make introspection data non-recursive Currently, our introspection data looks like this: <node> <interface name="org.freedesktop.DBus.Peer"> ... </interface> <interface name="org.freedesktop.DBus.Introspectable"> ... </interface> <interface name="org.freedesktop.DBus.Properties"> ... </interface> <node name="org"/> <node name="org/freedesktop"/> <node name="org/freedesktop/login1"/> <node name="org/freedesktop/login1/user"/> <node name="org/freedesktop/login1/user/self"/> <node name="org/freedesktop/login1/user/_1000"/> <node name="org/freedesktop/login1/seat"/> <node name="org/freedesktop/login1/seat/self"/> <node name="org/freedesktop/login1/seat/seat0"/> <node name="org/freedesktop/login1/session"/> <node name="org/freedesktop/login1/session/self"/> <node name="org/freedesktop/login1/session/c1"/> </node> (ordered alphabetically for better visibility) This is grossly incorrect. The spec says that we're allowed to return non-directed children, however, it does not allow us to return data recursively in multiple parents. If we return "org", then we must not return anything else that starts with "org/". It is unclear, whether we can include child-nodes as a tree. Moreover, it is usually not what the caller wants. Hence, this patch changes sd-bus to never return introspection data recursively. Instead, only a single child-layer is returned. This patch relies on enumerators to never return hierarchies. If someone registers an enumerator via sd_bus_add_enumerator, they better register sub-enumerators if they support *TRUE* hierarchies. Each enumerator is treated as a single layer and not filtered. Enumerators are still allowed to return nested data. However, that data is still required to be a single hierarchy. For instance, returning "/org/foo" and "/com/bar" is fine, but including "/com" or "/org" in that dataset is not. This should be the default for enumerators and I see no reason to filter in sd-bus. Moreover, filtering that data-set would require to sort the strv by path and then do prefix-filtering. This is O(n log n), which would be fine, but still better to avoid. Fixes #664.
2015-09-05 19:43:29 +02:00
/* if set, add_subtree() scans object-manager hierarchies recursively */
CHILDREN_SUBHIERARCHIES = 1 << 1,
sd-bus: make introspection data non-recursive Currently, our introspection data looks like this: <node> <interface name="org.freedesktop.DBus.Peer"> ... </interface> <interface name="org.freedesktop.DBus.Introspectable"> ... </interface> <interface name="org.freedesktop.DBus.Properties"> ... </interface> <node name="org"/> <node name="org/freedesktop"/> <node name="org/freedesktop/login1"/> <node name="org/freedesktop/login1/user"/> <node name="org/freedesktop/login1/user/self"/> <node name="org/freedesktop/login1/user/_1000"/> <node name="org/freedesktop/login1/seat"/> <node name="org/freedesktop/login1/seat/self"/> <node name="org/freedesktop/login1/seat/seat0"/> <node name="org/freedesktop/login1/session"/> <node name="org/freedesktop/login1/session/self"/> <node name="org/freedesktop/login1/session/c1"/> </node> (ordered alphabetically for better visibility) This is grossly incorrect. The spec says that we're allowed to return non-directed children, however, it does not allow us to return data recursively in multiple parents. If we return "org", then we must not return anything else that starts with "org/". It is unclear, whether we can include child-nodes as a tree. Moreover, it is usually not what the caller wants. Hence, this patch changes sd-bus to never return introspection data recursively. Instead, only a single child-layer is returned. This patch relies on enumerators to never return hierarchies. If someone registers an enumerator via sd_bus_add_enumerator, they better register sub-enumerators if they support *TRUE* hierarchies. Each enumerator is treated as a single layer and not filtered. Enumerators are still allowed to return nested data. However, that data is still required to be a single hierarchy. For instance, returning "/org/foo" and "/com/bar" is fine, but including "/com" or "/org" in that dataset is not. This should be the default for enumerators and I see no reason to filter in sd-bus. Moreover, filtering that data-set would require to sort the strv by path and then do prefix-filtering. This is O(n log n), which would be fine, but still better to avoid. Fixes #664.
2015-09-05 19:43:29 +02:00
};
static int add_subtree_to_set(
sd_bus *bus,
const char *prefix,
struct node *n,
unsigned flags,
Set *s,
sd_bus_error *error) {
struct node *i;
int r;
assert(bus);
assert(prefix);
assert(n);
assert(s);
r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
LIST_FOREACH(siblings, i, n->child) {
char *t;
if (!object_path_startswith(i->path, prefix))
continue;
t = strdup(i->path);
if (!t)
return -ENOMEM;
r = set_consume(s, t);
if (r < 0 && r != -EEXIST)
return r;
sd-bus: make introspection data non-recursive Currently, our introspection data looks like this: <node> <interface name="org.freedesktop.DBus.Peer"> ... </interface> <interface name="org.freedesktop.DBus.Introspectable"> ... </interface> <interface name="org.freedesktop.DBus.Properties"> ... </interface> <node name="org"/> <node name="org/freedesktop"/> <node name="org/freedesktop/login1"/> <node name="org/freedesktop/login1/user"/> <node name="org/freedesktop/login1/user/self"/> <node name="org/freedesktop/login1/user/_1000"/> <node name="org/freedesktop/login1/seat"/> <node name="org/freedesktop/login1/seat/self"/> <node name="org/freedesktop/login1/seat/seat0"/> <node name="org/freedesktop/login1/session"/> <node name="org/freedesktop/login1/session/self"/> <node name="org/freedesktop/login1/session/c1"/> </node> (ordered alphabetically for better visibility) This is grossly incorrect. The spec says that we're allowed to return non-directed children, however, it does not allow us to return data recursively in multiple parents. If we return "org", then we must not return anything else that starts with "org/". It is unclear, whether we can include child-nodes as a tree. Moreover, it is usually not what the caller wants. Hence, this patch changes sd-bus to never return introspection data recursively. Instead, only a single child-layer is returned. This patch relies on enumerators to never return hierarchies. If someone registers an enumerator via sd_bus_add_enumerator, they better register sub-enumerators if they support *TRUE* hierarchies. Each enumerator is treated as a single layer and not filtered. Enumerators are still allowed to return nested data. However, that data is still required to be a single hierarchy. For instance, returning "/org/foo" and "/com/bar" is fine, but including "/com" or "/org" in that dataset is not. This should be the default for enumerators and I see no reason to filter in sd-bus. Moreover, filtering that data-set would require to sort the strv by path and then do prefix-filtering. This is O(n log n), which would be fine, but still better to avoid. Fixes #664.
2015-09-05 19:43:29 +02:00
if ((flags & CHILDREN_RECURSIVE) &&
((flags & CHILDREN_SUBHIERARCHIES) || !i->object_managers)) {
r = add_subtree_to_set(bus, prefix, i, flags, s, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
}
}
return 0;
}
static int get_child_nodes(
sd_bus *bus,
const char *prefix,
struct node *n,
unsigned flags,
Set **_s,
sd_bus_error *error) {
Set *s = NULL;
int r;
assert(bus);
assert(prefix);
assert(n);
assert(_s);
s = set_new(&string_hash_ops);
if (!s)
return -ENOMEM;
sd-bus: make introspection data non-recursive Currently, our introspection data looks like this: <node> <interface name="org.freedesktop.DBus.Peer"> ... </interface> <interface name="org.freedesktop.DBus.Introspectable"> ... </interface> <interface name="org.freedesktop.DBus.Properties"> ... </interface> <node name="org"/> <node name="org/freedesktop"/> <node name="org/freedesktop/login1"/> <node name="org/freedesktop/login1/user"/> <node name="org/freedesktop/login1/user/self"/> <node name="org/freedesktop/login1/user/_1000"/> <node name="org/freedesktop/login1/seat"/> <node name="org/freedesktop/login1/seat/self"/> <node name="org/freedesktop/login1/seat/seat0"/> <node name="org/freedesktop/login1/session"/> <node name="org/freedesktop/login1/session/self"/> <node name="org/freedesktop/login1/session/c1"/> </node> (ordered alphabetically for better visibility) This is grossly incorrect. The spec says that we're allowed to return non-directed children, however, it does not allow us to return data recursively in multiple parents. If we return "org", then we must not return anything else that starts with "org/". It is unclear, whether we can include child-nodes as a tree. Moreover, it is usually not what the caller wants. Hence, this patch changes sd-bus to never return introspection data recursively. Instead, only a single child-layer is returned. This patch relies on enumerators to never return hierarchies. If someone registers an enumerator via sd_bus_add_enumerator, they better register sub-enumerators if they support *TRUE* hierarchies. Each enumerator is treated as a single layer and not filtered. Enumerators are still allowed to return nested data. However, that data is still required to be a single hierarchy. For instance, returning "/org/foo" and "/com/bar" is fine, but including "/com" or "/org" in that dataset is not. This should be the default for enumerators and I see no reason to filter in sd-bus. Moreover, filtering that data-set would require to sort the strv by path and then do prefix-filtering. This is O(n log n), which would be fine, but still better to avoid. Fixes #664.
2015-09-05 19:43:29 +02:00
r = add_subtree_to_set(bus, prefix, n, flags, s, error);
if (r < 0) {
set_free_free(s);
return r;
}
*_s = s;
return 0;
}
static int node_callbacks_run(
sd_bus *bus,
sd_bus_message *m,
struct node_callback *first,
bool require_fallback,
bool *found_object) {
struct node_callback *c;
int r;
assert(bus);
assert(m);
assert(found_object);
LIST_FOREACH(callbacks, c, first) {
_cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
sd_bus_slot *slot;
if (bus->nodes_modified)
return 0;
if (require_fallback && !c->is_fallback)
continue;
*found_object = true;
if (c->last_iteration == bus->iteration_counter)
continue;
c->last_iteration = bus->iteration_counter;
r = sd_bus_message_rewind(m, true);
if (r < 0)
return r;
slot = container_of(c, sd_bus_slot, node_callback);
bus->current_slot = sd_bus_slot_ref(slot);
bus->current_handler = c->callback;
bus->current_userdata = slot->userdata;
r = c->callback(m, slot->userdata, &error_buffer);
bus->current_userdata = NULL;
bus->current_handler = NULL;
bus->current_slot = sd_bus_slot_unref(slot);
r = bus_maybe_reply_error(m, r, &error_buffer);
if (r != 0)
return r;
}
return 0;
}
2013-12-10 17:41:39 +01:00
#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF)
static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) {
uint64_t cap;
int r;
assert(bus);
assert(m);
assert(c);
/* If the entire bus is trusted let's grant access */
if (bus->trusted)
return 0;
/* If the member is marked UNPRIVILEGED let's grant access */
if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED)
return 0;
/* Check have the caller has the requested capability
* set. Note that the flags value contains the capability
* number plus one, which we need to subtract here. We do this
* so that we have 0 as special value for "default
* capability". */
cap = CAPABILITY_SHIFT(c->vtable->flags);
if (cap == 0)
cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags);
if (cap == 0)
cap = CAP_SYS_ADMIN;
else
cap--;
2013-12-10 17:41:39 +01:00
r = sd_bus_query_sender_privilege(m, cap);
if (r < 0)
return r;
2013-12-10 17:41:39 +01:00
if (r > 0)
return 0;
2013-12-10 17:41:39 +01:00
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member);
}
static int method_callbacks_run(
sd_bus *bus,
sd_bus_message *m,
struct vtable_member *c,
bool require_fallback,
bool *found_object) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
const char *signature;
void *u;
int r;
assert(bus);
assert(m);
assert(c);
assert(found_object);
if (require_fallback && !c->parent->is_fallback)
return 0;
if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
r = sd_bus_message_sensitive(m);
if (r < 0)
return r;
}
2013-12-10 17:41:39 +01:00
r = check_access(bus, m, c, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error);
if (r <= 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
u = vtable_method_convert_userdata(c->vtable, u);
*found_object = true;
if (c->last_iteration == bus->iteration_counter)
return 0;
c->last_iteration = bus->iteration_counter;
r = sd_bus_message_rewind(m, true);
if (r < 0)
return r;
signature = sd_bus_message_get_signature(m, true);
if (!signature)
return -EINVAL;
if (!streq(strempty(c->vtable->x.method.signature), signature))
return sd_bus_reply_method_errorf(
m,
SD_BUS_ERROR_INVALID_ARGS,
"Invalid arguments '%s' to call %s.%s(), expecting '%s'.",
signature, c->interface, c->member, strempty(c->vtable->x.method.signature));
/* Keep track what the signature of the reply to this message
* should be, so that this can be enforced when sealing the
* reply. */
m->enforced_reply_signature = strempty(c->vtable->x.method.result);
if (c->vtable->x.method.handler) {
sd_bus_slot *slot;
slot = container_of(c->parent, sd_bus_slot, node_vtable);
bus->current_slot = sd_bus_slot_ref(slot);
bus->current_handler = c->vtable->x.method.handler;
bus->current_userdata = u;
r = c->vtable->x.method.handler(m, u, &error);
bus->current_userdata = NULL;
bus->current_handler = NULL;
bus->current_slot = sd_bus_slot_unref(slot);
return bus_maybe_reply_error(m, r, &error);
}
/* If the method callback is NULL, make this a successful NOP */
r = sd_bus_reply_method_return(m, NULL);
if (r < 0)
return r;
return 1;
}
static int invoke_property_get(
sd_bus *bus,
sd_bus_slot *slot,
const sd_bus_vtable *v,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
const void *p;
int r;
assert(bus);
assert(slot);
assert(v);
assert(path);
assert(interface);
assert(property);
assert(reply);
if (v->x.property.get) {
bus->current_slot = sd_bus_slot_ref(slot);
bus->current_userdata = userdata;
r = v->x.property.get(bus, path, interface, property, reply, userdata, error);
bus->current_userdata = NULL;
bus->current_slot = sd_bus_slot_unref(slot);
if (r < 0)
return r;
if (sd_bus_error_is_set(error))
return -sd_bus_error_get_errno(error);
return r;
}
/* Automatic handling if no callback is defined. */
if (streq(v->x.property.signature, "as"))
return sd_bus_message_append_strv(reply, *(char***) userdata);
assert(signature_is_single(v->x.property.signature, false));
assert(bus_type_is_basic(v->x.property.signature[0]));
switch (v->x.property.signature[0]) {
case SD_BUS_TYPE_STRING:
2013-10-22 13:40:54 +02:00
case SD_BUS_TYPE_SIGNATURE:
p = strempty(*(char**) userdata);
break;
case SD_BUS_TYPE_OBJECT_PATH:
p = *(char**) userdata;
2013-10-22 13:40:54 +02:00
assert(p);
break;
default:
p = userdata;
break;
}
return sd_bus_message_append_basic(reply, v->x.property.signature[0], p);
}
static int invoke_property_set(
sd_bus *bus,
sd_bus_slot *slot,
const sd_bus_vtable *v,
const char *path,
const char *interface,
const char *property,
sd_bus_message *value,
void *userdata,
sd_bus_error *error) {
int r;
assert(bus);
assert(slot);
assert(v);
assert(path);
assert(interface);
assert(property);
assert(value);
if (v->x.property.set) {
bus->current_slot = sd_bus_slot_ref(slot);
bus->current_userdata = userdata;
r = v->x.property.set(bus, path, interface, property, value, userdata, error);
bus->current_userdata = NULL;
bus->current_slot = sd_bus_slot_unref(slot);
if (r < 0)
return r;
if (sd_bus_error_is_set(error))
return -sd_bus_error_get_errno(error);
return r;
}
/* Automatic handling if no callback is defined. */
assert(signature_is_single(v->x.property.signature, false));
assert(bus_type_is_basic(v->x.property.signature[0]));
switch (v->x.property.signature[0]) {
case SD_BUS_TYPE_STRING:
case SD_BUS_TYPE_OBJECT_PATH:
case SD_BUS_TYPE_SIGNATURE: {
const char *p;
char *n;
r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p);
if (r < 0)
return r;
n = strdup(p);
if (!n)
return -ENOMEM;
free(*(char**) userdata);
*(char**) userdata = n;
break;
}
default:
r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata);
if (r < 0)
return r;
break;
}
return 1;
}
static int property_get_set_callbacks_run(
sd_bus *bus,
sd_bus_message *m,
struct vtable_member *c,
bool require_fallback,
bool is_get,
bool *found_object) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
sd_bus_slot *slot;
void *u = NULL;
int r;
assert(bus);
assert(m);
assert(c);
assert(found_object);
if (require_fallback && !c->parent->is_fallback)
return 0;
if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
r = sd_bus_message_sensitive(m);
if (r < 0)
return r;
}
r = vtable_property_get_userdata(bus, m->path, c, &u, &error);
if (r <= 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
slot = container_of(c->parent, sd_bus_slot, node_vtable);
*found_object = true;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
r = sd_bus_message_sensitive(reply);
if (r < 0)
return r;
}
if (is_get) {
/* Note that we do not protect against reexecution
* here (using the last_iteration check, see below),
* should the node tree have changed and we got called
* again. We assume that property Get() calls are
* ultimately without side-effects or if they aren't
* then at least idempotent. */
r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature);
if (r < 0)
return r;
2013-12-10 17:41:39 +01:00
/* Note that we do not do an access check here. Read
* access to properties is always unrestricted, since
* PropertiesChanged signals broadcast contents
* anyway. */
r = invoke_property_get(bus, slot, c->vtable, m->path, c->interface, c->member, reply, u, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
} else {
const char *signature = NULL;
char type = 0;
if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY)
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member);
/* Avoid that we call the set routine more than once
* if the processing of this message got restarted
* because the node tree changed. */
if (c->last_iteration == bus->iteration_counter)
return 0;
c->last_iteration = bus->iteration_counter;
r = sd_bus_message_peek_type(m, &type, &signature);
if (r < 0)
return r;
if (type != 'v')
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_SIGNATURE,
"Incorrect signature when setting property '%s', expected 'v', got '%c'.",
c->member, type);
if (!streq(strempty(signature), strempty(c->vtable->x.property.signature)))
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS,
"Incorrect parameters for property '%s', expected '%s', got '%s'.",
c->member, strempty(c->vtable->x.property.signature), strempty(signature));
r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature);
if (r < 0)
return r;
2013-12-10 17:41:39 +01:00
r = check_access(bus, m, c, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
r = invoke_property_set(bus, slot, c->vtable, m->path, c->interface, c->member, m, u, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
r = sd_bus_message_exit_container(m);
if (r < 0)
return r;
}
r = sd_bus_send(bus, reply, NULL);
if (r < 0)
return r;
return 1;
}
static int vtable_append_one_property(
sd_bus *bus,
sd_bus_message *reply,
const char *path,
struct node_vtable *c,
const sd_bus_vtable *v,
void *userdata,
sd_bus_error *error) {
sd_bus_slot *slot;
int r;
assert(bus);
assert(reply);
assert(path);
assert(c);
assert(v);
if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_SENSITIVE)) {
r = sd_bus_message_sensitive(reply);
if (r < 0)
return r;
}
r = sd_bus_message_open_container(reply, 'e', "sv");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", v->x.property.member);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'v', v->x.property.signature);
if (r < 0)
return r;
slot = container_of(c, sd_bus_slot, node_vtable);
r = invoke_property_get(bus, slot, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return 0;
}
static int vtable_append_all_properties(
sd_bus *bus,
sd_bus_message *reply,
const char *path,
struct node_vtable *c,
void *userdata,
sd_bus_error *error) {
const sd_bus_vtable *v;
int r;
assert(bus);
assert(reply);
assert(path);
assert(c);
if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
return 1;
v = c->vtable;
for (v = bus_vtable_next(c->vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(c->vtable, v)) {
2017-09-28 10:17:04 +02:00
if (!IN_SET(v->type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY))
continue;
if (v->flags & SD_BUS_VTABLE_HIDDEN)
continue;
/* Let's not include properties marked as "explicit" in any message that contains a generic
* dump of properties, but only in those generated as a response to an explicit request. */
if (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)
continue;
/* Let's not include properties marked only for invalidation on change (i.e. in contrast to
* those whose new values are included in PropertiesChanges message) in any signals. This is
* useful to ensure they aren't included in InterfacesAdded messages. */
if (reply->header->type != SD_BUS_MESSAGE_METHOD_RETURN &&
FLAGS_SET(v->flags, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
continue;
r = vtable_append_one_property(bus, reply, path, c, v, userdata, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
}
return 1;
}
static int property_get_all_callbacks_run(
sd_bus *bus,
sd_bus_message *m,
struct node_vtable *first,
bool require_fallback,
const char *iface,
bool *found_object) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
struct node_vtable *c;
bool found_interface;
int r;
assert(bus);
assert(m);
assert(found_object);
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{sv}");
if (r < 0)
return r;
found_interface = !iface || STR_IN_SET(iface,
"org.freedesktop.DBus.Properties",
"org.freedesktop.DBus.Peer",
"org.freedesktop.DBus.Introspectable");
LIST_FOREACH(vtables, c, first) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
void *u;
if (require_fallback && !c->is_fallback)
continue;
r = node_vtable_get_userdata(bus, m->path, c, &u, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
if (r == 0)
continue;
*found_object = true;
if (iface && !streq(c->interface, iface))
continue;
found_interface = true;
r = vtable_append_all_properties(bus, reply, m->path, c, u, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
}
if (!*found_object)
return 0;
if (!found_interface) {
r = sd_bus_reply_method_errorf(
m,
SD_BUS_ERROR_UNKNOWN_INTERFACE,
"Unknown interface '%s'.", iface);
if (r < 0)
return r;
return 1;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
r = sd_bus_send(bus, reply, NULL);
if (r < 0)
return r;
return 1;
}
static int bus_node_exists(
sd_bus *bus,
struct node *n,
const char *path,
bool require_fallback) {
struct node_vtable *c;
struct node_callback *k;
int r;
assert(bus);
assert(n);
assert(path);
/* Tests if there's anything attached directly to this node
* for the specified path */
if (!require_fallback && (n->enumerators || n->object_managers))
return true;
LIST_FOREACH(callbacks, k, n->callbacks) {
if (require_fallback && !k->is_fallback)
continue;
return 1;
}
LIST_FOREACH(vtables, c, n->vtables) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (require_fallback && !c->is_fallback)
continue;
r = node_vtable_get_userdata(bus, path, c, NULL, &error);
if (r != 0)
return r;
if (bus->nodes_modified)
return 0;
}
return 0;
}
int introspect_path(
sd_bus *bus,
const char *path,
struct node *n,
bool require_fallback,
bool ignore_nodes_modified,
bool *found_object,
char **ret,
sd_bus_error *error) {
_cleanup_set_free_free_ Set *s = NULL;
_cleanup_(introspect_free) struct introspect intro = {};
struct node_vtable *c;
bool empty;
int r;
if (!n) {
n = hashmap_get(bus->nodes, path);
if (!n)
return -ENOENT;
}
r = get_child_nodes(bus, path, n, 0, &s, error);
if (r < 0)
return r;
if (bus->nodes_modified && !ignore_nodes_modified)
return 0;
r = introspect_begin(&intro, bus->trusted);
if (r < 0)
return r;
r = introspect_write_default_interfaces(&intro, !require_fallback && n->object_managers);
if (r < 0)
return r;
empty = set_isempty(s);
LIST_FOREACH(vtables, c, n->vtables) {
if (require_fallback && !c->is_fallback)
continue;
r = node_vtable_get_userdata(bus, path, c, NULL, error);
if (r < 0)
return r;
if (bus->nodes_modified && !ignore_nodes_modified)
return 0;
if (r == 0)
continue;
empty = false;
if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
continue;
r = introspect_write_interface(&intro, c->interface, c->vtable);
if (r < 0)
return r;
}
if (empty) {
/* Nothing?, let's see if we exist at all, and if not
* refuse to do anything */
r = bus_node_exists(bus, n, path, require_fallback);
if (r <= 0)
return r;
if (bus->nodes_modified && !ignore_nodes_modified)
return 0;
}
if (found_object)
*found_object = true;
r = introspect_write_child_nodes(&intro, s, path);
if (r < 0)
return r;
r = introspect_finish(&intro, ret);
if (r < 0)
return r;
return 1;
}
static int process_introspect(
sd_bus *bus,
sd_bus_message *m,
struct node *n,
bool require_fallback,
bool *found_object) {
_cleanup_free_ char *s = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int r;
assert(bus);
assert(m);
assert(n);
assert(found_object);
r = introspect_path(bus, m->path, n, require_fallback, false, found_object, &s, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (r == 0)
/* nodes_modified == true */
return 0;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", s);
if (r < 0)
return r;
r = sd_bus_send(bus, reply, NULL);
if (r < 0)
return r;
return 1;
}
static int object_manager_serialize_path(
sd_bus *bus,
sd_bus_message *reply,
const char *prefix,
const char *path,
bool require_fallback,
sd_bus_error *error) {
const char *previous_interface = NULL;
bool found_something = false;
struct node_vtable *i;
struct node *n;
int r;
assert(bus);
assert(reply);
assert(prefix);
assert(path);
assert(error);
n = hashmap_get(bus->nodes, prefix);
if (!n)
return 0;
LIST_FOREACH(vtables, i, n->vtables) {
void *u;
if (require_fallback && !i->is_fallback)
continue;
r = node_vtable_get_userdata(bus, path, i, &u, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
if (r == 0)
continue;
if (!found_something) {
/* Open the object part */
r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "o", path);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0);
if (r < 0)
return r;
found_something = true;
}
if (!streq_ptr(previous_interface, i->interface)) {
/* Maybe close the previous interface part */
if (previous_interface) {
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
}
/* Open the new interface part */
r = sd_bus_message_open_container(reply, 'e', "sa{sv}");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", i->interface);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{sv}");
if (r < 0)
return r;
}
r = vtable_append_all_properties(bus, reply, path, i, u, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
previous_interface = i->interface;
}
if (previous_interface) {
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
}
if (found_something) {
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
}
return 1;
}
static int object_manager_serialize_path_and_fallbacks(
sd_bus *bus,
sd_bus_message *reply,
const char *path,
sd_bus_error *error) {
_cleanup_free_ char *prefix = NULL;
size_t pl;
int r;
assert(bus);
assert(reply);
assert(path);
assert(error);
/* First, add all vtables registered for this path */
r = object_manager_serialize_path(bus, reply, path, path, false, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
/* Second, add fallback vtables registered for any of the prefixes */
pl = strlen(path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
r = object_manager_serialize_path(bus, reply, prefix, path, true, error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
}
return 0;
}
static int process_get_managed_objects(
sd_bus *bus,
sd_bus_message *m,
struct node *n,
bool require_fallback,
bool *found_object) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_set_free_free_ Set *s = NULL;
Iterator i;
char *path;
int r;
assert(bus);
assert(m);
assert(n);
assert(found_object);
/* Spec says, GetManagedObjects() is only implemented on the root of a
* sub-tree. Therefore, we require a registered object-manager on
* exactly the queried path, otherwise, we refuse to respond. */
if (require_fallback || !n->object_managers)
return 0;
sd-bus: make introspection data non-recursive Currently, our introspection data looks like this: <node> <interface name="org.freedesktop.DBus.Peer"> ... </interface> <interface name="org.freedesktop.DBus.Introspectable"> ... </interface> <interface name="org.freedesktop.DBus.Properties"> ... </interface> <node name="org"/> <node name="org/freedesktop"/> <node name="org/freedesktop/login1"/> <node name="org/freedesktop/login1/user"/> <node name="org/freedesktop/login1/user/self"/> <node name="org/freedesktop/login1/user/_1000"/> <node name="org/freedesktop/login1/seat"/> <node name="org/freedesktop/login1/seat/self"/> <node name="org/freedesktop/login1/seat/seat0"/> <node name="org/freedesktop/login1/session"/> <node name="org/freedesktop/login1/session/self"/> <node name="org/freedesktop/login1/session/c1"/> </node> (ordered alphabetically for better visibility) This is grossly incorrect. The spec says that we're allowed to return non-directed children, however, it does not allow us to return data recursively in multiple parents. If we return "org", then we must not return anything else that starts with "org/". It is unclear, whether we can include child-nodes as a tree. Moreover, it is usually not what the caller wants. Hence, this patch changes sd-bus to never return introspection data recursively. Instead, only a single child-layer is returned. This patch relies on enumerators to never return hierarchies. If someone registers an enumerator via sd_bus_add_enumerator, they better register sub-enumerators if they support *TRUE* hierarchies. Each enumerator is treated as a single layer and not filtered. Enumerators are still allowed to return nested data. However, that data is still required to be a single hierarchy. For instance, returning "/org/foo" and "/com/bar" is fine, but including "/com" or "/org" in that dataset is not. This should be the default for enumerators and I see no reason to filter in sd-bus. Moreover, filtering that data-set would require to sort the strv by path and then do prefix-filtering. This is O(n log n), which would be fine, but still better to avoid. Fixes #664.
2015-09-05 19:43:29 +02:00
r = get_child_nodes(bus, m->path, n, CHILDREN_RECURSIVE, &s, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}");
if (r < 0)
return r;
SET_FOREACH(path, s, i) {
r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->nodes_modified)
return 0;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
r = sd_bus_send(bus, reply, NULL);
if (r < 0)
return r;
return 1;
}
static int object_find_and_run(
sd_bus *bus,
sd_bus_message *m,
const char *p,
bool require_fallback,
bool *found_object) {
struct node *n;
struct vtable_member vtable_key, *v;
int r;
assert(bus);
assert(m);
assert(p);
assert(found_object);
n = hashmap_get(bus->nodes, p);
if (!n)
return 0;
/* First, try object callbacks */
r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object);
if (r != 0)
return r;
if (bus->nodes_modified)
return 0;
if (!m->interface || !m->member)
return 0;
/* Then, look for a known method */
vtable_key.path = (char*) p;
vtable_key.interface = m->interface;
vtable_key.member = m->member;
v = hashmap_get(bus->vtable_methods, &vtable_key);
if (v) {
r = method_callbacks_run(bus, m, v, require_fallback, found_object);
if (r != 0)
return r;
if (bus->nodes_modified)
return 0;
}
/* Then, look for a known property */
if (streq(m->interface, "org.freedesktop.DBus.Properties")) {
bool get = false;
get = streq(m->member, "Get");
if (get || streq(m->member, "Set")) {
r = sd_bus_message_rewind(m, true);
if (r < 0)
return r;
vtable_key.path = (char*) p;
r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member);
if (r < 0)
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters");
v = hashmap_get(bus->vtable_properties, &vtable_key);
if (v) {
r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object);
if (r != 0)
return r;
}
} else if (streq(m->member, "GetAll")) {
const char *iface;
r = sd_bus_message_rewind(m, true);
if (r < 0)
return r;
r = sd_bus_message_read(m, "s", &iface);
if (r < 0)
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter");
if (iface[0] == 0)
iface = NULL;
r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object);
if (r != 0)
return r;
}
} else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
if (!isempty(sd_bus_message_get_signature(m, true)))
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters");
r = process_introspect(bus, m, n, require_fallback, found_object);
if (r != 0)
return r;
} else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) {
if (!isempty(sd_bus_message_get_signature(m, true)))
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters");
r = process_get_managed_objects(bus, m, n, require_fallback, found_object);
if (r != 0)
return r;
}
if (bus->nodes_modified)
return 0;
if (!*found_object) {
r = bus_node_exists(bus, n, m->path, require_fallback);
if (r < 0)
return bus_maybe_reply_error(m, r, NULL);
if (bus->nodes_modified)
return 0;
if (r > 0)
*found_object = true;
}
return 0;
}
int bus_process_object(sd_bus *bus, sd_bus_message *m) {
_cleanup_free_ char *prefix = NULL;
int r;
size_t pl;
bool found_object = false;
assert(bus);
assert(m);
if (bus->is_monitor)
2014-03-19 04:17:00 +01:00
return 0;
if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
return 0;
if (hashmap_isempty(bus->nodes))
return 0;
/* Never respond to broadcast messages */
if (bus->bus_client && !m->destination)
return 0;
2013-12-10 17:41:39 +01:00
assert(m->path);
assert(m->member);
pl = strlen(m->path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
do {
bus->nodes_modified = false;
r = object_find_and_run(bus, m, m->path, false, &found_object);
if (r != 0)
return r;
/* Look for fallback prefixes */
OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) {
if (bus->nodes_modified)
break;
r = object_find_and_run(bus, m, prefix, true, &found_object);
if (r != 0)
return r;
}
} while (bus->nodes_modified);
if (!found_object)
return 0;
if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") ||
sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) {
const char *interface = NULL, *property = NULL;
(void) sd_bus_message_rewind(m, true);
(void) sd_bus_message_read_basic(m, 's', &interface);
(void) sd_bus_message_read_basic(m, 's', &property);
r = sd_bus_reply_method_errorf(
m,
SD_BUS_ERROR_UNKNOWN_PROPERTY,
"Unknown interface %s or property %s.", strnull(interface), strnull(property));
} else
r = sd_bus_reply_method_errorf(
m,
SD_BUS_ERROR_UNKNOWN_METHOD,
"Unknown method %s or interface %s.", m->member, m->interface);
if (r < 0)
return r;
return 1;
}
static struct node *bus_node_allocate(sd_bus *bus, const char *path) {
struct node *n, *parent;
const char *e;
_cleanup_free_ char *s = NULL;
char *p;
int r;
assert(bus);
assert(path);
assert(path[0] == '/');
n = hashmap_get(bus->nodes, path);
if (n)
return n;
r = hashmap_ensure_allocated(&bus->nodes, &string_hash_ops);
if (r < 0)
return NULL;
s = strdup(path);
if (!s)
return NULL;
if (streq(path, "/"))
parent = NULL;
else {
e = strrchr(path, '/');
assert(e);
p = strndupa(path, MAX(1, e - path));
parent = bus_node_allocate(bus, p);
if (!parent)
return NULL;
}
n = new0(struct node, 1);
if (!n)
return NULL;
n->parent = parent;
n->path = TAKE_PTR(s);
r = hashmap_put(bus->nodes, n->path, n);
if (r < 0) {
free(n->path);
return mfree(n);
}
if (parent)
LIST_PREPEND(siblings, parent->child, n);
return n;
}
void bus_node_gc(sd_bus *b, struct node *n) {
assert(b);
if (!n)
return;
if (n->child ||
n->callbacks ||
n->vtables ||
n->enumerators ||
n->object_managers)
return;
assert_se(hashmap_remove(b->nodes, n->path) == n);
if (n->parent)
LIST_REMOVE(siblings, n->parent->child, n);
free(n->path);
bus_node_gc(b, n->parent);
free(n);
}
static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path) {
struct node *n;
assert(bus);
assert(path);
n = hashmap_get(bus->nodes, path);
if (!n) {
_cleanup_free_ char *prefix = NULL;
size_t pl;
pl = strlen(path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
n = hashmap_get(bus->nodes, prefix);
if (n)
break;
}
}
while (n && !n->object_managers)
n = n->parent;
if (out)
*out = n;
return !!n;
}
static int bus_add_object(
sd_bus *bus,
sd_bus_slot **slot,
bool fallback,
const char *path,
sd_bus_message_handler_t callback,
void *userdata) {
sd_bus_slot *s;
struct node *n;
int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(callback, -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
n = bus_node_allocate(bus, path);
if (!n)
return -ENOMEM;
s = bus_slot_allocate(bus, !slot, BUS_NODE_CALLBACK, sizeof(struct node_callback), userdata);
if (!s) {
r = -ENOMEM;
goto fail;
}
s->node_callback.callback = callback;
s->node_callback.is_fallback = fallback;
s->node_callback.node = n;
LIST_PREPEND(callbacks, n->callbacks, &s->node_callback);
bus->nodes_modified = true;
if (slot)
*slot = s;
return 0;
fail:
sd_bus_slot_unref(s);
bus_node_gc(bus, n);
return r;
}
_public_ int sd_bus_add_object(
sd_bus *bus,
sd_bus_slot **slot,
const char *path,
sd_bus_message_handler_t callback,
void *userdata) {
return bus_add_object(bus, slot, false, path, callback, userdata);
}
_public_ int sd_bus_add_fallback(
sd_bus *bus,
sd_bus_slot **slot,
const char *prefix,
sd_bus_message_handler_t callback,
void *userdata) {
return bus_add_object(bus, slot, true, prefix, callback, userdata);
}
2018-11-27 14:25:20 +01:00
static void vtable_member_hash_func(const struct vtable_member *m, struct siphash *state) {
assert(m);
string_hash_func(m->path, state);
string_hash_func(m->interface, state);
string_hash_func(m->member, state);
}
2018-11-27 14:25:20 +01:00
static int vtable_member_compare_func(const struct vtable_member *x, const struct vtable_member *y) {
int r;
assert(x);
assert(y);
r = strcmp(x->path, y->path);
if (r != 0)
return r;
r = strcmp(x->interface, y->interface);
if (r != 0)
return r;
return strcmp(x->member, y->member);
}
2018-11-27 14:25:20 +01:00
DEFINE_PRIVATE_HASH_OPS(vtable_member_hash_ops, struct vtable_member, vtable_member_hash_func, vtable_member_compare_func);
typedef enum {
NAMES_FIRST_PART = 1 << 0, /* first part of argument name list (input names). It is reset by names_are_valid() */
NAMES_PRESENT = 1 << 1, /* at least one argument name is present, so the names will checked.
This flag is set and used internally by names_are_valid(), but needs to be stored across calls for 2-parts list */
NAMES_SINGLE_PART = 1 << 2, /* argument name list consisting of a single part */
} names_flags;
static bool names_are_valid(const char *signature, const char **names, names_flags *flags) {
int r;
if ((*flags & NAMES_FIRST_PART || *flags & NAMES_SINGLE_PART) && **names != '\0')
*flags |= NAMES_PRESENT;
for (;*flags & NAMES_PRESENT;) {
size_t l;
if (!*signature)
break;
r = signature_element_length(signature, &l);
if (r < 0)
return false;
if (**names != '\0') {
if (!member_name_is_valid(*names))
return false;
*names += strlen(*names) + 1;
} else if (*flags & NAMES_PRESENT)
return false;
signature += l;
}
/* let's check if there are more argument names specified than the signature allows */
if (*flags & NAMES_PRESENT && **names != '\0' && !(*flags & NAMES_FIRST_PART))
return false;
*flags &= ~NAMES_FIRST_PART;
return true;
}
/* the current version of this struct is defined in sd-bus-vtable.h, but we need to list here the historical versions
to make sure the calling code is compatible with one of these */
struct sd_bus_vtable_221 {
uint8_t type:8;
uint64_t flags:56;
union {
struct {
size_t element_size;
} start;
struct {
const char *member;
const char *signature;
const char *result;
sd_bus_message_handler_t handler;
size_t offset;
} method;
struct {
const char *member;
const char *signature;
} signal;
struct {
const char *member;
const char *signature;
sd_bus_property_get_t get;
sd_bus_property_set_t set;
size_t offset;
} property;
} x;
};
/* Structure size up to v241 */
#define VTABLE_ELEMENT_SIZE_221 sizeof(struct sd_bus_vtable_221)
/* Size of the structure when "features" field was added. If the structure definition is augmented, a copy of
* the structure definition will need to be made (similarly to the sd_bus_vtable_221 above), and this
* definition updated to refer to it. */
#define VTABLE_ELEMENT_SIZE_242 sizeof(struct sd_bus_vtable)
static int vtable_features(const sd_bus_vtable *vtable) {
if (vtable[0].x.start.element_size < VTABLE_ELEMENT_SIZE_242 ||
sd-bus: add symbol to tell linker that new vtable functions are used In 856ad2a86bd9b3e264a090fcf4b0d05bfaa91030 sd_bus_add_object_vtable() and sd_bus_add_fallback_vtable() were changed to take an updated sd_bus_vtable[] array with additional 'features' and 'names' fields in the union. The commit tried to check whether the old or the new table format is used, by looking at the vtable[0].x.start.element_size field, on the assumption that the added fields caused the structure size to grow. Unfortunately, this assumption was false, and on arm32 (at least), the structure size is unchanged. In libsystemd we use symbol versioning and a major.minor.patch semantic versioning of the library name (major equals the number in the so-name). When systemd-242 was released, the minor number was (correctly) bumped, but this is not enough, because no new symbols were added or symbol versions changed. This means that programs compiled with the new systemd headers and library could be successfully linked to older versions of the library. For example rpm only looks at the so-name and the list of versioned symbols, completely ignoring the major.minor numbers in the library name. But the older library does not understand the new vtable format, and would return -EINVAL after failing the size check (on those architectures where the structure size did change, i.e. all 64 bit architectures). To force new libsystemd (with the functions that take the updated sd_bus_vtable[] format) to be used, let's pull in a dummy symbol from the table definition. This is a bit wasteful, because a dummy pointer has to be stored, but the effect is negligible. In particular, the pointer doesn't even change the size of the structure because if fits in an unused area in the union. The number stored in the new unsigned integer is not checked anywhere. If the symbol exists, we already know we have the new version of the library, so an additional check would not tell us anything. An alternative would be to make sd_bus_add_{object,fallback}_vtable() versioned symbols, using .symver linker annotations. We would provide sd_bus_add_{object,fallback}_vtable@LIBSYSTEMD_221 (for backwards compatibility) and e.g. sd_bus_add_{object,fallback}_vtable@@LIBSYSTEMD_242 (the default) with the new implementation. This would work too, but is more work. We would have to version at least those two functions. And it turns out that the .symver linker instructions have to located in the same compilation unit as the function being annotated. We first compile libsystemd.a, and then link it into libsystemd.so and various other targets, including libsystemd-shared.so, and the nss modules. If the .symver annotations were placed next to the function definitions (in bus-object.c), they would influence all targets that link libsystemd.a, and cause problems, because those functions should not be exported there. To export them only in libsystemd.so, compilation would have to be rearranged, so that the functions exported in libsystemd.so would not be present in libsystemd.a, but a separate compilation unit containg them and the .symver annotations would be linked solely into libsystemd.so. This is certainly possible, but more work than the approach in this patch. 856ad2a86bd9b3e264a090fcf4b0d05bfaa91030 has one more issue: it relies on the undefined fields in sd_bus_vtable[] array to be zeros. But the structure contains a union, and fields of the union do not have to be zero-initalized by the compiler. This means that potentially, we could have garbarge values there, for example when reading the old vtable format definition from the new function implementation. In practice this should not be an issue at all, because vtable definitions are static data and are placed in the ro-data section, which is fully initalized, so we know that those undefined areas will be zero. Things would be different if somebody defined the vtable array on the heap or on the stack. Let's just document that they should zero-intialize the unused areas in this case. The symbol checking code had to be updated because otherwise gcc warns about a cast from unsigned to a pointer.
2019-04-18 13:06:41 +02:00
!vtable[0].x.start.vtable_format_reference)
return 0;
return vtable[0].x.start.features;
}
bool bus_vtable_has_names(const sd_bus_vtable *vtable) {
return vtable_features(vtable) & _SD_BUS_VTABLE_PARAM_NAMES;
}
const sd_bus_vtable* bus_vtable_next(const sd_bus_vtable *vtable, const sd_bus_vtable *v) {
return (const sd_bus_vtable*) ((char*) v + vtable[0].x.start.element_size);
}
static int add_object_vtable_internal(
sd_bus *bus,
sd_bus_slot **slot,
const char *path,
const char *interface,
const sd_bus_vtable *vtable,
bool fallback,
sd_bus_object_find_t find,
void *userdata) {
sd_bus_slot *s = NULL;
struct node_vtable *i, *existing = NULL;
const sd_bus_vtable *v;
struct node *n;
int r;
const char *names = "";
names_flags nf;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(interface_name_is_valid(interface), -EINVAL);
assert_return(vtable, -EINVAL);
assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL);
assert_return(vtable[0].x.start.element_size == VTABLE_ELEMENT_SIZE_221 ||
vtable[0].x.start.element_size >= VTABLE_ELEMENT_SIZE_242,
-EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
assert_return(!streq(interface, "org.freedesktop.DBus.Properties") &&
!streq(interface, "org.freedesktop.DBus.Introspectable") &&
!streq(interface, "org.freedesktop.DBus.Peer") &&
!streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL);
r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops);
if (r < 0)
return r;
r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops);
if (r < 0)
return r;
n = bus_node_allocate(bus, path);
if (!n)
return -ENOMEM;
LIST_FOREACH(vtables, i, n->vtables) {
if (i->is_fallback != fallback) {
r = -EPROTOTYPE;
goto fail;
}
if (streq(i->interface, interface)) {
if (i->vtable == vtable) {
r = -EEXIST;
goto fail;
}
existing = i;
}
}
s = bus_slot_allocate(bus, !slot, BUS_NODE_VTABLE, sizeof(struct node_vtable), userdata);
if (!s) {
r = -ENOMEM;
goto fail;
}
s->node_vtable.is_fallback = fallback;
s->node_vtable.vtable = vtable;
s->node_vtable.find = find;
s->node_vtable.interface = strdup(interface);
if (!s->node_vtable.interface) {
r = -ENOMEM;
goto fail;
}
v = s->node_vtable.vtable;
for (v = bus_vtable_next(vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(vtable, v)) {
switch (v->type) {
case _SD_BUS_VTABLE_METHOD: {
struct vtable_member *m;
nf = NAMES_FIRST_PART;
if (bus_vtable_has_names(vtable))
names = strempty(v->x.method.names);
if (!member_name_is_valid(v->x.method.member) ||
!signature_is_valid(strempty(v->x.method.signature), false) ||
!signature_is_valid(strempty(v->x.method.result), false) ||
!names_are_valid(strempty(v->x.method.signature), &names, &nf) ||
!names_are_valid(strempty(v->x.method.result), &names, &nf) ||
!(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) ||
v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) {
r = -EINVAL;
goto fail;
}
m = new0(struct vtable_member, 1);
if (!m) {
r = -ENOMEM;
goto fail;
}
m->parent = &s->node_vtable;
m->path = n->path;
m->interface = s->node_vtable.interface;
m->member = v->x.method.member;
m->vtable = v;
r = hashmap_put(bus->vtable_methods, m, m);
if (r < 0) {
free(m);
goto fail;
}
break;
}
case _SD_BUS_VTABLE_WRITABLE_PROPERTY:
if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) {
r = -EINVAL;
goto fail;
}
if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) {
r = -EINVAL;
goto fail;
}
_fallthrough_;
case _SD_BUS_VTABLE_PROPERTY: {
struct vtable_member *m;
if (!member_name_is_valid(v->x.property.member) ||
!signature_is_single(v->x.property.signature, false) ||
!(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) ||
(v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ||
(!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 ||
((v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) && (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)) ||
2013-12-10 17:41:39 +01:00
(v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) {
r = -EINVAL;
goto fail;
}
m = new0(struct vtable_member, 1);
if (!m) {
r = -ENOMEM;
goto fail;
}
m->parent = &s->node_vtable;
m->path = n->path;
m->interface = s->node_vtable.interface;
m->member = v->x.property.member;
m->vtable = v;
r = hashmap_put(bus->vtable_properties, m, m);
if (r < 0) {
free(m);
goto fail;
}
break;
}
case _SD_BUS_VTABLE_SIGNAL:
nf = NAMES_SINGLE_PART;
if (bus_vtable_has_names(vtable))
names = strempty(v->x.signal.names);
if (!member_name_is_valid(v->x.signal.member) ||
2013-12-10 17:41:39 +01:00
!signature_is_valid(strempty(v->x.signal.signature), false) ||
!names_are_valid(strempty(v->x.signal.signature), &names, &nf) ||
2013-12-10 17:41:39 +01:00
v->flags & SD_BUS_VTABLE_UNPRIVILEGED) {
r = -EINVAL;
goto fail;
}
break;
default:
r = -EINVAL;
goto fail;
}
}
s->node_vtable.node = n;
LIST_INSERT_AFTER(vtables, n->vtables, existing, &s->node_vtable);
bus->nodes_modified = true;
if (slot)
*slot = s;
return 0;
fail:
sd_bus_slot_unref(s);
bus_node_gc(bus, n);
return r;
}
sd-bus: add symbol to tell linker that new vtable functions are used In 856ad2a86bd9b3e264a090fcf4b0d05bfaa91030 sd_bus_add_object_vtable() and sd_bus_add_fallback_vtable() were changed to take an updated sd_bus_vtable[] array with additional 'features' and 'names' fields in the union. The commit tried to check whether the old or the new table format is used, by looking at the vtable[0].x.start.element_size field, on the assumption that the added fields caused the structure size to grow. Unfortunately, this assumption was false, and on arm32 (at least), the structure size is unchanged. In libsystemd we use symbol versioning and a major.minor.patch semantic versioning of the library name (major equals the number in the so-name). When systemd-242 was released, the minor number was (correctly) bumped, but this is not enough, because no new symbols were added or symbol versions changed. This means that programs compiled with the new systemd headers and library could be successfully linked to older versions of the library. For example rpm only looks at the so-name and the list of versioned symbols, completely ignoring the major.minor numbers in the library name. But the older library does not understand the new vtable format, and would return -EINVAL after failing the size check (on those architectures where the structure size did change, i.e. all 64 bit architectures). To force new libsystemd (with the functions that take the updated sd_bus_vtable[] format) to be used, let's pull in a dummy symbol from the table definition. This is a bit wasteful, because a dummy pointer has to be stored, but the effect is negligible. In particular, the pointer doesn't even change the size of the structure because if fits in an unused area in the union. The number stored in the new unsigned integer is not checked anywhere. If the symbol exists, we already know we have the new version of the library, so an additional check would not tell us anything. An alternative would be to make sd_bus_add_{object,fallback}_vtable() versioned symbols, using .symver linker annotations. We would provide sd_bus_add_{object,fallback}_vtable@LIBSYSTEMD_221 (for backwards compatibility) and e.g. sd_bus_add_{object,fallback}_vtable@@LIBSYSTEMD_242 (the default) with the new implementation. This would work too, but is more work. We would have to version at least those two functions. And it turns out that the .symver linker instructions have to located in the same compilation unit as the function being annotated. We first compile libsystemd.a, and then link it into libsystemd.so and various other targets, including libsystemd-shared.so, and the nss modules. If the .symver annotations were placed next to the function definitions (in bus-object.c), they would influence all targets that link libsystemd.a, and cause problems, because those functions should not be exported there. To export them only in libsystemd.so, compilation would have to be rearranged, so that the functions exported in libsystemd.so would not be present in libsystemd.a, but a separate compilation unit containg them and the .symver annotations would be linked solely into libsystemd.so. This is certainly possible, but more work than the approach in this patch. 856ad2a86bd9b3e264a090fcf4b0d05bfaa91030 has one more issue: it relies on the undefined fields in sd_bus_vtable[] array to be zeros. But the structure contains a union, and fields of the union do not have to be zero-initalized by the compiler. This means that potentially, we could have garbarge values there, for example when reading the old vtable format definition from the new function implementation. In practice this should not be an issue at all, because vtable definitions are static data and are placed in the ro-data section, which is fully initalized, so we know that those undefined areas will be zero. Things would be different if somebody defined the vtable array on the heap or on the stack. Let's just document that they should zero-intialize the unused areas in this case. The symbol checking code had to be updated because otherwise gcc warns about a cast from unsigned to a pointer.
2019-04-18 13:06:41 +02:00
/* This symbol exists solely to tell the linker that the "new" vtable format is used. */
_public_ const unsigned sd_bus_object_vtable_format = 242;
_public_ int sd_bus_add_object_vtable(
sd_bus *bus,
sd_bus_slot **slot,
const char *path,
const char *interface,
const sd_bus_vtable *vtable,
void *userdata) {
return add_object_vtable_internal(bus, slot, path, interface, vtable, false, NULL, userdata);
}
_public_ int sd_bus_add_fallback_vtable(
sd_bus *bus,
sd_bus_slot **slot,
const char *prefix,
const char *interface,
const sd_bus_vtable *vtable,
sd_bus_object_find_t find,
void *userdata) {
return add_object_vtable_internal(bus, slot, prefix, interface, vtable, true, find, userdata);
}
_public_ int sd_bus_add_node_enumerator(
sd_bus *bus,
sd_bus_slot **slot,
const char *path,
sd_bus_node_enumerator_t callback,
void *userdata) {
sd_bus_slot *s;
struct node *n;
int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(callback, -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
n = bus_node_allocate(bus, path);
if (!n)
return -ENOMEM;
s = bus_slot_allocate(bus, !slot, BUS_NODE_ENUMERATOR, sizeof(struct node_enumerator), userdata);
if (!s) {
r = -ENOMEM;
goto fail;
}
s->node_enumerator.callback = callback;
s->node_enumerator.node = n;
LIST_PREPEND(enumerators, n->enumerators, &s->node_enumerator);
bus->nodes_modified = true;
if (slot)
*slot = s;
return 0;
fail:
sd_bus_slot_unref(s);
bus_node_gc(bus, n);
return r;
}
static int emit_properties_changed_on_interface(
sd_bus *bus,
const char *prefix,
const char *path,
const char *interface,
bool require_fallback,
bool *found_interface,
char **names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
bool has_invalidating = false, has_changing = false;
struct vtable_member key = {};
struct node_vtable *c;
struct node *n;
char **property;
void *u = NULL;
int r;
assert(bus);
assert(prefix);
assert(path);
assert(interface);
assert(found_interface);
n = hashmap_get(bus->nodes, prefix);
if (!n)
return 0;
r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
if (r < 0)
return r;
r = sd_bus_message_append(m, "s", interface);
if (r < 0)
return r;
r = sd_bus_message_open_container(m, 'a', "{sv}");
if (r < 0)
return r;
key.path = prefix;
key.interface = interface;
LIST_FOREACH(vtables, c, n->vtables) {
if (require_fallback && !c->is_fallback)
continue;
if (!streq(c->interface, 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;
*found_interface = true;
if (names) {
/* If the caller specified a list of
* properties we include exactly those in the
* PropertiesChanged message */
STRV_FOREACH(property, names) {
struct vtable_member *v;
assert_return(member_name_is_valid(*property), -EINVAL);
key.member = *property;
v = hashmap_get(bus->vtable_properties, &key);
if (!v)
return -ENOENT;
/* If there are two vtables for the same
* interface, let's handle this property when
* we come to that vtable. */
if (c != v->parent)
continue;
assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE ||
v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM);
assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM);
if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) {
has_invalidating = true;
continue;
}
has_changing = true;
r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
}
} else {
const sd_bus_vtable *v;
/* If the caller specified no properties list
* we include all properties that are marked
* as changing in the message. */
v = c->vtable;
for (v = bus_vtable_next(c->vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(c->vtable, v)) {
2017-09-28 10:17:04 +02:00
if (!IN_SET(v->type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY))
continue;
if (v->flags & SD_BUS_VTABLE_HIDDEN)
continue;
if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) {
has_invalidating = true;
continue;
}
if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE))
continue;
has_changing = true;
r = vtable_append_one_property(bus, m, m->path, c, v, u, &error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
}
}
}
if (!has_invalidating && !has_changing)
return 0;
r = sd_bus_message_close_container(m);
if (r < 0)
return r;
r = sd_bus_message_open_container(m, 'a', "s");
if (r < 0)
return r;
if (has_invalidating) {
LIST_FOREACH(vtables, c, n->vtables) {
if (require_fallback && !c->is_fallback)
continue;
if (!streq(c->interface, 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;
if (names) {
STRV_FOREACH(property, names) {
struct vtable_member *v;
key.member = *property;
assert_se(v = hashmap_get(bus->vtable_properties, &key));
assert(c == v->parent);
if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
continue;
r = sd_bus_message_append(m, "s", *property);
if (r < 0)
return r;
}
} else {
const sd_bus_vtable *v;
v = c->vtable;
for (v = bus_vtable_next(c->vtable, v); v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(c->vtable, v)) {
2017-09-28 10:17:04 +02:00
if (!IN_SET(v->type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY))
continue;
if (v->flags & SD_BUS_VTABLE_HIDDEN)
continue;
if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION))
continue;
r = sd_bus_message_append(m, "s", v->x.property.member);
if (r < 0)
return r;
}
}
}
}
r = sd_bus_message_close_container(m);
if (r < 0)
return r;
r = sd_bus_send(bus, m, NULL);
if (r < 0)
return r;
return 1;
}
_public_ int sd_bus_emit_properties_changed_strv(
sd_bus *bus,
const char *path,
const char *interface,
char **names) {
_cleanup_free_ char *prefix = NULL;
bool found_interface = false;
size_t pl;
int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(interface_name_is_valid(interface), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
/* A non-NULL but empty names list means nothing needs to be
generated. A NULL list OTOH indicates that all properties
that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be
included in the PropertiesChanged message. */
if (names && names[0] == NULL)
return 0;
BUS_DONT_DESTROY(bus);
pl = strlen(path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
do {
bus->nodes_modified = false;
r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names);
if (r != 0)
return r;
if (bus->nodes_modified)
continue;
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names);
if (r != 0)
return r;
if (bus->nodes_modified)
break;
}
} while (bus->nodes_modified);
return found_interface ? 0 : -ENOENT;
}
_public_ int sd_bus_emit_properties_changed(
sd_bus *bus,
const char *path,
const char *interface,
const char *name, ...) {
char **names;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(interface_name_is_valid(interface), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
if (!name)
return 0;
names = strv_from_stdarg_alloca(name);
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_(sd_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;
_cleanup_free_ char *prefix = NULL;
size_t pl;
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;
pl = strlen(path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
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;
}
_public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
struct node *object_manager;
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(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
r = bus_find_parent_object_manager(bus, &object_manager, path);
if (r < 0)
return r;
if (r == 0)
return -ESRCH;
BUS_DONT_DESTROY(bus);
do {
bus->nodes_modified = false;
m = sd_bus_message_unref(m);
r = sd_bus_message_new_signal(bus, &m, object_manager->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_(sd_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;
_cleanup_free_ char *prefix = NULL;
size_t pl;
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;
pl = strlen(path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
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;
}
_public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
struct node *object_manager;
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(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
r = bus_find_parent_object_manager(bus, &object_manager, path);
if (r < 0)
return r;
if (r == 0)
return -ESRCH;
BUS_DONT_DESTROY(bus);
do {
bus->nodes_modified = false;
m = sd_bus_message_unref(m);
r = sd_bus_message_new_signal(bus, &m, object_manager->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,
const char *prefix,
const char *path,
const char *interface,
bool require_fallback) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
bool found_interface = false;
struct node_vtable *c;
struct node *n;
void *u = NULL;
int r;
assert(bus);
assert(m);
assert(prefix);
assert(path);
assert(interface);
n = hashmap_get(bus->nodes, prefix);
if (!n)
return 0;
LIST_FOREACH(vtables, c, n->vtables) {
if (require_fallback && !c->is_fallback)
continue;
if (!streq(c->interface, 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;
if (!found_interface) {
r = sd_bus_message_append_basic(m, 's', interface);
if (r < 0)
return r;
r = sd_bus_message_open_container(m, 'a', "{sv}");
if (r < 0)
return r;
found_interface = true;
}
r = vtable_append_all_properties(bus, m, path, c, u, &error);
if (r < 0)
return r;
if (bus->nodes_modified)
return 0;
}
if (found_interface) {
r = sd_bus_message_close_container(m);
if (r < 0)
return r;
}
return found_interface;
}
static int interfaces_added_append_one(
sd_bus *bus,
sd_bus_message *m,
const char *path,
const char *interface) {
_cleanup_free_ char *prefix = NULL;
size_t pl;
int r;
assert(bus);
assert(m);
assert(path);
assert(interface);
r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false);
if (r != 0)
return r;
if (bus->nodes_modified)
return 0;
pl = strlen(path);
assert(pl <= BUS_PATH_SIZE_MAX);
prefix = new(char, pl + 1);
if (!prefix)
return -ENOMEM;
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true);
if (r != 0)
return r;
if (bus->nodes_modified)
return 0;
}
return -ENOENT;
}
_public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
struct node *object_manager;
char **i;
int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
if (strv_isempty(interfaces))
return 0;
r = bus_find_parent_object_manager(bus, &object_manager, path);
if (r < 0)
return r;
if (r == 0)
return -ESRCH;
BUS_DONT_DESTROY(bus);
do {
bus->nodes_modified = false;
2014-04-23 06:57:00 +02:00
m = sd_bus_message_unref(m);
r = sd_bus_message_new_signal(bus, &m, object_manager->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;
STRV_FOREACH(i, interfaces) {
assert_return(interface_name_is_valid(*i), -EINVAL);
r = sd_bus_message_open_container(m, 'e', "sa{sv}");
if (r < 0)
return r;
r = interfaces_added_append_one(bus, m, path, *i);
if (r < 0)
return r;
if (bus->nodes_modified)
break;
r = sd_bus_message_close_container(m);
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);
}
_public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) {
char **interfaces;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
interfaces = strv_from_stdarg_alloca(interface);
return sd_bus_emit_interfaces_added_strv(bus, path, interfaces);
}
_public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
struct node *object_manager;
int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
if (strv_isempty(interfaces))
return 0;
r = bus_find_parent_object_manager(bus, &object_manager, path);
if (r < 0)
return r;
if (r == 0)
return -ESRCH;
r = sd_bus_message_new_signal(bus, &m, object_manager->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_append_strv(m, interfaces);
if (r < 0)
return r;
return sd_bus_send(bus, m, NULL);
}
_public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) {
char **interfaces;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
if (!BUS_IS_OPEN(bus->state))
return -ENOTCONN;
interfaces = strv_from_stdarg_alloca(interface);
return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces);
}
_public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) {
sd_bus_slot *s;
struct node *n;
int r;
assert_return(bus, -EINVAL);
assert_return(bus = bus_resolve(bus), -ENOPKG);
assert_return(object_path_is_valid(path), -EINVAL);
assert_return(!bus_pid_changed(bus), -ECHILD);
n = bus_node_allocate(bus, path);
if (!n)
return -ENOMEM;
s = bus_slot_allocate(bus, !slot, BUS_NODE_OBJECT_MANAGER, sizeof(struct node_object_manager), NULL);
if (!s) {
r = -ENOMEM;
goto fail;
}
s->node_object_manager.node = n;
LIST_PREPEND(object_managers, n->object_managers, &s->node_object_manager);
bus->nodes_modified = true;
if (slot)
*slot = s;
return 0;
fail:
sd_bus_slot_unref(s);
bus_node_gc(bus, n);
return r;
}