diff options
Diffstat (limited to 'src/libsystemd-bus')
-rw-r--r-- | src/libsystemd-bus/bus-error.c | 33 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-internal.c | 23 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-internal.h | 65 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-introspect.c | 200 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-introspect.h | 41 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-message.c | 81 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-signature.c | 6 | ||||
-rw-r--r-- | src/libsystemd-bus/bus-signature.h | 2 | ||||
-rw-r--r-- | src/libsystemd-bus/sd-bus.c | 1973 | ||||
-rw-r--r-- | src/libsystemd-bus/test-bus-introspect.c | 63 | ||||
-rw-r--r-- | src/libsystemd-bus/test-bus-objects.c | 374 | ||||
-rw-r--r-- | src/libsystemd-bus/test-bus-signature.c | 67 |
12 files changed, 2728 insertions, 200 deletions
diff --git a/src/libsystemd-bus/bus-error.c b/src/libsystemd-bus/bus-error.c index 4696a88f76..28fe15467f 100644 --- a/src/libsystemd-bus/bus-error.c +++ b/src/libsystemd-bus/bus-error.c @@ -51,7 +51,34 @@ void sd_bus_error_free(sd_bus_error *e) { e->need_free = false; } -int sd_bus_error_set(sd_bus_error *e, const char *name, const char *format, ...) { +int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message) { + char *n, *m = NULL; + + if (!e) + return 0; + if (bus_error_is_dirty(e)) + return -EINVAL; + if (!name) + return -EINVAL; + + n = strdup(name); + if (!n) + return -ENOMEM; + + if (message) { + m = strdup(message); + if (!m) + return -ENOMEM; + } + + e->name = n; + e->message = m; + e->need_free = true; + + return 0; +} + +int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) { char *n, *m = NULL; va_list ap; int r; @@ -119,9 +146,7 @@ void sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *messa if (bus_error_is_dirty(e)) return; - e->name = name; - e->message = message; - e->need_free = false; + *e = SD_BUS_ERROR_MAKE(name, message); } int sd_bus_error_is_set(const sd_bus_error *e) { diff --git a/src/libsystemd-bus/bus-internal.c b/src/libsystemd-bus/bus-internal.c index cac948e875..afff7bd7c1 100644 --- a/src/libsystemd-bus/bus-internal.c +++ b/src/libsystemd-bus/bus-internal.c @@ -61,6 +61,29 @@ bool object_path_is_valid(const char *p) { return true; } +char* object_path_startswith(const char *a, const char *b) { + const char *p; + + if (!object_path_is_valid(a) || + !object_path_is_valid(b)) + return NULL; + + if (streq(b, "/")) + return (char*) a + 1; + + p = startswith(a, b); + if (!p) + return NULL; + + if (*p == 0) + return (char*) p; + + if (*p == '/') + return (char*) p + 1; + + return NULL; +} + bool interface_name_is_valid(const char *p) { const char *q; bool dot, found_dot = false; diff --git a/src/libsystemd-bus/bus-internal.h b/src/libsystemd-bus/bus-internal.h index 30b8d519a0..5795f74693 100644 --- a/src/libsystemd-bus/bus-internal.h +++ b/src/libsystemd-bus/bus-internal.h @@ -54,14 +54,63 @@ struct filter_callback { LIST_FIELDS(struct filter_callback, callbacks); }; -struct object_callback { +struct node { + char *path; + struct node *parent; + LIST_HEAD(struct node, child); + LIST_FIELDS(struct node, siblings); + + LIST_HEAD(struct node_callback, callbacks); + LIST_HEAD(struct node_vtable, vtables); + LIST_HEAD(struct node_enumerator, enumerators); + + bool object_manager; +}; + +struct node_callback { + struct node *node; + + bool is_fallback; sd_bus_message_handler_t callback; void *userdata; - char *path; + unsigned last_iteration; + + LIST_FIELDS(struct node_callback, callbacks); +}; + +struct node_enumerator { + struct node *node; + + sd_bus_node_enumerator_t callback; + void *userdata; + + unsigned last_iteration; + + LIST_FIELDS(struct node_enumerator, enumerators); +}; + +struct node_vtable { + struct node *node; + + char *interface; bool is_fallback; + const sd_bus_vtable *vtable; + void *userdata; + sd_bus_object_find_t find; unsigned last_iteration; + + LIST_FIELDS(struct node_vtable, vtables); +}; + +struct vtable_member { + const char *path; + const char *interface; + const char *member; + struct node_vtable *parent; + unsigned last_iteration; + const sd_bus_vtable *vtable; }; enum bus_state { @@ -109,7 +158,7 @@ struct sd_bus { bool processing:1; bool match_callbacks_modified:1; bool filter_callbacks_modified:1; - bool object_callbacks_modified:1; + bool nodes_modified:1; int use_memfd; @@ -131,7 +180,12 @@ struct sd_bus { Prioq *reply_callbacks_prioq; Hashmap *reply_callbacks; LIST_HEAD(struct filter_callback, filter_callbacks); - Hashmap *object_callbacks; + + Hashmap *nodes; + + + Hashmap *vtable_methods; + Hashmap *vtable_properties; union { struct sockaddr sa; @@ -213,10 +267,11 @@ static inline void bus_unrefp(sd_bus **b) { #define BUS_EXEC_ARGV_MAX 256 -bool object_path_is_valid(const char *p); bool interface_name_is_valid(const char *p); bool service_name_is_valid(const char *p); bool member_name_is_valid(const char *p); +bool object_path_is_valid(const char *p); +char *object_path_startswith(const char *a, const char *b); bool namespace_complex_pattern(const char *pattern, const char *value); bool path_complex_pattern(const char *pattern, const char *value); diff --git a/src/libsystemd-bus/bus-introspect.c b/src/libsystemd-bus/bus-introspect.c new file mode 100644 index 0000000000..8dc9a2de15 --- /dev/null +++ b/src/libsystemd-bus/bus-introspect.c @@ -0,0 +1,200 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "sd-bus-protocol.h" +#include "bus-introspect.h" +#include "bus-signature.h" +#include "bus-internal.h" + +int introspect_begin(struct introspect *i) { + assert(i); + + zero(*i); + + i->f = open_memstream(&i->introspection, &i->size); + if (!i->f) + return -ENOMEM; + + fputs(SD_BUS_INTROSPECT_DOCTYPE + "<node>\n", i->f); + + return 0; +} + +int introspect_write_default_interfaces(struct introspect *i, bool object_manager) { + assert(i); + + fputs(SD_BUS_INTROSPECT_INTERFACE_PEER + SD_BUS_INTROSPECT_INTERFACE_INTROSPECTABLE + SD_BUS_INTROSPECT_INTERFACE_PROPERTIES, i->f); + + if (object_manager) + fputs(SD_BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->f); + + return 0; +} + +int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix) { + char *node; + + assert(i); + assert(prefix); + + while ((node = set_steal_first(s))) { + const char *e; + + e = object_path_startswith(node, prefix); + if (e) + fprintf(i->f, " <node name=\"%s\"/>\n", e); + + free(node); + } + + return 0; +} + +static void introspect_write_flags(struct introspect *i, int type, int flags) { + if (flags & SD_BUS_VTABLE_DEPRECATED) + fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->f); + + if (type == _SD_BUS_VTABLE_METHOD && flags & SD_BUS_VTABLE_METHOD_NO_REPLY) + fputs(" <annotation name=\"org.freedesktop.DBus.Method.NoReply\" value=\"true\"/>\n", i->f); + + if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) { + if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>\n", i->f); + else if (flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY) + fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"invalidates\"/>\n", i->f); + } +} + +static int introspect_write_arguments(struct introspect *i, const char *signature, const char *direction) { + int r; + + for (;;) { + size_t l; + + if (!*signature) + return 0; + + r = signature_element_length(signature, &l); + if (r < 0) + return r; + + fprintf(i->f, " <arg type=\"%.*s\"", (int) l, signature); + + if (direction) + fprintf(i->f, " direction=\"%s\">\n", direction); + else + fputs(">\n", i->f); + + signature += l; + } +} + +int introspect_write_interface(struct introspect *i, const char *interface, const sd_bus_vtable *v) { + assert(i); + assert(interface); + assert(v); + + fprintf(i->f, " <interface name=\"%s\">\n", interface); + + for (; v->type != _SD_BUS_VTABLE_END; v++) { + + switch (v->type) { + + case _SD_BUS_VTABLE_START: + if (v->flags & SD_BUS_VTABLE_DEPRECATED) + fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->f); + break; + + case _SD_BUS_VTABLE_METHOD: + fprintf(i->f, " <method name=\"%s\">\n", v->method.member); + introspect_write_arguments(i, v->method.signature, "in"); + introspect_write_arguments(i, v->method.result, "out"); + introspect_write_flags(i, v->type, v->flags); + fputs(" </method>\n", i->f); + break; + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: + fprintf(i->f, " <property name=\"%s\" type=\"%s\" access=\"%s\">\n", + v->property.member, + v->property.signature, + v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read"); + introspect_write_flags(i, v->type, v->flags); + fputs(" </property>\n", i->f); + break; + + case _SD_BUS_VTABLE_SIGNAL: + fprintf(i->f, " <signal name=\"%s\">\n", v->signal.member); + introspect_write_arguments(i, v->signal.signature, NULL); + introspect_write_flags(i, v->type, v->flags); + fputs(" </signal>\n", i->f); + break; + } + + } + + fputs(" </interface>\n", i->f); + return 0; +} + +int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply) { + sd_bus_message *q; + int r; + + assert(i); + assert(m); + assert(reply); + + fputs("</node>\n", i->f); + fflush(i->f); + + if (ferror(i->f)) + return -ENOMEM; + + r = sd_bus_message_new_method_return(bus, m, &q); + if (r < 0) + return r; + + r = sd_bus_message_append(q, "s", i->introspection); + if (r < 0) { + sd_bus_message_unref(q); + return r; + } + + *reply = q; + return 0; +} + +void introspect_free(struct introspect *i) { + assert(i); + + if (i->f) + fclose(i->f); + + if (i->introspection) + free(i->introspection); + + zero(*i); +} diff --git a/src/libsystemd-bus/bus-introspect.h b/src/libsystemd-bus/bus-introspect.h new file mode 100644 index 0000000000..48c3885d94 --- /dev/null +++ b/src/libsystemd-bus/bus-introspect.h @@ -0,0 +1,41 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdio.h> + +#include "sd-bus.h" +#include "set.h" + +struct introspect { + FILE *f; + char *introspection; + size_t size; +}; + +int introspect_begin(struct introspect *i); +int introspect_write_default_interfaces(struct introspect *i, bool object_manager); +int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix); +int introspect_write_interface(struct introspect *i, const char *interface, const sd_bus_vtable *v); +int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply); +void introspect_free(struct introspect *i); diff --git a/src/libsystemd-bus/bus-message.c b/src/libsystemd-bus/bus-message.c index 760a148fad..2cf1a7a538 100644 --- a/src/libsystemd-bus/bus-message.c +++ b/src/libsystemd-bus/bus-message.c @@ -496,9 +496,13 @@ int sd_bus_message_new_method_call( sd_bus_message *t; int r; - if (!path) + if (destination && !service_name_is_valid(destination)) return -EINVAL; - if (!member) + if (!object_path_is_valid(path)) + return -EINVAL; + if (interface && !interface_name_is_valid(interface)) + return -EINVAL; + if (!member_name_is_valid(member)) return -EINVAL; if (!m) return -EINVAL; @@ -627,6 +631,58 @@ fail: return r; } +int sd_bus_message_new_method_errorf( + sd_bus *bus, + sd_bus_message *call, + sd_bus_message **m, + const char *name, + const char *format, + ...) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *t; + va_list ap; + int r; + + if (!name) + return -EINVAL; + if (!m) + return -EINVAL; + + r = message_new_reply(bus, call, SD_BUS_MESSAGE_TYPE_METHOD_ERROR, &t); + if (r < 0) + return r; + + r = message_append_field_string(t, SD_BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, name, &t->error.name); + if (r < 0) + goto fail; + + if (format) { + _cleanup_free_ char *message = NULL; + + va_start(ap, format); + r = vasprintf(&message, format, ap); + va_end(ap); + + if (r < 0) { + r = -ENOMEM; + goto fail; + } + + r = message_append_basic(t, SD_BUS_TYPE_STRING, message, (const void**) &t->error.message); + if (r < 0) + goto fail; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + + int bus_message_new_synthetic_error( sd_bus *bus, uint64_t serial, @@ -1558,7 +1614,7 @@ static int bus_message_open_array( assert(contents); assert(array_size); - if (!signature_is_single(contents)) + if (!signature_is_single(contents, true)) return -EINVAL; alignment = bus_type_get_alignment(contents[0]); @@ -1629,7 +1685,7 @@ static int bus_message_open_variant( assert(c); assert(contents); - if (!signature_is_single(contents)) + if (!signature_is_single(contents, false)) return -EINVAL; if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) @@ -2704,7 +2760,7 @@ static int bus_message_enter_array( assert(contents); assert(array_size); - if (!signature_is_single(contents)) + if (!signature_is_single(contents, true)) return -EINVAL; alignment = bus_type_get_alignment(contents[0]); @@ -2758,7 +2814,7 @@ static int bus_message_enter_variant( assert(c); assert(contents); - if (!signature_is_single(contents)) + if (!signature_is_single(contents, false)) return -EINVAL; if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) @@ -4290,3 +4346,16 @@ int bus_message_to_errno(sd_bus_message *m) { return bus_error_to_errno(&m->error); } + +int sd_bus_message_get_signature(sd_bus_message *m, int complete, const char **signature) { + struct bus_container *c; + + if (!m) + return -EINVAL; + if (!signature) + return -EINVAL; + + c = complete ? &m->root_container : message_get_container(m); + *signature = c->signature ?: ""; + return 0; +} diff --git a/src/libsystemd-bus/bus-signature.c b/src/libsystemd-bus/bus-signature.c index a92b7124c3..35c054baf6 100644 --- a/src/libsystemd-bus/bus-signature.c +++ b/src/libsystemd-bus/bus-signature.c @@ -110,13 +110,13 @@ int signature_element_length(const char *s, size_t *l) { return signature_element_length_internal(s, true, 0, 0, l); } -bool signature_is_single(const char *s) { +bool signature_is_single(const char *s, bool allow_dict_entry) { int r; size_t t; assert(s); - r = signature_element_length(s, &t); + r = signature_element_length_internal(s, allow_dict_entry, 0, 0, &t); if (r < 0) return false; @@ -129,7 +129,7 @@ bool signature_is_pair(const char *s) { if (!bus_type_is_basic(*s)) return false; - return signature_is_single(s + 1); + return signature_is_single(s + 1, false); } bool signature_is_valid(const char *s, bool allow_dict_entry) { diff --git a/src/libsystemd-bus/bus-signature.h b/src/libsystemd-bus/bus-signature.h index 4000e0612a..2e06e30548 100644 --- a/src/libsystemd-bus/bus-signature.h +++ b/src/libsystemd-bus/bus-signature.h @@ -24,7 +24,7 @@ #include <stdbool.h> #include <sys/types.h> -bool signature_is_single(const char *s); +bool signature_is_single(const char *s, bool allow_dict_entry); bool signature_is_pair(const char *s); bool signature_is_valid(const char *s, bool allow_dict_entry); diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c index db0880f21c..b1dcd8dc62 100644 --- a/src/libsystemd-bus/sd-bus.c +++ b/src/libsystemd-bus/sd-bus.c @@ -42,6 +42,8 @@ #include "bus-socket.h" #include "bus-kernel.h" #include "bus-control.h" +#include "bus-introspect.h" +#include "bus-signature.h" static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); @@ -57,9 +59,46 @@ static void bus_close_fds(sd_bus *b) { b->input_fd = b->output_fd = -1; } +static void bus_node_destroy(sd_bus *b, struct node *n) { + struct node_callback *c; + struct node_vtable *v; + struct node_enumerator *e; + + assert(b); + + if (!n) + return; + + while (n->child) + bus_node_destroy(b, n->child); + + while ((c = n->callbacks)) { + LIST_REMOVE(struct node_callback, callbacks, n->callbacks, c); + free(c); + } + + while ((v = n->vtables)) { + LIST_REMOVE(struct node_vtable, vtables, n->vtables, v); + free(v->interface); + free(v); + } + + while ((e = n->enumerators)) { + LIST_REMOVE(struct node_enumerator, enumerators, n->enumerators, e); + free(e); + } + + if (n->parent) + LIST_REMOVE(struct node, siblings, n->parent->child, n); + + assert_se(hashmap_remove(b->nodes, n->path) == n); + free(n->path); + free(n); +} + static void bus_free(sd_bus *b) { struct filter_callback *f; - struct object_callback *c; + struct node *n; unsigned i; assert(b); @@ -97,14 +136,16 @@ static void bus_free(sd_bus *b) { free(f); } - while ((c = hashmap_steal_first(b->object_callbacks))) { - free(c->path); - free(c); - } - - hashmap_free(b->object_callbacks); bus_match_free(&b->match_callbacks); + hashmap_free_free(b->vtable_methods); + hashmap_free_free(b->vtable_properties); + + while ((n = hashmap_first(b->nodes))) + bus_node_destroy(b, n); + + hashmap_free(b->nodes); + bus_kernel_flush_memfd(b); assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0); @@ -1818,13 +1859,10 @@ static int process_builtin(sd_bus *bus, sd_bus_message *m) { r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid)); } else { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - - sd_bus_error_set(&error, - "org.freedesktop.DBus.Error.UnknownMethod", + r = sd_bus_message_new_method_errorf( + bus, m, &reply, + "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method '%s' on interface '%s'.", m->member, m->interface); - - r = sd_bus_message_new_method_error(bus, m, &error, &reply); } if (r < 0) @@ -1837,90 +1875,446 @@ static int process_builtin(sd_bus *bus, sd_bus_message *m) { return 1; } -static int process_object(sd_bus *bus, sd_bus_message *m) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - struct object_callback *c; +static int node_vtable_get_userdata( + sd_bus *bus, + const char *path, + struct node_vtable *c, + void **userdata) { + + void *u; + int r; + + assert(bus); + assert(path); + assert(c); + assert(userdata); + + u = c->userdata; + if (c->find) { + r = c->find(bus, path, c->interface, &u, u); + if (r <= 0) + return r; + } + + *userdata = u; + return 1; +} + +static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); + + return (uint8_t*) u + p->property.offset; +} + +static int vtable_property_get_userdata( + sd_bus *bus, + const char *path, + struct vtable_member *p, + void **userdata) { + + void *u; + int r; + + assert(bus); + assert(path); + assert(p); + assert(userdata); + + r = node_vtable_get_userdata(bus, path, p->parent, &u); + if (r <= 0) + return r; + + *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) { + struct node_enumerator *c; + int r; + + assert(bus); + assert(prefix); + assert(s); + + LIST_FOREACH(enumerators, c, first) { + char **children = NULL, **k; + + r = c->callback(bus, prefix, &children, c->userdata); + if (r < 0) + return r; + + STRV_FOREACH(k, children) { + if (r < 0) { + free(*k); + continue; + } + + if (!object_path_is_valid(*k) && object_path_startswith(*k, prefix)) { + free(*k); + r = -EINVAL; + continue; + } + + r = set_consume(s, *k); + } + + free(children); + if (r < 0) + return r; + } + + return 0; +} + +static int add_subtree_to_set(sd_bus *bus, const char *prefix, struct node *n, Set *s) { + struct node *i; + int r; + + assert(bus); + assert(prefix); + assert(n); + assert(s); + + r = add_enumerated_to_set(bus, prefix, n->enumerators, s); + if (r < 0) + return r; + + LIST_FOREACH(siblings, i, n->child) { + char *t; + + t = strdup(i->path); + if (!t) + return -ENOMEM; + + r = set_consume(s, t); + if (r < 0 && r != -EEXIST) + return r; + + r = add_subtree_to_set(bus, prefix, i, s); + if (r < 0) + return r; + } + + return 0; +} + +static int get_child_nodes(sd_bus *bus, const char *prefix, struct node *n, Set **_s) { + Set *s = NULL; + int r; + + assert(bus); + assert(n); + assert(_s); + + s = set_new(string_hash_func, string_compare_func); + if (!s) + return -ENOMEM; + + r = add_subtree_to_set(bus, prefix, n, s); + 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; - bool found = false; - size_t pl; assert(bus); assert(m); + assert(found_object); - if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + LIST_FOREACH(callbacks, c, first) { + if (require_fallback && !c->is_fallback) + continue; + + *found_object = true; + + if (c->last_iteration == bus->iteration_counter) + continue; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = c->callback(bus, m, c->userdata); + if (r != 0) + return r; + } + + return 0; +} + +static int method_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct vtable_member *c, + bool require_fallback, + bool *found_object) { + + 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 (hashmap_isempty(bus->object_callbacks)) + r = node_vtable_get_userdata(bus, m->path, c->parent, &u); + if (r <= 0) + return r; + + *found_object = true; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = sd_bus_message_get_signature(m, true, &signature); + if (r < 0) + return r; + + if (!streq(c->vtable->method.signature, signature)) { + r = sd_bus_reply_method_errorf(bus, m, + "org.freedesktop.DBus.Error.InvalidArgs", + "Invalid arguments '%s' to call %s:%s, expecting '%s'.", + signature, c->interface, c->member, c->vtable->method.signature); + if (r < 0) + return r; + + return 1; + } + + return c->vtable->method.handler(bus, m, u); +} + +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_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + void *u; + int r; + + assert(bus); + assert(m); + assert(found_object); + + if (require_fallback && !c->parent->is_fallback) return 0; - pl = strlen(m->path); + r = vtable_property_get_userdata(bus, m->path, c, &u); + if (r <= 0) + return r; - do { - char p[pl+1]; + *found_object = true; - bus->object_callbacks_modified = false; + r = sd_bus_message_new_method_return(bus, m, &reply); + if (r < 0) + return r; - c = hashmap_get(bus->object_callbacks, m->path); - if (c && c->last_iteration != bus->iteration_counter) { + c->last_iteration = bus->iteration_counter; - c->last_iteration = bus->iteration_counter; + if (is_get) { + r = sd_bus_message_open_container(reply, 'v', c->vtable->property.signature); + if (r < 0) + return r; - r = sd_bus_message_rewind(m, true); + if (c->vtable->property.get) { + r = c->vtable->property.get(bus, m->path, c->interface, c->member, reply, &error, u); if (r < 0) return r; + } else + assert_not_reached("automatic properties not supported yet"); - r = c->callback(bus, m, c->userdata); - if (r != 0) + if (sd_bus_error_is_set(&error)) { + r = sd_bus_reply_method_error(bus, m, &error); + if (r < 0) return r; - found = true; + return 1; } - /* Look for fallback prefixes */ - strcpy(p, m->path); - for (;;) { - char *e; + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; - if (bus->object_callbacks_modified) - break; + } else { + if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + sd_bus_error_setf(&error, "org.freedesktop.DBus.Error.PropertyReadOnly", "Property '%s' is not writable.", c->member); + else { + r = sd_bus_message_enter_container(m, 'v', c->vtable->property.signature); + if (r < 0) + return r; - e = strrchr(p, '/'); - if (e == p || !e) - break; + if (c->vtable->property.set) { + r = c->vtable->property.set(bus, m->path, c->interface, c->member, m, &error, u); + if (r < 0) + return r; + } else + assert_not_reached("automatic properties not supported yet"); + } - *e = 0; + if (sd_bus_error_is_set(&error)) { + r = sd_bus_reply_method_error(bus, m, &error); + if (r < 0) + return r; - c = hashmap_get(bus->object_callbacks, p); - if (c && c->last_iteration != bus->iteration_counter && c->is_fallback) { + return 1; + } - c->last_iteration = bus->iteration_counter; + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; - r = c->callback(bus, m, c->userdata); - if (r != 0) - return r; + return 1; +} - found = true; - } - } +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) { - } while (bus->object_callbacks_modified); + const sd_bus_vtable *v; + int r; - /* We found some handlers but none wanted to take this, then - * return this -- with one exception, we can handle - * introspection minimally ourselves */ - if (!found || sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) - return 0; + assert(bus); + assert(reply); + assert(c); + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", c->interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'v', v->property.signature); + if (r < 0) + return r; + + r = v->property.get(bus, path, c->interface, v->property.member, reply, error, vtable_property_convert_userdata(v, userdata)); + if (r < 0) + return r; + + if (sd_bus_error_is_set(error)) + 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; + } - sd_bus_error_set(&error, - "org.freedesktop.DBus.Error.UnknownMethod", - "Unknown method '%s' or interface '%s'.", m->member, m->interface); + 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_bus_message_unref_ sd_bus_message *reply = NULL; + struct node_vtable *c; + bool found_interface = false; + int r; + + assert(bus); + assert(m); + assert(found_object); - r = sd_bus_message_new_method_error(bus, m, &error, &reply); + r = sd_bus_message_new_method_return(bus, m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + LIST_FOREACH(vtables, c, first) { + _cleanup_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); + if (r < 0) + return r; + if (r == 0) + continue; + + *found_object = true; + + if (iface && !streq(c->interface, iface)) + continue; + found_interface = true; + + c->last_iteration = bus->iteration_counter; + + r = vtable_append_all_properties(bus, reply, m->path, c, u, &error); + if (r < 0) + return r; + + if (sd_bus_error_is_set(&error)) { + r = sd_bus_reply_method_error(bus, m, &error); + if (r < 0) + return r; + + return 1; + } + } + + if (!found_interface) { + r = sd_bus_reply_method_errorf( + bus, m, + "org.freedesktop.DBus.Error.UnknownInterface", + "Unknown interface '%s'.", iface); + if (r < 0) + return r; + + return 1; + } + + r = sd_bus_message_close_container(reply); if (r < 0) return r; @@ -1931,84 +2325,359 @@ static int process_object(sd_bus *bus, sd_bus_message *m) { return 1; } -static int process_introspect(sd_bus *bus, sd_bus_message *m) { +static bool bus_node_with_object_manager(sd_bus *bus, struct node *n) { + assert(bus); + + if (n->object_manager) + return true; + + if (n->parent) + return bus_node_with_object_manager(bus, n->parent); + + return false; +} + +static bool bus_node_exists(sd_bus *bus, struct node *n, const char *path, bool require_fallback) { + struct node_vtable *c; + struct node_callback *k; + + assert(bus); + assert(n); + + if (n->child) + return true; + + LIST_FOREACH(callbacks, k, n->callbacks) { + if (require_fallback && !k->is_fallback) + continue; + + return true; + } + + LIST_FOREACH(vtables, c, n->vtables) { + + if (require_fallback && !c->is_fallback) + continue; + + return true; + } + + return !require_fallback && (n->enumerators || n->object_manager); + +} + +static int process_introspect( + sd_bus *bus, + sd_bus_message *m, + struct node *n, + bool require_fallback, + bool *found_object) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_free_ char *introspection = NULL; _cleanup_set_free_free_ Set *s = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct object_callback *c; - Iterator i; - size_t size = 0; - char *node; + struct introspect intro; + struct node_vtable *c; + bool empty; int r; assert(bus); assert(m); + assert(n); + assert(found_object); - if (!sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) - return 0; + r = get_child_nodes(bus, m->path, n, &s); + if (r < 0) + return r; - if (!m->path) - return 0; + r = introspect_begin(&intro); + if (r < 0) + return r; - s = set_new(string_hash_func, string_compare_func); - if (!s) - return -ENOMEM; + r = introspect_write_default_interfaces(&intro, bus_node_with_object_manager(bus, n)); + if (r < 0) + return r; - HASHMAP_FOREACH(c, bus->object_callbacks, i) { - const char *e; - char *a, *p; + empty = set_isempty(s); - if (streq(c->path, "/")) + LIST_FOREACH(vtables, c, n->vtables) { + void *u; + + if (require_fallback && !c->is_fallback) continue; - if (streq(m->path, "/")) - e = c->path; - else { - e = startswith(c->path, m->path); - if (!e || *e != '/') - continue; - } + r = node_vtable_get_userdata(bus, m->path, c, &u); + if (r < 0) + return r; + if (r == 0) + continue; - a = strdup(e+1); - if (!a) - return -ENOMEM; + empty = false; - p = strchr(a, '/'); - if (p) - *p = 0; + r = introspect_write_interface(&intro, c->interface, c->vtable); + if (r < 0) + goto finish; + } - r = set_consume(s, a); - if (r < 0 && r != -EEXIST) + 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) return r; + + if (r == 0) + goto finish; } - f = open_memstream(&introspection, &size); - if (!f) - return -ENOMEM; + *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_vtable( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + struct node_vtable *c, + sd_bus_error *error) { + + void *u; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(c); + assert(error); + + r = node_vtable_get_userdata(bus, path, c, &u); + if (r <= 0) + return r; + + r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", c->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, c, u, error); + if (r < 0) + return r; + + 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 object_manager_serialize_path( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + bool require_fallback, + sd_bus_error *error) { + + struct node_vtable *i; + struct node *n; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(error); + + n = hashmap_get(bus->nodes, path); + if (!n) + return 0; + + r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); + if (r < 0) + return r; - fputs(SD_BUS_INTROSPECT_DOCTYPE, f); - fputs("<node>\n", f); - fputs(SD_BUS_INTROSPECT_INTERFACE_PEER, f); - fputs(SD_BUS_INTROSPECT_INTERFACE_INTROSPECTABLE, f); + 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; - while ((node = set_steal_first(s))) { - fprintf(f, " <node name=\"%s\"/>\n", node); - free(node); + LIST_FOREACH(vtables, i, n->vtables) { + + if (require_fallback && !i->is_fallback) + continue; + + r = object_manager_serialize_vtable(bus, reply, path, i, error); + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return 0; } - fputs("</node>\n", f); + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; - fflush(f); + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; - if (ferror(f)) - return -ENOMEM; + return 1; +} + +static int object_manager_serialize_path_and_fallbacks( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + sd_bus_error *error) { + + 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, false, error); + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return 0; + + /* Second, add fallback vtables registered for any of the prefixes */ + pl = strlen(path); + if (pl > 1) { + char p[pl + 1]; + strcpy(p, path); + + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (e == p || !e) + break; + + *e = 0; + + r = object_manager_serialize_path(bus, reply, p, true, error); + if (r < 0) + return r; + + if (sd_bus_error_is_set(error)) + 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_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_set_free_free_ Set *s = NULL; + bool empty; + int r; + + assert(bus); + assert(m); + assert(n); + assert(found_object); + + if (!bus_node_with_object_manager(bus, n)) + return 0; + + r = get_child_nodes(bus, m->path, n, &s); + if (r < 0) + return r; r = sd_bus_message_new_method_return(bus, m, &reply); if (r < 0) return r; - r = sd_bus_message_append(reply, "s", introspection); + r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return r; + + empty = set_isempty(s); + if (empty) { + struct node_vtable *c; + + /* Hmm, so we have no children? Then let's check + * whether we exist at all, i.e. whether at least one + * vtable exists. */ + + LIST_FOREACH(vtables, c, n->vtables) { + + if (require_fallback && !c->is_fallback) + continue; + + if (r < 0) + return r; + if (r == 0) + continue; + + empty = false; + break; + } + + if (empty) + return 0; + } else { + Iterator i; + char *path; + + SET_FOREACH(path, s, i) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + + r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error); + if (r < 0) + return -ENOMEM; + + if (sd_bus_error_is_set(&error)) { + r = sd_bus_reply_method_error(bus, m, &error); + if (r < 0) + return r; + + return 1; + } + } + } + + r = sd_bus_message_close_container(reply); if (r < 0) return r; @@ -2019,6 +2688,182 @@ static int process_introspect(sd_bus *bus, sd_bus_message *m) { 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 (!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; + } + + /* 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 r; + + 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 r; + + 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")) { + + 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")) { + + r = process_get_managed_objects(bus, m, n, require_fallback, found_object); + if (r != 0) + return r; + } + + if (!*found_object) { + r = bus_node_exists(bus, n, m->path, require_fallback); + if (r < 0) + return r; + + if (r > 0) + *found_object = true; + } + + return 0; +} + +static int process_object(sd_bus *bus, sd_bus_message *m) { + int r; + size_t pl; + bool found_object = false; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return 0; + + if (!m->path) + return 0; + + if (hashmap_isempty(bus->nodes)) + return 0; + + pl = strlen(m->path); + do { + char p[pl+1]; + + 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 */ + strcpy(p, m->path); + for (;;) { + char *e; + + if (streq(p, "/")) + break; + + if (bus->nodes_modified) + break; + + e = strrchr(p, '/'); + assert(e); + if (e == p) + *(e+1) = 0; + else + *e = 0; + + r = object_find_and_run(bus, m, p, 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( + bus, m, + "org.freedesktop.DBus.Error.UnknownProperty", + "Unknown property or interface."); + else + r = sd_bus_reply_method_errorf( + bus, m, + "org.freedesktop.DBus.Error.UnknownMethod", + "Unknown method '%s' or interface '%s'.", m->member, m->interface); + + if (r < 0) + return r; + + return 1; +} + static int process_message(sd_bus *bus, sd_bus_message *m) { int r; @@ -2047,11 +2892,7 @@ static int process_message(sd_bus *bus, sd_bus_message *m) { if (r != 0) return r; - r = process_object(bus, m); - if (r != 0) - return r; - - return process_introspect(bus, m); + return process_object(bus, m); } static int process_running(sd_bus *bus, sd_bus_message **ret) { @@ -2090,16 +2931,11 @@ static int process_running(sd_bus *bus, sd_bus_message **ret) { } if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - - sd_bus_error_set(&error, "org.freedesktop.DBus.Error.UnknownObject", "Unknown object '%s'.", m->path); - - r = sd_bus_message_new_method_error(bus, m, &error, &reply); - if (r < 0) - return r; - r = sd_bus_send(bus, reply, NULL); + r = sd_bus_reply_method_errorf( + bus, m, + "org.freedesktop.DBus.Error.UnknownObject", + "Unknown object '%s'.", m->path); if (r < 0) return r; } @@ -2305,52 +3141,128 @@ int sd_bus_remove_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *u return 0; } +static struct node *bus_node_allocate(sd_bus *bus, const char *path) { + struct node *n, *parent; + const char *e; + char *s, *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_func, string_compare_func); + 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, path - e)); + + parent = bus_node_allocate(bus, p); + if (!parent) { + free(s); + return NULL; + } + } + + n = new0(struct node, 1); + if (!n) + return NULL; + + n->parent = parent; + n->path = s; + + r = hashmap_put(bus->nodes, s, n); + if (r < 0) { + free(s); + free(n); + return NULL; + } + + if (parent) + LIST_PREPEND(struct node, siblings, parent->child, n); + + return n; +} + +static 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_manager) + return; + + assert(hashmap_remove(b->nodes, n->path) == n); + + if (n->parent) + LIST_REMOVE(struct node, siblings, n->parent->child, n); + + free(n->path); + bus_node_gc(b, n->parent); + free(n); +} + static int bus_add_object( - sd_bus *bus, + sd_bus *b, bool fallback, const char *path, sd_bus_message_handler_t callback, void *userdata) { - struct object_callback *c; + struct node_callback *c; + struct node *n; int r; - if (!bus) + if (!b) return -EINVAL; - if (!path) + if (!object_path_is_valid(path)) return -EINVAL; if (!callback) return -EINVAL; - if (bus_pid_changed(bus)) + if (bus_pid_changed(b)) return -ECHILD; - r = hashmap_ensure_allocated(&bus->object_callbacks, string_hash_func, string_compare_func); - if (r < 0) - return r; - - c = new0(struct object_callback, 1); - if (!c) + n = bus_node_allocate(b, path); + if (!n) return -ENOMEM; - c->path = strdup(path); - if (!c->path) { - free(c); - return -ENOMEM; + c = new0(struct node_callback, 1); + if (!c) { + r = -ENOMEM; + goto fail; } + c->node = n; c->callback = callback; c->userdata = userdata; c->is_fallback = fallback; - bus->object_callbacks_modified = true; - r = hashmap_put(bus->object_callbacks, c->path, c); - if (r < 0) { - free(c->path); - free(c); - return r; - } - + LIST_PREPEND(struct node_callback, callbacks, n->callbacks, c); return 0; + +fail: + free(c); + bus_node_gc(b, n); + return r; } static int bus_remove_object( @@ -2360,30 +3272,33 @@ static int bus_remove_object( sd_bus_message_handler_t callback, void *userdata) { - struct object_callback *c; + struct node_callback *c; + struct node *n; if (!bus) return -EINVAL; - if (!path) + if (!object_path_is_valid(path)) return -EINVAL; if (!callback) return -EINVAL; if (bus_pid_changed(bus)) return -ECHILD; - c = hashmap_get(bus->object_callbacks, path); - if (!c) + n = hashmap_get(bus->nodes, path); + if (!n) return 0; - if (c->callback != callback || c->userdata != userdata || c->is_fallback != fallback) + LIST_FOREACH(callbacks, c, n->callbacks) + if (c->callback == callback && c->userdata == userdata && c->is_fallback == fallback) + break; + if (!c) return 0; - bus->object_callbacks_modified = true; - assert_se(c == hashmap_remove(bus->object_callbacks, c->path)); - - free(c->path); + LIST_REMOVE(struct node_callback, callbacks, n->callbacks, c); free(c); + bus_node_gc(bus, n); + return 1; } @@ -2605,6 +3520,40 @@ int sd_bus_reply_method_error( return sd_bus_send(bus, m, NULL); } +int sd_bus_reply_method_errorf( + sd_bus *bus, + sd_bus_message *call, + const char *name, + const char *format, + ...) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + char *n, *m; + va_list ap; + int r; + + n = strdup(name); + if (!n) + return -ENOMEM; + + if (format) { + va_start(ap, format); + r = vasprintf(&m, format, ap); + va_end(ap); + + if (r < 0) { + free(n); + return -ENOMEM; + } + } + + error.name = n; + error.message = m; + error.need_free = true; + + return sd_bus_reply_method_error(bus, call, &error); +} + bool bus_pid_changed(sd_bus *bus) { assert(bus); @@ -2613,3 +3562,711 @@ bool bus_pid_changed(sd_bus *bus) { return bus->original_pid != getpid(); } + +static void free_node_vtable(sd_bus *bus, struct node_vtable *w) { + assert(bus); + + if (!w) + return; + + if (w->interface && w->node && w->vtable) { + const sd_bus_vtable *v; + + for (v = w->vtable; v->type != _SD_BUS_VTABLE_END; w++) { + struct vtable_member *x = NULL; + + switch (v->type) { + + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member key; + + key.path = w->node->path; + key.interface = w->interface; + key.member = v->method.member; + + x = hashmap_remove(bus->vtable_methods, &key); + break; + } + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { + struct vtable_member key; + + key.path = w->node->path; + key.interface = w->interface; + key.member = v->property.member; + x = hashmap_remove(bus->vtable_properties, &key); + break; + }} + + free(x); + } + } + + free(w->interface); + free(w); +} + +static unsigned vtable_member_hash_func(const void *a) { + const struct vtable_member *m = a; + + return + string_hash_func(m->path) ^ + string_hash_func(m->interface) ^ + string_hash_func(m->member); +} + +static int vtable_member_compare_func(const void *a, const void *b) { + const struct vtable_member *x = a, *y = b; + int r; + + 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); +} + +static int add_object_vtable_internal( + sd_bus *bus, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + bool fallback, + sd_bus_object_find_t find, + void *userdata) { + + struct node_vtable *c, *i; + const sd_bus_vtable *v; + struct node *n; + int r; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!interface_name_is_valid(interface)) + return -EINVAL; + if (!vtable || vtable[0].type != _SD_BUS_VTABLE_START || vtable[0].start.element_size != sizeof(struct sd_bus_vtable)) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = hashmap_ensure_allocated(&bus->vtable_methods, vtable_member_hash_func, vtable_member_compare_func); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&bus->vtable_properties, vtable_member_hash_func, vtable_member_compare_func); + if (r < 0) + return r; + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + LIST_FOREACH(vtables, i, n->vtables) { + if (streq(i->interface, interface)) { + r = -EEXIST; + goto fail; + } + + if (i->is_fallback != fallback) { + r = -EPROTOTYPE; + goto fail; + } + } + + c = new0(struct node_vtable, 1); + if (!c) { + r = -ENOMEM; + goto fail; + } + + c->node = n; + c->is_fallback = fallback; + c->vtable = vtable; + c->userdata = userdata; + c->find = find; + + c->interface = strdup(interface); + if (!c->interface) { + r = -ENOMEM; + goto fail; + } + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + + switch (v->type) { + + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member *m; + + if (!member_name_is_valid(v->method.member) || + !signature_is_valid(v->method.signature, false) || + !signature_is_valid(v->method.result, false) || + !v->method.handler || + v->flags & (SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY)) { + r = -EINVAL; + goto fail; + } + + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->parent = c; + m->path = n->path; + m->interface = c->interface; + m->member = v->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_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { + struct vtable_member *m; + + if (!member_name_is_valid(v->property.member) || + !signature_is_single(v->property.signature, false) || + v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY || + (v->flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY && !(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE))) { + r = -EINVAL; + goto fail; + } + + + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->parent = c; + m->path = n->path; + m->interface = c->interface; + m->member = v->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: + + if (!member_name_is_valid(v->signal.member) || + !signature_is_single(v->signal.signature, false)) { + r = -EINVAL; + goto fail; + } + + break; + + default: + r = -EINVAL; + goto fail; + } + } + + LIST_PREPEND(struct node_vtable, vtables, n->vtables, c); + return 0; + +fail: + if (c) + free_node_vtable(bus, c); + + bus_node_gc(bus, n); + return 0; +} + +static int remove_object_vtable_internal( + sd_bus *bus, + const char *path, + const char *interface, + bool fallback) { + + struct node_vtable *c; + struct node *n; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!interface_name_is_valid(interface)) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + n = hashmap_get(bus->nodes, path); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) + if (streq(c->interface, interface) && c->is_fallback == fallback) + break; + + if (!c) + return 0; + + LIST_REMOVE(struct node_vtable, vtables, n->vtables, c); + + free_node_vtable(bus, c); + return 1; +} + +int sd_bus_add_object_vtable( + sd_bus *bus, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + void *userdata) { + + return add_object_vtable_internal(bus, path, interface, vtable, false, NULL, userdata); +} + +int sd_bus_remove_object_vtable( + sd_bus *bus, + const char *path, + const char *interface) { + + return remove_object_vtable_internal(bus, path, interface, false); +} + +int sd_bus_add_fallback_vtable( + sd_bus *bus, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + sd_bus_object_find_t find, + void *userdata) { + + return add_object_vtable_internal(bus, path, interface, vtable, true, find, userdata); +} + +int sd_bus_remove_fallback_vtable( + sd_bus *bus, + const char *path, + const char *interface) { + + return remove_object_vtable_internal(bus, path, interface, true); +} + +int sd_bus_add_node_enumerator( + sd_bus *bus, + const char *path, + sd_bus_node_enumerator_t callback, + void *userdata) { + + struct node_enumerator *c; + struct node *n; + int r; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!callback) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + c = new0(struct node_enumerator, 1); + if (!c) { + r = -ENOMEM; + goto fail; + } + + c->node = n; + c->callback = callback; + c->userdata = userdata; + + LIST_PREPEND(struct node_enumerator, enumerators, n->enumerators, c); + return 0; + +fail: + free(c); + bus_node_gc(bus, n); + return r; +} + +int sd_bus_remove_node_enumerator( + sd_bus *bus, + const char *path, + sd_bus_node_enumerator_t callback, + void *userdata) { + + struct node_enumerator *c; + struct node *n; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!callback) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + n = hashmap_get(bus->nodes, path); + if (!n) + return 0; + + LIST_FOREACH(enumerators, c, n->enumerators) + if (c->callback == callback && c->userdata == userdata) + break; + + if (!c) + return 0; + + LIST_REMOVE(struct node_enumerator, enumerators, n->enumerators, c); + free(c); + + bus_node_gc(bus, n); + + return 1; +} + +static int emit_properties_changed_on_interface( + sd_bus *bus, + const char *prefix, + const char *path, + const char *interface, + bool require_fallback, + char **names) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + bool has_invalidating = false; + struct vtable_member key; + struct node_vtable *c; + struct node *n; + char **property; + void *u; + int r; + + assert(bus); + 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)) + break; + + r = node_vtable_get_userdata(bus, path, c, &u); + if (r < 0) + return r; + if (r > 0) + break; + } + + if (!c) + return 0; + + r = sd_bus_message_new_signal(bus, path, "org.freedesktop.DBus", "PropertiesChanged", &m); + 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; + + STRV_FOREACH(property, names) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + struct vtable_member *v; + + key.member = *property; + v = hashmap_get(bus->vtable_properties, &key); + if (!v) + return -ENOENT; + + assert(c == v->parent); + + if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + return -EDOM; + if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY) { + has_invalidating = true; + continue; + } + + r = sd_bus_message_open_container(m, 'e', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", *n); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', v->vtable->property.signature); + if (r < 0) + return r; + + r = v->vtable->property.get(bus, m->path, interface, *property, m, &error, u); + if (r < 0) + return r; + + if (sd_bus_error_is_set(&error)) + return bus_error_to_errno(&error); + + 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_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) { + 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_INVALIDATE_ONLY)) + continue; + + r = sd_bus_message_append(m, "s", *property); + 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; +} + +int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) { + size_t pl; + int r; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!interface_name_is_valid(interface)) + return -EINVAL; + + r = emit_properties_changed_on_interface(bus, path, path, interface, false, names); + if (r != 0) + return r; + + pl = strlen(path); + if (pl > 1 ) { + char p[pl+1]; + + strcpy(p, path); + for (;;) { + char *e; + + if (streq(p, "/")) + break; + + e = strrchr(p, '/'); + assert(e); + if (e == p) + *(e+1) = 0; + else + *e = 0; + + r = emit_properties_changed_on_interface(bus, p, path, interface, true, names); + if (r != 0) + return r; + } + } + + return -ENOENT; +} + +int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...) { + _cleanup_strv_free_ char **names = NULL; + va_list ap; + + va_start(ap, name); + names = strv_new_ap(name, ap); + va_end(ap); + + if (!names) + return -ENOMEM; + + return sd_bus_emit_properties_changed_strv(bus, path, interface, names); +} + +int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interfaces, ...) { + return -ENOSYS; +} + +int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interfaces, ...) { + return -ENOSYS; +} + +int sd_bus_get_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + sd_bus_message **reply, + const char *type) { + + sd_bus_message *rep = NULL; + int r; + + if (interface && !interface_name_is_valid(interface)) + return -EINVAL; + if (!member_name_is_valid(member)) + return -EINVAL; + if (!signature_is_single(type, false)) + return -EINVAL; + if (!reply) + return -EINVAL; + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &rep, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(rep, 'v', type); + if (r < 0) { + sd_bus_message_unref(rep); + return r; + } + + *reply = rep; + return 0; +} + +int sd_bus_set_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + const char *type, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (interface && !interface_name_is_valid(interface)) + return -EINVAL; + if (!member_name_is_valid(member)) + return -EINVAL; + if (!signature_is_single(type, false)) + return -EINVAL; + + r = sd_bus_message_new_method_call(bus, destination, path, "org.freedesktop.DBus.Properties", "Set", &m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', type); + if (r < 0) + return r; + + va_start(ap, type); + r = bus_message_append_ap(m, type, ap); + va_end(ap); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, 0, error, NULL); +} + +int sd_bus_add_object_manager(sd_bus *bus, const char *path) { + struct node *n; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + n->object_manager = true; + return 0; +} + +int sd_bus_remove_object_manager(sd_bus *bus, const char *path) { + struct node *n; + + if (!bus) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + n = hashmap_get(bus->nodes, path); + if (!n) + return 0; + + if (!n->object_manager) + return 0; + + n->object_manager = false; + bus_node_gc(bus, n); + return 1; +} diff --git a/src/libsystemd-bus/test-bus-introspect.c b/src/libsystemd-bus/test-bus-introspect.c new file mode 100644 index 0000000000..ea26cdd101 --- /dev/null +++ b/src/libsystemd-bus/test-bus-introspect.c @@ -0,0 +1,63 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "log.h" +#include "bus-introspect.h" + +static int prop_get(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) { + return -EINVAL; +} + +static int prop_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) { + return -EINVAL; +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Hello", "ssas", "a(uu)", 0, NULL), + SD_BUS_METHOD("DeprecatedHello", "", "", SD_BUS_VTABLE_DEPRECATED, NULL), + SD_BUS_METHOD("DeprecatedHelloNoReply", "", "", SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_METHOD_NO_REPLY, NULL), + SD_BUS_SIGNAL("Wowza", "sss", 0), + SD_BUS_SIGNAL("DeprecatedWowza", "ut", SD_BUS_VTABLE_DEPRECATED), + SD_BUS_WRITABLE_PROPERTY("AProperty", "s", prop_get, prop_set, 0, 0), + SD_BUS_PROPERTY("AReadOnlyDeprecatedProperty", "(ut)", prop_get, 0, SD_BUS_VTABLE_DEPRECATED), + SD_BUS_PROPERTY("ChangingProperty", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Invalidating", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY), + SD_BUS_VTABLE_END +}; + +int main(int argc, char *argv[]) { + struct introspect intro; + + log_set_max_level(LOG_DEBUG); + + assert_se(introspect_begin(&intro) >= 0); + + assert_se(introspect_write_interface(&intro, "org.foo", vtable) >= 0); + + fflush(intro.f); + fputs(intro.introspection, stdout); + + introspect_free(&intro); + + return 0; +} diff --git a/src/libsystemd-bus/test-bus-objects.c b/src/libsystemd-bus/test-bus-objects.c new file mode 100644 index 0000000000..a95789fe9c --- /dev/null +++ b/src/libsystemd-bus/test-bus-objects.c @@ -0,0 +1,374 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <assert.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> + +#include "log.h" +#include "util.h" +#include "macro.h" +#include "strv.h" + +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" + +/* Test: + * + * sd_bus_add_object_manager() + * sd_bus_emit_properties_changed() + * + * Add in: + * + * automatic properties + * node hierarchy updates during dispatching + * emit_interfaces_added/emit_interfaces_removed + * + */ + +struct context { + int fds[2]; + bool quit; + char *something; +}; + +static int something_handler(sd_bus *bus, sd_bus_message *m, void *userdata) { + struct context *c = userdata; + const char *s; + char *n = NULL; + int r; + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + + n = strjoin("<<<", s, ">>>", NULL); + assert_se(n); + + free(c->something); + c->something = n; + + log_info("AlterSomething() called, got %s, returning %s", s, n); + + r = sd_bus_reply_method_return(bus, m, "s", n); + assert_se(r >= 0); + + return 1; +} + +static int exit_handler(sd_bus *bus, sd_bus_message *m, void *userdata) { + struct context *c = userdata; + int r; + + c->quit = true; + + log_info("Exit called"); + + r = sd_bus_reply_method_return(bus, m, ""); + assert_se(r >= 0); + + return 1; +} + +static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) { + struct context *c = userdata; + int r; + + log_info("property get for %s called", property); + + r = sd_bus_message_append(reply, "s", c->something); + assert_se(r >= 0); + + return 1; +} + +static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, sd_bus_error *error, void *userdata) { + struct context *c = userdata; + const char *s; + char *n; + int r; + + log_info("property set for %s called", property); + + r = sd_bus_message_read(value, "s", &s); + assert_se(r >= 0); + + n = strdup(s); + assert_se(n); + + free(c->something); + c->something = n; + + return 1; +} + +static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) { + _cleanup_free_ char *s = NULL; + const char *x; + int r; + + assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0); + r = sd_bus_message_append(reply, "s", s); + assert_se(r >= 0); + + assert_se(x = startswith(path, "/value/")); + + assert_se(PTR_TO_UINT(userdata) == 30); + + + return 1; +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("AlterSomething", "s", "s", 0, something_handler), + SD_BUS_METHOD("Exit", "", "", 0, exit_handler), + SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0), + SD_BUS_VTABLE_END +}; + +static const sd_bus_vtable vtable2[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Value", "s", value_handler, 10, 0), + SD_BUS_VTABLE_END +}; + +static int enumerator_callback(sd_bus *b, const char *path, char ***nodes, void *userdata) { + + if (object_path_startswith("/value", path)) + assert_se(*nodes = strv_new("/value/a", "/value/b", "/value/c", NULL)); + + return 1; +} + +static void *server(void *p) { + struct context *c = p; + sd_bus *bus = NULL; + sd_id128_t id; + int r; + + c->quit = false; + + assert_se(sd_id128_randomize(&id) >= 0); + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); + assert_se(sd_bus_set_server(bus, 1, id) >= 0); + + assert_se(sd_bus_add_object_vtable(bus, "/foo", "org.freedesktop.systemd.test", vtable, c) >= 0); + assert_se(sd_bus_add_object_vtable(bus, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0); + assert_se(sd_bus_add_fallback_vtable(bus, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0); + assert_se(sd_bus_add_node_enumerator(bus, "/value", enumerator_callback, NULL) >= 0); + + assert_se(sd_bus_start(bus) >= 0); + + log_error("Entering event loop on server"); + + while (!c->quit) { + log_error("Loop!"); + + r = sd_bus_process(bus, NULL); + if (r < 0) { + log_error("Failed to process requests: %s", strerror(-r)); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error("Failed to wait: %s", strerror(-r)); + goto fail; + } + + continue; + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return INT_TO_PTR(r); +} + +static int client(struct context *c) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_unref_ sd_bus *bus = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + const char *s; + int r; + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "s", "hallo"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "<<<hallo>>>")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, ""); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.UnknownMethod")); + + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "as", 1, "hallo"); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.InvalidArgs")); + + sd_bus_error_free(&error); + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "<<<hallo>>>")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, "s", "test"); + assert_se(r >= 0); + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "test")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r <= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/value/xuzz", "org.freedesktop.systemd.ValueTest", "Value", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + log_info("read %s", s); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r <= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r <= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r <= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", ""); + assert_se(r <= 0); + + bus_message_dump(reply); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "org.freedesktop.systemd.ValueTest2"); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.UnknownInterface")); + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, ""); + assert_se(r >= 0); + + sd_bus_flush(bus); + + return 0; +} + +int main(int argc, char *argv[]) { + struct context c; + pthread_t s; + void *p; + int r, q; + + zero(c); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); + + r = pthread_create(&s, NULL, server, &c); + if (r != 0) + return -r; + + r = client(&c); + + q = pthread_join(s, &p); + if (q != 0) + return -q; + + if (r < 0) + return r; + + if (PTR_TO_INT(p) < 0) + return PTR_TO_INT(p); + + free(c.something); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd-bus/test-bus-signature.c b/src/libsystemd-bus/test-bus-signature.c index cab0daa77e..c4304697f0 100644 --- a/src/libsystemd-bus/test-bus-signature.c +++ b/src/libsystemd-bus/test-bus-signature.c @@ -28,29 +28,30 @@ int main(int argc, char *argv[]) { - assert_se(signature_is_single("y")); - assert_se(signature_is_single("u")); - assert_se(signature_is_single("v")); - assert_se(signature_is_single("as")); - assert_se(signature_is_single("(ss)")); - assert_se(signature_is_single("()")); - assert_se(signature_is_single("(()()()()())")); - assert_se(signature_is_single("(((())))")); - assert_se(signature_is_single("((((s))))")); - assert_se(signature_is_single("{ss}")); - assert_se(signature_is_single("a{ss}")); - assert_se(!signature_is_single("uu")); - assert_se(!signature_is_single("")); - assert_se(!signature_is_single("(")); - assert_se(!signature_is_single(")")); - assert_se(!signature_is_single("())")); - assert_se(!signature_is_single("((())")); - assert_se(!signature_is_single("{)")); - assert_se(!signature_is_single("{}")); - assert_se(!signature_is_single("{sss}")); - assert_se(!signature_is_single("{s}")); - assert_se(!signature_is_single("{ass}")); - assert_se(!signature_is_single("a}")); + assert_se(signature_is_single("y", false)); + assert_se(signature_is_single("u", false)); + assert_se(signature_is_single("v", false)); + assert_se(signature_is_single("as", false)); + assert_se(signature_is_single("(ss)", false)); + assert_se(signature_is_single("()", false)); + assert_se(signature_is_single("(()()()()())", false)); + assert_se(signature_is_single("(((())))", false)); + assert_se(signature_is_single("((((s))))", false)); + assert_se(signature_is_single("{ss}", true)); + assert_se(signature_is_single("a{ss}", false)); + assert_se(!signature_is_single("uu", false)); + assert_se(!signature_is_single("", false)); + assert_se(!signature_is_single("(", false)); + assert_se(!signature_is_single(")", false)); + assert_se(!signature_is_single("())", false)); + assert_se(!signature_is_single("((())", false)); + assert_se(!signature_is_single("{)", false)); + assert_se(!signature_is_single("{}", true)); + assert_se(!signature_is_single("{sss}", true)); + assert_se(!signature_is_single("{s}", true)); + assert_se(!signature_is_single("{ss}", false)); + assert_se(!signature_is_single("{ass}", true)); + assert_se(!signature_is_single("a}", true)); assert_se(signature_is_pair("yy")); assert_se(signature_is_pair("ss")); @@ -112,5 +113,25 @@ int main(int argc, char *argv[]) { assert_se(!namespace_simple_pattern("", "foo")); assert_se(!namespace_simple_pattern("foo", "")); + assert_se(streq(object_path_startswith("/foo/bar", "/foo"), "bar")); + assert_se(streq(object_path_startswith("/foo", "/foo"), "")); + assert_se(streq(object_path_startswith("/foo", "/"), "foo")); + assert_se(streq(object_path_startswith("/", "/"), "")); + assert_se(!object_path_startswith("/foo", "/bar")); + assert_se(!object_path_startswith("/", "/bar")); + assert_se(!object_path_startswith("/foo", "")); + + assert_se(object_path_is_valid("/foo/bar")); + assert_se(object_path_is_valid("/foo")); + assert_se(object_path_is_valid("/")); + assert_se(object_path_is_valid("/foo5")); + assert_se(object_path_is_valid("/foo_5")); + assert_se(!object_path_is_valid("")); + assert_se(!object_path_is_valid("/foo/")); + assert_se(!object_path_is_valid("//")); + assert_se(!object_path_is_valid("//foo")); + assert_se(!object_path_is_valid("/foo//bar")); + assert_se(!object_path_is_valid("/foo/aaaäöä")); + return 0; } |