/* 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 #include #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1." #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/" static const char introspection[] = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "" " \n" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; 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; }