diff options
Diffstat (limited to 'src/pulsecore')
-rw-r--r-- | src/pulsecore/core-util.c | 22 | ||||
-rw-r--r-- | src/pulsecore/core-util.h | 7 | ||||
-rw-r--r-- | src/pulsecore/core.h | 9 | ||||
-rw-r--r-- | src/pulsecore/dbus-util.c | 495 | ||||
-rw-r--r-- | src/pulsecore/dbus-util.h | 35 | ||||
-rw-r--r-- | src/pulsecore/protocol-dbus.c | 988 | ||||
-rw-r--r-- | src/pulsecore/protocol-dbus.h | 197 |
7 files changed, 1753 insertions, 0 deletions
diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 6494244e..e83563f9 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -2720,6 +2720,28 @@ char *pa_replace(const char*s, const char*a, const char *b) { return pa_strbuf_tostring_free(sb); } +char *pa_escape(const char *p, const char *chars) { + const char *s; + const char *c; + pa_strbuf *buf = pa_strbuf_new(); + + for (s = p; *s; ++s) { + if (*s == '\\') + pa_strbuf_putc(buf, '\\'); + else if (chars) { + for (c = chars; *c; ++c) { + if (*s == *c) { + pa_strbuf_putc(buf, '\\'); + break; + } + } + } + pa_strbuf_putc(buf, *s); + } + + return pa_strbuf_tostring_free(buf); +} + char *pa_unescape(char *p) { char *s, *d; pa_bool_t escaped = FALSE; diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index 3d3aec71..3db55106 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -222,6 +222,13 @@ unsigned pa_ncpus(void); char *pa_replace(const char*s, const char*a, const char *b); +/* Escapes p by inserting backslashes in front of backslashes. chars is a + * regular (ie. NULL-terminated) string containing additional characters that + * should be escaped. chars can be NULL. The caller has to free the returned + * string. */ +char *pa_escape(const char *p, const char *chars); + +/* Does regular backslash unescaping. Returns the argument p. */ char *pa_unescape(char *p); char *pa_realpath(const char *path); diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index f6ec7122..fc4eabfa 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -52,6 +52,13 @@ typedef enum pa_suspend_cause { #include <pulsecore/sink-input.h> #include <pulsecore/msgobject.h> +typedef enum pa_server_type { + PA_SERVER_TYPE_UNSET, + PA_SERVER_TYPE_USER, + PA_SERVER_TYPE_SYSTEM, + PA_SERVER_TYPE_NONE +} pa_server_type_t; + typedef enum pa_core_state { PA_CORE_STARTUP, PA_CORE_RUNNING, @@ -161,6 +168,8 @@ struct pa_core { pa_resample_method_t resample_method; int realtime_priority; + pa_server_type_t server_type; + /* hooks */ pa_hook hooks[PA_CORE_HOOK_MAX]; }; diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c index 4e6148f0..b45e6a6c 100644 --- a/src/pulsecore/dbus-util.c +++ b/src/pulsecore/dbus-util.c @@ -28,6 +28,7 @@ #include <pulse/rtclock.h> #include <pulse/timeval.h> +#include <pulse/utf8.h> #include <pulse/xmalloc.h> #include <pulsecore/core-rtclock.h> @@ -290,6 +291,28 @@ pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, pa_bool return pconn; } +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing(pa_mainloop_api *m, pa_bool_t use_rtclock, DBusConnection *conn) { + pa_dbus_wrap_connection *pconn; + + pa_assert(m); + pa_assert(conn); + + pconn = pa_xnew(pa_dbus_wrap_connection, 1); + pconn->mainloop = m; + pconn->connection = dbus_connection_ref(conn); + pconn->use_rtclock = use_rtclock; + + dbus_connection_set_exit_on_disconnect(conn, FALSE); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL); + dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); + + pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn); + + return pconn; +} + void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) { pa_assert(c); @@ -422,3 +445,475 @@ void pa_dbus_free_pending_list(pa_dbus_pending **p) { pa_dbus_pending_free(i); } } + +void pa_dbus_send_error(DBusConnection *c, DBusMessage *in_reply_to, const char *name, const char *format, ...) { + va_list ap; + char *message; + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(name); + pa_assert(format); + + va_start(ap, format); + message = pa_vsprintf_malloc(format, ap); + va_end(ap); + pa_assert_se((reply = dbus_message_new_error(in_reply_to, name, message))); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(message); +} + +void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to) { + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) { + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + pa_assert_se(dbus_message_append_args(reply, type, data, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +static const char *signature_from_basic_type(int type) { + switch (type) { + case DBUS_TYPE_BOOLEAN: return DBUS_TYPE_BOOLEAN_AS_STRING; + case DBUS_TYPE_BYTE: return DBUS_TYPE_BYTE_AS_STRING; + case DBUS_TYPE_INT16: return DBUS_TYPE_INT16_AS_STRING; + case DBUS_TYPE_UINT16: return DBUS_TYPE_UINT16_AS_STRING; + case DBUS_TYPE_INT32: return DBUS_TYPE_INT32_AS_STRING; + case DBUS_TYPE_UINT32: return DBUS_TYPE_UINT32_AS_STRING; + case DBUS_TYPE_INT64: return DBUS_TYPE_INT64_AS_STRING; + case DBUS_TYPE_UINT64: return DBUS_TYPE_UINT64_AS_STRING; + case DBUS_TYPE_DOUBLE: return DBUS_TYPE_DOUBLE_AS_STRING; + case DBUS_TYPE_STRING: return DBUS_TYPE_STRING_AS_STRING; + case DBUS_TYPE_OBJECT_PATH: return DBUS_TYPE_OBJECT_PATH_AS_STRING; + case DBUS_TYPE_SIGNATURE: return DBUS_TYPE_SIGNATURE_AS_STRING; + default: pa_assert_not_reached(); + } +} + +void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_VARIANT, signature_from_basic_type(type), &variant_iter)); + pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data)); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &variant_iter)); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +/* Note: returns sizeof(char*) for strings, object paths and signatures. */ +static unsigned basic_type_size(int type) { + switch (type) { + case DBUS_TYPE_BOOLEAN: return sizeof(dbus_bool_t); + case DBUS_TYPE_BYTE: return 1; + case DBUS_TYPE_INT16: return sizeof(dbus_int16_t); + case DBUS_TYPE_UINT16: return sizeof(dbus_uint16_t); + case DBUS_TYPE_INT32: return sizeof(dbus_int32_t); + case DBUS_TYPE_UINT32: return sizeof(dbus_uint32_t); + case DBUS_TYPE_INT64: return sizeof(dbus_int64_t); + case DBUS_TYPE_UINT64: return sizeof(dbus_uint64_t); + case DBUS_TYPE_DOUBLE: return sizeof(double); + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: return sizeof(char*); + default: pa_assert_not_reached(); + } +} + +void pa_dbus_send_basic_array_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int item_type, void *array, unsigned n) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_dbus_append_basic_array_variant(&msg_iter, item_type, array, n); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(proplist); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_dbus_append_proplist_variant(&msg_iter, proplist); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n) { + DBusMessageIter array_iter; + unsigned i; + unsigned item_size; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + item_size = basic_type_size(item_type); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, signature_from_basic_type(item_type), &array_iter)); + + for (i = 0; i < n; ++i) + pa_assert_se(dbus_message_iter_append_basic(&array_iter, item_type, &((uint8_t*) array)[i * item_size])); + + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); +}; + +void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, signature_from_basic_type(type), &variant_iter)); + pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n) { + DBusMessageIter variant_iter; + char *array_signature; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + array_signature = pa_sprintf_malloc("a%c", *signature_from_basic_type(item_type)); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_signature, &variant_iter)); + pa_dbus_append_basic_array(&variant_iter, item_type, array, n); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); + + pa_xfree(array_signature); +} + +void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_basic_variant(&dict_entry_iter, type, data); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +void pa_dbus_append_basic_array_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int item_type, const void *array, unsigned n) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_basic_array_variant(&dict_entry_iter, item_type, array, n); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist) { + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter array_iter; + void *state = NULL; + const char *key; + + pa_assert(iter); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{say}", &dict_iter)); + + while ((key = pa_proplist_iterate(proplist, &state))) { + const void *value = NULL; + size_t nbytes; + + pa_assert_se(pa_proplist_get(proplist, key, &value, &nbytes) >= 0); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + + pa_assert_se(dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_ARRAY, "y", &array_iter)); + pa_assert_se(dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, &value, nbytes)); + pa_assert_se(dbus_message_iter_close_container(&dict_entry_iter, &array_iter)); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(iter, &dict_iter)); +} + +void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{say}", &variant_iter)); + pa_dbus_append_proplist(&variant_iter, proplist); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_proplist_variant(&dict_entry_iter, proplist); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +int pa_dbus_get_basic_set_property_arg(DBusConnection *c, DBusMessage *msg, int type, void *data) { + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(c); + pa_assert(msg); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + /* Skip the interface and property name arguments. */ + if (!dbus_message_iter_init(msg, &msg_iter) || !dbus_message_iter_next(&msg_iter) || !dbus_message_iter_next(&msg_iter)) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Too few arguments."); + return -1; + } + + if (dbus_message_iter_get_arg_type(&msg_iter) != DBUS_TYPE_VARIANT) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Message argument isn't a variant."); + return -1; + } + + dbus_message_iter_recurse(&msg_iter, &variant_iter); + + if (pa_dbus_get_basic_arg(c, msg, &variant_iter, type, data) < 0) + return -1; + + return 0; +} + +int pa_dbus_get_fixed_array_set_property_arg(DBusConnection *c, DBusMessage *msg, int item_type, void *data, unsigned *n) { + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(c); + pa_assert(msg); + pa_assert(dbus_type_is_fixed(item_type)); + pa_assert(data); + pa_assert(n); + + /* Skip the interface and property name arguments. */ + if (!dbus_message_iter_init(msg, &msg_iter) || !dbus_message_iter_next(&msg_iter) || !dbus_message_iter_next(&msg_iter)) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Too few arguments."); + return -1; + } + + if (dbus_message_iter_get_arg_type(&msg_iter) != DBUS_TYPE_VARIANT) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Message argument isn't a variant."); + return -1; + } + + dbus_message_iter_recurse(&msg_iter, &variant_iter); + + if (pa_dbus_get_fixed_array_arg(c, msg, &variant_iter, item_type, data, n) < 0) + return -1; + + return 0; +} + +int pa_dbus_get_basic_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter, int type, void *data) { + int arg_type; + + pa_assert(c); + pa_assert(msg); + pa_assert(iter); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + arg_type = dbus_message_iter_get_arg_type(iter); + if (arg_type != type) { + if (arg_type == DBUS_TYPE_INVALID) + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Too few arguments. D-Bus type '%c' expected.", (char) type); + else + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong argument type: '%c'. Expected type '%c'.", (char) arg_type, (char) type); + return -1; + } + + dbus_message_iter_get_basic(iter, data); + + dbus_message_iter_next(iter); + + return 0; +} + +int pa_dbus_get_fixed_array_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter, int item_type, void *array, unsigned *n) { + DBusMessageIter array_iter; + int signed_n; + int arg_type; + int element_type; + + pa_assert(c); + pa_assert(msg); + pa_assert(iter); + pa_assert(dbus_type_is_fixed(item_type)); + pa_assert(array); + pa_assert(n); + + arg_type = dbus_message_iter_get_arg_type(iter); + if (arg_type != DBUS_TYPE_ARRAY) { + if (arg_type == DBUS_TYPE_INVALID) + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Too few arguments. An array of type '%c' was expected.", (char) item_type); + else + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong argument type: '%c'. An array of type '%c' was expected.", (char) arg_type, (char) item_type); + return -1; + } + + element_type = dbus_message_iter_get_element_type(iter); + if (element_type != item_type) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong array element type: '%c'. Element type '%c' was expected.", (char) element_type, (char) item_type); + return -1; + } + + dbus_message_iter_recurse(iter, &array_iter); + + dbus_message_iter_get_fixed_array(&array_iter, array, &signed_n); + + dbus_message_iter_next(iter); + + pa_assert(signed_n >= 0); + + *n = signed_n; + + return 0; +} + +pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter) { + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + int arg_type; + pa_proplist *proplist = NULL; + const char *key; + const uint8_t *value; + int value_length; + + pa_assert(c); + pa_assert(msg); + pa_assert(iter); + + arg_type = dbus_message_iter_get_arg_type(iter); + if (arg_type != DBUS_TYPE_ARRAY) { + if (arg_type == DBUS_TYPE_INVALID) + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Too few arguments. An array was expected."); + else + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong argument type: '%c'. An array was expected.", (char) arg_type); + return NULL; + } + + arg_type = dbus_message_iter_get_element_type(iter); + if (arg_type != DBUS_TYPE_DICT_ENTRY) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong array element type: '%c'. A dictionary entry was expected.", (char) arg_type); + return NULL; + } + + proplist = pa_proplist_new(); + + dbus_message_iter_recurse(iter, &dict_iter); + + while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + + arg_type = dbus_message_iter_get_arg_type(&dict_entry_iter); + if (arg_type != DBUS_TYPE_STRING) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong dict key type: '%c'. A string was expected.", (char) arg_type); + goto fail; + } + + dbus_message_iter_get_basic(&dict_entry_iter, &key); + dbus_message_iter_next(&dict_entry_iter); + + if (strlen(key) <= 0 || !pa_ascii_valid(key)) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Invalid property list key."); + goto fail; + } + + arg_type = dbus_message_iter_get_arg_type(&dict_entry_iter); + if (arg_type != DBUS_TYPE_ARRAY) { + if (arg_type == DBUS_TYPE_INVALID) + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Dict value missing."); + else + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong dict value type: '%c'. An array was expected.", (char) arg_type); + goto fail; + } + + arg_type = dbus_message_iter_get_element_type(&dict_entry_iter); + if (arg_type != DBUS_TYPE_BYTE) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Wrong dict value item type: '%c'. A byte was expected.", (char) arg_type); + goto fail; + } + + dbus_message_iter_get_fixed_array(&dict_entry_iter, &value, &value_length); + + pa_assert(value_length >= 0); + + pa_assert_se(pa_proplist_set(proplist, key, value, value_length) >= 0); + + dbus_message_iter_next(&dict_iter); + } + + dbus_message_iter_next(iter); + + return proplist; + +fail: + if (proplist) + pa_proplist_free(proplist); + + return NULL; +} diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h index 9ff298d8..9cee293c 100644 --- a/src/pulsecore/dbus-util.h +++ b/src/pulsecore/dbus-util.h @@ -26,11 +26,13 @@ #include <pulsecore/llist.h> #include <pulse/mainloop-api.h> +#include <pulse/proplist.h> /* A wrap connection is not shared or refcounted, it is available in client side */ typedef struct pa_dbus_wrap_connection pa_dbus_wrap_connection; pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *mainloop, pa_bool_t use_rtclock, DBusBusType type, DBusError *error); +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing(pa_mainloop_api *mainloop, pa_bool_t use_rtclock, DBusConnection *conn); void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn); DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn); @@ -60,4 +62,37 @@ void pa_dbus_sync_pending_list(pa_dbus_pending **p); /* Free up a list of pa_dbus_pending_call objects */ void pa_dbus_free_pending_list(pa_dbus_pending **p); +/* Sends an error message as the reply to the given message. */ +void pa_dbus_send_error(DBusConnection *c, DBusMessage *in_reply_to, const char *name, const char *format, ...) PA_GCC_PRINTF_ATTR(4, 5); + +void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to); +void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data); +void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data); +void pa_dbus_send_basic_array_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int item_type, void *array, unsigned n); +void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist); + +void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n); +void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n); +void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data); +void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data); +void pa_dbus_append_basic_array_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int item_type, const void *array, unsigned n); +void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist); +void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist); +void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist); + +/* Helper functions for extracting the value argument of a Set call. If the + * message is invalid, an error reply is sent and a negative number is + * returned. */ +int pa_dbus_get_basic_set_property_arg(DBusConnection *c, DBusMessage *msg, int type, void *data); +int pa_dbus_get_fixed_array_set_property_arg(DBusConnection *c, DBusMessage *msg, int item_type, void *data, unsigned *n); + +/* If the arguments can't be read from the iterator, an error reply is sent and + * a negative number is returned. */ +int pa_dbus_get_basic_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter, int type, void *data); +int pa_dbus_get_fixed_array_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter, int item_type, void *array, unsigned *n); + +/* Returns a new proplist that the caller has to free. If the proplist can't be + * read from the iterator, an error reply is sent and NULL is returned. */ +pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter); + #endif diff --git a/src/pulsecore/protocol-dbus.c b/src/pulsecore/protocol-dbus.c new file mode 100644 index 00000000..cf561c83 --- /dev/null +++ b/src/pulsecore/protocol-dbus.c @@ -0,0 +1,988 @@ +/*** + 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 <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/shared.h> +#include <pulsecore/strbuf.h> + +#include "protocol-dbus.h" + +struct pa_dbus_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_hashmap *objects; /* Object path -> struct object_entry */ + pa_hashmap *connections; /* DBusConnection -> struct connection_entry */ + pa_idxset *extensions; /* Strings */ + + pa_hook hooks[PA_DBUS_PROTOCOL_HOOK_MAX]; +}; + +struct object_entry { + char *path; + pa_hashmap *interfaces; /* Interface name -> struct interface_entry */ + char *introspection; +}; + +struct connection_entry { + DBusConnection *connection; + pa_client *client; + + pa_bool_t listening_for_all_signals; + + /* Contains object paths. If this is empty, then signals from all objects + * are accepted. Only used when listening_for_all_signals == TRUE. */ + pa_idxset *all_signals_objects; + + /* Signal name -> idxset. The idxsets contain object paths. If an idxset is + * empty, then that signal is accepted from all objects. Only used when + * listening_for_all_signals == FALSE. */ + pa_hashmap *listening_signals; +}; + +struct interface_entry { + char *name; + pa_hashmap *method_handlers; + pa_hashmap *property_handlers; + pa_dbus_receive_cb_t get_all_properties_cb; + pa_dbus_signal_info *signals; + unsigned n_signals; + void *userdata; +}; + +char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) { + char *address = NULL; + char *runtime_path = NULL; + char *escaped_path = NULL; + + switch (server_type) { + case PA_SERVER_TYPE_USER: + pa_assert_se((runtime_path = pa_runtime_path(PA_DBUS_SOCKET_NAME))); + pa_assert_se((escaped_path = dbus_address_escape_value(runtime_path))); + address = pa_sprintf_malloc("unix:path=%s", escaped_path); + break; + + case PA_SERVER_TYPE_SYSTEM: + pa_assert_se((escaped_path = dbus_address_escape_value(PA_DBUS_SYSTEM_SOCKET_PATH))); + address = pa_sprintf_malloc("unix:path=%s", escaped_path); + break; + + case PA_SERVER_TYPE_NONE: + address = pa_xnew0(char, 1); + break; + + default: + pa_assert_not_reached(); + } + + pa_xfree(runtime_path); + pa_xfree(escaped_path); + + return address; +} + +static pa_dbus_protocol *dbus_protocol_new(pa_core *c) { + pa_dbus_protocol *p; + unsigned i; + + pa_assert(c); + + p = pa_xnew(pa_dbus_protocol, 1); + PA_REFCNT_INIT(p); + p->core = pa_core_ref(c); + p->objects = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + p->connections = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + p->extensions = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i) + pa_hook_init(&p->hooks[i], p); + + pa_assert_se(pa_shared_set(c, "dbus-protocol", p) >= 0); + + return p; +} + +pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c) { + pa_dbus_protocol *p; + + if ((p = pa_shared_get(c, "dbus-protocol"))) + return pa_dbus_protocol_ref(p); + + return dbus_protocol_new(c); +} + +pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_dbus_protocol_unref(pa_dbus_protocol *p) { + unsigned i; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + pa_assert(pa_hashmap_isempty(p->objects)); + pa_assert(pa_hashmap_isempty(p->connections)); + pa_assert(pa_idxset_isempty(p->extensions)); + + pa_hashmap_free(p->objects, NULL, NULL); + pa_hashmap_free(p->connections, NULL, NULL); + pa_idxset_free(p->extensions, NULL, NULL); + + for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i) + pa_hook_done(&p->hooks[i]); + + pa_assert_se(pa_shared_remove(p->core, "dbus-protocol") >= 0); + + pa_core_unref(p->core); + + pa_xfree(p); +} + +static void update_introspection(struct object_entry *oe) { + pa_strbuf *buf; + void *interfaces_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, "<node>\n"); + + PA_HASHMAP_FOREACH(iface_entry, oe->interfaces, interfaces_state) { + pa_dbus_method_handler *method_handler; + pa_dbus_property_handler *property_handler; + void *handlers_state = NULL; + unsigned i; + unsigned j; + + pa_strbuf_printf(buf, " <interface name=\"%s\">\n", iface_entry->name); + + PA_HASHMAP_FOREACH(method_handler, iface_entry->method_handlers, handlers_state) { + pa_strbuf_printf(buf, " <method name=\"%s\">\n", method_handler->method_name); + + for (i = 0; i < method_handler->n_arguments; ++i) + pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\" direction=\"%s\"/>\n", + method_handler->arguments[i].name, + method_handler->arguments[i].type, + method_handler->arguments[i].direction); + + pa_strbuf_puts(buf, " </method>\n"); + } + + handlers_state = NULL; + + PA_HASHMAP_FOREACH(property_handler, iface_entry->property_handlers, handlers_state) + pa_strbuf_printf(buf, " <property name=\"%s\" type=\"%s\" access=\"%s\"/>\n", + property_handler->property_name, + property_handler->type, + property_handler->get_cb ? (property_handler->set_cb ? "readwrite" : "read") : "write"); + + for (i = 0; i < iface_entry->n_signals; ++i) { + pa_strbuf_printf(buf, " <signal name=\"%s\">\n", iface_entry->signals[i].name); + + for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) + pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\"/>\n", iface_entry->signals[i].arguments[j].name, + iface_entry->signals[i].arguments[j].type); + + pa_strbuf_puts(buf, " </signal>\n"); + } + + pa_strbuf_puts(buf, " </interface>\n"); + } + + pa_strbuf_puts(buf, " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n"); + + pa_strbuf_puts(buf, "</node>\n"); + + pa_xfree(oe->introspection); + oe->introspection = pa_strbuf_tostring_free(buf); +} + +enum find_result_t { + FOUND_METHOD, + FOUND_GET_PROPERTY, + FOUND_SET_PROPERTY, + FOUND_GET_ALL, + PROPERTY_ACCESS_DENIED, + NO_SUCH_METHOD, + NO_SUCH_PROPERTY, + INVALID_MESSAGE_ARGUMENTS +}; + +static enum find_result_t find_handler_by_property(struct object_entry *obj_entry, + DBusMessage *msg, + const char *property, + struct interface_entry **iface_entry, + pa_dbus_property_handler **property_handler) { + void *state = NULL; + + pa_assert(obj_entry); + pa_assert(msg); + pa_assert(property); + pa_assert(iface_entry); + pa_assert(property_handler); + + PA_HASHMAP_FOREACH(*iface_entry, obj_entry->interfaces, state) { + if ((*property_handler = pa_hashmap_get((*iface_entry)->property_handlers, property))) { + if (dbus_message_has_member(msg, "Get")) + return (*property_handler)->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED; + else if (dbus_message_has_member(msg, "Set")) + return (*property_handler)->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED; + else + pa_assert_not_reached(); + } + } + + return NO_SUCH_PROPERTY; +} + +static enum find_result_t find_handler_by_method(struct object_entry *obj_entry, + const char *method, + struct interface_entry **iface_entry, + pa_dbus_method_handler **method_handler) { + void *state = NULL; + + pa_assert(obj_entry); + pa_assert(method); + pa_assert(iface_entry); + pa_assert(method_handler); + + PA_HASHMAP_FOREACH(*iface_entry, obj_entry->interfaces, state) { + if ((*method_handler = pa_hashmap_get((*iface_entry)->method_handlers, method))) + return FOUND_METHOD; + } + + return NO_SUCH_METHOD; +} + +static enum find_result_t find_handler_from_properties_call(struct object_entry *obj_entry, + DBusMessage *msg, + struct interface_entry **iface_entry, + pa_dbus_property_handler **property_handler, + const char **attempted_property) { + const char *interface; + + pa_assert(obj_entry); + pa_assert(msg); + pa_assert(iface_entry); + + if (dbus_message_has_member(msg, "GetAll")) { + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) + return INVALID_MESSAGE_ARGUMENTS; + + if (*interface) { + if ((*iface_entry = pa_hashmap_get(obj_entry->interfaces, interface))) + return FOUND_GET_ALL; + else { + return NO_SUCH_METHOD; /* XXX: NO_SUCH_INTERFACE or something like that might be more accurate. */ + } + } else { + pa_assert_se((*iface_entry = pa_hashmap_first(obj_entry->interfaces))); + return FOUND_GET_ALL; + } + } else { + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, attempted_property, DBUS_TYPE_INVALID)) + return INVALID_MESSAGE_ARGUMENTS; + + if (*interface) { + if ((*iface_entry = pa_hashmap_get(obj_entry->interfaces, interface)) && + (*property_handler = pa_hashmap_get((*iface_entry)->property_handlers, *attempted_property))) { + if (dbus_message_has_member(msg, "Get")) + return (*property_handler)->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED; + else if (dbus_message_has_member(msg, "Set")) + return (*property_handler)->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED; + else + pa_assert_not_reached(); + } else + return NO_SUCH_PROPERTY; + } else + return find_handler_by_property(obj_entry, msg, *attempted_property, iface_entry, property_handler); + } +} + +static enum find_result_t find_handler(struct object_entry *obj_entry, + DBusMessage *msg, + struct interface_entry **iface_entry, + pa_dbus_method_handler **method_handler, + pa_dbus_property_handler **property_handler, + const char **attempted_property) { + const char *interface; + + pa_assert(obj_entry); + pa_assert(msg); + pa_assert(iface_entry); + pa_assert(method_handler); + pa_assert(property_handler); + pa_assert(attempted_property); + + *iface_entry = NULL; + *method_handler = NULL; + + if (dbus_message_has_interface(msg, DBUS_INTERFACE_PROPERTIES)) + return find_handler_from_properties_call(obj_entry, msg, iface_entry, property_handler, attempted_property); + + else if ((interface = dbus_message_get_interface(msg))) { + if ((*iface_entry = pa_hashmap_get(obj_entry->interfaces, interface)) && + (*method_handler = pa_hashmap_get((*iface_entry)->method_handlers, dbus_message_get_member(msg)))) + return FOUND_METHOD; + else { + pa_log("Message has unknown interface or there's no method handler."); + return NO_SUCH_METHOD; + } + + } else { /* The method call doesn't contain an interface. */ + if (dbus_message_has_member(msg, "Get") || dbus_message_has_member(msg, "Set") || dbus_message_has_member(msg, "GetAll")) { + if (find_handler_by_method(obj_entry, dbus_message_get_member(msg), iface_entry, method_handler) == FOUND_METHOD) + return FOUND_METHOD; /* The object has a method named Get, Set or GetAll in some other interface than .Properties. */ + else + /* Assume this is a .Properties call. */ + return find_handler_from_properties_call(obj_entry, msg, iface_entry, property_handler, attempted_property); + + } else /* This is not a .Properties call. */ + return find_handler_by_method(obj_entry, dbus_message_get_member(msg), iface_entry, method_handler); + } +} + +static DBusHandlerResult handle_message_cb(DBusConnection *connection, DBusMessage *message, void *user_data) { + pa_dbus_protocol *p = user_data; + struct object_entry *obj_entry = NULL; + struct interface_entry *iface_entry = NULL; + pa_dbus_method_handler *method_handler = NULL; + pa_dbus_property_handler *property_handler = NULL; + const char *attempted_property = NULL; + + pa_assert(connection); + pa_assert(message); + pa_assert(p); + pa_assert(p->objects); + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + pa_log_debug("Received message: destination = %s, interface = %s, member = %s", + dbus_message_get_path(message), + dbus_message_get_interface(message), + dbus_message_get_member(message)); + + pa_assert_se((obj_entry = pa_hashmap_get(p->objects, dbus_message_get_path(message)))); + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") || + (!dbus_message_get_interface(message) && dbus_message_has_member(message, "Introspect"))) { + pa_dbus_send_basic_value_reply(connection, message, DBUS_TYPE_STRING, &obj_entry->introspection); + goto finish; + } + + switch (find_handler(obj_entry, message, &iface_entry, &method_handler, &property_handler, &attempted_property)) { + case FOUND_METHOD: + method_handler->receive_cb(connection, message, iface_entry->userdata); + break; + + case FOUND_GET_PROPERTY: + property_handler->get_cb(connection, message, iface_entry->userdata); + break; + + case FOUND_SET_PROPERTY: + property_handler->set_cb(connection, message, iface_entry->userdata); + break; + + case FOUND_GET_ALL: + if (iface_entry->get_all_properties_cb) + iface_entry->get_all_properties_cb(connection, message, iface_entry->userdata); + /* TODO: Write an else branch where a dummy response is sent. */ + break; + + case PROPERTY_ACCESS_DENIED: + pa_dbus_send_error(connection, message, DBUS_ERROR_ACCESS_DENIED, "%s access denied for property %s", dbus_message_get_member(message), attempted_property); + break; + + case NO_SUCH_METHOD: + pa_dbus_send_error(connection, message, DBUS_ERROR_UNKNOWN_METHOD, "%s: No such method", dbus_message_get_member(message)); + break; + + case NO_SUCH_PROPERTY: + pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", attempted_property); + break; + + case INVALID_MESSAGE_ARGUMENTS: + pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS, "Invalid arguments for %s", dbus_message_get_member(message)); + break; + + default: + pa_assert_not_reached(); + } + +finish: + return DBUS_HANDLER_RESULT_HANDLED; +} + +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(pa_dbus_protocol *p, struct object_entry *obj_entry) { + struct connection_entry *conn_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(obj_entry); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) + pa_assert_se(dbus_connection_register_object_path(conn_entry->connection, obj_entry->path, &vtable, p)); +} + +static pa_dbus_arg_info *copy_args(const pa_dbus_arg_info *src, unsigned n) { + pa_dbus_arg_info *dst; + unsigned i; + + if (n == 0) + return NULL; + + pa_assert(src); + + dst = pa_xnew0(pa_dbus_arg_info, n); + + for (i = 0; i < n; ++i) { + dst[i].name = pa_xstrdup(src[i].name); + dst[i].type = pa_xstrdup(src[i].type); + dst[i].direction = pa_xstrdup(src[i].direction); + } + + return dst; +} + +static pa_hashmap *create_method_handlers(const pa_dbus_interface_info *info) { + pa_hashmap *handlers; + unsigned i; + + pa_assert(info); + pa_assert(info->method_handlers || info->n_method_handlers == 0); + + handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < info->n_method_handlers; ++i) { + pa_dbus_method_handler *h = pa_xnew(pa_dbus_method_handler, 1); + h->method_name = pa_xstrdup(info->method_handlers[i].method_name); + h->arguments = copy_args(info->method_handlers[i].arguments, info->method_handlers[i].n_arguments); + h->n_arguments = info->method_handlers[i].n_arguments; + h->receive_cb = info->method_handlers[i].receive_cb; + + pa_hashmap_put(handlers, h->method_name, h); + } + + return handlers; +} + +static pa_hashmap *create_property_handlers(const pa_dbus_interface_info *info) { + pa_hashmap *handlers; + unsigned i = 0; + + pa_assert(info); + pa_assert(info->property_handlers || info->n_property_handlers == 0); + + handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < info->n_property_handlers; ++i) { + pa_dbus_property_handler *h = pa_xnew(pa_dbus_property_handler, 1); + h->property_name = pa_xstrdup(info->property_handlers[i].property_name); + h->type = pa_xstrdup(info->property_handlers[i].type); + h->get_cb = info->property_handlers[i].get_cb; + h->set_cb = info->property_handlers[i].set_cb; + + pa_hashmap_put(handlers, h->property_name, h); + } + + return handlers; +} + +static pa_dbus_signal_info *copy_signals(const pa_dbus_interface_info *info) { + pa_dbus_signal_info *dst; + unsigned i; + + pa_assert(info); + + if (info->n_signals == 0) + return NULL; + + pa_assert(info->signals); + + dst = pa_xnew(pa_dbus_signal_info, info->n_signals); + + for (i = 0; i < info->n_signals; ++i) { + dst[i].name = pa_xstrdup(info->signals[i].name); + dst[i].arguments = copy_args(info->signals[i].arguments, info->signals[i].n_arguments); + dst[i].n_arguments = info->signals[i].n_arguments; + } + + return dst; +} + +int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, + const char *path, + const pa_dbus_interface_info *info, + void *userdata) { + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + pa_bool_t obj_entry_created = FALSE; + + pa_assert(p); + pa_assert(path); + pa_assert(info); + pa_assert(info->name); + pa_assert(info->method_handlers || info->n_method_handlers == 0); + pa_assert(info->property_handlers || info->n_property_handlers == 0); + pa_assert(info->get_all_properties_cb || info->n_property_handlers == 0); + pa_assert(info->signals || info->n_signals == 0); + + if (!(obj_entry = pa_hashmap_get(p->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(p->objects, path, obj_entry); + obj_entry_created = TRUE; + } + + if (pa_hashmap_get(obj_entry->interfaces, info->name) != NULL) + goto fail; /* The interface was already registered. */ + + iface_entry = pa_xnew(struct interface_entry, 1); + iface_entry->name = pa_xstrdup(info->name); + iface_entry->method_handlers = create_method_handlers(info); + iface_entry->property_handlers = create_property_handlers(info); + iface_entry->get_all_properties_cb = info->get_all_properties_cb; + iface_entry->signals = copy_signals(info); + iface_entry->n_signals = info->n_signals; + 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(p, obj_entry); + + pa_log_debug("Interface %s added for object %s", iface_entry->name, obj_entry->path); + + return 0; + +fail: + if (obj_entry_created) { + pa_hashmap_remove(p->objects, path); + pa_xfree(obj_entry); + } + + return -1; +} + +static void unregister_object(pa_dbus_protocol *p, struct object_entry *obj_entry) { + struct connection_entry *conn_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(obj_entry); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) + pa_assert_se(dbus_connection_unregister_object_path(conn_entry->connection, obj_entry->path)); +} + +static void method_handler_free_cb(void *p, void *userdata) { + pa_dbus_method_handler *h = p; + unsigned i; + + pa_assert(h); + + pa_xfree((char *) h->method_name); + + for (i = 0; i < h->n_arguments; ++i) { + pa_xfree((char *) h->arguments[i].name); + pa_xfree((char *) h->arguments[i].type); + pa_xfree((char *) h->arguments[i].direction); + } + + pa_xfree((pa_dbus_arg_info *) h->arguments); +} + +static void property_handler_free_cb(void *p, void *userdata) { + pa_dbus_property_handler *h = p; + + pa_assert(h); + + pa_xfree((char *) h->property_name); + pa_xfree((char *) h->type); + + pa_xfree(h); +} + +int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface) { + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + unsigned i; + + pa_assert(p); + pa_assert(path); + pa_assert(interface); + + if (!(obj_entry = pa_hashmap_get(p->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); + pa_hashmap_free(iface_entry->method_handlers, method_handler_free_cb, NULL); + pa_hashmap_free(iface_entry->property_handlers, property_handler_free_cb, NULL); + + for (i = 0; i < iface_entry->n_signals; ++i) { + unsigned j; + + pa_xfree((char *) iface_entry->signals[i].name); + + for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) { + pa_xfree((char *) iface_entry->signals[i].arguments[j].name); + pa_xfree((char *) iface_entry->signals[i].arguments[j].type); + pa_assert(iface_entry->signals[i].arguments[j].direction == NULL); + } + + pa_xfree((pa_dbus_arg_info *) iface_entry->signals[i].arguments); + } + + pa_xfree(iface_entry->signals); + pa_xfree(iface_entry); + + if (pa_hashmap_isempty(obj_entry->interfaces)) { + unregister_object(p, obj_entry); + + pa_hashmap_remove(p->objects, path); + pa_xfree(obj_entry->path); + pa_hashmap_free(obj_entry->interfaces, NULL, NULL); + pa_xfree(obj_entry->introspection); + pa_xfree(obj_entry); + } + + return 0; +} + +static void register_all_objects(pa_dbus_protocol *p, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(conn); + + PA_HASHMAP_FOREACH(obj_entry, p->objects, state) + pa_assert_se(dbus_connection_register_object_path(conn, obj_entry->path, &vtable, p)); +} + +int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + pa_assert(client); + + if (pa_hashmap_get(p->connections, conn)) + return -1; /* The connection was already registered. */ + + register_all_objects(p, conn); + + conn_entry = pa_xnew(struct connection_entry, 1); + conn_entry->connection = dbus_connection_ref(conn); + conn_entry->client = client; + conn_entry->listening_for_all_signals = FALSE; + conn_entry->all_signals_objects = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + conn_entry->listening_signals = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + pa_hashmap_put(p->connections, conn, conn_entry); + + return 0; +} + +static void unregister_all_objects(pa_dbus_protocol *p, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(conn); + + PA_HASHMAP_FOREACH(obj_entry, p->objects, state) + pa_assert_se(dbus_connection_unregister_object_path(conn, obj_entry->path)); +} + +static void free_listened_object_name_cb(void *p, void *userdata) { + pa_assert(p); + + pa_xfree(p); +} + +static void free_listening_signals_idxset_cb(void *p, void *userdata) { + pa_idxset *set = p; + + pa_assert(set); + + pa_idxset_free(set, free_listened_object_name_cb, NULL); +} + +int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + + if (!(conn_entry = pa_hashmap_remove(p->connections, conn))) + return -1; + + unregister_all_objects(p, conn); + + dbus_connection_unref(conn_entry->connection); + pa_idxset_free(conn_entry->all_signals_objects, free_listened_object_name_cb, NULL); + pa_hashmap_free(conn_entry->listening_signals, free_listening_signals_idxset_cb, NULL); + pa_xfree(conn_entry); + + return 0; +} + +pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + + if (!(conn_entry = pa_hashmap_get(p->connections, conn))) + return NULL; + + return conn_entry->client; +} + +void pa_dbus_protocol_add_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal, char **objects, unsigned n_objects) { + struct connection_entry *conn_entry; + pa_idxset *object_set; + char *object_path; + unsigned i; + + pa_assert(p); + pa_assert(conn); + pa_assert(objects || n_objects == 0); + + pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn))); + + /* all_signals_objects will either be emptied or replaced with new objects, + * so we empty it here unconditionally. If listening_for_all_signals is + * currently FALSE, the idxset is empty already. */ + while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL))) + pa_xfree(object_path); + + if (signal) { + conn_entry->listening_for_all_signals = FALSE; + + /* Replace the old object list with a new one. */ + if ((object_set = pa_hashmap_remove(conn_entry->listening_signals, signal))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + object_set = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < n_objects; ++i) + pa_idxset_put(object_set, pa_xstrdup(objects[i]), NULL); + + pa_hashmap_put(conn_entry->listening_signals, signal, object_set); + + } else { + conn_entry->listening_for_all_signals = TRUE; + + /* We're not interested in individual signals anymore, so let's empty + * listening_signals. */ + while ((object_set = pa_hashmap_steal_first(conn_entry->listening_signals))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + + for (i = 0; i < n_objects; ++i) + pa_idxset_put(conn_entry->all_signals_objects, pa_xstrdup(objects[i]), NULL); + } +} + +void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal) { + struct connection_entry *conn_entry; + pa_idxset *object_set; + + pa_assert(p); + pa_assert(conn); + + pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn))); + + if (signal) { + if ((object_set = pa_hashmap_get(conn_entry->listening_signals, signal))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + + } else { + char *object_path; + + conn_entry->listening_for_all_signals = FALSE; + + while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL))) + pa_xfree(object_path); + + while ((object_set = pa_hashmap_steal_first(conn_entry->listening_signals))) + pa_idxset_free(object_set, free_listened_object_name_cb, NULL); + } +} + +void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal) { + struct connection_entry *conn_entry; + void *state = NULL; + pa_idxset *object_set; + DBusMessage *signal_copy; + char *signal_string; + + pa_assert(p); + pa_assert(signal); + pa_assert(dbus_message_get_type(signal) == DBUS_MESSAGE_TYPE_SIGNAL); + pa_assert_se(dbus_message_get_interface(signal)); + pa_assert_se(dbus_message_get_member(signal)); + + signal_string = pa_sprintf_malloc("%s.%s", dbus_message_get_interface(signal), dbus_message_get_member(signal)); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) { + if ((conn_entry->listening_for_all_signals /* Case 1: listening for all signals */ + && (pa_idxset_get_by_data(conn_entry->all_signals_objects, dbus_message_get_path(signal), NULL) + || pa_idxset_isempty(conn_entry->all_signals_objects))) + + || (!conn_entry->listening_for_all_signals /* Case 2: not listening for all signals */ + && (object_set = pa_hashmap_get(conn_entry->listening_signals, signal_string)) + && (pa_idxset_get_by_data(object_set, dbus_message_get_path(signal), NULL) + || pa_idxset_isempty(object_set)))) { + + pa_assert_se(signal_copy = dbus_message_copy(signal)); + pa_assert_se(dbus_connection_send(conn_entry->connection, signal_copy, NULL)); + dbus_message_unref(signal_copy); + } + } + + pa_xfree(signal_string); +} + +const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n) { + const char **extensions; + const char *ext_name; + void *state = NULL; + unsigned i = 0; + + pa_assert(p); + pa_assert(n); + + *n = pa_idxset_size(p->extensions); + + if (*n <= 0) + return NULL; + + extensions = pa_xnew(const char *, *n); + + while ((ext_name = pa_idxset_iterate(p->extensions, &state, NULL))) + extensions[i++] = ext_name; + + return extensions; +} + +int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name) { + char *internal_name; + + pa_assert(p); + pa_assert(name); + + internal_name = pa_xstrdup(name); + + if (pa_idxset_put(p->extensions, internal_name, NULL) < 0) { + pa_xfree(internal_name); + return -1; + } + + pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED], internal_name); + + return 0; +} + +int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name) { + char *internal_name; + + pa_assert(p); + pa_assert(name); + + if (!(internal_name = pa_idxset_remove_by_data(p->extensions, name, NULL))) + return -1; + + pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED], internal_name); + + pa_xfree(internal_name); + + return 0; +} + +pa_hook_slot *pa_dbus_protocol_hook_connect(pa_dbus_protocol *p, pa_dbus_protocol_hook_t hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) { + pa_assert(p); + pa_assert(hook < PA_DBUS_PROTOCOL_HOOK_MAX); + pa_assert(cb); + + return pa_hook_connect(&p->hooks[hook], prio, cb, data); +} diff --git a/src/pulsecore/protocol-dbus.h b/src/pulsecore/protocol-dbus.h new file mode 100644 index 00000000..d771b4fc --- /dev/null +++ b/src/pulsecore/protocol-dbus.h @@ -0,0 +1,197 @@ +#ifndef fooprotocoldbushfoo +#define fooprotocoldbushfoo + +/*** + 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. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/core.h> +#include <pulsecore/macro.h> + +#define PA_DBUS_DEFAULT_PORT 24883 +#define PA_DBUS_SOCKET_NAME "dbus-socket" + +#define PA_DBUS_SYSTEM_SOCKET_PATH PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_DBUS_SOCKET_NAME + +#define PA_DBUS_CORE_INTERFACE "org.PulseAudio.Core1" +#define PA_DBUS_CORE_OBJECT_PATH "/org/pulseaudio/core1" + +#define PA_DBUS_ERROR_NO_SUCH_PROPERTY PA_DBUS_CORE_INTERFACE ".NoSuchPropertyError" +#define PA_DBUS_ERROR_NOT_FOUND PA_DBUS_CORE_INTERFACE ".NotFoundError" + +/* 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. */ +char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type); + +typedef struct pa_dbus_protocol pa_dbus_protocol; + +/* This function either creates a new pa_dbus_protocol object, or if one + * already exists, increases the reference count. */ +pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c); + +pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p); +void pa_dbus_protocol_unref(pa_dbus_protocol *p); + +/* Called when a received message needs handling. Completely ignoring the + * message isn't a good idea; if you can't handle the message, reply with an + * error. + * + * All messages are method calls. */ +typedef void (*pa_dbus_receive_cb_t)(DBusConnection *conn, DBusMessage *msg, void *userdata); + +typedef struct pa_dbus_arg_info { + const char *name; + const char *type; + const char *direction; /* NULL for signal arguments. */ +} pa_dbus_arg_info; + +typedef struct pa_dbus_signal_info { + const char *name; + const pa_dbus_arg_info *arguments; /* NULL, if the signal has no args. */ + unsigned n_arguments; +} pa_dbus_signal_info; + +typedef struct pa_dbus_method_handler { + const char *method_name; + const pa_dbus_arg_info *arguments; /* NULL, if the method has no args. */ + unsigned n_arguments; + pa_dbus_receive_cb_t receive_cb; +} pa_dbus_method_handler; + +typedef struct pa_dbus_property_handler { + const char *property_name; + const char *type; + + /* The access mode for the property is determined by checking whether + * get_cb or set_cb is NULL. */ + pa_dbus_receive_cb_t get_cb; + pa_dbus_receive_cb_t set_cb; +} pa_dbus_property_handler; + +typedef struct pa_dbus_interface_info { + const char* name; + const pa_dbus_method_handler *method_handlers; /* NULL, if the interface has no methods. */ + unsigned n_method_handlers; + const pa_dbus_property_handler *property_handlers; /* NULL, if the interface has no properties. */ + unsigned n_property_handlers; + const pa_dbus_receive_cb_t get_all_properties_cb; /* May be NULL, in which case GetAll returns an error. */ + const pa_dbus_signal_info *signals; /* NULL, if the interface has no signals. */ + unsigned n_signals; +} pa_dbus_interface_info; + + +/* The following functions may only be called from the main thread. */ + +/* Registers the given interface to the given object path. It doesn't matter + * whether or not the object has already been registered; if it is, then its + * interface set is extended. + * + * Introspection requests are handled automatically. + * + * Userdata is passed to all the callbacks. + * + * Fails and returns a negative number if the object already has the interface + * registered. */ +int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, const char *path, const pa_dbus_interface_info *info, void *userdata); + +/* Returns a negative number if the given object doesn't have the given + * interface registered. */ +int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface); + +/* Fails and returns a negative number if the connection is already + * registered. */ +int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client); + +/* Returns a negative number if the connection isn't registered. */ +int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn); + +/* Returns NULL if the connection isn't registered. */ +pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn); + +/* Enables signal receiving for the given connection. The connection must have + * been registered earlier. The signal string must contain both the signal + * interface and the signal name, concatenated using a period as the separator. + * + * If the signal argument is NULL, all signals will be sent to the connection, + * otherwise calling this function only adds the given signal to the list of + * signals that will be delivered to the connection. + * + * The objects argument is a list of object paths. If the list is not empty, + * only signals from the given objects are delivered. If this function is + * called multiple time for the same connection and signal, the latest call + * always replaces the previous object list. */ +void pa_dbus_protocol_add_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal, char **objects, unsigned n_objects); + +/* Disables the delivery of the signal for the given connection. The connection + * must have been registered. If signal is NULL, all signals are disabled. If + * signal is non-NULL and _add_signal_listener() was previously called with + * NULL signal (causing all signals to be enabled), this function doesn't do + * anything. Also, if the signal wasn't enabled before, this function doesn't + * do anything in that case either. */ +void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal); + +/* Sends the given signal to all interested clients. By default no signals are + * sent - clients have to explicitly to request signals by calling + * .Core1.ListenForSignal. That method's handler then calls + * pa_dbus_protocol_add_signal_listener(). */ +void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal); + +/* Returns an array of extension identifier strings. The strings pointers point + * to the internal copies, so don't free the strings. The caller must free the + * array, however. Also, do not save the returned pointer or any of the string + * pointers, because the contained strings may be freed at any time. If you + * need to save the array, copy it. */ +const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n); + +/* Modules that want to provide a D-Bus interface for clients should register + * an identifier that the clients can use to check whether the additional + * functionality is available. + * + * This function registers the extension with the given name. It is recommended + * that the name follows the D-Bus interface naming convention, so that the + * names remain unique in case there will be at some point in the future + * extensions that aren't included with the main PulseAudio source tree. For + * in-tree extensions the convention is to use the org.PulseAudio.Ext + * namespace. + * + * It is suggested that the name contains a version number, and whenever the + * extension interface is modified in non-backwards compatible way, the version + * number is incremented. + * + * Fails and returns a negative number if the extension is already registered. + */ +int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name); + +/* Returns a negative number if the extension isn't registered. */ +int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name); + +/* All hooks have the pa_dbus_protocol object as hook data. */ +typedef enum pa_dbus_protocol_hook { + PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, /* Extension name as call data. */ + PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, /* Extension name as call data. */ + PA_DBUS_PROTOCOL_HOOK_MAX +} pa_dbus_protocol_hook_t; + +pa_hook_slot *pa_dbus_protocol_hook_connect(pa_dbus_protocol *p, pa_dbus_protocol_hook_t hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data); + +#endif |