679 lines
15 KiB
C
679 lines
15 KiB
C
/***
|
|
Copyright 2009 Lennart Poettering
|
|
|
|
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 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.
|
|
***/
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
|
|
#include "reserve.h"
|
|
#include <jack/control.h>
|
|
|
|
#define RESERVE_ERROR_NO_MEMORY "org.freedesktop.ReserveDevice1.Error.NoMemory"
|
|
#define RESERVE_ERROR_PROTOCOL_VIOLATION "org.freedesktop.ReserveDevice1.Error.Protocol"
|
|
#define RESERVE_ERROR_RELEASE_DENIED "org.freedesktop.ReserveDevice1.Error.ReleaseDenied"
|
|
|
|
struct rd_device {
|
|
int ref;
|
|
|
|
char *device_name;
|
|
char *application_name;
|
|
char *application_device_name;
|
|
char *service_name;
|
|
char *object_path;
|
|
int32_t priority;
|
|
|
|
DBusConnection *connection;
|
|
|
|
unsigned int owning:1;
|
|
unsigned int registered:1;
|
|
unsigned int filtering:1;
|
|
unsigned int gave_up:1;
|
|
|
|
rd_request_cb_t request_cb;
|
|
void *userdata;
|
|
};
|
|
|
|
|
|
#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>";
|
|
|
|
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) {
|
|
|
|
rd_device *d;
|
|
DBusError error;
|
|
DBusMessage *reply = NULL;
|
|
|
|
dbus_error_init(&error);
|
|
|
|
d = (rd_device*)userdata;
|
|
assert(d->ref >= 1);
|
|
|
|
if (dbus_message_is_method_call(
|
|
m,
|
|
"org.freedesktop.ReserveDevice1",
|
|
"RequestRelease")) {
|
|
|
|
int32_t priority;
|
|
dbus_bool_t ret;
|
|
|
|
if (!dbus_message_get_args(
|
|
m,
|
|
&error,
|
|
DBUS_TYPE_INT32, &priority,
|
|
DBUS_TYPE_INVALID))
|
|
goto invalid;
|
|
|
|
ret = FALSE;
|
|
|
|
if (priority > d->priority && d->request_cb) {
|
|
d->ref++;
|
|
|
|
if (d->request_cb(d, 0) > 0) {
|
|
ret = TRUE;
|
|
d->gave_up = 1;
|
|
}
|
|
|
|
rd_release(d);
|
|
}
|
|
|
|
if (!(reply = dbus_message_new_method_return(m)))
|
|
goto oom;
|
|
|
|
if (!dbus_message_append_args(
|
|
reply,
|
|
DBUS_TYPE_BOOLEAN, &ret,
|
|
DBUS_TYPE_INVALID))
|
|
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.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 (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
|
|
const char *empty = "";
|
|
|
|
if (strcmp(property, "ApplicationName") == 0 && 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 (strcmp(property, "ApplicationDeviceName") == 0) {
|
|
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 (strcmp(property, "Priority") == 0) {
|
|
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_unref(reply);
|
|
|
|
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 DBusHandlerResult filter_handler(
|
|
DBusConnection *c,
|
|
DBusMessage *m,
|
|
void *userdata) {
|
|
|
|
DBusMessage *reply;
|
|
rd_device *d;
|
|
DBusError error;
|
|
|
|
dbus_error_init(&error);
|
|
|
|
d = (rd_device*)userdata;
|
|
|
|
if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
|
|
const char *name;
|
|
|
|
if (!dbus_message_get_args(
|
|
m,
|
|
&error,
|
|
DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID))
|
|
goto invalid;
|
|
|
|
if (strcmp(name, d->service_name) == 0 && d->owning) {
|
|
d->owning = 0;
|
|
|
|
if (!d->gave_up) {
|
|
d->ref++;
|
|
|
|
if (d->request_cb)
|
|
d->request_cb(d, 1);
|
|
d->gave_up = 1;
|
|
|
|
rd_release(d);
|
|
}
|
|
|
|
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 DBusObjectPathVTable vtable;
|
|
|
|
int rd_acquire(
|
|
rd_device **_d,
|
|
DBusConnection *connection,
|
|
const char *device_name,
|
|
const char *application_name,
|
|
int32_t priority,
|
|
rd_request_cb_t request_cb,
|
|
DBusError *error) {
|
|
|
|
rd_device *d = NULL;
|
|
int r, k;
|
|
DBusError _error;
|
|
DBusMessage *m = NULL, *reply = NULL;
|
|
dbus_bool_t good;
|
|
vtable.message_function = object_handler;
|
|
|
|
if (!error) {
|
|
error = &_error;
|
|
dbus_error_init(error);
|
|
}
|
|
|
|
if (!_d) {
|
|
assert(0);
|
|
r = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
if (!connection) {
|
|
assert(0);
|
|
r = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
if (!device_name) {
|
|
assert(0);
|
|
r = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
if (!request_cb && priority != INT32_MAX) {
|
|
assert(0);
|
|
r = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
if (!(d = (rd_device *)calloc(sizeof(rd_device), 1))) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for rd_device struct");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
d->ref = 1;
|
|
|
|
if (!(d->device_name = strdup(device_name))) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate device name string");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (!(d->application_name = strdup(application_name))) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot duplicate application name string");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
d->priority = priority;
|
|
d->connection = dbus_connection_ref(connection);
|
|
d->request_cb = request_cb;
|
|
|
|
if (!(d->service_name = (char*)malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for service name string");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
|
|
|
|
if (!(d->object_path = (char*)malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for object path string");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
|
|
|
|
if ((k = dbus_bus_request_name(
|
|
d->connection,
|
|
d->service_name,
|
|
DBUS_NAME_FLAG_DO_NOT_QUEUE|
|
|
(priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
|
|
error)) < 0) {
|
|
jack_error("dbus_bus_request_name() failed. (1)");
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
switch (k) {
|
|
case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
|
|
case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
|
|
goto success;
|
|
case DBUS_REQUEST_NAME_REPLY_EXISTS:
|
|
break;
|
|
case DBUS_REQUEST_NAME_REPLY_IN_QUEUE : /* DBUS_NAME_FLAG_DO_NOT_QUEUE was specified */
|
|
default: /* unknown reply returned */
|
|
jack_error("request name reply with unexpected value %d.", k);
|
|
assert(0);
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
if (priority <= INT32_MIN) {
|
|
r = -EBUSY;
|
|
dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\"", priority, device_name);
|
|
goto fail;
|
|
}
|
|
|
|
if (!(m = dbus_message_new_method_call(
|
|
d->service_name,
|
|
d->object_path,
|
|
"org.freedesktop.ReserveDevice1",
|
|
"RequestRelease"))) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot allocate memory for RequestRelease method call");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (!dbus_message_append_args(
|
|
m,
|
|
DBUS_TYPE_INT32, &d->priority,
|
|
DBUS_TYPE_INVALID)) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot append args for RequestRelease method call");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (!(reply = dbus_connection_send_with_reply_and_block(
|
|
d->connection,
|
|
m,
|
|
5000, /* 5s */
|
|
error))) {
|
|
|
|
if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
|
|
dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
|
|
dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
|
|
/* This must be treated as denied. */
|
|
jack_info("Device reservation request with priority %"PRIi32" denied for \"%s\": %s (%s)", priority, device_name, error->name, error->message);
|
|
r = -EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
jack_error("dbus_connection_send_with_reply_and_block(RequestRelease) failed.");
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
if (!dbus_message_get_args(
|
|
reply,
|
|
error,
|
|
DBUS_TYPE_BOOLEAN, &good,
|
|
DBUS_TYPE_INVALID)) {
|
|
jack_error("RequestRelease() reply is invalid.");
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
if (!good) {
|
|
dbus_set_error(error, RESERVE_ERROR_RELEASE_DENIED, "Device reservation request with priority %"PRIi32" denied for \"%s\" via RequestRelease()", priority, device_name);
|
|
r = -EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
if ((k = dbus_bus_request_name(
|
|
d->connection,
|
|
d->service_name,
|
|
DBUS_NAME_FLAG_DO_NOT_QUEUE|
|
|
(priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
|
|
DBUS_NAME_FLAG_REPLACE_EXISTING,
|
|
error)) < 0) {
|
|
jack_error("dbus_bus_request_name() failed. (2)");
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
|
|
/* this is racy, another contender may have acquired the device */
|
|
dbus_set_error(error, RESERVE_ERROR_PROTOCOL_VIOLATION, "request name reply is not DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER but %d.", k);
|
|
r = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
success:
|
|
d->owning = 1;
|
|
|
|
if (!(dbus_connection_try_register_object_path(
|
|
d->connection,
|
|
d->object_path,
|
|
&vtable,
|
|
d,
|
|
error))) {
|
|
jack_error("cannot register object path \"%s\": %s", d->object_path, error->message);
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
d->registered = 1;
|
|
|
|
if (!dbus_connection_add_filter(
|
|
d->connection,
|
|
filter_handler,
|
|
d,
|
|
NULL)) {
|
|
dbus_set_error(error, RESERVE_ERROR_NO_MEMORY, "Cannot add filter");
|
|
r = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
d->filtering = 1;
|
|
|
|
*_d = d;
|
|
return 0;
|
|
|
|
fail:
|
|
if (m)
|
|
dbus_message_unref(m);
|
|
|
|
if (reply)
|
|
dbus_message_unref(reply);
|
|
|
|
if (&_error == error)
|
|
dbus_error_free(&_error);
|
|
|
|
if (d)
|
|
rd_release(d);
|
|
|
|
return r;
|
|
}
|
|
|
|
void rd_release(
|
|
rd_device *d) {
|
|
|
|
if (!d)
|
|
return;
|
|
|
|
assert(d->ref > 0);
|
|
|
|
if (--d->ref)
|
|
return;
|
|
|
|
|
|
if (d->filtering)
|
|
dbus_connection_remove_filter(
|
|
d->connection,
|
|
filter_handler,
|
|
d);
|
|
|
|
if (d->registered)
|
|
dbus_connection_unregister_object_path(
|
|
d->connection,
|
|
d->object_path);
|
|
|
|
if (d->owning) {
|
|
DBusError error;
|
|
dbus_error_init(&error);
|
|
|
|
dbus_bus_release_name(
|
|
d->connection,
|
|
d->service_name,
|
|
&error);
|
|
|
|
dbus_error_free(&error);
|
|
}
|
|
|
|
free(d->device_name);
|
|
free(d->application_name);
|
|
free(d->application_device_name);
|
|
free(d->service_name);
|
|
free(d->object_path);
|
|
|
|
if (d->connection)
|
|
dbus_connection_unref(d->connection);
|
|
|
|
free(d);
|
|
}
|
|
|
|
int rd_set_application_device_name(rd_device *d, const char *n) {
|
|
char *t;
|
|
|
|
if (!d)
|
|
return -EINVAL;
|
|
|
|
assert(d->ref > 0);
|
|
|
|
if (!(t = strdup(n)))
|
|
return -ENOMEM;
|
|
|
|
free(d->application_device_name);
|
|
d->application_device_name = t;
|
|
return 0;
|
|
}
|
|
|
|
void rd_set_userdata(rd_device *d, void *userdata) {
|
|
|
|
if (!d)
|
|
return;
|
|
|
|
assert(d->ref > 0);
|
|
d->userdata = userdata;
|
|
}
|
|
|
|
void* rd_get_userdata(rd_device *d) {
|
|
|
|
if (!d)
|
|
return NULL;
|
|
|
|
assert(d->ref > 0);
|
|
|
|
return d->userdata;
|
|
}
|