diff options
Diffstat (limited to 'src/tools/reserve.c')
-rw-r--r-- | src/tools/reserve.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/src/tools/reserve.c b/src/tools/reserve.c new file mode 100644 index 0000000..2329a14 --- /dev/null +++ b/src/tools/reserve.c @@ -0,0 +1,527 @@ +/* DBus device reservation API + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef NAME +#define NAME "reserve" +#endif + +#include "reserve.h" + +#include <spa/utils/string.h> +#include <pipewire/log.h> + +#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." +#define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/" + +static const char introspection[] = + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "<node>" + " <!-- If you are looking for documentation make sure to check out\n" + " http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n" + " <interface name=\"org.freedesktop.ReserveDevice1\">" + " <method name=\"RequestRelease\">" + " <arg name=\"priority\" type=\"i\" direction=\"in\"/>" + " <arg name=\"result\" type=\"b\" direction=\"out\"/>" + " </method>" + " <property name=\"Priority\" type=\"i\" access=\"read\"/>" + " <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>" + " <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>" + " </interface>" + " <interface name=\"org.freedesktop.DBus.Properties\">" + " <method name=\"Get\">" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>" + " </method>" + " </interface>" + " <interface name=\"org.freedesktop.DBus.Introspectable\">" + " <method name=\"Introspect\">" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" + " </method>" + " </interface>" + "</node>"; + +struct rd_device { + DBusConnection *connection; + + int32_t priority; + char *service_name; + char *object_path; + char *application_name; + char *application_device_name; + + const struct rd_device_callbacks *callbacks; + void *data; + + DBusMessage *reply; + + unsigned int filtering:1; + unsigned int registered:1; + unsigned int acquiring:1; + unsigned int owning:1; +}; + +static dbus_bool_t add_variant(DBusMessage *m, int type, const void *data) +{ + DBusMessageIter iter, sub; + char t[2]; + + t[0] = (char) type; + t[1] = 0; + + dbus_message_iter_init_append(m, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub)) + return false; + + if (!dbus_message_iter_append_basic(&sub, type, data)) + return false; + + if (!dbus_message_iter_close_container(&iter, &sub)) + return false; + + return true; +} + +static DBusHandlerResult object_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct rd_device *d = userdata; + DBusError error; + DBusMessage *reply = NULL; + + dbus_error_init(&error); + + if (dbus_message_is_method_call(m, "org.freedesktop.ReserveDevice1", + "RequestRelease")) { + int32_t priority; + + if (!dbus_message_get_args(m, &error, + DBUS_TYPE_INT32, &priority, + DBUS_TYPE_INVALID)) + goto invalid; + + pw_log_debug("%p: request release priority:%d", d, priority); + + if (!(reply = dbus_message_new_method_return(m))) + goto oom; + + if (d->reply) + rd_device_complete_release(d, false); + d->reply = reply; + + if (priority > d->priority && d->callbacks->release) + d->callbacks->release(d->data, d, 0); + else + rd_device_complete_release(d, false); + + return DBUS_HANDLER_RESULT_HANDLED; + + } else if (dbus_message_is_method_call( + m, + "org.freedesktop.DBus.Properties", + "Get")) { + + const char *interface, *property; + + if (!dbus_message_get_args( m, &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) + goto invalid; + + if (spa_streq(interface, "org.freedesktop.ReserveDevice1")) { + const char *empty = ""; + + if (spa_streq(property, "ApplicationName") && d->application_name) { + if (!(reply = dbus_message_new_method_return(m))) + goto oom; + + if (!add_variant(reply, + DBUS_TYPE_STRING, + d->application_name ? (const char**) &d->application_name : &empty)) + goto oom; + + } else if (spa_streq(property, "ApplicationDeviceName")) { + if (!(reply = dbus_message_new_method_return(m))) + goto oom; + + if (!add_variant(reply, + DBUS_TYPE_STRING, + d->application_device_name ? (const char**) &d->application_device_name : &empty)) + goto oom; + + } else if (spa_streq(property, "Priority")) { + if (!(reply = dbus_message_new_method_return(m))) + goto oom; + + if (!add_variant(reply, + DBUS_TYPE_INT32, &d->priority)) + goto oom; + } else { + if (!(reply = dbus_message_new_error_printf(m, + DBUS_ERROR_UNKNOWN_METHOD, + "Unknown property %s", property))) + goto oom; + } + + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + } else if (dbus_message_is_method_call( + m, + "org.freedesktop.DBus.Introspectable", + "Introspect")) { + const char *i = introspection; + + if (!(reply = dbus_message_new_method_return(m))) + goto oom; + + if (!dbus_message_append_args(reply, + DBUS_TYPE_STRING, &i, + DBUS_TYPE_INVALID)) + goto oom; + + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +invalid: + if (!(reply = dbus_message_new_error(m, + DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"))) + goto oom; + + if (!dbus_connection_send(c, reply, NULL)) + goto oom; + + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; + +oom: + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NEED_MEMORY; +} + +static const struct DBusObjectPathVTable vtable ={ + .message_function = object_handler +}; + +static DBusHandlerResult filter_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct rd_device *d = userdata; + DBusError error; + const char *name; + + dbus_error_init(&error); + + if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameAcquired")) { + if (!dbus_message_get_args( m, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + goto invalid; + + if (!spa_streq(name, d->service_name)) + goto invalid; + + pw_log_debug("%p: acquired %s, %s", d, name, d->service_name); + + d->owning = true; + + if (!d->registered) { + if (!(dbus_connection_register_object_path(d->connection, + d->object_path, + &vtable, + d))) + goto invalid; + + if (!spa_streq(name, d->service_name)) + goto invalid; + + d->registered = true; + + if (d->callbacks->acquired) + d->callbacks->acquired(d->data, d); + } + } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) { + if (!dbus_message_get_args( m, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + goto invalid; + + if (!spa_streq(name, d->service_name)) + goto invalid; + + pw_log_debug("%p: lost %s", d, name); + + d->owning = false; + + if (d->registered) { + dbus_connection_unregister_object_path(d->connection, + d->object_path); + d->registered = false; + } + } + if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *old, *new; + if (!dbus_message_get_args( m, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) + goto invalid; + + if (!spa_streq(name, d->service_name) || d->owning) + goto invalid; + + pw_log_debug("%p: changed %s: %s -> %s", d, name, old, new); + + if (old == NULL || *old == 0) { + if (d->callbacks->busy && !d->acquiring) + d->callbacks->busy(d->data, d, name, 0); + } else { + if (d->callbacks->available) + d->callbacks->available(d->data, d, name); + } + } + +invalid: + dbus_error_free(&error); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +struct rd_device * +rd_device_new(DBusConnection *connection, const char *device_name, const char *application_name, + int32_t priority, const struct rd_device_callbacks *callbacks, void *data) +{ + struct rd_device *d; + int res; + + d = calloc(1, sizeof(struct rd_device)); + if (d == NULL) + return NULL; + + d->connection = connection; + d->priority = priority; + d->callbacks = callbacks; + d->data = data; + + d->application_name = strdup(application_name); + + d->object_path = spa_aprintf(OBJECT_PREFIX "%s", device_name); + if (d->object_path == NULL) { + res = -errno; + goto error_free; + } + d->service_name = spa_aprintf(SERVICE_PREFIX "%s", device_name); + if (d->service_name == NULL) { + res = -errno; + goto error_free; + } + + if (!dbus_connection_add_filter(d->connection, + filter_handler, + d, + NULL)) { + res = -ENOMEM; + goto error_free; + } + dbus_bus_add_match(d->connection, + "type='signal',sender='org.freedesktop.DBus'," + "interface='org.freedesktop.DBus',member='NameLost'", NULL); + dbus_bus_add_match(d->connection, + "type='signal',sender='org.freedesktop.DBus'," + "interface='org.freedesktop.DBus',member='NameAcquired'", NULL); + dbus_bus_add_match(d->connection, + "type='signal',sender='org.freedesktop.DBus'," + "interface='org.freedesktop.DBus',member='NameOwnerChanged'", NULL); + + dbus_connection_ref(d->connection); + + pw_log_debug("%p: new device %s", d, device_name); + + return d; + +error_free: + free(d->service_name); + free(d->object_path); + free(d); + errno = -res; + return NULL; +} + +int rd_device_acquire(struct rd_device *d) +{ + int res; + DBusError error; + + dbus_error_init(&error); + + pw_log_debug("%p: reserve %s", d, d->service_name); + + d->acquiring = true; + + if ((res = dbus_bus_request_name(d->connection, + d->service_name, + (d->priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0), + &error)) < 0) { + pw_log_warn("%p: reserve failed: %s", d, error.message); + dbus_error_free(&error); + return -EIO; + } + + pw_log_debug("%p: reserve result: %d", d, res); + + if (res == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER || + res == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) + return 0; + + if (res == DBUS_REQUEST_NAME_REPLY_EXISTS || + res == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) + return -EBUSY; + + return -EIO; +} + +int rd_device_request_release(struct rd_device *d) +{ + DBusMessage *m = NULL; + + if (d->priority <= INT32_MIN) + return -EBUSY; + + if ((m = dbus_message_new_method_call(d->service_name, + d->object_path, + "org.freedesktop.ReserveDevice1", + "RequestRelease")) == NULL) { + return -ENOMEM; + } + if (!dbus_message_append_args(m, + DBUS_TYPE_INT32, &d->priority, + DBUS_TYPE_INVALID)) { + dbus_message_unref(m); + return -ENOMEM; + } + if (!dbus_connection_send(d->connection, m, NULL)) { + return -EIO; + } + return 0; +} + +int rd_device_complete_release(struct rd_device *d, int res) +{ + dbus_bool_t ret = res != 0; + + if (d->reply == NULL) + return -EINVAL; + + pw_log_debug("%p: complete release %d", d, res); + + if (!dbus_message_append_args(d->reply, + DBUS_TYPE_BOOLEAN, &ret, + DBUS_TYPE_INVALID)) { + res = -ENOMEM; + goto exit; + } + + if (!dbus_connection_send(d->connection, d->reply, NULL)) { + res = -EIO; + goto exit; + } + res = 0; +exit: + dbus_message_unref(d->reply); + d->reply = NULL; + return res; +} + +void rd_device_release(struct rd_device *d) +{ + pw_log_debug("%p: release %d", d, d->owning); + + if (d->owning) { + DBusError error; + dbus_error_init(&error); + + dbus_bus_release_name(d->connection, + d->service_name, &error); + dbus_error_free(&error); + } + d->acquiring = false; +} + +void rd_device_destroy(struct rd_device *d) +{ + dbus_connection_remove_filter(d->connection, + filter_handler, d); + + if (d->registered) + dbus_connection_unregister_object_path(d->connection, + d->object_path); + + rd_device_release(d); + + free(d->service_name); + free(d->object_path); + free(d->application_name); + free(d->application_device_name); + if (d->reply) + dbus_message_unref(d->reply); + + dbus_connection_unref(d->connection); + + free(d); +} + +int rd_device_set_application_device_name(struct rd_device *d, const char *name) +{ + char *t; + + if (!d) + return -EINVAL; + + if (!(t = strdup(name))) + return -ENOMEM; + + free(d->application_device_name); + d->application_device_name = t; + + return 0; +} |