From 29ddb38fea134c6132e4f2dd608e9da3871eaebe Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 9 Oct 2013 02:37:10 +0200 Subject: [PATCH] libsystemd-bus: add lightweight object vtable implementation for exposing objects on the bus This adds a lightweight scheme how to define interfaces in static fixed arrays which then can be easily registered on a bus connection. This makes it much easier to write bus services. This automatically handles implementation of the Properties, ObjectManager, and Introspection bus interfaces. --- .gitignore | 2 + Makefile.am | 28 +- TODO | 2 + src/libsystemd-bus/bus-error.c | 33 +- src/libsystemd-bus/bus-internal.c | 23 + src/libsystemd-bus/bus-internal.h | 65 +- src/libsystemd-bus/bus-introspect.c | 200 ++ src/libsystemd-bus/bus-introspect.h | 41 + src/libsystemd-bus/bus-message.c | 81 +- src/libsystemd-bus/bus-signature.c | 6 +- src/libsystemd-bus/bus-signature.h | 2 +- src/libsystemd-bus/sd-bus.c | 2697 +++++++++++++++++----- src/libsystemd-bus/test-bus-introspect.c | 63 + src/libsystemd-bus/test-bus-objects.c | 374 +++ src/libsystemd-bus/test-bus-signature.c | 67 +- src/systemd/sd-bus-protocol.h | 39 +- src/systemd/sd-bus-vtable.h | 127 + src/systemd/sd-bus.h | 54 +- 18 files changed, 3322 insertions(+), 582 deletions(-) create mode 100644 src/libsystemd-bus/bus-introspect.c create mode 100644 src/libsystemd-bus/bus-introspect.h create mode 100644 src/libsystemd-bus/test-bus-introspect.c create mode 100644 src/libsystemd-bus/test-bus-objects.c create mode 100644 src/systemd/sd-bus-vtable.h diff --git a/.gitignore b/.gitignore index 5b38c0b2e..786a0beb2 100644 --- a/.gitignore +++ b/.gitignore @@ -87,12 +87,14 @@ /tags /test-boot-timestamp /test-bus-chat +/test-bus-introspect /test-bus-kernel /test-bus-kernel-bloom /test-bus-kernel-benchmark /test-bus-marshal /test-bus-match /test-bus-memfd +/test-bus-objects /test-bus-signature /test-bus-server /test-bus-zero-copy diff --git a/Makefile.am b/Makefile.am index 8d9c58758..4c7646d84 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1956,6 +1956,8 @@ libsystemd_bus_la_SOURCES = \ src/libsystemd-bus/bus-match.h \ src/libsystemd-bus/bus-bloom.c \ src/libsystemd-bus/bus-bloom.h \ + src/libsystemd-bus/bus-introspect.c \ + src/libsystemd-bus/bus-introspect.h \ src/libsystemd-bus/kdbus.h \ src/libsystemd-bus/sd-memfd.c @@ -1981,7 +1983,9 @@ tests += \ test-bus-kernel-bloom \ test-bus-kernel-benchmark \ test-bus-memfd \ - test-bus-zero-copy + test-bus-zero-copy \ + test-bus-introspect \ + test-bus-objects noinst_PROGRAMS += \ busctl @@ -2031,6 +2035,18 @@ test_bus_server_LDADD = \ libsystemd-bus.la \ libsystemd-id128-internal.la +test_bus_objects_SOURCES = \ + src/libsystemd-bus/test-bus-objects.c + +test_bus_objects_CFLAGS = \ + $(AM_CFLAGS) \ + -pthread + +test_bus_objects_LDADD = \ + libsystemd-shared.la \ + libsystemd-bus.la \ + libsystemd-id128-internal.la + test_bus_match_SOURCES = \ src/libsystemd-bus/test-bus-match.c @@ -2095,6 +2111,16 @@ test_bus_zero_copy_LDADD = \ libsystemd-shared.la \ libsystemd-bus.la +test_bus_introspect_SOURCES = \ + src/libsystemd-bus/test-bus-introspect.c + +test_bus_introspect_CFLAGS = \ + $(AM_CFLAGS) + +test_bus_introspect_LDADD = \ + libsystemd-shared.la \ + libsystemd-bus.la + busctl_SOURCES = \ src/libsystemd-bus/busctl.c diff --git a/TODO b/TODO index 6764919e7..4a4746370 100644 --- a/TODO +++ b/TODO @@ -54,6 +54,8 @@ Features: * tmpfiles: when applying ownership to /run/log/journal also do this for the journal fails contained in it +* rework list.h to use typeof() and thus simplify most linked list macros by not requring the type to be specified + * we probably should replace the left-over uses of strv_append() and replace them by strv_push() or strv_extend() * move config_parse_path_strv() out of conf-parser.c diff --git a/src/libsystemd-bus/bus-error.c b/src/libsystemd-bus/bus-error.c index 4696a88f7..28fe15467 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 cac948e87..afff7bd7c 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 30b8d519a..5795f7469 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 000000000..8dc9a2de1 --- /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 . +***/ + +#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 + "\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, " \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(" \n", i->f); + + if (type == _SD_BUS_VTABLE_METHOD && flags & SD_BUS_VTABLE_METHOD_NO_REPLY) + fputs(" \n", i->f); + + if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) { + if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + fputs(" \n", i->f); + else if (flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY) + fputs(" \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, " 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, " \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(" \n", i->f); + break; + + case _SD_BUS_VTABLE_METHOD: + fprintf(i->f, " \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(" \n", i->f); + break; + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: + fprintf(i->f, " \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(" \n", i->f); + break; + + case _SD_BUS_VTABLE_SIGNAL: + fprintf(i->f, " \n", v->signal.member); + introspect_write_arguments(i, v->signal.signature, NULL); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + } + + } + + fputs(" \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("\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 000000000..48c3885d9 --- /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 . +***/ + +#include +#include + +#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 760a148fa..2cf1a7a53 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 a92b7124c..35c054baf 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 4000e0612..2e06e3054 100644 --- a/src/libsystemd-bus/bus-signature.h +++ b/src/libsystemd-bus/bus-signature.h @@ -24,7 +24,7 @@ #include #include -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 db0880f21..b1dcd8dc6 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,779 +1875,2398 @@ 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; - bool found = false; - size_t pl; assert(bus); - assert(m); + 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; + } - if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) - return 0; + *userdata = u; + return 1; +} - if (hashmap_isempty(bus->object_callbacks)) - return 0; +static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); - pl = strlen(m->path); + return (uint8_t*) u + p->property.offset; +} - do { - char p[pl+1]; +static int vtable_property_get_userdata( + sd_bus *bus, + const char *path, + struct vtable_member *p, + void **userdata) { - bus->object_callbacks_modified = false; + void *u; + int r; - c = hashmap_get(bus->object_callbacks, m->path); - if (c && c->last_iteration != bus->iteration_counter) { + assert(bus); + assert(path); + assert(p); + assert(userdata); - c->last_iteration = bus->iteration_counter; + r = node_vtable_get_userdata(bus, path, p->parent, &u); + if (r <= 0) + return r; - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; + *userdata = vtable_property_convert_userdata(p->vtable, u); + return 1; +} - r = c->callback(bus, m, c->userdata); - if (r != 0) - return r; +static int add_enumerated_to_set(sd_bus *bus, const char *prefix, struct node_enumerator *first, Set *s) { + struct node_enumerator *c; + int r; - found = true; - } + assert(bus); + assert(prefix); + assert(s); - /* Look for fallback prefixes */ - strcpy(p, m->path); - for (;;) { - char *e; + LIST_FOREACH(enumerators, c, first) { + char **children = NULL, **k; - if (bus->object_callbacks_modified) - break; + r = c->callback(bus, prefix, &children, c->userdata); + if (r < 0) + return r; - e = strrchr(p, '/'); - if (e == p || !e) - break; + STRV_FOREACH(k, children) { + if (r < 0) { + free(*k); + continue; + } - *e = 0; + if (!object_path_is_valid(*k) && object_path_startswith(*k, prefix)) { + free(*k); + r = -EINVAL; + continue; + } - c = hashmap_get(bus->object_callbacks, p); - if (c && c->last_iteration != bus->iteration_counter && c->is_fallback) { + r = set_consume(s, *k); + } - c->last_iteration = bus->iteration_counter; + free(children); + if (r < 0) + return r; + } - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; + return 0; +} - r = c->callback(bus, m, c->userdata); - if (r != 0) - return r; +static int add_subtree_to_set(sd_bus *bus, const char *prefix, struct node *n, Set *s) { + struct node *i; + int r; - found = true; - } - } + assert(bus); + assert(prefix); + assert(n); + assert(s); - } while (bus->object_callbacks_modified); + r = add_enumerated_to_set(bus, prefix, n->enumerators, s); + if (r < 0) + return 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; + LIST_FOREACH(siblings, i, n->child) { + char *t; - sd_bus_error_set(&error, - "org.freedesktop.DBus.Error.UnknownMethod", - "Unknown method '%s' or interface '%s'.", m->member, m->interface); + t = strdup(i->path); + if (!t) + return -ENOMEM; - r = sd_bus_message_new_method_error(bus, m, &error, &reply); - if (r < 0) - return r; + r = set_consume(s, t); + if (r < 0 && r != -EEXIST) + return r; - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; + r = add_subtree_to_set(bus, prefix, i, s); + if (r < 0) + return r; + } - return 1; + return 0; } -static int process_introspect(sd_bus *bus, sd_bus_message *m) { - _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; +static int get_child_nodes(sd_bus *bus, const char *prefix, struct node *n, Set **_s) { + Set *s = NULL; int r; assert(bus); - assert(m); - - if (!sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) - return 0; - - if (!m->path) - return 0; + assert(n); + assert(_s); s = set_new(string_hash_func, string_compare_func); if (!s) return -ENOMEM; - HASHMAP_FOREACH(c, bus->object_callbacks, i) { - const char *e; - char *a, *p; - - if (streq(c->path, "/")) - continue; - - if (streq(m->path, "/")) - e = c->path; - else { - e = startswith(c->path, m->path); - if (!e || *e != '/') - continue; - } - - a = strdup(e+1); - if (!a) - return -ENOMEM; - - p = strchr(a, '/'); - if (p) - *p = 0; - - r = set_consume(s, a); - if (r < 0 && r != -EEXIST) - return r; + r = add_subtree_to_set(bus, prefix, n, s); + if (r < 0) { + set_free_free(s); + return r; } - f = open_memstream(&introspection, &size); - if (!f) - return -ENOMEM; + *_s = s; + return 0; +} - fputs(SD_BUS_INTROSPECT_DOCTYPE, f); - fputs("\n", f); - fputs(SD_BUS_INTROSPECT_INTERFACE_PEER, f); - fputs(SD_BUS_INTROSPECT_INTERFACE_INTROSPECTABLE, f); +static int node_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct node_callback *first, + bool require_fallback, + bool *found_object) { - while ((node = set_steal_first(s))) { - fprintf(f, " \n", node); - free(node); - } + struct node_callback *c; + int r; - fputs("\n", f); + assert(bus); + assert(m); + assert(found_object); - fflush(f); + LIST_FOREACH(callbacks, c, first) { + if (require_fallback && !c->is_fallback) + continue; - if (ferror(f)) - return -ENOMEM; + *found_object = true; - r = sd_bus_message_new_method_return(bus, m, &reply); - if (r < 0) - return r; + if (c->last_iteration == bus->iteration_counter) + continue; - r = sd_bus_message_append(reply, "s", introspection); - 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; + return 0; } -static int process_message(sd_bus *bus, sd_bus_message *m) { +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); - bus->iteration_counter++; + if (require_fallback && !c->parent->is_fallback) + return 0; - r = process_hello(bus, m); - if (r != 0) + r = node_vtable_get_userdata(bus, m->path, c->parent, &u); + if (r <= 0) return r; - r = process_reply(bus, m); - if (r != 0) - return r; + *found_object = true; - r = process_filter(bus, m); - if (r != 0) + r = sd_bus_message_rewind(m, true); + if (r < 0) return r; - r = process_match(bus, m); - if (r != 0) + r = sd_bus_message_get_signature(m, true, &signature); + if (r < 0) return r; - r = process_builtin(bus, m); - 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; - r = process_object(bus, m); - if (r != 0) - return r; + return 1; + } - return process_introspect(bus, m); + return c->vtable->method.handler(bus, m, u); } -static int process_running(sd_bus *bus, sd_bus_message **ret) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; +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(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + assert(m); + assert(found_object); - r = process_timeout(bus); - if (r != 0) - goto null_message; + if (require_fallback && !c->parent->is_fallback) + return 0; - r = dispatch_wqueue(bus); - if (r != 0) - goto null_message; + r = vtable_property_get_userdata(bus, m->path, c, &u); + if (r <= 0) + return r; - r = dispatch_rqueue(bus, &m); + *found_object = true; + + r = sd_bus_message_new_method_return(bus, m, &reply); if (r < 0) return r; - if (!m) - goto null_message; - r = process_message(bus, m); - if (r != 0) - goto null_message; + c->last_iteration = bus->iteration_counter; - if (ret) { - r = sd_bus_message_rewind(m, true); + if (is_get) { + r = sd_bus_message_open_container(reply, 'v', c->vtable->property.signature); if (r < 0) return r; - *ret = m; - m = NULL; - return 1; - } + 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"); - 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; + if (sd_bus_error_is_set(&error)) { + r = sd_bus_reply_method_error(bus, m, &error); + if (r < 0) + return r; - sd_bus_error_set(&error, "org.freedesktop.DBus.Error.UnknownObject", "Unknown object '%s'.", m->path); + return 1; + } - r = sd_bus_message_new_method_error(bus, m, &error, &reply); + r = sd_bus_message_close_container(reply); if (r < 0) return r; - r = sd_bus_send(bus, reply, NULL); + } 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; + + 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"); + } + + 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_exit_container(m); if (r < 0) return r; } - return 1; - -null_message: - if (r >= 0 && ret) - *ret = NULL; + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; - return r; + return 1; } -int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { +static int vtable_append_all_properties( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + struct node_vtable *c, + void *userdata, + sd_bus_error *error) { + + const sd_bus_vtable *v; int r; - /* Returns 0 when we didn't do anything. This should cause the - * caller to invoke sd_bus_wait() before returning the next + 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; + } + + 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_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; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +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_set_free_free_ Set *s = NULL; + struct introspect intro; + struct node_vtable *c; + bool empty; + int r; + + assert(bus); + assert(m); + assert(n); + assert(found_object); + + r = get_child_nodes(bus, m->path, n, &s); + if (r < 0) + return r; + + r = introspect_begin(&intro); + if (r < 0) + return r; + + r = introspect_write_default_interfaces(&intro, bus_node_with_object_manager(bus, n)); + if (r < 0) + return r; + + empty = set_isempty(s); + + LIST_FOREACH(vtables, c, n->vtables) { + 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; + + empty = false; + + r = introspect_write_interface(&intro, c->interface, c->vtable); + if (r < 0) + goto finish; + } + + 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; + } + + *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; + + 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; + + 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; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 1; +} + +static int object_manager_serialize_path_and_fallbacks( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + sd_bus_error *error) { + + 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_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; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int object_find_and_run(sd_bus *bus, sd_bus_message *m, const char *p, bool require_fallback, bool *found_object) { + struct node *n; + struct vtable_member vtable_key, *v; + int r; + + assert(bus); + assert(m); + assert(p); + assert(found_object); + + n = hashmap_get(bus->nodes, p); + if (!n) + return 0; + + /* First, try object callbacks */ + r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object); + if (r != 0) + return r; + + if (!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; + + assert(bus); + assert(m); + + bus->iteration_counter++; + + r = process_hello(bus, m); + if (r != 0) + return r; + + r = process_reply(bus, m); + if (r != 0) + return r; + + r = process_filter(bus, m); + if (r != 0) + return r; + + r = process_match(bus, m); + if (r != 0) + return r; + + r = process_builtin(bus, m); + if (r != 0) + return r; + + return process_object(bus, m); +} + +static int process_running(sd_bus *bus, sd_bus_message **ret) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = process_timeout(bus); + if (r != 0) + goto null_message; + + r = dispatch_wqueue(bus); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(bus, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + r = process_message(bus, m); + if (r != 0) + goto null_message; + + if (ret) { + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + *ret = m; + m = NULL; + return 1; + } + + if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL) { + + r = sd_bus_reply_method_errorf( + bus, m, + "org.freedesktop.DBus.Error.UnknownObject", + "Unknown object '%s'.", m->path); + if (r < 0) + return r; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { + int r; + + /* Returns 0 when we didn't do anything. This should cause the + * caller to invoke sd_bus_wait() before returning the next * time. Returns > 0 when we did something, which possibly * means *ret is filled in with an unprocessed message. */ - if (!bus) - return -EINVAL; - if (bus_pid_changed(bus)) - return -ECHILD; + if (!bus) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + /* We don't allow recursively invoking sd_bus_process(). */ + if (bus->processing) + return -EBUSY; + + switch (bus->state) { + + case BUS_UNSET: + case BUS_CLOSED: + return -ENOTCONN; + + case BUS_OPENING: + r = bus_socket_process_opening(bus); + if (r < 0) + return r; + if (ret) + *ret = NULL; + return r; + + case BUS_AUTHENTICATING: + + r = bus_socket_process_authenticating(bus); + if (r < 0) + return r; + if (ret) + *ret = NULL; + return r; + + case BUS_RUNNING: + case BUS_HELLO: + + bus->processing = true; + r = process_running(bus, ret); + bus->processing = false; + + return r; + } + + assert_not_reached("Unknown state"); +} + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { + struct pollfd p[2] = {}; + int r, e, n; + struct timespec ts; + usec_t until, m; + + assert(bus); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + if (need_more) + e |= POLLIN; + + r = sd_bus_get_timeout(bus, &until); + if (r < 0) + return r; + if (r == 0) + m = (uint64_t) -1; + else { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = bus->input_fd; + if (bus->output_fd == bus->input_fd) { + p[0].events = e; + n = 1; + } else { + p[0].events = e & POLLIN; + p[1].fd = bus->output_fd; + p[1].events = e & POLLOUT; + n = 2; + } + + r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { + + if (!bus) + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + + if (bus->rqueue_size > 0) + return 0; + + return bus_poll(bus, false, timeout_usec); +} + +int sd_bus_flush(sd_bus *bus) { + int r; + + if (!bus) + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) + return 0; + + for (;;) { + r = dispatch_wqueue(bus); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) + return 0; + + r = bus_poll(bus, false, (uint64_t) -1); + if (r < 0) + return r; + } +} + +int sd_bus_add_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata) { + struct filter_callback *f; + + if (!bus) + return -EINVAL; + if (!callback) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + f = new0(struct filter_callback, 1); + if (!f) + return -ENOMEM; + f->callback = callback; + f->userdata = userdata; + + bus->filter_callbacks_modified = true; + LIST_PREPEND(struct filter_callback, callbacks, bus->filter_callbacks, f); + return 0; +} + +int sd_bus_remove_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata) { + struct filter_callback *f; + + if (!bus) + return -EINVAL; + if (!callback) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + LIST_FOREACH(callbacks, f, bus->filter_callbacks) { + if (f->callback == callback && f->userdata == userdata) { + bus->filter_callbacks_modified = true; + LIST_REMOVE(struct filter_callback, callbacks, bus->filter_callbacks, f); + free(f); + return 1; + } + } + + 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 *b, + bool fallback, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + struct node_callback *c; + struct node *n; + int r; + + if (!b) + return -EINVAL; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!callback) + return -EINVAL; + if (bus_pid_changed(b)) + return -ECHILD; + + n = bus_node_allocate(b, path); + if (!n) + 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; + + 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( + sd_bus *bus, + bool fallback, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + struct node_callback *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(callbacks, c, n->callbacks) + if (c->callback == callback && c->userdata == userdata && c->is_fallback == fallback) + break; + if (!c) + return 0; + + LIST_REMOVE(struct node_callback, callbacks, n->callbacks, c); + free(c); + + bus_node_gc(bus, n); + + return 1; +} + +int sd_bus_add_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata) { + return bus_add_object(bus, false, path, callback, userdata); +} + +int sd_bus_remove_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata) { + return bus_remove_object(bus, false, path, callback, userdata); +} + +int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata) { + return bus_add_object(bus, true, prefix, callback, userdata); +} + +int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata) { + return bus_remove_object(bus, true, prefix, callback, userdata); +} + +int sd_bus_add_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + uint64_t cookie = 0; + int r = 0; + + if (!bus) + return -EINVAL; + if (!match) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + goto finish; + + if (bus->bus_client) { + cookie = ++bus->match_cookie; + + r = bus_add_match_internal(bus, match, components, n_components, cookie); + if (r < 0) + goto finish; + } + + bus->match_callbacks_modified = true; + r = bus_match_add(&bus->match_callbacks, components, n_components, callback, userdata, cookie, NULL); + if (r < 0) { + if (bus->bus_client) + bus_remove_match_internal(bus, match, cookie); + } + +finish: + bus_match_parse_free(components, n_components); + return r; +} + +int sd_bus_remove_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + int r = 0, q = 0; + uint64_t cookie = 0; + + if (!bus) + return -EINVAL; + if (!match) + return -EINVAL; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + return r; + + bus->match_callbacks_modified = true; + r = bus_match_remove(&bus->match_callbacks, components, n_components, callback, userdata, &cookie); + + if (bus->bus_client) + q = bus_remove_match_internal(bus, match, cookie); + + bus_match_parse_free(components, n_components); + + return r < 0 ? r : q; +} + +int sd_bus_emit_signal( + sd_bus *bus, + const char *path, + const char *interface, + const char *member, + const char *types, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (!bus) + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = sd_bus_message_new_signal(bus, path, interface, member, &m); + if (r < 0) + return r; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +int sd_bus_call_method( + 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 *types, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (!bus) + + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + + r = sd_bus_message_new_method_call(bus, destination, path, interface, member, &m); + if (r < 0) + return r; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + + return sd_bus_send_with_reply_and_block(bus, m, 0, error, reply); +} + +int sd_bus_reply_method_return( + sd_bus *bus, + sd_bus_message *call, + const char *types, ...) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + va_list ap; + int r; + + if (!bus) + return -EINVAL; + if (!call) + return -EINVAL; + if (!call->sealed) + return -EPERM; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + + if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_return(bus, call, &m); + if (r < 0) + return r; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +int sd_bus_reply_method_error( + sd_bus *bus, + sd_bus_message *call, + const sd_bus_error *e) { + + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + if (!bus) + return -EINVAL; + if (!call) + return -EINVAL; + if (!call->sealed) + return -EPERM; + if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + return -EINVAL; + if (!sd_bus_error_is_set(e)) + return -EINVAL; + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + if (bus_pid_changed(bus)) + return -ECHILD; + + if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_error(bus, call, e, &m); + if (r < 0) + return r; + + 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); + + /* We don't support people creating a bus connection and + * keeping it around over a fork(). Let's complain. */ + + 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; + } - /* We don't allow recursively invoking sd_bus_process(). */ - if (bus->processing) - return -EBUSY; + c->node = n; + c->is_fallback = fallback; + c->vtable = vtable; + c->userdata = userdata; + c->find = find; - switch (bus->state) { + c->interface = strdup(interface); + if (!c->interface) { + r = -ENOMEM; + goto fail; + } - case BUS_UNSET: - case BUS_CLOSED: - return -ENOTCONN; + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - case BUS_OPENING: - r = bus_socket_process_opening(bus); - if (r < 0) - return r; - if (ret) - *ret = NULL; - return r; + switch (v->type) { - case BUS_AUTHENTICATING: + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member *m; - r = bus_socket_process_authenticating(bus); - if (r < 0) - return r; - if (ret) - *ret = NULL; - return r; + 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; + } - case BUS_RUNNING: - case BUS_HELLO: + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } - bus->processing = true; - r = process_running(bus, ret); - bus->processing = false; + m->parent = c; + m->path = n->path; + m->interface = c->interface; + m->member = v->method.member; + m->vtable = v; - return r; - } + r = hashmap_put(bus->vtable_methods, m, m); + if (r < 0) { + free(m); + goto fail; + } - assert_not_reached("Unknown state"); -} + break; + } -static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { - struct pollfd p[2] = {}; - int r, e, n; - struct timespec ts; - usec_t until, m; + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { + struct vtable_member *m; - assert(bus); + 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; + } - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - e = sd_bus_get_events(bus); - if (e < 0) - return e; + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } - if (need_more) - e |= POLLIN; + m->parent = c; + m->path = n->path; + m->interface = c->interface; + m->member = v->property.member; + m->vtable = v; - r = sd_bus_get_timeout(bus, &until); - if (r < 0) - return r; - if (r == 0) - m = (uint64_t) -1; - else { - usec_t nw; - nw = now(CLOCK_MONOTONIC); - m = until > nw ? until - nw : 0; - } + r = hashmap_put(bus->vtable_properties, m, m); + if (r < 0) { + free(m); + goto fail; + } - if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) - m = timeout_usec; + break; + } - p[0].fd = bus->input_fd; - if (bus->output_fd == bus->input_fd) { - p[0].events = e; - n = 1; - } else { - p[0].events = e & POLLIN; - p[1].fd = bus->output_fd; - p[1].events = e & POLLOUT; - n = 2; + 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; + } } - r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); - if (r < 0) - return -errno; + LIST_PREPEND(struct node_vtable, vtables, n->vtables, c); + return 0; - return r > 0 ? 1 : 0; +fail: + if (c) + free_node_vtable(bus, c); + + bus_node_gc(bus, n); + return 0; } -int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { +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 (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; + if (!object_path_is_valid(path)) + return -EINVAL; + if (!interface_name_is_valid(interface)) + return -EINVAL; if (bus_pid_changed(bus)) return -ECHILD; - if (bus->rqueue_size > 0) + n = hashmap_get(bus->nodes, path); + if (!n) return 0; - return bus_poll(bus, false, timeout_usec); -} + LIST_FOREACH(vtables, c, n->vtables) + if (streq(c->interface, interface) && c->is_fallback == fallback) + break; -int sd_bus_flush(sd_bus *bus) { - int r; + if (!c) + return 0; - if (!bus) - return -EINVAL; - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - if (bus_pid_changed(bus)) - return -ECHILD; + LIST_REMOVE(struct node_vtable, vtables, n->vtables, c); - r = bus_ensure_running(bus); - if (r < 0) - return r; + free_node_vtable(bus, c); + return 1; +} - if (bus->wqueue_size <= 0) - return 0; +int sd_bus_add_object_vtable( + sd_bus *bus, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + void *userdata) { - for (;;) { - r = dispatch_wqueue(bus); - if (r < 0) - return r; + return add_object_vtable_internal(bus, path, interface, vtable, false, NULL, userdata); +} - if (bus->wqueue_size <= 0) - return 0; +int sd_bus_remove_object_vtable( + sd_bus *bus, + const char *path, + const char *interface) { - r = bus_poll(bus, false, (uint64_t) -1); - if (r < 0) - return r; - } + return remove_object_vtable_internal(bus, path, interface, false); } -int sd_bus_add_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata) { - struct filter_callback *f; +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) { - if (!bus) - return -EINVAL; - if (!callback) - return -EINVAL; - if (bus_pid_changed(bus)) - return -ECHILD; + return add_object_vtable_internal(bus, path, interface, vtable, true, find, userdata); +} - f = new0(struct filter_callback, 1); - if (!f) - return -ENOMEM; - f->callback = callback; - f->userdata = userdata; +int sd_bus_remove_fallback_vtable( + sd_bus *bus, + const char *path, + const char *interface) { - bus->filter_callbacks_modified = true; - LIST_PREPEND(struct filter_callback, callbacks, bus->filter_callbacks, f); - return 0; + return remove_object_vtable_internal(bus, path, interface, true); } -int sd_bus_remove_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata) { - struct filter_callback *f; +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; - LIST_FOREACH(callbacks, f, bus->filter_callbacks) { - if (f->callback == callback && f->userdata == userdata) { - bus->filter_callbacks_modified = true; - LIST_REMOVE(struct filter_callback, callbacks, bus->filter_callbacks, f); - free(f); - return 1; - } + 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; } -static int bus_add_object( +int sd_bus_remove_node_enumerator( sd_bus *bus, - bool fallback, const char *path, - sd_bus_message_handler_t callback, + sd_bus_node_enumerator_t callback, void *userdata) { - struct object_callback *c; - int r; + struct node_enumerator *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; - r = hashmap_ensure_allocated(&bus->object_callbacks, string_hash_func, string_compare_func); + 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; - c = new0(struct object_callback, 1); - if (!c) - return -ENOMEM; + key.path = prefix; + key.interface = interface; - c->path = strdup(path); - if (!c->path) { - free(c); - return -ENOMEM; - } + STRV_FOREACH(property, names) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + struct vtable_member *v; - c->callback = callback; - c->userdata = userdata; - c->is_fallback = fallback; + key.member = *property; + v = hashmap_get(bus->vtable_properties, &key); + if (!v) + return -ENOENT; - bus->object_callbacks_modified = true; - r = hashmap_put(bus->object_callbacks, c->path, c); - if (r < 0) { - free(c->path); - free(c); - return r; - } + assert(c == v->parent); - return 0; -} + 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; + } -static int bus_remove_object( - sd_bus *bus, - bool fallback, - const char *path, - sd_bus_message_handler_t callback, - void *userdata) { + r = sd_bus_message_open_container(m, 'e', "sv"); + if (r < 0) + return r; - struct object_callback *c; + r = sd_bus_message_append(m, "s", *n); + if (r < 0) + return r; - if (!bus) - return -EINVAL; - if (!path) - return -EINVAL; - if (!callback) - return -EINVAL; - if (bus_pid_changed(bus)) - return -ECHILD; + r = sd_bus_message_open_container(m, 'v', v->vtable->property.signature); + if (r < 0) + return r; - c = hashmap_get(bus->object_callbacks, path); - if (!c) - return 0; + r = v->vtable->property.get(bus, m->path, interface, *property, m, &error, u); + if (r < 0) + return r; - if (c->callback != callback || c->userdata != userdata || c->is_fallback != fallback) - return 0; + if (sd_bus_error_is_set(&error)) + return bus_error_to_errno(&error); - bus->object_callbacks_modified = true; - assert_se(c == hashmap_remove(bus->object_callbacks, c->path)); + r = sd_bus_message_close_container(m); + if (r < 0) + return r; - free(c->path); - free(c); + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } - return 1; -} + r = sd_bus_message_close_container(m); + if (r < 0) + return r; -int sd_bus_add_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata) { - return bus_add_object(bus, false, path, callback, userdata); -} + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; -int sd_bus_remove_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata) { - return bus_remove_object(bus, false, path, callback, userdata); -} + if (has_invalidating) { + STRV_FOREACH(property, names) { + struct vtable_member *v; -int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata) { - return bus_add_object(bus, true, prefix, callback, userdata); -} + key.member = *property; + assert_se(v = hashmap_get(bus->vtable_properties, &key)); + assert(c == v->parent); -int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata) { - return bus_remove_object(bus, true, prefix, callback, userdata); + 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_add_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { - struct bus_match_component *components = NULL; - unsigned n_components = 0; - uint64_t cookie = 0; - int r = 0; +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 (!match) + if (!object_path_is_valid(path)) + return -EINVAL; + if (!interface_name_is_valid(interface)) return -EINVAL; - if (bus_pid_changed(bus)) - return -ECHILD; - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - goto finish; + r = emit_properties_changed_on_interface(bus, path, path, interface, false, names); + if (r != 0) + return r; - if (bus->bus_client) { - cookie = ++bus->match_cookie; + pl = strlen(path); + if (pl > 1 ) { + char p[pl+1]; - r = bus_add_match_internal(bus, match, components, n_components, cookie); - if (r < 0) - goto finish; - } + strcpy(p, path); + for (;;) { + char *e; - bus->match_callbacks_modified = true; - r = bus_match_add(&bus->match_callbacks, components, n_components, callback, userdata, cookie, NULL); - if (r < 0) { - if (bus->bus_client) - bus_remove_match_internal(bus, match, cookie); + 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; + } } -finish: - bus_match_parse_free(components, n_components); - return r; + return -ENOENT; } -int sd_bus_remove_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata) { - struct bus_match_component *components = NULL; - unsigned n_components = 0; - int r = 0, q = 0; - uint64_t cookie = 0; - - if (!bus) - return -EINVAL; - if (!match) - return -EINVAL; - if (bus_pid_changed(bus)) - return -ECHILD; +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; - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - return r; + va_start(ap, name); + names = strv_new_ap(name, ap); + va_end(ap); - bus->match_callbacks_modified = true; - r = bus_match_remove(&bus->match_callbacks, components, n_components, callback, userdata, &cookie); + if (!names) + return -ENOMEM; - if (bus->bus_client) - q = bus_remove_match_internal(bus, match, cookie); + return sd_bus_emit_properties_changed_strv(bus, path, interface, names); +} - bus_match_parse_free(components, n_components); +int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interfaces, ...) { + return -ENOSYS; +} - return r < 0 ? r : q; +int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interfaces, ...) { + return -ENOSYS; } -int sd_bus_emit_signal( +int sd_bus_get_property( sd_bus *bus, + const char *destination, const char *path, const char *interface, const char *member, - const char *types, ...) { + sd_bus_error *error, + sd_bus_message **reply, + const char *type) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - va_list ap; + sd_bus_message *rep = NULL; int r; - if (!bus) + 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; - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - if (bus_pid_changed(bus)) - return -ECHILD; - r = sd_bus_message_new_signal(bus, path, interface, member, &m); + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &rep, "ss", strempty(interface), member); if (r < 0) return r; - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) + r = sd_bus_message_enter_container(rep, 'v', type); + if (r < 0) { + sd_bus_message_unref(rep); return r; + } - return sd_bus_send(bus, m, NULL); + *reply = rep; + return 0; } -int sd_bus_call_method( +int sd_bus_set_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 *types, ...) { + const char *type, ...) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; va_list ap; int r; - if (!bus) - + 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 (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - if (bus_pid_changed(bus)) - return -ECHILD; - r = sd_bus_message_new_method_call(bus, destination, path, interface, member, &m); + r = sd_bus_message_new_method_call(bus, destination, path, "org.freedesktop.DBus.Properties", "Set", &m); if (r < 0) return r; - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); + 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; - return sd_bus_send_with_reply_and_block(bus, m, 0, error, reply); -} + r = sd_bus_message_close_container(m); + if (r < 0) + return r; -int sd_bus_reply_method_return( - sd_bus *bus, - sd_bus_message *call, - const char *types, ...) { + return sd_bus_send_with_reply_and_block(bus, m, 0, error, NULL); +} - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - va_list ap; - int r; +int sd_bus_add_object_manager(sd_bus *bus, const char *path) { + struct node *n; if (!bus) return -EINVAL; - if (!call) - return -EINVAL; - if (!call->sealed) - return -EPERM; - if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) + if (!object_path_is_valid(path)) return -EINVAL; - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; if (bus_pid_changed(bus)) return -ECHILD; - if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - r = sd_bus_message_new_method_return(bus, call, &m); - if (r < 0) - return r; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; - return sd_bus_send(bus, m, NULL); + n->object_manager = true; + return 0; } -int sd_bus_reply_method_error( - sd_bus *bus, - sd_bus_message *call, - const sd_bus_error *e) { - - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; +int sd_bus_remove_object_manager(sd_bus *bus, const char *path) { + struct node *n; if (!bus) return -EINVAL; - if (!call) - return -EINVAL; - if (!call->sealed) - return -EPERM; - if (call->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL) - return -EINVAL; - if (!sd_bus_error_is_set(e)) + if (!object_path_is_valid(path)) return -EINVAL; - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; if (bus_pid_changed(bus)) return -ECHILD; - if (call->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED) + n = hashmap_get(bus->nodes, path); + if (!n) return 0; - r = sd_bus_message_new_method_error(bus, call, e, &m); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -bool bus_pid_changed(sd_bus *bus) { - assert(bus); - - /* We don't support people creating a bus connection and - * keeping it around over a fork(). Let's complain. */ + if (!n->object_manager) + return 0; - return bus->original_pid != getpid(); + 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 000000000..ea26cdd10 --- /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 . +***/ + +#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 000000000..a95789fe9 --- /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 . +***/ + +#include +#include +#include +#include +#include + +#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, "<<>>")); + + 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, "<<>>")); + + 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 cab0daa77..c4304697f 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; } diff --git a/src/systemd/sd-bus-protocol.h b/src/systemd/sd-bus-protocol.h index a26d27c31..568005b3b 100644 --- a/src/systemd/sd-bus-protocol.h +++ b/src/systemd/sd-bus-protocol.h @@ -108,6 +108,21 @@ enum { "\n" +#define SD_BUS_INTROSPECT_INTERFACE_PEER \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define SD_BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + #define SD_BUS_INTROSPECT_INTERFACE_PROPERTIES \ " \n" \ " \n" \ @@ -131,21 +146,21 @@ enum { " \n" \ " \n" -#define SD_BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ - " \n" \ - " \n" \ - " \n" \ +#define SD_BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \ + " \n" \ + " \n" \ + " \n" \ " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ " \n" -#define SD_BUS_INTROSPECT_INTERFACE_PEER \ - "\n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - "\n" - #ifdef __cplusplus } #endif diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h new file mode 100644 index 000000000..a1f078e54 --- /dev/null +++ b/src/systemd/sd-bus-vtable.h @@ -0,0 +1,127 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosdbusvtablehfoo +#define foosdbusvtablehfoo + +/*** + 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 . +***/ + +typedef struct sd_bus_vtable sd_bus_vtable; + +#include "sd-bus.h" + +enum { + _SD_BUS_VTABLE_START = '<', + _SD_BUS_VTABLE_END = '>', + _SD_BUS_VTABLE_METHOD = 'M', + _SD_BUS_VTABLE_SIGNAL = 'S', + _SD_BUS_VTABLE_PROPERTY = 'P', + _SD_BUS_VTABLE_WRITABLE_PROPERTY = 'W', + _SD_BUS_VTABLE_CHILDREN = 'C' +}; + +enum { + SD_BUS_VTABLE_DEPRECATED = 1, + SD_BUS_VTABLE_METHOD_NO_REPLY = 2, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE = 4, + SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY = 8, +}; + +struct sd_bus_vtable { + /* Please do not initialize this structure directly, use the + * macros below instead */ + + int type; + int flags; + union { + struct { + size_t element_size; + } start; + struct { + const char *member; + const char *signature; + const char *result; + sd_bus_message_handler_t handler; + } 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; + }; +}; + +#define SD_BUS_VTABLE_START(_flags) \ + { \ + .type = _SD_BUS_VTABLE_START, \ + .flags = _flags, \ + .start.element_size = sizeof(sd_bus_vtable), \ + } + +#define SD_BUS_METHOD(_member, _signature, _result, _flags, _handler) \ + { \ + .type = _SD_BUS_VTABLE_METHOD, \ + .flags = _flags, \ + .method.member = _member, \ + .method.signature = _signature, \ + .method.result = _result, \ + .method.handler = _handler, \ + } + +#define SD_BUS_SIGNAL(_member, _signature, _flags) \ + { \ + .type = _SD_BUS_VTABLE_SIGNAL, \ + .flags = _flags, \ + .signal.member = _member, \ + .signal.signature = _signature, \ + } + +#define SD_BUS_PROPERTY(_member, _signature, _get, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_PROPERTY, \ + .flags = _flags, \ + .property.member = _member, \ + .property.signature = _signature, \ + .property.get = _get, \ + .property.offset = _offset, \ + } + +#define SD_BUS_WRITABLE_PROPERTY(_member, _signature, _get, _set, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_WRITABLE_PROPERTY, \ + .flags = _flags, \ + .property.member = _member, \ + .property.signature = _signature, \ + .property.get = _get, \ + .property.set = _set, \ + .property.offset = _offset, \ + } + +#define SD_BUS_VTABLE_END \ + { \ + .type = _SD_BUS_VTABLE_END, \ + } + +#endif diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h index 878001ccb..101e75131 100644 --- a/src/systemd/sd-bus.h +++ b/src/systemd/sd-bus.h @@ -26,8 +26,6 @@ #include #include -#include "sd-bus-protocol.h" -#include "sd-memfd.h" #ifdef __cplusplus extern "C" { @@ -41,6 +39,8 @@ extern "C" { # endif #endif +/* Types */ + typedef struct sd_bus sd_bus; typedef struct sd_bus_message sd_bus_message; @@ -50,8 +50,21 @@ typedef struct { int need_free; } sd_bus_error; +/* Callbacks */ + typedef int (*sd_bus_message_handler_t)(sd_bus *bus, sd_bus_message *m, void *userdata); +typedef int (*sd_bus_property_get_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata); +typedef int (*sd_bus_property_set_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, sd_bus_error *error, void *userdata); + +typedef int (*sd_bus_object_find_t) (sd_bus *bus, const char *path, const char *interface, void **found, void *userdata); + +typedef int (*sd_bus_node_enumerator_t) (sd_bus *bus, const char *path, char ***nodes, void *userdata); + +#include "sd-bus-protocol.h" +#include "sd-bus-vtable.h" +#include "sd-memfd.h" + /* Connections */ int sd_bus_open_system(sd_bus **ret); @@ -98,21 +111,33 @@ int sd_bus_flush(sd_bus *bus); int sd_bus_add_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata); int sd_bus_remove_filter(sd_bus *bus, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_remove_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); + int sd_bus_add_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata); int sd_bus_remove_object(sd_bus *bus, const char *path, sd_bus_message_handler_t callback, void *userdata); int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata); int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_remove_match(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_object_vtable(sd_bus *bus, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); +int sd_bus_remove_object_vtable(sd_bus *bus, const char *path, const char *interface); -/* Message object */ +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); +int sd_bus_remove_fallback_vtable(sd_bus *bus, const char *path, const char *interface); +int sd_bus_add_node_enumerator(sd_bus *bus, const char *path, sd_bus_node_enumerator_t callback, void *userdata); +int sd_bus_remove_node_enumerator(sd_bus *bus, const char *path, sd_bus_node_enumerator_t callback, void *userdata); + +int sd_bus_add_object_manager(sd_bus *bus, const char *path); +int sd_bus_remove_object_manager(sd_bus *bus, const char *path); + +/* Message object */ int sd_bus_message_new_signal(sd_bus *bus, const char *path, const char *interface, const char *member, sd_bus_message **m); int sd_bus_message_new_method_call(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message **m); int sd_bus_message_new_method_return(sd_bus *bus, sd_bus_message *call, sd_bus_message **m); int sd_bus_message_new_method_error(sd_bus *bus, sd_bus_message *call, const sd_bus_error *e, sd_bus_message **m); +int sd_bus_message_new_method_errorf(sd_bus *bus, sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...); sd_bus_message* sd_bus_message_ref(sd_bus_message *m); sd_bus_message* sd_bus_message_unref(sd_bus_message *m); @@ -175,12 +200,26 @@ int sd_bus_message_exit_container(sd_bus_message *m); int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents); int sd_bus_message_rewind(sd_bus_message *m, int complete); +int sd_bus_message_get_signature(sd_bus_message *m, int complete, const char **signature); + /* Convenience calls */ -int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); int sd_bus_call_method(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 *types, ...); +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); +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, ...); int sd_bus_reply_method_return(sd_bus *bus, sd_bus_message *call, const char *types, ...); int sd_bus_reply_method_error(sd_bus *bus, sd_bus_message *call, const sd_bus_error *e); +int sd_bus_reply_method_errorf(sd_bus *bus, sd_bus_message *call, const char *name, const char *format, ...); + +int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); + +int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names); +int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...); + +int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces); /* MISSING */ +int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...); /* MISSING */ +int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces); /* MISSING */ +int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...); /* MISSING */ /* Bus management */ @@ -199,7 +238,8 @@ int sd_bus_get_owner_machine_id(sd_bus *bus, const char *name, sd_id128_t *machi #define SD_BUS_ERROR_MAKE(name, message) ((sd_bus_error) {(name), (message), 0}) void sd_bus_error_free(sd_bus_error *e); -int sd_bus_error_set(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_attr_(3, 0); +int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_attr_(3, 0); +int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message); void sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message); int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); int sd_bus_error_is_set(const sd_bus_error *e); -- 2.30.2