Systemd/src/libsystemd/sd-bus/bus-objects.c
Zbigniew Jędrzejewski-Szmek 8dd8a286d1 sd-bus: add symbol to tell linker that new vtable functions are used
In 856ad2a86b 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.

856ad2a86b 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-23 12:23:12 +02:00

2973 lines
94 KiB
C

/* 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;
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);
return (uint8_t*) u + p->x.method.offset;
}
static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) {
assert(p);
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;
}
enum {
/* if set, add_subtree() works recursively */
CHILDREN_RECURSIVE = 1 << 0,
/* if set, add_subtree() scans object-manager hierarchies recursively */
CHILDREN_SUBHIERARCHIES = 1 << 1,
};
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;
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;
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;
}
#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--;
r = sd_bus_query_sender_privilege(m, cap);
if (r < 0)
return r;
if (r > 0)
return 0;
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;
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:
case SD_BUS_TYPE_SIGNATURE:
p = strempty(*(char**) userdata);
break;
case SD_BUS_TYPE_OBJECT_PATH:
p = *(char**) userdata;
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;
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 (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;
/* 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' || !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;
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);
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)) {
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_EXPLICIT)
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 ||
streq(iface, "org.freedesktop.DBus.Properties") ||
streq(iface, "org.freedesktop.DBus.Peer") ||
streq(iface, "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;
}
static int process_introspect(
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;
const char *previous_interface = NULL;
struct introspect intro;
struct node_vtable *c;
bool empty;
int r;
assert(bus);
assert(m);
assert(n);
assert(found_object);
r = get_child_nodes(bus, m->path, n, 0, &s, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
if (bus->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, m->path, c, NULL, &error);
if (r < 0) {
r = bus_maybe_reply_error(m, r, &error);
goto finish;
}
if (bus->nodes_modified) {
r = 0;
goto finish;
}
if (r == 0)
continue;
empty = false;
if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
continue;
if (!streq_ptr(previous_interface, c->interface)) {
if (previous_interface)
fputs(" </interface>\n", intro.f);
fprintf(intro.f, " <interface name=\"%s\">\n", c->interface);
}
r = introspect_write_interface(&intro, c->vtable);
if (r < 0)
goto finish;
previous_interface = c->interface;
}
if (previous_interface)
fputs(" </interface>\n", intro.f);
if (empty) {
/* Nothing?, let's see if we exist at all, and if not
* refuse to do anything */
r = bus_node_exists(bus, n, m->path, require_fallback);
if (r <= 0) {
r = bus_maybe_reply_error(m, r, &error);
goto finish;
}
if (bus->nodes_modified) {
r = 0;
goto finish;
}
}
*found_object = true;
r = introspect_write_child_nodes(&intro, s, m->path);
if (r < 0)
goto finish;
r = introspect_finish(&intro, bus, m, &reply);
if (r < 0)
goto finish;
r = sd_bus_send(bus, reply, NULL);
if (r < 0)
goto finish;
r = 1;
finish:
introspect_free(&intro);
return r;
}
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;
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)
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;
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"))
r = sd_bus_reply_method_errorf(
m,
SD_BUS_ERROR_UNKNOWN_PROPERTY,
"Unknown property or interface.");
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);
}
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);
}
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);
}
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_original {
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_ORIGINAL sizeof(struct sd_bus_vtable_original)
/* Current structure size */
#define VTABLE_ELEMENT_SIZE sizeof(struct sd_bus_vtable)
static int vtable_features(const sd_bus_vtable *vtable) {
if (vtable[0].x.start.element_size < VTABLE_ELEMENT_SIZE ||
!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) {
if (vtable[0].x.start.element_size == VTABLE_ELEMENT_SIZE_ORIGINAL) {
const struct sd_bus_vtable_original *v2 = (const struct sd_bus_vtable_original *)v;
v2++;
v = (const sd_bus_vtable*)v2;
} else /* current version */
v++;
return v;
}
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_ORIGINAL ||
vtable[0].x.start.element_size == VTABLE_ELEMENT_SIZE,
-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)) ||
(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) ||
!signature_is_valid(strempty(v->x.signal.signature), false) ||
!names_are_valid(strempty(v->x.signal.signature), &names, &nf) ||
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;
}
/* 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)) {
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)) {
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;
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;
}