From 123c6a3c6ffc9903c0855e38445fc3b6588311ce Mon Sep 17 00:00:00 2001 From: Tanu Kaskinen Date: Fri, 19 Jun 2009 10:28:08 +0300 Subject: dbus-common: Implement infrastructure for registering D-Bus objects on all client connections and for receiving method calls from clients. --- src/pulsecore/dbus-common.c | 430 +++++++++++++++++++++++++++++++++++++++++ src/pulsecore/dbus-common.h | 34 ++++ src/pulsecore/dbus-objs/core.c | 120 ++++++++++++ src/pulsecore/dbus-objs/core.h | 39 ++++ 4 files changed, 623 insertions(+) create mode 100644 src/pulsecore/dbus-objs/core.c create mode 100644 src/pulsecore/dbus-objs/core.h (limited to 'src/pulsecore') diff --git a/src/pulsecore/dbus-common.c b/src/pulsecore/dbus-common.c index 05931e0a..350add82 100644 --- a/src/pulsecore/dbus-common.c +++ b/src/pulsecore/dbus-common.c @@ -25,10 +25,35 @@ #include +#include + #include +#include +#include +#include #include "dbus-common.h" +struct dbus_state { + pa_core *core; + pa_hashmap *objects; /* Object path -> struct object_entry */ + pa_idxset *connections; /* DBusConnections */ +}; + +struct object_entry { + char *path; + pa_hashmap *interfaces; /* Interface name -> struct interface_entry */ + char *introspection; +}; + +struct interface_entry { + char *name; + char **methods; + char *introspection_snippet; + DBusObjectPathMessageFunction receive; + void *userdata; +}; + char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) { char *address = NULL; char *runtime_path = NULL; @@ -71,3 +96,408 @@ char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) { return address; } + +static void update_introspection(struct object_entry *oe) { + pa_strbuf *buf; + void *state = NULL; + struct interface_entry *iface_entry = NULL; + + pa_assert(oe); + + buf = pa_strbuf_new(); + pa_strbuf_puts(buf, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); + pa_strbuf_puts(buf, ""); + + while ((iface_entry = pa_hashmap_iterate(oe->interfaces, &state, NULL))) + pa_strbuf_puts(buf, iface_entry->introspection_snippet); + + pa_strbuf_puts(buf, " " + " " + " " + " " + " "); + + pa_strbuf_puts(buf, ""); + + pa_xfree(oe->introspection); + oe->introspection = pa_strbuf_tostring_free(buf); +} + +static struct interface_entry *find_interface(struct object_entry *obj_entry, DBusMessage *msg) { + const char *interface; + struct interface_entry *iface_entry; + void *state = NULL; + + pa_assert(obj_entry); + pa_assert(msg); + + if ((interface = dbus_message_get_interface(msg))) + return pa_hashmap_get(obj_entry->interfaces, interface); + + /* NULL interface, we'll have to search for an interface that contains the + * method. */ + + while ((iface_entry = pa_hashmap_iterate(obj_entry->interfaces, &state, NULL))) { + char *method; + char **pos = iface_entry->methods; + + while ((method = *pos++)) { + if (!strcmp(dbus_message_get_member(msg), method)) + return iface_entry; + } + } + + return NULL; +} + +static DBusHandlerResult handle_message_cb(DBusConnection *connection, DBusMessage *message, void *user_data) { + struct dbus_state *dbus_state = user_data; + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + DBusMessage *reply = NULL; + + pa_assert(connection); + pa_assert(message); + pa_assert(dbus_state); + pa_assert(dbus_state->objects); + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + pa_assert_se((obj_entry = pa_hashmap_get(dbus_state->objects, dbus_message_get_path(message)))); + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { + if (!(reply = dbus_message_new_method_return(message))) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &obj_entry->introspection, DBUS_TYPE_INVALID)) + goto fail; + + if (!dbus_connection_send(connection, reply, NULL)) + goto oom; + + pa_log_debug("%s.%s handled.", obj_entry->path, "Introspect"); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (!(iface_entry = find_interface(obj_entry, message))) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + return iface_entry->receive(connection, message, iface_entry->userdata); + +fail: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusObjectPathVTable vtable = { + .unregister_function = NULL, + .message_function = handle_message_cb, + .dbus_internal_pad1 = NULL, + .dbus_internal_pad2 = NULL, + .dbus_internal_pad3 = NULL, + .dbus_internal_pad4 = NULL +}; + +static void register_object(struct dbus_state *dbus_state, struct object_entry *obj_entry) { + DBusConnection *conn; + void *state = NULL; + + pa_assert(dbus_state); + pa_assert(obj_entry); + + if (!dbus_state->connections) + return; + + while ((conn = pa_idxset_iterate(dbus_state->connections, &state, NULL))) { + if (!dbus_connection_register_object_path(conn, obj_entry->path, &vtable, dbus_state)) + pa_log_debug("dbus_connection_register_object_path() failed."); + } +} + +static char **copy_methods(const char * const *methods) { + unsigned n = 0; + char **copy; + unsigned i; + + while (methods[n++]) + ; + + copy = pa_xnew0(char *, n); + + for (i = 0; i < n - 1; ++i) + copy[i] = pa_xstrdup(methods[i]); + + return copy; +} + +int pa_dbus_add_interface(pa_core *c, const char* path, const char* interface, const char * const *methods, const char* introspection_snippet, DBusObjectPathMessageFunction receive_cb, void *userdata) { + struct dbus_state *dbus_state; + pa_hashmap *objects; + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + pa_bool_t state_created = FALSE; + pa_bool_t object_map_created = FALSE; + pa_bool_t obj_entry_created = FALSE; + + pa_assert(c); + pa_assert(path); + pa_assert(introspection_snippet); + pa_assert(receive_cb); + + if (!(dbus_state = pa_hashmap_get(c->shared, "dbus-state"))) { + dbus_state = pa_xnew0(struct dbus_state, 1); + dbus_state->core = c; + pa_hashmap_put(c->shared, "dbus-state", dbus_state); + state_created = TRUE; + } + + if (!(objects = dbus_state->objects)) { + objects = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + dbus_state->objects = objects; + object_map_created = TRUE; + } + + if (!(obj_entry = pa_hashmap_get(objects, path))) { + obj_entry = pa_xnew(struct object_entry, 1); + obj_entry->path = pa_xstrdup(path); + obj_entry->interfaces = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + obj_entry->introspection = NULL; + pa_hashmap_put(objects, path, obj_entry); + obj_entry_created = TRUE; + } + + if (pa_hashmap_get(obj_entry->interfaces, interface) != NULL) + goto fail; /* The interface was already registered. */ + + iface_entry = pa_xnew(struct interface_entry, 1); + iface_entry->name = pa_xstrdup(interface); + iface_entry->methods = copy_methods(methods); + iface_entry->introspection_snippet = pa_xstrdup(introspection_snippet); + iface_entry->receive = receive_cb; + iface_entry->userdata = userdata; + pa_hashmap_put(obj_entry->interfaces, iface_entry->name, iface_entry); + + update_introspection(obj_entry); + + if (obj_entry_created) + register_object(dbus_state, obj_entry); + + return 0; + +fail: + if (obj_entry_created) { + pa_hashmap_remove(objects, path); + pa_xfree(obj_entry); + } + + if (object_map_created) { + dbus_state->objects = NULL; + pa_hashmap_free(objects, NULL, NULL); + } + + if (state_created) { + pa_hashmap_remove(c->shared, "dbus-state"); + pa_xfree(dbus_state); + } + + return -1; +} + +static void unregister_object(struct dbus_state *dbus_state, struct object_entry *obj_entry) { + DBusConnection *conn; + void *state = NULL; + + pa_assert(dbus_state); + pa_assert(obj_entry); + + if (!dbus_state->connections) + return; + + while ((conn = pa_idxset_iterate(dbus_state->connections, &state, NULL))) { + if (!dbus_connection_unregister_object_path(conn, obj_entry->path)) + pa_log_debug("dbus_connection_unregister_object_path() failed."); + } +} + +static void free_methods(char **methods) { + char **pos = methods; + + while (*pos++) + pa_xfree(*pos); + + pa_xfree(methods); +} + +int pa_dbus_remove_interface(pa_core *c, const char* path, const char* interface) { + struct dbus_state *dbus_state; + pa_hashmap *objects; + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + + pa_assert(c); + pa_assert(path); + pa_assert(interface); + + if (!(dbus_state = pa_hashmap_get(c->shared, "dbus-state"))) + return -1; + + if (!(objects = dbus_state->objects)) + return -1; + + if (!(obj_entry = pa_hashmap_get(objects, path))) + return -1; + + if (!(iface_entry = pa_hashmap_remove(obj_entry->interfaces, interface))) + return -1; + + update_introspection(obj_entry); + + pa_xfree(iface_entry->name); + free_methods(iface_entry->methods); + pa_xfree(iface_entry->introspection_snippet); + pa_xfree(iface_entry); + + if (pa_hashmap_isempty(obj_entry->interfaces)) { + unregister_object(dbus_state, obj_entry); + + pa_hashmap_remove(objects, path); + pa_xfree(obj_entry->path); + pa_hashmap_free(obj_entry->interfaces, NULL, NULL); + pa_xfree(obj_entry->introspection); + pa_xfree(obj_entry); + } + + if (pa_hashmap_isempty(objects)) { + dbus_state->objects = NULL; + pa_hashmap_free(objects, NULL, NULL); + } + + if (!dbus_state->objects && !dbus_state->connections) { + pa_hashmap_remove(c->shared, "dbus-state"); + pa_xfree(dbus_state); + } + + return 0; +} + +static void register_all_objects(struct dbus_state *dbus_state, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(dbus_state); + pa_assert(conn); + + if (!dbus_state->objects) + return; + + while ((obj_entry = pa_hashmap_iterate(dbus_state->objects, &state, NULL))) { + if (!dbus_connection_register_object_path(conn, obj_entry->path, &vtable, dbus_state)) + pa_log_debug("dbus_connection_register_object_path() failed."); + } +} + +int pa_dbus_register_connection(pa_core *c, DBusConnection *conn) { + struct dbus_state *dbus_state; + pa_idxset *connections; + pa_bool_t state_created = FALSE; + pa_bool_t connection_set_created = FALSE; + + pa_assert(c); + pa_assert(conn); + + if (!(dbus_state = pa_hashmap_get(c->shared, "dbus-state"))) { + dbus_state = pa_xnew0(struct dbus_state, 1); + dbus_state->core = c; + pa_hashmap_put(c->shared, "dbus-state", dbus_state); + state_created = TRUE; + } + + if (!(connections = dbus_state->connections)) { + connections = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + dbus_state->connections = connections; + connection_set_created = TRUE; + } + + if (pa_idxset_get_by_data(connections, conn, NULL)) + goto fail; /* The connection was already registered. */ + + register_all_objects(dbus_state, conn); + + pa_idxset_put(connections, dbus_connection_ref(conn), NULL); + + return 0; + +fail: + if (connection_set_created) { + dbus_state->connections = NULL; + pa_idxset_free(connections, NULL, NULL); + } + + if (state_created) { + pa_hashmap_remove(c->shared, "dbus-state"); + pa_xfree(dbus_state); + } + + return -1; +} + +static void unregister_all_objects(struct dbus_state *dbus_state, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(dbus_state); + pa_assert(conn); + + if (!dbus_state->objects) + return; + + while ((obj_entry = pa_hashmap_iterate(dbus_state->objects, &state, NULL))) { + if (!dbus_connection_unregister_object_path(conn, obj_entry->path)) + pa_log_debug("dus_connection_unregister_object_path() failed."); + } +} + +int pa_dbus_unregister_connection(pa_core *c, DBusConnection *conn) { + struct dbus_state *dbus_state; + pa_idxset *connections; + + pa_assert(c); + pa_assert(conn); + + if (!(dbus_state = pa_hashmap_get(c->shared, "dbus-state"))) + return -1; + + if (!(connections = dbus_state->connections)) + return -1; + + if (!pa_idxset_remove_by_data(connections, conn, NULL)) + return -1; + + unregister_all_objects(dbus_state, conn); + + dbus_connection_unref(conn); + + if (pa_idxset_isempty(connections)) { + dbus_state->connections = NULL; + pa_idxset_free(connections, NULL, NULL); + } + + if (!dbus_state->objects && !dbus_state->connections) { + pa_hashmap_remove(c->shared, "dbus-state"); + pa_xfree(dbus_state); + } + + return 0; +} diff --git a/src/pulsecore/dbus-common.h b/src/pulsecore/dbus-common.h index 26bd05d4..23c7c221 100644 --- a/src/pulsecore/dbus-common.h +++ b/src/pulsecore/dbus-common.h @@ -22,6 +22,8 @@ USA. ***/ +#include + #include #include @@ -30,10 +32,42 @@ #define PA_DBUS_SYSTEM_SOCKET_PATH PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_DBUS_SOCKET_NAME +/* NOTE: These functions may only be called from the main thread. */ + /* Returns the default address of the server type in the escaped form. For * PA_SERVER_TYPE_NONE an empty string is returned. The caller frees the * string. This function may fail in some rare cases, in which case NULL is * returned. */ char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type); +/* Registers the given interface to the given object path. This is additive: it + * doesn't matter whether or not the object has already been registered; if it + * is, then its interface set is just extended. + * + * Introspection requests are handled automatically. For that to work, the + * caller gives an XML snippet containing the interface introspection element. + * All interface snippets are automatically combined to provide the final + * introspection string. + * + * The introspection snippet contains the interface name and the methods, but + * since this function doesn't do XML parsing, the interface name and the set + * of method names have to be supplied separately. If the interface doesn't + * contain any methods, NULL may be given as the methods parameter, otherwise + * the methods parameter must be a NULL-terminated array of strings. + * + * Fails and returns a negative number if the object already has the interface + * registered. */ +int pa_dbus_add_interface(pa_core *c, const char* path, const char* interface, const char * const *methods, const char* introspection_snippet, DBusObjectPathMessageFunction receive_cb, void *userdata); + +/* Returns a negative number if the given object doesn't have the given + * interface registered. */ +int pa_dbus_remove_interface(pa_core *c, const char* path, const char* interface); + +/* Fails and returns a negative number if the connection is already + * registered. */ +int pa_dbus_register_connection(pa_core *c, DBusConnection *conn); + +/* Returns a negative number if the connection wasn't registered. */ +int pa_dbus_unregister_connection(pa_core *c, DBusConnection *conn); + #endif diff --git a/src/pulsecore/dbus-objs/core.c b/src/pulsecore/dbus-objs/core.c new file mode 100644 index 00000000..f59c478a --- /dev/null +++ b/src/pulsecore/dbus-objs/core.c @@ -0,0 +1,120 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio 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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include +#include + +#include "core.h" + +#define OBJECT_NAME "/org/pulseaudio/core" +#define INTERFACE_NAME "org.pulseaudio.Core" + +struct pa_dbusobj_core { + pa_core *core; +}; + +static const char *introspection_snippet = + " " + " " + " " + " " + " "; + +static const char *methods[] = { + "Test", + NULL +}; + +static DBusHandlerResult handle_test(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_core *c) { + DBusMessage *reply = NULL; + const char *reply_message = "Hello!"; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!(reply = dbus_message_new_method_return(msg))) + goto oom; + + if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &reply_message, DBUS_TYPE_INVALID)) + goto fail; + + if (!dbus_connection_send(conn, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + +fail: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static DBusHandlerResult receive_cb(DBusConnection *connection, DBusMessage *message, void *user_data) { + pa_dbusobj_core *c = user_data; + + pa_assert(connection); + pa_assert(message); + pa_assert(c); + + if (dbus_message_is_method_call(message, INTERFACE_NAME, "Test")) + return handle_test(connection, message, c); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +pa_dbusobj_core *pa_dbusobj_core_new(pa_core *core) { + pa_dbusobj_core *c; + + pa_assert(core); + + c = pa_xnew(pa_dbusobj_core, 1); + c->core = core; + + pa_dbus_add_interface(core, OBJECT_NAME, INTERFACE_NAME, methods, introspection_snippet, receive_cb, c); + + return c; +} + +void pa_dbusobj_core_free(pa_dbusobj_core *c) { + pa_assert(c); + + pa_dbus_remove_interface(c->core, OBJECT_NAME, INTERFACE_NAME); + + pa_xfree(c); +} diff --git a/src/pulsecore/dbus-objs/core.h b/src/pulsecore/dbus-objs/core.h new file mode 100644 index 00000000..8e59cc3d --- /dev/null +++ b/src/pulsecore/dbus-objs/core.h @@ -0,0 +1,39 @@ +#ifndef foodbusobjscorehfoo +#define foodbusobjscorehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + PulseAudio 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. + + PulseAudio 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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus object at path /org/pulseaudio/core. + * The implemented interface is org.pulseaudio.Core. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Core interface + * documentation. + */ + +#include + +typedef struct pa_dbusobj_core pa_dbusobj_core; + +pa_dbusobj_core *pa_dbusobj_core_new(pa_core *core); +void pa_dbusobj_core_free(pa_dbusobj_core *c); + +#endif -- cgit