sd_bus_error *error) {
sd_bus_slot *s;
- void *u, *found_u;
+ void *u, *found_u = NULL;
int r;
assert(bus);
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;
}
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 = check_access(bus, m, c, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
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 (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),
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));
+ 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)
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;
if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN)
return 1;
- for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; 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;
+ /* 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 (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");
+ 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;
return 0;
}
-static int process_introspect(
+int introspect_path(
sd_bus *bus,
- sd_bus_message *m,
+ const char *path,
struct node *n,
bool require_fallback,
- bool *found_object) {
+ bool ignore_nodes_modified,
+ bool *found_object,
+ char **ret,
+ sd_bus_error *error) {
- _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;
+ _cleanup_(introspect_free) struct introspect intro = {};
struct node_vtable *c;
bool empty;
int r;
- assert(bus);
- assert(m);
- assert(n);
- assert(found_object);
+ if (!n) {
+ n = hashmap_get(bus->nodes, path);
+ if (!n)
+ return -ENOENT;
+ }
- r = get_child_nodes(bus, m->path, n, 0, &s, &error);
+ r = get_child_nodes(bus, path, n, 0, &s, error);
if (r < 0)
- return bus_maybe_reply_error(m, r, &error);
- if (bus->nodes_modified)
+ return r;
+ if (bus->nodes_modified && !ignore_nodes_modified)
return 0;
r = introspect_begin(&intro, bus->trusted);
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;
- }
+ 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;
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);
+ r = introspect_write_interface(&intro, c->interface, c->vtable);
if (r < 0)
- goto finish;
-
- previous_interface = c->interface;
+ return r;
}
- 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;
- }
+ r = bus_node_exists(bus, n, path, require_fallback);
+ if (r <= 0)
+ return r;
+ if (bus->nodes_modified && !ignore_nodes_modified)
+ return 0;
}
- *found_object = true;
+ if (found_object)
+ *found_object = true;
- r = introspect_write_child_nodes(&intro, s, m->path);
+ r = introspect_write_child_nodes(&intro, s, path);
if (r < 0)
- goto finish;
+ return r;
- r = introspect_finish(&intro, bus, m, &reply);
+ r = introspect_finish(&intro, ret);
if (r < 0)
- goto finish;
+ return r;
- r = sd_bus_send(bus, reply, NULL);
+ 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)
- goto finish;
+ return bus_maybe_reply_error(m, r, &error);
+ if (r == 0)
+ /* nodes_modified == true */
+ return 0;
- r = 1;
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
-finish:
- introspect_free(&intro);
- 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(
const char *path,
sd_bus_error *error) {
- char *prefix;
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
int r;
assert(bus);
return 0;
/* Second, add fallback vtables registered for any of the prefixes */
- prefix = newa(char, strlen(path) + 1);
+ 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)
_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;
if (r < 0)
return r;
- SET_FOREACH(path, s, i) {
+ SET_FOREACH(path, s) {
r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
}
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(m->member);
pl = strlen(m->path);
- do {
- char prefix[pl+1];
+ 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);
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"))
+ 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 property or interface.");
- else
+ "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);
+ "Unknown method %s or interface %s.", m->member, m->interface);
if (r < 0)
return r;
n = hashmap_get(bus->nodes, path);
if (!n) {
- char *prefix;
+ _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;
- prefix = newa(char, strlen(path) + 1);
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
n = hashmap_get(bus->nodes, prefix);
if (n)
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 ||
+ !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 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(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 == sizeof(struct sd_bus_vtable), -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") &&
goto fail;
}
- for (v = s->node_vtable.vtable+1; v->type != _SD_BUS_VTABLE_END; v++) {
+ 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;
}
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;
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,
* we include all properties that are marked
* as changing in the message. */
- for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; 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;
} else {
const sd_bus_vtable *v;
- for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; 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;
const char *interface,
char **names) {
+ _cleanup_free_ char *prefix = NULL;
bool found_interface = false;
- char *prefix;
+ size_t pl;
int r;
assert_return(bus, -EINVAL);
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;
if (bus->nodes_modified)
continue;
- prefix = newa(char, strlen(path) + 1);
OBJECT_PATH_FOREACH_PREFIX(prefix, path) {
r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names);
if (r != 0)
static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path) {
_cleanup_set_free_ Set *s = NULL;
- char *prefix;
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
int r;
assert(bus);
if (bus->nodes_modified)
return 0;
- prefix = newa(char, strlen(path) + 1);
+ 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)
static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path) {
_cleanup_set_free_ Set *s = NULL;
- char *prefix;
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
int r;
assert(bus);
if (bus->nodes_modified)
return 0;
- prefix = newa(char, strlen(path) + 1);
+ 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)
const char *path,
const char *interface) {
- char *prefix;
+ _cleanup_free_ char *prefix = NULL;
+ size_t pl;
int r;
assert(bus);
if (bus->nodes_modified)
return 0;
- prefix = newa(char, strlen(path) + 1);
+ 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)